summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.clang-format167
-rw-r--r--.gitignore45
-rw-r--r--CMakeLists.txt96
-rw-r--r--LICENSE.APLv2203
-rw-r--r--NOTICE3
-rw-r--r--aitt.pc.in8
-rw-r--r--android/aitt/.gitignore1
-rw-r--r--android/aitt/CMakeLists.txt21
-rw-r--r--android/aitt/build.gradle164
-rw-r--r--android/aitt/proguard-rules.pro21
-rw-r--r--android/aitt/src/main/AndroidManifest.xml5
-rw-r--r--android/aitt/src/main/java/com/samsung/android/aitt/Aitt.java687
-rw-r--r--android/aitt/src/main/java/com/samsung/android/aitt/AittMessage.java153
-rw-r--r--android/aitt/src/main/jni/aitt_jni.cc368
-rw-r--r--android/aitt/src/main/jni/aitt_jni.h60
-rw-r--r--android/aitt/src/test/java/com/samsung/android/aitt/AittMessageUnitTest.java124
-rw-r--r--android/aitt/src/test/java/com/samsung/android/aitt/AittUnitTest.java795
-rw-r--r--android/aitt/src/test/resources/robolectric.properties1
-rw-r--r--android/flatbuffers/build.gradle79
-rw-r--r--android/flatbuffers/proguard-rules.pro21
-rw-r--r--android/flatbuffers/src/main/AndroidManifest.xml4
-rw-r--r--android/modules/webrtc/.gitignore1
-rw-r--r--android/modules/webrtc/build.gradle39
-rw-r--r--android/modules/webrtc/proguard-rules.pro21
-rw-r--r--android/modules/webrtc/src/main/AndroidManifest.xml8
-rw-r--r--android/modules/webrtc/src/main/java/com/samsung/android/modules/webrtc/WebRTC.java698
-rw-r--r--android/modules/webrtc/src/main/java/com/samsung/android/modules/webrtc/WebRTCServer.java114
-rw-r--r--android/modules/webrtc/src/test/java/com/samsung/android/modules/webrtc/ExampleUnitTest.java17
-rw-r--r--android/mosquitto/build.gradle80
-rw-r--r--android/mosquitto/src/main/AndroidManifest.xml4
-rw-r--r--android/settings.gradle4
-rw-r--r--build.gradle25
-rw-r--r--cmake/aitt_android_flatbuffers.cmake14
-rw-r--r--cmake/aitt_android_glib.cmake27
-rw-r--r--cmake/aitt_android_mosquitto.cmake14
-rwxr-xr-xcommon/AITTEx.cc59
-rwxr-xr-xcommon/AITTEx.h49
-rw-r--r--common/AittDiscovery.cc197
-rw-r--r--common/CMakeLists.txt12
-rw-r--r--common/MQ.cc427
-rw-r--r--common/MQ.h93
-rw-r--r--common/MSG.cc98
-rw-r--r--common/MainLoopHandler.cc187
-rw-r--r--common/MainLoopHandler.h72
-rw-r--r--common/aitt_internal.h94
-rw-r--r--common/aitt_internal_definitions.h33
-rw-r--r--common/aitt_internal_profiling.h134
-rw-r--r--common/aitt_platform.h51
-rw-r--r--common/tizen/aitt_platform.h22
-rw-r--r--debian/aitt-dev.install2
-rw-r--r--debian/aitt-plugins.install1
-rw-r--r--debian/aitt.install2
-rw-r--r--debian/changelog5
-rw-r--r--debian/compat1
-rw-r--r--debian/control30
-rw-r--r--debian/copyright3
-rwxr-xr-xdebian/rules71
-rw-r--r--gradle.properties21
-rw-r--r--gradle/wrapper/gradle-wrapper.properties6
-rw-r--r--gradlew185
-rw-r--r--gradlew.bat89
-rw-r--r--include/AITT.h73
-rw-r--r--include/AittDiscovery.h69
-rw-r--r--include/AittTransport.h55
-rw-r--r--include/AittTypes.h66
-rw-r--r--include/MSG.h52
-rw-r--r--include/aitt_c.h366
-rw-r--r--mock/MQMockTest.h37
-rw-r--r--mock/MQTTMock.h50
-rw-r--r--mock/mosquitto.cc110
-rw-r--r--modules/main.cc40
-rw-r--r--modules/tcp/CMakeLists.txt14
-rw-r--r--modules/tcp/Module.cc513
-rw-r--r--modules/tcp/Module.h135
-rw-r--r--modules/tcp/TCP.cc157
-rw-r--r--modules/tcp/TCP.h43
-rw-r--r--modules/tcp/TCPServer.cc132
-rw-r--r--modules/tcp/TCPServer.h37
-rw-r--r--modules/tcp/samples/CMakeLists.txt3
-rw-r--r--modules/tcp/samples/tcp_test.cc235
-rw-r--r--modules/tcp/tests/CMakeLists.txt19
-rw-r--r--modules/tcp/tests/TCPServer_test.cc121
-rw-r--r--modules/tcp/tests/TCP_test.cc149
-rw-r--r--modules/webrtc/CMakeLists.txt24
-rw-r--r--modules/webrtc/CameraHandler.cc170
-rw-r--r--modules/webrtc/CameraHandler.h61
-rw-r--r--modules/webrtc/Config.h62
-rw-r--r--modules/webrtc/IfaceServer.h40
-rw-r--r--modules/webrtc/Module.cc127
-rw-r--r--modules/webrtc/Module.h66
-rw-r--r--modules/webrtc/MqttServer.cc289
-rw-r--r--modules/webrtc/MqttServer.h80
-rw-r--r--modules/webrtc/PublishStream.cc210
-rw-r--r--modules/webrtc/PublishStream.h71
-rw-r--r--modules/webrtc/SubscribeStream.cc202
-rw-r--r--modules/webrtc/SubscribeStream.h68
-rw-r--r--modules/webrtc/WebRtcEventHandler.h90
-rw-r--r--modules/webrtc/WebRtcMessage.cc53
-rw-r--r--modules/webrtc/WebRtcMessage.h29
-rw-r--r--modules/webrtc/WebRtcPeer.cc58
-rw-r--r--modules/webrtc/WebRtcPeer.h37
-rw-r--r--modules/webrtc/WebRtcRoom.cc141
-rw-r--r--modules/webrtc/WebRtcRoom.h90
-rw-r--r--modules/webrtc/WebRtcState.cc187
-rw-r--r--modules/webrtc/WebRtcState.h75
-rw-r--r--modules/webrtc/WebRtcStream.cc414
-rw-r--r--modules/webrtc/WebRtcStream.h107
-rw-r--r--modules/webrtc/tests/CMakeLists.txt21
-rw-r--r--modules/webrtc/tests/MockPublishStream.h103
-rw-r--r--modules/webrtc/tests/MockSubscribeStream.h100
-rw-r--r--modules/webrtc/tests/WEBRTC_test.cc598
-rw-r--r--packaging/aitt.manifest5
-rw-r--r--packaging/aitt.spec104
-rw-r--r--settings.gradle4
-rw-r--r--src/AITT.cc137
-rw-r--r--src/AITTImpl.cc437
-rw-r--r--src/AITTImpl.h103
-rw-r--r--src/TransportModuleLoader.cc105
-rw-r--r--src/TransportModuleLoader.h50
-rw-r--r--src/aitt_c.cc282
-rw-r--r--tests/AITT_TCP_test.cc112
-rw-r--r--tests/AITT_manualtest.cc76
-rw-r--r--tests/AITT_test.cc556
-rw-r--r--tests/CMakeLists.txt62
-rw-r--r--tests/MQ_mocktest.cc247
-rw-r--r--tests/MQ_test.cc75
-rw-r--r--tests/MainLoopHandler_test.cc225
-rw-r--r--tests/RequestResponse_test.cc393
-rw-r--r--tests/TransportModuleLoader_test.cc49
-rw-r--r--tests/aitt_c_manualtest.cc101
-rw-r--r--tests/aitt_c_test.cc370
-rw-r--r--tests/aitt_tests.h77
-rw-r--r--tools/CMakeLists.txt7
-rw-r--r--tools/FlexbufPrinter.cc133
-rw-r--r--tools/FlexbufPrinter.h34
-rw-r--r--tools/discovery_viewer.cc129
136 files changed, 15958 insertions, 0 deletions
diff --git a/.clang-format b/.clang-format
new file mode 100644
index 0000000..f9a6b2c
--- /dev/null
+++ b/.clang-format
@@ -0,0 +1,167 @@
+---
+Language: Cpp
+# BasedOnStyle: Google
+AccessModifierOffset: -2
+AlignAfterOpenBracket: DontAlign
+AlignConsecutiveMacros: false
+AlignConsecutiveAssignments: false
+AlignConsecutiveDeclarations: false
+AlignEscapedNewlines: Left
+AlignOperands: true
+AlignTrailingComments: true
+AllowAllArgumentsOnNextLine: false
+AllowAllConstructorInitializersOnNextLine: true
+AllowAllParametersOfDeclarationOnNextLine: true
+AllowShortBlocksOnASingleLine: Never
+AllowShortCaseLabelsOnASingleLine: false
+AllowShortFunctionsOnASingleLine: InlineOnly
+AllowShortLambdasOnASingleLine: All
+AllowShortIfStatementsOnASingleLine: Never
+AllowShortLoopsOnASingleLine: false
+AlwaysBreakAfterDefinitionReturnType: None
+AlwaysBreakAfterReturnType: None
+AlwaysBreakBeforeMultilineStrings: true
+AlwaysBreakTemplateDeclarations: Yes
+BinPackArguments: true
+BinPackParameters: true
+BraceWrapping:
+ AfterCaseLabel: false
+ AfterClass: false
+ AfterControlStatement: false
+ AfterEnum: false
+ AfterFunction: true
+ AfterNamespace: false
+ AfterObjCDeclaration: false
+ AfterStruct: false
+ AfterUnion: false
+ AfterExternBlock: false
+ BeforeCatch: false
+ BeforeElse: false
+ IndentBraces: false
+ SplitEmptyFunction: true
+ SplitEmptyRecord: true
+ SplitEmptyNamespace: true
+BreakBeforeBinaryOperators: NonAssignment
+BreakBeforeBraces: Custom
+BreakBeforeInheritanceComma: false
+BreakInheritanceList: BeforeColon
+BreakBeforeTernaryOperators: true
+BreakConstructorInitializersBeforeComma: false
+BreakConstructorInitializers: BeforeColon
+BreakAfterJavaFieldAnnotations: false
+BreakStringLiterals: true
+ColumnLimit: 100
+CommentPragmas: '^ IWYU pragma:'
+CompactNamespaces: false
+ConstructorInitializerAllOnOneLineOrOnePerLine: true
+ConstructorInitializerIndentWidth: 6
+ContinuationIndentWidth: 6
+Cpp11BracedListStyle: true
+DeriveLineEnding: true
+DerivePointerAlignment: true
+DisableFormat: false
+ExperimentalAutoDetectBinPacking: false
+FixNamespaceComments: true
+ForEachMacros:
+ - foreach
+ - Q_FOREACH
+ - BOOST_FOREACH
+IncludeBlocks: Regroup
+IncludeCategories:
+ - Regex: '^<ext/.*\.h>'
+ Priority: 2
+ SortPriority: 0
+ - Regex: '^<.*\.h>'
+ Priority: 1
+ SortPriority: 0
+ - Regex: '^<.*'
+ Priority: 2
+ SortPriority: 0
+ - Regex: '.*'
+ Priority: 3
+ SortPriority: 0
+IncludeIsMainRegex: '([-_](test|unittest|manualtest))?$'
+IncludeIsMainSourceRegex: ''
+IndentCaseLabels: false
+IndentGotoLabels: true
+IndentPPDirectives: None
+IndentWidth: 4
+IndentWrappedFunctionNames: false
+JavaScriptQuotes: Leave
+JavaScriptWrapImports: true
+KeepEmptyLinesAtTheStartOfBlocks: false
+MacroBlockBegin: ''
+MacroBlockEnd: ''
+MaxEmptyLinesToKeep: 1
+NamespaceIndentation: None
+ObjCBinPackProtocolList: Never
+ObjCBlockIndentWidth: 2
+ObjCSpaceAfterProperty: false
+ObjCSpaceBeforeProtocolList: true
+PenaltyBreakAssignment: 2
+PenaltyBreakBeforeFirstCallParameter: 1
+PenaltyBreakComment: 300
+PenaltyBreakFirstLessLess: 120
+PenaltyBreakString: 1000
+PenaltyBreakTemplateDeclaration: 10
+PenaltyExcessCharacter: 1000000
+PenaltyReturnTypeOnItsOwnLine: 200
+PointerAlignment: Right
+RawStringFormats:
+ - Language: Cpp
+ Delimiters:
+ - cc
+ - CC
+ - cpp
+ - Cpp
+ - CPP
+ - 'c++'
+ - 'C++'
+ CanonicalDelimiter: ''
+ BasedOnStyle: google
+ - Language: TextProto
+ Delimiters:
+ - pb
+ - PB
+ - proto
+ - PROTO
+ EnclosingFunctions:
+ - EqualsProto
+ - EquivToProto
+ - PARSE_PARTIAL_TEXT_PROTO
+ - PARSE_TEST_PROTO
+ - PARSE_TEXT_PROTO
+ - ParseTextOrDie
+ - ParseTextProtoOrDie
+ CanonicalDelimiter: ''
+ BasedOnStyle: google
+ReflowComments: true
+SortIncludes: true
+SortUsingDeclarations: true
+SpaceAfterCStyleCast: false
+SpaceAfterLogicalNot: false
+SpaceAfterTemplateKeyword: true
+SpaceBeforeAssignmentOperators: true
+SpaceBeforeCpp11BracedList: false
+SpaceBeforeCtorInitializerColon: true
+SpaceBeforeInheritanceColon: true
+SpaceBeforeParens: ControlStatements
+SpaceBeforeRangeBasedForLoopColon: true
+SpaceInEmptyBlock: false
+SpaceInEmptyParentheses: false
+SpacesBeforeTrailingComments: 2
+SpacesInAngles: false
+SpacesInConditionalStatement: false
+SpacesInContainerLiterals: true
+SpacesInCStyleCastParentheses: false
+SpacesInParentheses: false
+SpacesInSquareBrackets: false
+SpaceBeforeSquareBrackets: false
+Standard: Auto
+StatementMacros:
+ - Q_UNUSED
+ - QT_REQUIRE_VERSION
+TabWidth: 4
+UseCRLF: false
+UseTab: Never
+...
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..0c9507e
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,45 @@
+profiling-result
+debian/aitt-dev.debhelper.log
+debian/aitt.debhelper.log
+debian/aitt
+debian/aitt-dev
+debian/aitt-plugins
+debian/.debhelper/
+debian/aitt.substvars
+debian/aitt-dev.substvars
+debian/aitt-plugins.substvars
+debian/files
+debian/tmp/
+build
+out
+aitt_gcov.info
+sam_cli.cfg
+
+# Built application files
+*.aar
+jacoco.exec
+
+# Gradle files
+.gradle/
+.galaxy/
+build/
+*.jar
+
+# Local configuration file (sdk path, etc)
+local.properties
+
+# Proguard folder generated by Eclipse
+proguard/
+
+# Log Files
+*.log
+
+# IntelliJ
+*.iml
+.idea/
+
+# External native build folder
+.cxx/
+
+#Third-party dependencies
+third_party/
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644
index 0000000..c6fde39
--- /dev/null
+++ b/CMakeLists.txt
@@ -0,0 +1,96 @@
+CMAKE_MINIMUM_REQUIRED(VERSION 3.4.1)
+SET(CMAKE_SKIP_BUILD_RPATH true)
+PROJECT(aitt VERSION 0.0.1 LANGUAGES CXX)
+SET_PROPERTY(GLOBAL PROPERTY GLOBAL_DEPENDS_DEBUG_MODE 0)
+SET(CMAKE_POSITION_INDEPENDENT_CODE TRUE)
+SET(CMAKE_CXX_STANDARD 17)
+SET(CMAKE_CXX_STANDARD_REQUIRED ON)
+SET(CMAKE_CXX_EXTENSIONS OFF)
+
+OPTION(VERSIONING "Specify Library Verion" ON)
+
+INCLUDE(GNUInstallDirs)
+INCLUDE(FindPkgConfig)
+
+IF(PLATFORM STREQUAL "android")
+ SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -fdiagnostics-color")
+ ADD_DEFINITIONS(-DANDROID)
+ INCLUDE(${PROJECT_ROOT_DIR}/cmake/aitt_android_flatbuffers.cmake)
+ INCLUDE(${PROJECT_ROOT_DIR}/cmake/aitt_android_glib.cmake)
+ INCLUDE(${PROJECT_ROOT_DIR}/cmake/aitt_android_mosquitto.cmake)
+ SET(AITT_NEEDS_LIBRARIES ${GLIB_LIBRARIES} ${MOSQUITTO_LIBRARY} ${FLATBUFFERS_LIBRARY})
+ELSE(PLATFORM STREQUAL "android")
+ IF(PLATFORM STREQUAL "tizen")
+ ADD_DEFINITIONS(-DTIZEN)
+ ADD_DEFINITIONS(-DPLATFORM=${PLATFORM})
+ SET(ADDITIONAL_OPT "-DTIZEN")
+ SET(PKGS dlog)
+ ENDIF(PLATFORM STREQUAL "tizen")
+ SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Werror -Wno-psabi -fdiagnostics-color -fvisibility=hidden")
+ PKG_CHECK_MODULES(AITT_NEEDS REQUIRED ${PKGS} libmosquitto flatbuffers glib-2.0)
+ INCLUDE_DIRECTORIES(${AITT_NEEDS_INCLUDE_DIRS})
+ LINK_DIRECTORIES(${AITT_NEEDS_LIBRARY_DIRS})
+ENDIF(PLATFORM STREQUAL "android")
+
+FIND_PACKAGE(Threads REQUIRED)
+
+IF(LOG_STDOUT)
+ ADD_DEFINITIONS(-DLOG_STDOUT)
+ENDIF(LOG_STDOUT)
+
+ADD_DEFINITIONS(-DLOG_TAG="AITT")
+
+IF(COVERAGE_TEST)
+ ADD_COMPILE_OPTIONS(--coverage)
+ ADD_LINK_OPTIONS(--coverage -Wl,--dynamic-list-data)
+ENDIF(COVERAGE_TEST)
+
+SET(AITT_COMMON ${PROJECT_NAME}-common)
+
+INCLUDE_DIRECTORIES(include common)
+
+AUX_SOURCE_DIRECTORY(src AITT_SRC)
+
+ADD_LIBRARY(M_LOADER_OBJ OBJECT src/TransportModuleLoader.cc)
+LIST(REMOVE_ITEM AITT_SRC src/TransportModuleLoader.cc)
+
+ADD_LIBRARY(${PROJECT_NAME} SHARED ${AITT_SRC} $<TARGET_OBJECTS:M_LOADER_OBJ>)
+TARGET_LINK_LIBRARIES(${PROJECT_NAME} Threads::Threads ${CMAKE_DL_LIBS} ${AITT_COMMON})
+TARGET_LINK_LIBRARIES(${PROJECT_NAME} ${AITT_NEEDS_LIBRARIES})
+
+TARGET_COMPILE_OPTIONS(${PROJECT_NAME} PUBLIC ${AITT_NEEDS_CFLAGS_OTHER})
+IF(VERSIONING)
+ SET_TARGET_PROPERTIES(${PROJECT_NAME} PROPERTIES
+ VERSION ${PROJECT_VERSION}
+ SOVERSION ${PROJECT_VERSION_MAJOR}
+ )
+ENDIF(VERSIONING)
+INSTALL(TARGETS ${PROJECT_NAME} DESTINATION ${CMAKE_INSTALL_LIBDIR})
+
+FILE(GLOB HEADERS include/*.h)
+INSTALL(FILES ${HEADERS} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME})
+
+CONFIGURE_FILE(${PROJECT_NAME}.pc.in ${PROJECT_NAME}.pc @ONLY)
+INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}.pc DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig)
+SET_DIRECTORY_PROPERTIES(PROPERTIES ADDITIONAL_CMAKE_CLEAN_FILES ${PROJECT_NAME}.pc)
+
+IF(BUILD_TESTING)
+ ENABLE_TESTING() # NOTE: Must comes first than unittest subdirectories
+ SET(AITT_TEST_BINDIR ${CMAKE_INSTALL_BINDIR})
+ ADD_SUBDIRECTORY(tests)
+ ADD_SUBDIRECTORY(tools)
+ENDIF(BUILD_TESTING)
+
+OPTION(WITH_TCP "Build TCP module?" ON)
+IF(WITH_TCP)
+ ADD_SUBDIRECTORY(modules/tcp)
+ENDIF()
+IF(PLATFORM STREQUAL "tizen")
+ OPTION(WITH_WEBRTC "Build WebRtc module?" ON)
+ IF(WITH_WEBRTC)
+ ADD_DEFINITIONS(-DWITH_WEBRTC)
+ ADD_SUBDIRECTORY(modules/webrtc)
+ ENDIF()
+ENDIF(PLATFORM STREQUAL "tizen")
+
+ADD_SUBDIRECTORY(common)
diff --git a/LICENSE.APLv2 b/LICENSE.APLv2
new file mode 100644
index 0000000..a4da8fd
--- /dev/null
+++ b/LICENSE.APLv2
@@ -0,0 +1,203 @@
+Copyright (c) 2021-2022 Samsung Electronics Co., Ltd. All rights reserved.
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/NOTICE b/NOTICE
new file mode 100644
index 0000000..5bb20c7
--- /dev/null
+++ b/NOTICE
@@ -0,0 +1,3 @@
+Copyright (c) 2021-2022 Samsung Electronics Co., Ltd. All rights reserved.
+Except as noted, this software is licensed under Apache License, Version 2.
+Please, see the LICENSE.APLv2 file for Apache license, version 2 terms and conditions.
diff --git a/aitt.pc.in b/aitt.pc.in
new file mode 100644
index 0000000..1cd7e88
--- /dev/null
+++ b/aitt.pc.in
@@ -0,0 +1,8 @@
+libdir=@CMAKE_INSTALL_FULL_LIBDIR@
+includedir=@CMAKE_INSTALL_FULL_INCLUDEDIR@/@PROJECT_NAME@
+
+Name: @PROJECT_NAME@
+Description: Development files for the modualrized AI framework
+Version: @PROJECT_VERSION@
+Libs: -L${libdir} -l@PROJECT_NAME@
+Cflags: -I${includedir} @ADDITIONAL_OPT@
diff --git a/android/aitt/.gitignore b/android/aitt/.gitignore
new file mode 100644
index 0000000..796b96d
--- /dev/null
+++ b/android/aitt/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/android/aitt/CMakeLists.txt b/android/aitt/CMakeLists.txt
new file mode 100644
index 0000000..4d1b82f
--- /dev/null
+++ b/android/aitt/CMakeLists.txt
@@ -0,0 +1,21 @@
+cmake_minimum_required(VERSION 3.4.1)
+project("aitt-android" CXX)
+
+if(NOT DEFINED PROJECT_ROOT_DIR)
+ set(PROJECT_ROOT_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../..)
+endif(NOT DEFINED PROJECT_ROOT_DIR)
+
+find_library(ALOG NAMES log)
+set(LOG_LIBRARIES ${ALOG})
+
+add_subdirectory(${PROJECT_ROOT_DIR} ${CMAKE_BINARY_DIR}/aitt)
+
+include_directories(${PROJECT_ROOT_DIR}/include)
+
+set(ANDROID_SRC src/main/jni/aitt_jni.cc)
+
+add_library(${PROJECT_NAME} SHARED ${ANDROID_SRC})
+
+target_link_libraries(${PROJECT_NAME} aitt ${LOG_LIBRARIES})
+
+add_dependencies(${PROJECT_NAME} aitt)
diff --git a/android/aitt/build.gradle b/android/aitt/build.gradle
new file mode 100644
index 0000000..2fb82be
--- /dev/null
+++ b/android/aitt/build.gradle
@@ -0,0 +1,164 @@
+plugins {
+ id 'com.android.library'
+ id "de.undercouch.download" version "5.0.1"
+}
+
+def thirdPartyDir = new File ("${rootProject.projectDir}/third_party")
+
+def flatbuffersDir = new File("${thirdPartyDir}/flatbuffers-2.0.0")
+def mosquittoDir = new File("${thirdPartyDir}/mosquitto-2.0.14")
+
+android {
+ compileSdkVersion 31
+ ndkVersion "21.3.6528147"
+ defaultConfig {
+ minSdkVersion 28
+ targetSdkVersion 31
+ versionCode 1
+ versionName '1.0'
+ project.archivesBaseName = "aitt"
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ externalNativeBuild {
+ cmake {
+ arguments '-DLOG_STDOUT=ON'
+ arguments '-DCMAKE_VERBOSE_MAKEFILE=1'
+ arguments '-DCMAKE_INSTALL_PREFIX:PATH=/usr'
+ arguments '-DANDROID_STL=c++_shared'
+ arguments "-DANDROID_NDK_HOME=${System.env.ANDROID_NDK_ROOT}"
+ arguments "-DGSTREAMER_ROOT_ANDROID=${System.env.GSTREAMER_ROOT_ANDROID}"
+ arguments '-DBUILD_TESTING=OFF'
+ arguments '-DUSE_PREBUILT=OFF'
+ arguments '-DVERSIONING=OFF'
+ arguments '-DPLATFORM=android'
+ arguments '-DCOVERAGE=OFF'
+ abiFilters 'arm64-v8a', 'x86'
+ cppFlags "-std=c++17"
+ targets "aitt-android", "aitt-transport-tcp"
+ }
+ }
+ }
+
+ externalNativeBuild {
+ cmake {
+ path file('./CMakeLists.txt')
+ }
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+ packagingOptions {
+ jniLibs {
+ useLegacyPackaging = true
+ }
+ pickFirst 'lib/armeabi-v7a/libaitt.so'
+ }
+ libraryVariants.all { variant ->
+ variant.outputs.all {
+ outputFileName = "${archivesBaseName}-${defaultConfig.versionName}.aar"
+ }
+ }
+
+ testOptions {
+ unitTests.returnDefaultValues = true
+ unitTests.includeAndroidResources = true
+ unitTests.all {
+ jacoco {
+ includeNoLocationClasses = true
+ excludes = ['jdk.internal.*']
+ }
+ }
+ }
+}
+
+dependencies {
+ compileOnly project(":android:flatbuffers")
+ compileOnly project(":android:mosquitto")
+
+ implementation 'androidx.appcompat:appcompat:1.4.1'
+ implementation 'com.google.flatbuffers:flatbuffers-java:2.0.0'
+ implementation fileTree(include: ['*.jar'], dir: 'modules/webrtc')
+ implementation project(path: ':android:modules:webrtc')
+ testImplementation 'junit:junit:4.13.2'
+ testImplementation 'org.mockito:mockito-core:2.25.0'
+ testImplementation 'org.powermock:powermock-core:2.0.0-beta.5'
+ testImplementation 'org.powermock:powermock-module-junit4:2.0.0-beta.5'
+ testImplementation 'org.powermock:powermock-api-mockito2:2.0.0-beta.5'
+ androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
+}
+
+task downloadFlatBuffers(type: Download) {
+ doFirst {
+ println("Downloading FlatBuffers")
+ }
+ src "https://github.com/google/flatbuffers/archive/refs/tags/v2.0.0.zip"
+ dest new File(thirdPartyDir, "flatbuffers.zip")
+ onlyIfModified true
+}
+
+task unzipFlatBuffers(type: Copy, dependsOn: downloadFlatBuffers) {
+ doFirst {
+ println("Unzipping FlatBuffers")
+ }
+ from zipTree(downloadFlatBuffers.dest)
+ into thirdPartyDir
+ onlyIf { !flatbuffersDir.exists() }
+}
+
+task downloadMosquitto(type: Download) {
+ doFirst {
+ println("Downloading Mosquitto")
+ }
+ src "https://github.com/eclipse/mosquitto/archive/refs/tags/v2.0.14.zip"
+ dest new File(thirdPartyDir, "mosquitto-2.0.14.zip")
+ onlyIfModified true
+}
+
+task unzipMosquitto(type: Copy, dependsOn: downloadMosquitto) {
+ doFirst {
+ println("Unzipping Mosquitto")
+ }
+ from zipTree(downloadMosquitto.dest)
+ into thirdPartyDir
+ onlyIf { !mosquittoDir.exists() }
+}
+
+preBuild.dependsOn(unzipFlatBuffers)
+preBuild.dependsOn(unzipMosquitto)
+preBuild.dependsOn ":android:flatbuffers:build"
+preBuild.dependsOn ":android:mosquitto:build"
+
+apply plugin: 'jacoco'
+
+task jacocoTestReport(type: JacocoReport, dependsOn: ['testDebugUnitTest']) {
+
+ reports {
+ xml.enabled = true
+ html.enabled = true
+ }
+
+ def fileFilter = ['**/R.class', '**/R$*.class', '**/BuildConfig.*', '**/Manifest*.*', '**/*Test*.*', 'android/**/*.*']
+ def debugTree = fileTree(dir : "${buildDir}/intermediates/javac/debugUnitTest", excludes: fileFilter)
+
+ def mainSrc = "${project.projectDir}/src/main/java"
+
+ getSourceDirectories().setFrom(files([mainSrc]))
+ getClassDirectories().setFrom(files([debugTree]))
+ getExecutionData().setFrom(fileTree(dir: "$buildDir", includes: [
+ "jacoco/testDebugUnitTest.exec",
+ "outputs/code-coverage/connected/*coverage.ec"
+ ]))
+}
+
+task wrapper(type: Wrapper) {
+ gradleVersion = '4.1'
+}
+
+build.dependsOn jacocoTestReport
diff --git a/android/aitt/proguard-rules.pro b/android/aitt/proguard-rules.pro
new file mode 100644
index 0000000..f1b4245
--- /dev/null
+++ b/android/aitt/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
diff --git a/android/aitt/src/main/AndroidManifest.xml b/android/aitt/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..e51cebf
--- /dev/null
+++ b/android/aitt/src/main/AndroidManifest.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.samsung.android.aitt">
+ <uses-permission android:name="android.permission.INTERNET" />
+</manifest>
diff --git a/android/aitt/src/main/java/com/samsung/android/aitt/Aitt.java b/android/aitt/src/main/java/com/samsung/android/aitt/Aitt.java
new file mode 100644
index 0000000..b335314
--- /dev/null
+++ b/android/aitt/src/main/java/com/samsung/android/aitt/Aitt.java
@@ -0,0 +1,687 @@
+/*
+ * Copyright (c) 2022 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.samsung.android.aitt;
+
+import android.content.Context;
+import android.util.Log;
+import android.util.Pair;
+
+import androidx.annotation.Nullable;
+
+import com.google.flatbuffers.FlexBuffers;
+import com.google.flatbuffers.FlexBuffersBuilder;
+import com.samsung.android.modules.webrtc.WebRTC;
+import com.samsung.android.modules.webrtc.WebRTCServer;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Class creates a Java layer to operate between application and JNI layer for AITT C++
+ * 1. Connect to MQTT broker
+ * 2. Subscribe to a topic with protocol and other params
+ * 3. Publish to a topic using protocol and other params
+ * 4. Unsubscribe to a topic
+ * 5. Invoke JNI api's which interact with aitt c++
+ */
+public class Aitt {
+ private static final String TAG = "AITT_ANDROID";
+ private static final String WILL_LEAVE_NETWORK = "disconnected";
+ private static final String AITT_LOCALHOST = "127.0.0.1";
+ private static final int AITT_PORT = 1883;
+ private static final String JAVA_SPECIFIC_DISCOVERY_TOPIC = "/java/aitt/discovery/";
+ private static final String JOIN_NETWORK = "connected";
+ private static final String RESPONSE_POSTFIX = "_AittRe_";
+ private static final String INVALID_TOPIC = "Invalid topic";
+ private static final String STATUS = "status";
+
+ /**
+ * Load aitt-android library
+ */
+ static {
+ try {
+ System.loadLibrary("aitt-android");
+ }catch (UnsatisfiedLinkError e){
+ // only ignore exception in non-android env
+ if ("Dalvik".equals(System.getProperty("java.vm.name"))) throw e;
+ }
+ }
+ private HashMap<String, ArrayList<SubscribeCallback>> subscribeCallbacks = new HashMap<>();
+ private HashMap<String, HostTable> publishTable = new HashMap<>();
+ private HashMap<String, Pair<Protocol , Object>> subscribeMap = new HashMap<>();
+ private HashMap<String, Long> aittSubId = new HashMap<String, Long>();
+ private ConnectionCallback connectionCallback = null;
+
+ private long instance = 0;
+ private String ip;
+ private Context appContext;
+ //ToDo - For now using sample app parameters, later fetch frameWidth & frameHeight from app
+ private Integer frameWidth = 640, frameHeight = 480;
+
+ /**
+ * QoS levels to define the guarantee of delivery for a message
+ */
+ public enum QoS {
+ AT_MOST_ONCE, // Fire and forget
+ AT_LEAST_ONCE, // Receiver is able to receive multiple times
+ EXACTLY_ONCE, // Receiver only receives exactly once
+ }
+
+ /**
+ * List of protocols supported by AITT framework
+ */
+ public enum Protocol {
+ MQTT(0x1 << 0), // Publish message through the MQTT
+ TCP(0x1 << 1), // Publish message to peers using the TCP
+ UDP(0x1 << 2), // Publish message to peers using the UDP
+ SRTP(0x1 << 3), // Publish message to peers using the SRTP
+ WEBRTC(0x1 << 4); // Publish message to peers using the WEBRTC
+
+ private final int value;
+
+ private Protocol(int value) {
+ this.value = value;
+ }
+
+ public int getValue() {
+ return value;
+ }
+
+ public static Protocol fromInt(long value) {
+ for (Protocol type : values()) {
+ if (type.getValue() == value) {
+ return type;
+ }
+ }
+ return null;
+ }
+ }
+
+ /**
+ * HostTable to store String and PortTable instances
+ */
+ private static class HostTable {
+ HashMap<String, PortTable> hostMap = new HashMap<>();
+ }
+
+ /**
+ * PortTable to store port with protocol, webRTC transportHandler object instance
+ */
+ private static class PortTable {
+ HashMap<Integer, Pair<Protocol , Object>> portMap = new HashMap<>();
+ }
+
+ /**
+ * Interface to implement connection status callback
+ */
+ public interface ConnectionCallback {
+ void onConnected();
+ void onDisconnected();
+ }
+
+ /**
+ * Interface to implement callback method for subscribe call
+ */
+ public interface SubscribeCallback {
+ void onMessageReceived(AittMessage message);
+ }
+
+ /**
+ * Aitt constructor to create AITT object
+ * @param appContext context of the application invoking the constructor
+ * @param id Unique identifier for the Aitt instance
+ */
+ public Aitt(Context appContext , String id) throws InstantiationException {
+ this(appContext , id, AITT_LOCALHOST, false);
+ }
+
+ /**
+ * Aitt constructor to create AITT object
+ * @param appContext context of the application invoking the constructor
+ * @param id Unique identifier for the Aitt instance
+ * @param ip IP address of the device, on which application is running
+ * @param clearSession "clear" the current session when the client disconnects
+ */
+ public Aitt(Context appContext, String id, String ip, boolean clearSession) throws InstantiationException {
+ if (appContext == null) {
+ throw new IllegalArgumentException("Invalid appContext");
+ }
+ if (id == null || id.isEmpty()) {
+ throw new IllegalArgumentException("Invalid id");
+ }
+ instance = initJNI(id, ip, clearSession);
+ if (instance == 0L) {
+ throw new InstantiationException("Failed to instantiate native instance");
+ }
+ this.ip = ip;
+ this.appContext = appContext;
+ }
+
+ /**
+ * Method to set connection status callback
+ * @param callback ConnectionCallback to which status should be updated
+ */
+ public void setConnectionCallback(ConnectionCallback callback) {
+ if (callback == null) {
+ throw new IllegalArgumentException("Invalid callback");
+ }
+ connectionCallback = callback;
+ setConnectionCallbackJNI(instance);
+ }
+
+ /**
+ * Method to connect to MQTT broker
+ * @param brokerIp Broker IP address to which, device has to connect
+ */
+ public void connect(@Nullable String brokerIp) {
+ connect(brokerIp, AITT_PORT);
+ }
+
+ /**
+ * Method to connect to MQTT broker
+ * @param brokerIp Broker IP address to which, device has to connect
+ * @param port Broker port number to which, device has to connect
+ */
+ public void connect(@Nullable String brokerIp, int port) {
+ if (brokerIp == null || brokerIp.isEmpty()) {
+ brokerIp = AITT_LOCALHOST;
+ }
+ connectJNI(instance, brokerIp, port);
+ //Subscribe to java discovery topic
+ subscribeJNI(instance, JAVA_SPECIFIC_DISCOVERY_TOPIC, Protocol.MQTT.getValue(), QoS.EXACTLY_ONCE.ordinal());
+ }
+
+ /**
+ * Method to disconnect from MQTT broker
+ */
+ public void disconnect() {
+ publishJNI(instance, JAVA_SPECIFIC_DISCOVERY_TOPIC, new byte[0], 0, Protocol.MQTT.getValue(), QoS.AT_LEAST_ONCE.ordinal(), true);
+
+ disconnectJNI(instance);
+ try {
+ close();
+ } catch (Exception e) {
+ Log.e(TAG, "Error during disconnect", e);
+ }
+ }
+
+ /**
+ * Method to publish message to a specific topic
+ * @param topic String to which message needs to be published
+ * @param message Byte message that needs to be published
+ */
+ public void publish(String topic, byte[] message) {
+ EnumSet<Protocol> protocolSet = EnumSet.of(Protocol.MQTT);
+ publish(topic, message, protocolSet, QoS.AT_MOST_ONCE, false);
+ }
+
+ /**
+ * Method to publish message to a specific topic
+ * @param topic String to which message needs to be published
+ * @param message Byte message that needs to be published
+ * @param protocol Protocol to be used to publish message
+ * @param qos QoS at which the message should be delivered
+ * @param retain Boolean to decide whether or not the message should be retained by the broker
+ */
+ public void publish(String topic, byte[] message, Protocol protocol, QoS qos, boolean retain) {
+ EnumSet<Protocol> protocolSet = EnumSet.of(protocol);
+ publish(topic, message, protocolSet, qos, retain);
+ }
+
+ /**
+ * Method to publish message to a specific topic
+ * @param topic String to which message needs to be published
+ * @param message Byte message that needs to be published
+ * @param protocols Protocol to be used to publish message
+ * @param qos QoS at which the message should be delivered
+ * @param retain Boolean to decide whether or not the message should be retained by the broker
+ */
+ public void publish(String topic, byte[] message, EnumSet<Protocol> protocols, QoS qos, boolean retain) {
+ if (topic == null || topic.isEmpty()) {
+ throw new IllegalArgumentException(INVALID_TOPIC);
+ }
+ if (protocols.isEmpty()) {
+ throw new IllegalArgumentException("Invalid protocols");
+ }
+ if (protocols.contains(Protocol.WEBRTC)) {
+ try {
+ synchronized (this) {
+ if (!publishTable.containsKey(topic)) {
+ Log.e(TAG, "Invalid publish request over unsubscribed topic");
+ return;
+ }
+ HostTable hostTable = publishTable.get(topic);
+ for (String hostIp : hostTable.hostMap.keySet()) {
+ PortTable portTable = hostTable.hostMap.get(hostIp);
+ for (Integer port : portTable.portMap.keySet()) {
+ Object transportHandler = portTable.portMap.get(port).second;
+ publishWebRTC(portTable, topic, transportHandler, hostIp, port, message);
+ }
+ }
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Error during publish", e);
+ }
+ } else {
+ int proto = protocolsToInt(protocols);
+ publishJNI(instance, topic, message, message.length, proto, qos.ordinal(), retain);
+ }
+ }
+
+ /**
+ * Method used to identify data type for webRTC channel and transfer data
+ * @param portTable portTable has information about port and associated protocol with transport Handler object
+ * @param topic The topic to which data is published
+ * @param transportHandler WebRTC object instance
+ * @param ip IP address of the destination
+ * @param port Port number of the destination
+ * @param message Data to be tranferred over WebRTC
+ */
+ private void publishWebRTC(PortTable portTable, String topic, Object transportHandler, String ip, int port, byte[] message) {
+ WebRTC.DataType dataType = topic.endsWith(RESPONSE_POSTFIX) ? WebRTC.DataType.MESSAGE : WebRTC.DataType.VIDEOFRAME;
+ WebRTC webrtcHandler;
+ if (transportHandler == null) {
+ webrtcHandler = new WebRTC(dataType, appContext);
+ transportHandler = webrtcHandler;
+ portTable.portMap.replace(port, new Pair<>(Protocol.WEBRTC, transportHandler));
+ webrtcHandler.connect(ip, port);
+ } else {
+ webrtcHandler = (WebRTC) transportHandler;
+ }
+ if (dataType == WebRTC.DataType.MESSAGE) {
+ webrtcHandler.sendMessageData(message);
+ } else if (dataType == WebRTC.DataType.VIDEOFRAME) {
+ webrtcHandler.sendVideoData(message, frameWidth, frameHeight);
+ }
+ }
+
+ /**
+ * Method to subscribe to a specific topic
+ * @param topic String to which applications can subscribe, to receive data
+ * @param callback Callback object specific to a subscribe call
+ */
+ public void subscribe(String topic, SubscribeCallback callback) {
+ EnumSet<Protocol> protocolSet = EnumSet.of(Protocol.MQTT);
+ subscribe(topic, callback, protocolSet, QoS.AT_MOST_ONCE);
+ }
+
+ /**
+ * Method to subscribe to a specific topic
+ * @param topic String to which applications can subscribe, to receive data
+ * @param callback Callback object specific to a subscribe call
+ * @param protocol Protocol supported by application, invoking subscribe
+ * @param qos QoS at which the message should be delivered
+ */
+ public void subscribe(String topic, SubscribeCallback callback, Protocol protocol, QoS qos) {
+ EnumSet<Protocol> protocolSet = EnumSet.of(protocol);
+ subscribe(topic, callback, protocolSet, qos);
+ }
+
+ /**
+ * Method to subscribe to a specific topic
+ * @param topic String to which applications can subscribe, to receive data
+ * @param callback Callback object specific to a subscribe call
+ * @param protocols Protocol supported by application, invoking subscribe
+ * @param qos QoS at which the message should be delivered
+ */
+ public void subscribe(String topic, SubscribeCallback callback, EnumSet<Protocol> protocols, QoS qos) {
+ if (topic == null || topic.isEmpty()) {
+ throw new IllegalArgumentException(INVALID_TOPIC);
+ }
+ if (callback == null) {
+ throw new IllegalArgumentException("Invalid callback");
+ }
+ if (protocols.isEmpty()) {
+ throw new IllegalArgumentException("Invalid protocols");
+ }
+ try {
+ if (protocols.contains(Protocol.WEBRTC)) {
+ WebRTC.ReceiveDataCallback cb = frame -> {
+ AittMessage message = new AittMessage(frame);
+ message.setTopic(topic);
+ messageReceived(message);
+ };
+ WebRTC.DataType dataType = topic.endsWith(RESPONSE_POSTFIX) ? WebRTC.DataType.MESSAGE : WebRTC.DataType.VIDEOFRAME;
+ WebRTCServer ws = new WebRTCServer(appContext, dataType, cb);
+ int serverPort = ws.start();
+ if (serverPort < 0) {
+ throw new IllegalArgumentException("Failed to start webRTC server-socket");
+ }
+ synchronized (this) {
+ subscribeMap.put(topic, new Pair(Protocol.WEBRTC, ws));
+ }
+ byte[] data = wrapPublishData(topic, serverPort);
+ publishJNI(instance, JAVA_SPECIFIC_DISCOVERY_TOPIC, data, data.length, Protocol.MQTT.value, QoS.EXACTLY_ONCE.ordinal(), true);
+ } else {
+ int proto = protocolsToInt(protocols);
+ Long pObject = subscribeJNI(instance, topic, proto, qos.ordinal());
+ synchronized (this) {
+ aittSubId.put(topic, pObject);
+ }
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Error during subscribe", e);
+ }
+ addCallBackToSubscribeMap(topic, callback);
+ }
+
+ /**
+ * Method to wrap topic, device IP address, webRTC server instance port number for publishing
+ * @param topic Topic to which the application has subscribed to
+ * @param serverPort Port number of the WebRTC server instance
+ * @return Byte data wrapped, contains topic, device IP, webRTC server port number
+ */
+ private byte[] wrapPublishData(String topic, int serverPort) {
+ FlexBuffersBuilder fbb = new FlexBuffersBuilder(ByteBuffer.allocate(512));
+ {
+ int smap = fbb.startMap();
+ fbb.putString(STATUS, JOIN_NETWORK);
+ fbb.putString("host", this.ip);
+ {
+ int smap1 = fbb.startMap();
+ fbb.putInt("protocol", Protocol.WEBRTC.value);
+ fbb.putInt("port", serverPort);
+ fbb.endMap(topic, smap1);
+ }
+ fbb.endMap(null, smap);
+ }
+ ByteBuffer buffer = fbb.finish();
+ byte[] data = new byte[buffer.remaining()];
+ buffer.get(data, 0, data.length);
+ return data;
+ }
+
+ /**
+ * Method to map subscribe callback instance to subscribing topic
+ * @param topic String to which application can subscribe
+ * @param callback Subscribe callback instance created during subscribe call
+ */
+ private void addCallBackToSubscribeMap(String topic, SubscribeCallback callback) {
+ synchronized (this) {
+ try {
+ ArrayList<SubscribeCallback> cbList = subscribeCallbacks.get(topic);
+
+ if (cbList != null) {
+ // check whether the list already contains same callback
+ if (!cbList.contains(callback)) {
+ cbList.add(callback);
+ }
+ } else {
+ cbList = new ArrayList<>();
+ cbList.add(callback);
+ subscribeCallbacks.put(topic, cbList);
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Error during callback add", e);
+ }
+ }
+ }
+
+ /**
+ * Method to unsubscribe to a topic, subscribed by application
+ * @param topic String topic to which application had subscribed
+ */
+ public void unsubscribe(String topic) {
+ if (topic == null || topic.isEmpty()) {
+ throw new IllegalArgumentException(INVALID_TOPIC);
+ }
+
+ boolean isRemoved = false;
+ try {
+ synchronized (this) {
+ if (subscribeMap.containsKey(topic) && subscribeMap.get(topic).first == Protocol.WEBRTC) {
+ WebRTCServer ws = (WebRTCServer) subscribeMap.get(topic).second;
+ ws.stop();
+ subscribeMap.remove(topic);
+ isRemoved = true;
+ }
+ }
+
+ if (!isRemoved) {
+ Long paittSubId = null;
+ synchronized (this) {
+ if (aittSubId.containsKey(topic)) {
+ paittSubId = aittSubId.get(topic);
+ }
+ }
+ if (paittSubId != null) {
+ unsubscribeJNI(instance, paittSubId);
+ }
+ }
+
+ synchronized (this) {
+ subscribeCallbacks.remove(topic);
+ aittSubId.remove(topic);
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Error during unsubscribe", e);
+ }
+ }
+
+ /**
+ * Method invoked from JNI layer to Java layer for MQTT connection status update
+ * @param status Status of the MQTT connection
+ */
+ private void connectionStatusCallback(int status) {
+ if (status == 0) {
+ connectionCallback.onDisconnected();
+ } else {
+ connectionCallback.onConnected();
+ }
+ }
+
+ /**
+ * Method invoked from JNI layer to Java layer for message exchange
+ * @param topic Topic to which message callback is called
+ * @param payload Byte data shared from JNI layer to Java layer
+ */
+ private void messageCallback(String topic, byte[] payload) {
+ try {
+ if (topic.compareTo(JAVA_SPECIFIC_DISCOVERY_TOPIC) == 0) {
+ if (payload.length <= 0) {
+ Log.e(TAG, "Invalid payload, Ignore");
+ return;
+ }
+ discoveryMessageCallback(payload);
+ } else {
+ AittMessage message = new AittMessage(payload);
+ message.setTopic(topic);
+ messageReceived(message);
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Error processing callback ", e);
+ }
+ }
+
+ /**
+ * This API is called when MQTT subscribe callback is invoked at aitt C++.
+ * It has discovery information in a "payload"
+ * @param payload
+ * Flexbuffer discovery message expected
+ * {
+ * "status": "connected",
+ * "host": "127.0.0.1",
+ * "/customTopic/aitt/faceRecog": {
+ * "protocol": 1,
+ * "port": 108081,
+ * },
+ * "/customTopic/aitt/ASR": {
+ * "protocol": 2,
+ * "port": 102020,
+ * },
+ *
+ * ...
+ *
+ * "/customTopic/aitt/+": {
+ * "protocol": 3,
+ * "port": 20123,
+ * },
+ * }
+ */
+
+ /**
+ * Method to receive discovery message with device, protocol and other details and update publish table
+ * @param payload Byte data having discovery related message
+ */
+ private void discoveryMessageCallback(byte[] payload) {
+ try {
+ ByteBuffer buffer = ByteBuffer.wrap(payload);
+ FlexBuffers.Map map = FlexBuffers.getRoot(buffer).asMap();
+ String host = map.get("host").asString();
+ String status = map.get(STATUS).asString();
+ if (status != null && status.compareTo(WILL_LEAVE_NETWORK) == 0) {
+ synchronized (this) {
+ for (Map.Entry<String, HostTable> entry : publishTable.entrySet()) {
+ HostTable hostTable = entry.getValue();
+ if (hostTable != null) {
+ hostTable.hostMap.remove(host);
+ }
+ }
+ }
+ return;
+ }
+
+ FlexBuffers.KeyVector topics = map.keys();
+ for (int i = 0; i < topics.size(); i++) {
+ String _topic = topics.get(i).toString();
+ if (_topic.compareTo("host") == 0 || _topic.compareTo(STATUS) == 0) {
+ continue;
+ }
+
+ FlexBuffers.Map _map = map.get(_topic).asMap();
+ int port = _map.get("port").asInt();
+ long p = _map.get("protocol").asUInt();
+ Protocol protocol = Protocol.fromInt(p);
+ updatePublishTable(_topic, host, port, protocol);
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Error during discovery callback processing", e);
+ }
+ }
+
+ /**
+ * Method used to update Publish table of the application
+ * @param topic The topic to which, other parties have subscribed to
+ * @param host String which specifies a particular host
+ * @param port Port of the party which subscribed to given topic
+ * @param protocol protocol supported by the party which subscribed to given topic
+ */
+ private void updatePublishTable(String topic, String host, int port, Protocol protocol) {
+ synchronized(this) {
+ if (!publishTable.containsKey(topic)) {
+ PortTable portTable = new PortTable();
+ portTable.portMap.put(port, new Pair(protocol , null));
+ HostTable hostTable = new HostTable();
+ hostTable.hostMap.put(host, portTable);
+ publishTable.put(topic, hostTable);
+ return;
+ }
+
+ HostTable hostTable = publishTable.get(topic);
+ if (!hostTable.hostMap.containsKey(host)) {
+ PortTable portTable = new PortTable();
+ portTable.portMap.put(port, new Pair(protocol , null));
+ hostTable.hostMap.put(host, portTable);
+ return;
+ }
+
+ PortTable portTable = hostTable.hostMap.get(host);
+ if (portTable.portMap.containsKey(port)) {
+ portTable.portMap.replace(port, new Pair(protocol , null));
+ return;
+ }
+
+ portTable.portMap.put(port, new Pair(protocol , null));
+ }
+ }
+
+ /**
+ * Method that receives message from JNI layer for topics other than discovery topics
+ * @param message The data received from JNI layer to be sent to application layer
+ */
+ private void messageReceived(AittMessage message) {
+ try {
+ String topic = message.getTopic();
+ synchronized (this) {
+ ArrayList<SubscribeCallback> cbList = subscribeCallbacks.get(topic);
+
+ if (cbList != null) {
+ for (int i = 0; i < cbList.size(); i++) {
+ cbList.get(i).onMessageReceived(message);
+ }
+ }
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Error during messageReceived", e);
+ }
+ }
+
+ /**
+ * Method used to convert EnumSet protocol into int
+ * @param protocols List of protocols
+ * @return The protocol value
+ */
+ private int protocolsToInt(EnumSet<Protocol> protocols) {
+ int proto = 0;
+ for (Protocol p : Protocol.values()) {
+ if (protocols.contains(p)) {
+ proto += p.getValue();
+ }
+ }
+ return proto;
+ }
+
+ /**
+ * Method to close all the callbacks and release resources
+ */
+ public void close() {
+ synchronized (this) {
+ if(subscribeCallbacks!=null) {
+ subscribeCallbacks.clear();
+ subscribeCallbacks = null;
+ }
+ if(aittSubId!=null) {
+ aittSubId.clear();
+ aittSubId = null;
+ }
+ }
+ }
+
+ /* native API's set */
+ /* Native API to initialize JNI */
+ private native long initJNI(String id, String ip, boolean clearSession);
+
+ /* Native API for connecting to broker */
+ private native void connectJNI(long instance, final String host, int port);
+
+ /* Native API for disconnecting from broker */
+ private native void disconnectJNI(long instance);
+
+ /* Native API for setting connection callback */
+ private native void setConnectionCallbackJNI(long instance);
+
+ /* Native API for publishing to a topic */
+ private native void publishJNI(long instance, final String topic, final byte[] data, long datalen, int protocol, int qos, boolean retain);
+
+ /* Native API for subscribing to a topic */
+ private native long subscribeJNI(long instance, final String topic, int protocol, int qos);
+
+ /* Native API for unsubscribing a topic */
+ private native void unsubscribeJNI(long instance, final long aittSubId);
+}
diff --git a/android/aitt/src/main/java/com/samsung/android/aitt/AittMessage.java b/android/aitt/src/main/java/com/samsung/android/aitt/AittMessage.java
new file mode 100644
index 0000000..7ba570f
--- /dev/null
+++ b/android/aitt/src/main/java/com/samsung/android/aitt/AittMessage.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright (c) 2022 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.samsung.android.aitt;
+
+public class AittMessage {
+ private String topic;
+ private String correlation;
+ private String replyTopic;
+ private int sequence;
+ private boolean endSequence;
+ private byte[] payload;
+
+ /**
+ * AittMessage constructor for initializing
+ */
+ public AittMessage() {
+ setPayload(new byte[]{});
+ }
+
+ /**
+ * AittMessage constructor with params for initializing
+ * @param payload The message data set as payload
+ */
+ public AittMessage(byte[] payload) {
+ setPayload(payload);
+ }
+
+ /**
+ * Method to get Topic assigned with AittMessage object
+ * @return The string topic
+ */
+ public String getTopic() {
+ return topic;
+ }
+
+ /**
+ * Method to set topic to AittMessage object
+ * @param topic String topic to be assigned to the object
+ */
+ public void setTopic(String topic) {
+ this.topic = topic;
+ }
+
+ /**
+ * Method to get Correlation
+ * @return the correlation
+ */
+ public String getCorrelation() {
+ return correlation;
+ }
+
+ /**
+ * Method to set correlation
+ * @param correlation correlation string to be set
+ */
+ public void setCorrelation(String correlation) {
+ this.correlation = correlation;
+ }
+
+ /**
+ * Method to get the reply topic
+ * @return the string of reply topic
+ */
+ public String getReplyTopic() {
+ return replyTopic;
+ }
+
+ /**
+ * Method to set reply topic
+ * @param replyTopic String that is set as reply topic
+ */
+ public void setReplyTopic(String replyTopic) {
+ this.replyTopic = replyTopic;
+ }
+
+ /**
+ * Method used to get sequence
+ * @return the sequence
+ */
+ public int getSequence() {
+ return sequence;
+ }
+
+ /**
+ * Method used to set sequence
+ * @param sequence the sequence value to be set
+ */
+ public void setSequence(int sequence) {
+ this.sequence = sequence;
+ }
+
+ /**
+ * Method used to increase the sequence by one
+ */
+ public void increaseSequence() {
+ sequence = sequence+1;
+ }
+
+ /**
+ * Method used to set endSequence
+ * @param endSequence boolean value to be set to end sequence
+ */
+ public void setEndSequence(boolean endSequence) {
+ this.endSequence = endSequence;
+ }
+
+ /**
+ * Method used to get if sequence is ended
+ * @return The state of sequence
+ */
+ public boolean isEndSequence() {
+ return endSequence;
+ }
+
+ /**
+ * Method used to retrieve payload
+ * @return The data in byte[] format
+ */
+ public byte[] getPayload() {
+ return payload;
+ }
+
+ /**
+ * Method used to clear the payload
+ */
+ public void clearPayload() {
+ this.payload = new byte[]{};
+ }
+
+ /**
+ * Method used to set payload to AittMessage object
+ * @param payload the byte[] message/payload to be set
+ */
+ public void setPayload(byte[] payload) {
+ if (payload == null) {
+ throw new NullPointerException();
+ }
+ this.payload = payload;
+ }
+}
diff --git a/android/aitt/src/main/jni/aitt_jni.cc b/android/aitt/src/main/jni/aitt_jni.cc
new file mode 100644
index 0000000..801b906
--- /dev/null
+++ b/android/aitt/src/main/jni/aitt_jni.cc
@@ -0,0 +1,368 @@
+/*
+ * Copyright (c) 2022 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "aitt_jni.h"
+
+AittNativeInterface::CallbackContext AittNativeInterface::cbContext = {
+ .jvm = nullptr,
+ .messageCallbackMethodID = nullptr,
+};
+
+AittNativeInterface::AittNativeInterface(std::string &mqId, std::string &ip, bool clearSession)
+ : cbObject(nullptr), aitt(mqId, ip, clearSession)
+{
+}
+
+AittNativeInterface::~AittNativeInterface(void)
+{
+ if (cbObject != nullptr) {
+ JNIEnv *env = nullptr;
+ bool attached = false;
+ int JNIStatus = cbContext.jvm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_6);
+ if (JNIStatus == JNI_EDETACHED) {
+ if (cbContext.jvm->AttachCurrentThread(&env, nullptr) != 0) {
+ JNI_LOG(ANDROID_LOG_ERROR, TAG, "Failed to attach current thread");
+ } else {
+ attached = true;
+ }
+ } else if (JNIStatus == JNI_EVERSION) {
+ JNI_LOG(ANDROID_LOG_ERROR, TAG, "Unsupported version");
+ }
+
+ if (env != nullptr) {
+ env->DeleteGlobalRef(cbObject);
+ cbObject = nullptr;
+ }
+ if (attached) {
+ cbContext.jvm->DetachCurrentThread();
+ }
+ }
+}
+
+std::string AittNativeInterface::GetStringUTF(JNIEnv *env, jstring str)
+{
+ if (env == nullptr) {
+ JNI_LOG(ANDROID_LOG_ERROR, TAG, "Env is null");
+ return nullptr;
+ }
+ const char *cstr = env->GetStringUTFChars(str, 0);
+ if (cstr == nullptr) {
+ JNI_LOG(ANDROID_LOG_ERROR, TAG, "Failed to get string UTF chars");
+ return nullptr;
+ }
+ std::string _str(cstr);
+ env->ReleaseStringUTFChars(str, cstr);
+ return _str;
+}
+
+void AittNativeInterface::Java_com_samsung_android_aitt_Aitt_connectJNI(JNIEnv *env,
+ jobject jniInterfaceObject, jlong handle, jstring host, jint port)
+{
+ if (env == nullptr || jniInterfaceObject == nullptr) {
+ JNI_LOG(ANDROID_LOG_ERROR, TAG, "Env or Jobject is null");
+ return;
+ }
+ AittNativeInterface *instance = reinterpret_cast<AittNativeInterface *>(handle);
+ std::string brokerIp = GetStringUTF(env, host);
+ if (brokerIp.empty()) {
+ return;
+ }
+
+ int brokerPort = (int)port;
+
+ try {
+ instance->aitt.Connect(brokerIp, brokerPort);
+ } catch (std::exception &e) {
+ JNI_LOG(ANDROID_LOG_ERROR, TAG, "Failed to connect");
+ JNI_LOG(ANDROID_LOG_ERROR, TAG, e.what());
+ }
+}
+
+void AittNativeInterface::Java_com_samsung_android_aitt_Aitt_publishJNI(JNIEnv *env,
+ jobject jniInterfaceObject, jlong handle, jstring topic, jbyteArray data, jlong datalen,
+ jint protocol, jint qos, jboolean retain)
+{
+ if (env == nullptr || jniInterfaceObject == nullptr) {
+ JNI_LOG(ANDROID_LOG_ERROR, TAG, "Env or jobject is null");
+ return;
+ }
+ AittNativeInterface *instance = reinterpret_cast<AittNativeInterface *>(handle);
+ std::string customTopic = GetStringUTF(env, topic);
+ if (customTopic.empty()) {
+ return;
+ }
+
+ int num_bytes = (int)datalen;
+ const char *cdata = (char *)env->GetByteArrayElements(data, 0);
+ if (cdata == nullptr) {
+ JNI_LOG(ANDROID_LOG_ERROR, TAG, "Failed to get byte array elements");
+ return;
+ }
+ const void *_data = reinterpret_cast<const void *>(cdata);
+
+ AittProtocol _protocol = static_cast<AittProtocol>(protocol);
+ AittQoS _qos = static_cast<AittQoS>(qos);
+ bool _retain = (bool)retain;
+
+ try {
+ instance->aitt.Publish(customTopic, _data, num_bytes, _protocol, _qos, _retain);
+ } catch (std::exception &e) {
+ JNI_LOG(ANDROID_LOG_ERROR, TAG, "Failed to publish");
+ JNI_LOG(ANDROID_LOG_ERROR, TAG, e.what());
+ }
+ env->ReleaseByteArrayElements(data, reinterpret_cast<jbyte *>((char *)cdata), 0);
+}
+
+void AittNativeInterface::Java_com_samsung_android_aitt_Aitt_disconnectJNI(JNIEnv *env,
+ jobject jniInterfaceObject, jlong handle)
+{
+ if (env == nullptr || jniInterfaceObject == nullptr) {
+ JNI_LOG(ANDROID_LOG_ERROR, TAG, "Env or Jobject is null");
+ return;
+ }
+ AittNativeInterface *instance = reinterpret_cast<AittNativeInterface *>(handle);
+ try {
+ instance->aitt.Disconnect();
+ } catch (std::exception &e) {
+ JNI_LOG(ANDROID_LOG_ERROR, TAG, "Failed to disconnect");
+ JNI_LOG(ANDROID_LOG_ERROR, TAG, e.what());
+ }
+}
+
+jlong AittNativeInterface::Java_com_samsung_android_aitt_Aitt_subscribeJNI(JNIEnv *env,
+ jobject jniInterfaceObject, jlong handle, jstring topic, jint protocol, jint qos)
+{
+ if (env == nullptr || jniInterfaceObject == nullptr) {
+ JNI_LOG(ANDROID_LOG_ERROR, TAG, "Env or Jobject is null");
+ return 0L;
+ }
+ AittNativeInterface *instance = reinterpret_cast<AittNativeInterface *>(handle);
+ std::string customTopic = GetStringUTF(env, topic);
+ if (customTopic.empty()) {
+ return 0L;
+ }
+
+ AittProtocol _protocol = static_cast<AittProtocol>(protocol);
+ AittQoS _qos = static_cast<AittQoS>(qos);
+
+ AittSubscribeID _id = nullptr;
+ try {
+ _id = instance->aitt.Subscribe(
+ customTopic,
+ [&](aitt::MSG *handle, const void *msg, const int szmsg, void *cbdata) -> void {
+ AittNativeInterface *instance = reinterpret_cast<AittNativeInterface *>(cbdata);
+ JNIEnv *env;
+ int JNIStatus =
+ cbContext.jvm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_6);
+ if (JNIStatus == JNI_EDETACHED) {
+ if (cbContext.jvm->AttachCurrentThread(&env, nullptr) != 0) {
+ JNI_LOG(ANDROID_LOG_ERROR, TAG, "Failed to attach current thread");
+ return;
+ }
+ } else if (JNIStatus == JNI_EVERSION) {
+ JNI_LOG(ANDROID_LOG_ERROR, TAG, "Unsupported version");
+ return;
+ }
+ if (env != nullptr && instance->cbObject != nullptr) {
+ jstring _topic = env->NewStringUTF(handle->GetTopic().c_str());
+ if (env->ExceptionCheck() == true) {
+ JNI_LOG(ANDROID_LOG_ERROR, TAG, "Failed to create new UTF string");
+ cbContext.jvm->DetachCurrentThread();
+ return;
+ }
+
+ jbyteArray array = env->NewByteArray(szmsg);
+ auto _msg = reinterpret_cast<unsigned char *>(const_cast<void *>(msg));
+ env->SetByteArrayRegion(array, 0, szmsg, reinterpret_cast<jbyte *>(_msg));
+ if (env->ExceptionCheck() == true) {
+ JNI_LOG(ANDROID_LOG_ERROR, TAG, "Failed to set byte array");
+ cbContext.jvm->DetachCurrentThread();
+ return;
+ }
+
+ env->CallVoidMethod(instance->cbObject, cbContext.messageCallbackMethodID,
+ _topic, array);
+ if (env->ExceptionCheck() == true) {
+ JNI_LOG(ANDROID_LOG_ERROR, TAG, "Failed to call void method");
+ cbContext.jvm->DetachCurrentThread();
+ return;
+ }
+ }
+ cbContext.jvm->DetachCurrentThread();
+ },
+ reinterpret_cast<void *>(instance), _protocol, _qos);
+ } catch (std::exception &e) {
+ JNI_LOG(ANDROID_LOG_ERROR, TAG, "Failed to subscribe");
+ JNI_LOG(ANDROID_LOG_ERROR, TAG, e.what());
+ }
+ return (jlong)_id;
+}
+
+void AittNativeInterface::Java_com_samsung_android_aitt_Aitt_unsubscribeJNI(JNIEnv *env,
+ jobject jniInterfaceObject, jlong handle, jlong aittSubId)
+{
+ if (env == nullptr || jniInterfaceObject == nullptr) {
+ JNI_LOG(ANDROID_LOG_ERROR, TAG, "Env or Jobject is null");
+ return;
+ }
+ AittNativeInterface *instance = reinterpret_cast<AittNativeInterface *>(handle);
+ void *subId = reinterpret_cast<void *>(aittSubId);
+ try {
+ instance->aitt.Unsubscribe(static_cast<AittSubscribeID>(subId));
+ } catch (std::exception &e) {
+ JNI_LOG(ANDROID_LOG_ERROR, TAG, "Failed to unsubscribe");
+ JNI_LOG(ANDROID_LOG_ERROR, TAG, e.what());
+ }
+}
+
+void AittNativeInterface::Java_com_samsung_android_aitt_Aitt_setConnectionCallbackJNI(JNIEnv *env,
+ jobject jniInterfaceObject, jlong handle)
+{
+ if (env == nullptr || jniInterfaceObject == nullptr) {
+ JNI_LOG(ANDROID_LOG_ERROR, TAG, "Env or Jobject is null");
+ return;
+ }
+ AittNativeInterface *instance = reinterpret_cast<AittNativeInterface *>(handle);
+
+ try {
+ instance->aitt.SetConnectionCallback(
+ [&](AITT &handle, int status, void *user_data) -> void {
+ AittNativeInterface *instance = reinterpret_cast<AittNativeInterface *>(user_data);
+ JNIEnv *env;
+ int JNIStatus =
+ cbContext.jvm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_6);
+ if (JNIStatus == JNI_EDETACHED) {
+ if (cbContext.jvm->AttachCurrentThread(&env, nullptr) != 0) {
+ JNI_LOG(ANDROID_LOG_ERROR, TAG, "Failed to attach current thread");
+ return;
+ }
+ } else if (JNIStatus == JNI_EVERSION) {
+ JNI_LOG(ANDROID_LOG_ERROR, TAG, "Unsupported version");
+ return;
+ }
+ if (env != nullptr && instance->cbObject != nullptr) {
+ env->CallVoidMethod(instance->cbObject, cbContext.connectionCallbackMethodID,
+ (jint)status);
+ if (env->ExceptionCheck() == true) {
+ JNI_LOG(ANDROID_LOG_ERROR, TAG, "Failed to call void method");
+ cbContext.jvm->DetachCurrentThread();
+ return;
+ }
+ }
+ cbContext.jvm->DetachCurrentThread();
+ },
+ reinterpret_cast<void *>(instance));
+ } catch (std::exception &e) {
+ JNI_LOG(ANDROID_LOG_ERROR, TAG, "Failed to set connection callback");
+ JNI_LOG(ANDROID_LOG_ERROR, TAG, e.what());
+ }
+}
+
+jlong AittNativeInterface::Java_com_samsung_android_aitt_Aitt_initJNI(JNIEnv *env,
+ jobject jniInterfaceObject, jstring id, jstring ip, jboolean clearSession)
+{
+ if (env == nullptr || jniInterfaceObject == nullptr) {
+ JNI_LOG(ANDROID_LOG_ERROR, TAG, "Env or Jobject is null");
+ return JNI_ERR;
+ }
+ std::string mqId = GetStringUTF(env, id);
+ if (mqId.empty()) {
+ return 0L;
+ }
+
+ std::string selfIp = GetStringUTF(env, ip);
+ if (selfIp.empty()) {
+ return 0L;
+ }
+
+ bool _clearSession = clearSession;
+
+ AittNativeInterface *instance;
+ try {
+ instance = new AittNativeInterface(mqId, selfIp, _clearSession);
+ } catch (std::exception &e) {
+ JNI_LOG(ANDROID_LOG_ERROR, TAG, "Failed to create new instance");
+ JNI_LOG(ANDROID_LOG_ERROR, TAG, e.what());
+ return 0L;
+ }
+
+ if (env->GetJavaVM(&cbContext.jvm) != JNI_OK) {
+ JNI_LOG(ANDROID_LOG_ERROR, TAG, "Unable to get Java VM");
+ delete instance;
+ instance = nullptr;
+ return 0L;
+ }
+ try {
+ instance->cbObject = env->NewGlobalRef(jniInterfaceObject);
+
+ jclass callbackClass = env->FindClass("com/samsung/android/aitt/Aitt");
+ cbContext.messageCallbackMethodID =
+ env->GetMethodID(callbackClass, "messageCallback", "(Ljava/lang/String;[B)V");
+ cbContext.connectionCallbackMethodID =
+ env->GetMethodID(callbackClass, "connectionStatusCallback", "(I)V");
+ env->DeleteLocalRef(callbackClass);
+ } catch (std::exception &e) {
+ JNI_LOG(ANDROID_LOG_ERROR, TAG, e.what());
+ delete instance;
+ instance = nullptr;
+ return 0L;
+ }
+
+ return reinterpret_cast<long>(instance);
+}
+
+JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved)
+{
+ JNIEnv *env = nullptr;
+ if (vm->GetEnv((void **)(&env), JNI_VERSION_1_6) != JNI_OK) {
+ JNI_LOG(ANDROID_LOG_ERROR, TAG, "Not a valid JNI version");
+ return JNI_ERR;
+ }
+ jclass klass = env->FindClass("com/samsung/android/aitt/Aitt");
+ if (nullptr == klass) {
+ JNI_LOG(ANDROID_LOG_ERROR, TAG, "klass is null");
+ return JNI_ERR;
+ }
+ static JNINativeMethod aitt_jni_methods[] = {
+ {"initJNI", "(Ljava/lang/String;Ljava/lang/String;Z)J",
+ reinterpret_cast<void *>(
+ AittNativeInterface::Java_com_samsung_android_aitt_Aitt_initJNI)},
+ {"connectJNI", "(JLjava/lang/String;I)V",
+ reinterpret_cast<void *>(
+ AittNativeInterface::Java_com_samsung_android_aitt_Aitt_connectJNI)},
+ {"subscribeJNI", "(JLjava/lang/String;II)J",
+ reinterpret_cast<void *>(
+ AittNativeInterface::Java_com_samsung_android_aitt_Aitt_subscribeJNI)},
+ {"publishJNI", "(JLjava/lang/String;[BJIIZ)V",
+ reinterpret_cast<void *>(
+ AittNativeInterface::Java_com_samsung_android_aitt_Aitt_publishJNI)},
+ {"unsubscribeJNI", "(JJ)V",
+ reinterpret_cast<void *>(
+ AittNativeInterface::Java_com_samsung_android_aitt_Aitt_unsubscribeJNI)},
+ {"disconnectJNI", "(J)V",
+ reinterpret_cast<void *>(
+ AittNativeInterface::Java_com_samsung_android_aitt_Aitt_disconnectJNI)},
+ {"setConnectionCallbackJNI", "(J)V",
+ reinterpret_cast<void *>(
+ AittNativeInterface::Java_com_samsung_android_aitt_Aitt_setConnectionCallbackJNI)}};
+ if (env->RegisterNatives(klass, aitt_jni_methods,
+ sizeof(aitt_jni_methods) / sizeof(aitt_jni_methods[0]))) {
+ env->DeleteLocalRef(klass);
+ return JNI_ERR;
+ }
+ env->DeleteLocalRef(klass);
+ JNI_LOG(ANDROID_LOG_INFO, TAG, "JNI loaded successfully");
+ return JNI_VERSION_1_6;
+}
diff --git a/android/aitt/src/main/jni/aitt_jni.h b/android/aitt/src/main/jni/aitt_jni.h
new file mode 100644
index 0000000..69e3262
--- /dev/null
+++ b/android/aitt/src/main/jni/aitt_jni.h
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2022 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+#include <AITT.h>
+#include <android/log.h>
+#include <jni.h>
+#include <string>
+
+#define TAG "AITT_ANDROID_JNI"
+#define JNI_LOG(a, b, c) __android_log_write(a, b, c)
+
+using AITT = aitt::AITT;
+
+class AittNativeInterface {
+ private:
+ struct CallbackContext {
+ JavaVM *jvm;
+ jmethodID messageCallbackMethodID;
+ jmethodID connectionCallbackMethodID;
+ };
+
+ private:
+ AittNativeInterface(std::string &mqId, std::string &ip, bool clearSession);
+ virtual ~AittNativeInterface(void);
+ static std::string GetStringUTF(JNIEnv *env, jstring str);
+
+ public:
+ static jlong Java_com_samsung_android_aitt_Aitt_initJNI(JNIEnv *env, jobject jniInterfaceObject,
+ jstring id, jstring ip, jboolean clearSession);
+ static void Java_com_samsung_android_aitt_Aitt_connectJNI(JNIEnv *env, jobject jniInterfaceObject, jlong handle,
+ jstring host, jint port);
+ static jlong Java_com_samsung_android_aitt_Aitt_subscribeJNI(JNIEnv *env, jobject jniInterfaceObject, jlong handle,
+ jstring topic, jint protocol, jint qos);
+ static void Java_com_samsung_android_aitt_Aitt_publishJNI(JNIEnv *env, jobject jniInterfaceObject, jlong handle,
+ jstring topic, jbyteArray data, jlong datalen, jint protocol,
+ jint qos, jboolean retain);
+ static void Java_com_samsung_android_aitt_Aitt_unsubscribeJNI(JNIEnv *env, jobject jniInterfaceObject, jlong handle,
+ jlong aittSubId);
+ static void Java_com_samsung_android_aitt_Aitt_disconnectJNI(JNIEnv *env, jobject jniInterfaceObject, jlong handle);
+ static void Java_com_samsung_android_aitt_Aitt_setConnectionCallbackJNI(JNIEnv *env, jobject jniInterfaceObject, jlong handle);
+
+ private:
+ AITT aitt;
+ jobject cbObject;
+ static CallbackContext cbContext;
+};
diff --git a/android/aitt/src/test/java/com/samsung/android/aitt/AittMessageUnitTest.java b/android/aitt/src/test/java/com/samsung/android/aitt/AittMessageUnitTest.java
new file mode 100644
index 0000000..abbb0a8
--- /dev/null
+++ b/android/aitt/src/test/java/com/samsung/android/aitt/AittMessageUnitTest.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (c) 2022 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.samsung.android.aitt;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import org.junit.Test;
+
+public class AittMessageUnitTest {
+
+ private final String topic = "aittMessage/topic";
+ private final String correlation = "correlation";
+ private final String replyTopic = "aittMessage/replyTopic";
+ private final int sequence = 007;
+ private final boolean endSequence = true;
+ private final String message = "Aitt Message";
+
+ @Test
+ public void testAittMessageInitialize_P01(){
+ AittMessage aittMessage = new AittMessage();
+ assertNotNull("Not null AittMessage Object", aittMessage);
+ }
+
+ @Test
+ public void testAittMessageInitializePayload_P02(){
+ byte[] payload = message.getBytes();
+ AittMessage aittMessage = new AittMessage(payload);
+ assertNotNull("Not null AittMessage Object", aittMessage);
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void testAittMessageInitializeInvalidPayload_N01() throws NullPointerException{
+ byte[] payload = null;
+ AittMessage aittMessage = new AittMessage(payload);
+ assertNull("Null AittMessage Object", aittMessage);
+ }
+
+ @Test
+ public void testTopic_P03(){
+ byte[] payload = message.getBytes();
+ AittMessage aittMessage = new AittMessage(payload);
+ aittMessage.setTopic(topic);
+ String newTopic = aittMessage.getTopic();
+ assertEquals("Received topic and set topic are equal", topic, newTopic);
+ }
+
+ @Test
+ public void testCorrelation_P04(){
+ byte[] payload = message.getBytes();
+ AittMessage aittMessage = new AittMessage(payload);
+ aittMessage.setCorrelation(correlation);
+ String newCorrelation = aittMessage.getCorrelation();
+ assertEquals("Received correlation and set correlation are equal", correlation, newCorrelation);
+ }
+
+ @Test
+ public void testReplyTopic_P05(){
+ byte[] payload = message.getBytes();
+ AittMessage aittMessage = new AittMessage(payload);
+ aittMessage.setReplyTopic(replyTopic);
+ String newReplyTopic = aittMessage.getReplyTopic();
+ assertEquals("Received replyTopic and set replyTopic are equal", replyTopic, newReplyTopic);
+ }
+
+ @Test
+ public void testSequence_P06(){
+ byte[] payload = message.getBytes();
+ AittMessage aittMessage = new AittMessage(payload);
+ aittMessage.setSequence(sequence);
+ aittMessage.increaseSequence();
+ int newSequence = aittMessage.getSequence();
+ assertEquals("Received sequence and set sequence are equal", sequence+1, newSequence);
+ }
+
+ @Test
+ public void testEndSequence_P07(){
+ byte[] payload = message.getBytes();
+ AittMessage aittMessage = new AittMessage(payload);
+ aittMessage.setEndSequence(endSequence);
+ boolean bool = aittMessage.isEndSequence();
+ assertEquals("Received endSequence and set endSequence are equal", endSequence, bool);
+ }
+
+ @Test
+ public void testPayload_P08(){
+ AittMessage aittMessage = new AittMessage();
+ byte[] payload = message.getBytes();
+ aittMessage.setPayload(payload);
+ byte[] newPayload = aittMessage.getPayload();
+ assertEquals("Received payload and set payload are equal", payload, newPayload);
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void TestInvalidPayload_N02() throws NullPointerException {
+ AittMessage aittMessage = new AittMessage();
+ byte[] payload = null;
+ aittMessage.setPayload(payload);
+ }
+
+ @Test
+ public void testClearPayload_P09(){
+ byte[] payload = message.getBytes();
+ AittMessage aittMessage = new AittMessage(payload);
+ aittMessage.clearPayload();
+ byte[] newPayload = aittMessage.getPayload();
+ assertEquals("Received payload and expected payload are equal", 0, newPayload.length);
+ }
+}
diff --git a/android/aitt/src/test/java/com/samsung/android/aitt/AittUnitTest.java b/android/aitt/src/test/java/com/samsung/android/aitt/AittUnitTest.java
new file mode 100644
index 0000000..a7d1789
--- /dev/null
+++ b/android/aitt/src/test/java/com/samsung/android/aitt/AittUnitTest.java
@@ -0,0 +1,795 @@
+/*
+ * Copyright (c) 2022 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.samsung.android.aitt;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.mock;
+
+import android.content.Context;
+
+import com.google.flatbuffers.FlexBuffersBuilder;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.powermock.api.mockito.PowerMockito;
+import org.powermock.api.support.membermodification.MemberMatcher;
+import org.powermock.core.classloader.annotations.PrepareForTest;
+import org.powermock.modules.junit4.PowerMockRunner;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.EnumSet;
+
+@RunWith(PowerMockRunner.class)
+@PrepareForTest(Aitt.class)
+public class AittUnitTest {
+ @Mock
+ private final Context appContext = mock(Context.class);
+
+ private static final String JOIN_NETWORK = "connected";
+ private static final String WILL_LEAVE_NETWORK = "disconnected";
+ private static final String JAVA_SPECIFIC_DISCOVERY_TOPIC = "/java/aitt/discovery/";
+ private static final String AITT_LOCALHOST = "127.0.0.1";
+ private static final int DISCOVERY_MESSAGES_COUNT = 6;
+ private final String brokerIp = "192.168.0.1";
+ private final int port = 1803;
+ private final String topic = "aitt/test";
+ private final String message = "test message";
+ private final String aittId = "aitt";
+
+ private Method messageCallbackMethod;
+
+ @Before
+ public void initialize() {
+ try {
+ PowerMockito.replace(MemberMatcher.method(Aitt.class, "initJNI")).with(new InvocationHandler() {
+ @Override
+ public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
+ return 1L;
+ }
+ });
+ PowerMockito.replace(MemberMatcher.method(Aitt.class, "connectJNI")).with(new InvocationHandler() {
+ @Override
+ public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
+ return null;
+ }
+ });
+ PowerMockito.replace(MemberMatcher.method(Aitt.class, "disconnectJNI")).with(new InvocationHandler() {
+ @Override
+ public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
+ return null;
+ }
+ });
+ PowerMockito.replace(MemberMatcher.method(Aitt.class, "setConnectionCallbackJNI")).with(new InvocationHandler() {
+ @Override
+ public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
+ return null;
+ }
+ });
+ PowerMockito.replace(MemberMatcher.method(Aitt.class, "publishJNI")).with(new InvocationHandler() {
+ @Override
+ public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
+ return null;
+ }
+ });
+ PowerMockito.replace(MemberMatcher.method(Aitt.class, "subscribeJNI")).with(new InvocationHandler() {
+ @Override
+ public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
+ return 1L;
+ }
+ });
+ PowerMockito.replace(MemberMatcher.method(Aitt.class, "unsubscribeJNI")).with(new InvocationHandler() {
+ @Override
+ public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
+ return null;
+ }
+ });
+
+ messageCallbackMethod = Aitt.class.getDeclaredMethod("messageCallback", String.class, byte[].class);
+ messageCallbackMethod.setAccessible(true);
+ } catch(Exception e) {
+ fail("Failed to mock Aitt " + e);
+ }
+ }
+
+ private byte[] createDiscoveryMessage(int count) {
+ int start;
+ FlexBuffersBuilder builder = new FlexBuffersBuilder(ByteBuffer.allocate(512));
+ start = builder.startMap();
+ switch (count) {
+ case 1:
+ /*
+ * {
+ * "status": "connected",
+ * "host": "127.0.0.1",
+ * "aitt/topic1": {
+ * "protocol": TCP,
+ * "port": 1000,
+ * }
+ * }
+ */
+ builder.putString("status", JOIN_NETWORK);
+ builder.putString("host", AITT_LOCALHOST);
+ int secondStart = builder.startMap();
+ builder.putInt("port", 1000);
+ builder.putInt("protocol", Aitt.Protocol.TCP.getValue());
+ builder.endMap("aitt/topic1", secondStart);
+ break;
+ case 2:
+ /*
+ * {
+ * "status": "connected",
+ * "host": "127.0.0.2",
+ * "aitt/topic1": {
+ * "protocol": MQTT,
+ * "port": 2000,
+ * }
+ * }
+ */
+ builder.putString("status", JOIN_NETWORK);
+ builder.putString("host", "127.0.0.2");
+ secondStart = builder.startMap();
+ builder.putInt("port", 2000);
+ builder.putInt("protocol", Aitt.Protocol.MQTT.getValue());
+ builder.endMap("aitt/topic1", secondStart);
+ break;
+ case 3:
+ /*
+ * {
+ * "status": "connected",
+ * "host": "127.0.0.1",
+ * "aitt/topic2": {
+ * "protocol": MQTT,
+ * "port": 2000,
+ * }
+ * }
+ */
+ builder.putString("status", JOIN_NETWORK);
+ builder.putString("host",AITT_LOCALHOST);
+ secondStart = builder.startMap();
+ builder.putInt("port", 2000);
+ builder.putInt("protocol", Aitt.Protocol.MQTT.getValue());
+ builder.endMap("aitt/topic2", secondStart);
+ break;
+ case 4:
+ /*
+ * {
+ * "status": "connected",
+ * "host": "127.0.0.1",
+ * "aitt/topic2": {
+ * "protocol": TCP,
+ * "port": 4000,
+ * }
+ * }
+ */
+ builder.putString("status", JOIN_NETWORK);
+ builder.putString("host",AITT_LOCALHOST);
+ secondStart = builder.startMap();
+ builder.putInt("port", 4000);
+ builder.putInt("protocol", Aitt.Protocol.TCP.getValue());
+ builder.endMap("aitt/topic2", secondStart);
+ break;
+ case 5:
+ /*
+ * {
+ * "status": "connected",
+ * "host": "127.0.0.1",
+ * "aitt/topic2": {
+ * "protocol": WEBRTC,
+ * "port": 2000,
+ * }
+ * }
+ */
+ builder.putString("status", JOIN_NETWORK);
+ builder.putString("host",AITT_LOCALHOST);
+ secondStart = builder.startMap();
+ builder.putInt("port", 2000);
+ builder.putInt("protocol", Aitt.Protocol.WEBRTC.getValue());
+ builder.endMap("aitt/topic2", secondStart);
+ break;
+ case 6:
+ /*
+ * {
+ * "status": "disconnected",
+ * "host": "127.0.0.1",
+ * "aitt/topic1": {
+ * "protocol": TCP,
+ * "port": 1000,
+ * }
+ * }
+ */
+ builder.putString("status", WILL_LEAVE_NETWORK);
+ builder.putString("host",AITT_LOCALHOST);
+ secondStart = builder.startMap();
+ builder.putInt("port", 1000);
+ builder.putInt("protocol", Aitt.Protocol.TCP.getValue());
+ builder.endMap("aitt/topic1", secondStart);
+ break;
+ default:
+ return null;
+ }
+ builder.endMap(null, start);
+ ByteBuffer bb = builder.finish();
+ byte[] array = new byte[bb.remaining()];
+ bb.get(array,0,array.length);
+ return array;
+ }
+
+ @Test
+ public void testAittConstructor_P(){
+ String id = "aitt";
+ try {
+ Aitt aitt = new Aitt(appContext, id);
+ assertNotNull("Aitt Instance not null", aitt);
+ } catch(Exception e) {
+ fail("Failed testInitialize " + e);
+ }
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testInitializeInvalidId_N() {
+ String _id = "";
+ try {
+ Aitt aitt = new Aitt(appContext, _id);
+ aitt.close();
+ } catch(InstantiationException e) {
+ fail("Error during testInitializeInvalidId" + e);
+ }
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testInitializeInvalidContext_N() {
+ String _id = "";
+ try {
+ Aitt aitt = new Aitt(null, _id);
+ aitt.close();
+ } catch(InstantiationException e) {
+ fail("Error during testInitializeInvalidContext" + e);
+ }
+ }
+
+ @Test(expected = InstantiationException.class)
+ public void testConstructorFail_N() throws InstantiationException {
+ try{
+ PowerMockito.replace(MemberMatcher.method(Aitt.class, "initJNI")).with(new InvocationHandler() {
+ @Override
+ public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
+ return 0L;
+ }
+ });
+ } catch(Exception e) {
+ fail("Failed to replace method" + e);
+ }
+ String id = "aitt";
+ Aitt aitt = new Aitt(appContext,id);
+ aitt.close();
+ }
+
+ @Test
+ public void testConnect_P() {
+ try {
+ Aitt aitt = new Aitt(appContext, aittId);
+
+ assertNotNull("Aitt Instance not null", aitt);
+ aitt.connect(brokerIp, port);
+
+ aitt.close();
+ } catch(Exception e) {
+ fail("Failed testConnect " + e);
+ }
+ }
+
+ @Test
+ public void testConnectWithoutIP_P() {
+ try {
+ Aitt aitt = new Aitt(appContext, aittId);
+
+ assertNotNull("Aitt Instance not null", aitt);
+ aitt.connect(null);
+
+ aitt.close();
+ } catch(Exception e) {
+ fail("Failed testConnectWithoutIP " + e);
+ }
+ }
+
+ @Test
+ public void testDisconnect_P() {
+ try {
+ Aitt aitt = new Aitt(appContext, aittId);
+
+ assertNotNull("Aitt Instance not null", aitt);
+ aitt.connect(brokerIp, port);
+
+ aitt.disconnect();
+ } catch(Exception e) {
+ fail("Failed testDisconnect " + e);
+ }
+ }
+
+ @Test
+ public void testPublishMqtt_P() {
+ try {
+ Aitt aitt = new Aitt(appContext, aittId);
+
+ assertNotNull("Aitt Instance not null", aitt);
+ aitt.connect(brokerIp, port);
+
+ byte[] payload = message.getBytes();
+ aitt.publish(topic, payload);
+
+ aitt.disconnect();
+ } catch(Exception e) {
+ fail("Failed testPublishMqtt " + e);
+ }
+ }
+
+ @Test
+ public void testPublishWebRTC_P() {
+ try {
+ Aitt aitt = new Aitt(appContext, aittId);
+
+ assertNotNull("Aitt Instance not null", aitt);
+ aitt.connect(brokerIp, port);
+
+ byte[] payload = message.getBytes();
+ aitt.publish(topic, payload, Aitt.Protocol.WEBRTC, Aitt.QoS.AT_MOST_ONCE, false);
+
+ aitt.disconnect();
+ } catch(Exception e) {
+ fail("Failed testPublishWebRTC " + e);
+ }
+ }
+
+ @Test
+ public void testPublishInvalidTopic_N(){
+ try {
+ Aitt aitt = new Aitt(appContext, aittId);
+ aitt.connect(brokerIp, port);
+ String _topic = "";
+ byte[] payload = message.getBytes();
+
+ assertThrows(IllegalArgumentException.class, () -> {
+ aitt.publish(_topic, payload);
+ });
+
+ aitt.disconnect();
+ } catch(Exception e){
+ fail("Failed testPublishInvalidTopic" + e);
+ }
+ }
+
+ @Test
+ public void testPublishAnyProtocol_P() {
+ try {
+ Aitt aitt = new Aitt(appContext, aittId);
+
+ assertNotNull("Aitt Instance not null", aitt);
+ aitt.connect(brokerIp, port);
+
+ byte[] payload = message.getBytes();
+ aitt.publish(topic, payload, Aitt.Protocol.TCP, Aitt.QoS.AT_LEAST_ONCE, false);
+
+ aitt.disconnect();
+ } catch(Exception e) {
+ fail("Failed testPublishAnyProtocol " + e);
+ }
+ }
+
+ @Test
+ public void testPublishProtocolSet_P() {
+ try {
+ Aitt aitt = new Aitt(appContext, aittId);
+
+ assertNotNull("Aitt Instance not null", aitt);
+ aitt.connect(brokerIp, port);
+
+ byte[] payload = message.getBytes();
+ EnumSet<Aitt.Protocol> protocols = EnumSet.of(Aitt.Protocol.MQTT, Aitt.Protocol.TCP);
+ aitt.publish(topic, payload, protocols, Aitt.QoS.AT_MOST_ONCE, false);
+
+ aitt.disconnect();
+ } catch(Exception e) {
+ fail("Failed testPublishProtocolSet " + e);
+ }
+ }
+
+ @Test
+ public void testPublishInvalidProtocol_N(){
+ try{
+ Aitt aitt = new Aitt(appContext, aittId);
+ aitt.connect(brokerIp,port);
+ byte[] payload = message.getBytes();
+ EnumSet<Aitt.Protocol> protocols = EnumSet.noneOf(Aitt.Protocol.class);
+
+ assertThrows(IllegalArgumentException.class, () -> {
+ aitt.publish(topic, payload, protocols, Aitt.QoS.AT_MOST_ONCE, false);
+ });
+
+ aitt.disconnect();
+ } catch(Exception e){
+ fail("Failed testPublishInvalidProtocol" + e);
+ }
+ }
+
+ @Test
+ public void testSubscribeMqtt_P() {
+ try {
+ Aitt aitt = new Aitt(appContext, aittId);
+
+ assertNotNull("Aitt Instance not null", aitt);
+ aitt.connect(brokerIp, port);
+
+ aitt.subscribe(topic, new Aitt.SubscribeCallback() {
+ @Override
+ public void onMessageReceived(AittMessage message) {
+ String _topic = message.getTopic();
+ byte[] payload = message.getPayload();
+ }
+ });
+
+ aitt.disconnect();
+ } catch(Exception e) {
+ fail("Failed testSubscribeMqtt " + e);
+ }
+ }
+
+ @Test
+ public void testSubscribeWebRTC_P() {
+ try {
+ Aitt aitt = new Aitt(appContext, aittId);
+
+ assertNotNull("Aitt Instance not null", aitt);
+ aitt.connect(brokerIp, port);
+
+ aitt.subscribe(topic, new Aitt.SubscribeCallback() {
+ @Override
+ public void onMessageReceived(AittMessage message) {
+ String _topic = message.getTopic();
+ byte[] payload = message.getPayload();
+ }
+ },
+ Aitt.Protocol.WEBRTC, Aitt.QoS.AT_MOST_ONCE);
+
+ aitt.disconnect();
+ } catch(Exception e) {
+ fail("Failed testSubscribeWebRTC " + e);
+ }
+ }
+
+
+ @Test
+ public void testSubscribeInvalidTopic_N() {
+
+ try{
+ Aitt aitt = new Aitt(appContext, aittId);
+ aitt.connect(brokerIp, port);
+
+ String _topic = "";
+
+ assertThrows(IllegalArgumentException.class, () -> {
+ aitt.subscribe(_topic, new Aitt.SubscribeCallback() {
+ @Override
+ public void onMessageReceived(AittMessage message) {
+ }
+ });
+ });
+
+ aitt.disconnect();
+ } catch(Exception e){
+ fail("Failed testSubscribeInvalidTopic " + e);
+ }
+ }
+
+ @Test
+ public void testSubscribeInvalidCallback_N() {
+ try {
+ Aitt aitt = new Aitt(appContext, aittId);
+
+ aitt.connect(brokerIp, port);
+
+ String _topic = "topic";
+
+ assertThrows(IllegalArgumentException.class, () -> {
+ aitt.subscribe(_topic, null);
+ });
+
+ aitt.disconnect();
+ } catch(Exception e) {
+ fail("Failed testSubscribeInvalidCallback " + e);
+ }
+ }
+
+ @Test
+ public void testSubscribeAnyProtocol_P() {
+ try {
+ Aitt aitt = new Aitt(appContext, aittId);
+
+ assertNotNull("Aitt Instance not null", aitt);
+ aitt.connect(brokerIp, port);
+
+ aitt.subscribe(topic, new Aitt.SubscribeCallback() {
+ @Override
+ public void onMessageReceived(AittMessage message) {
+ String _topic = message.getTopic();
+ byte[] payload = message.getPayload();
+ }
+ },
+ Aitt.Protocol.UDP, Aitt.QoS.AT_MOST_ONCE);
+
+ aitt.disconnect();
+ } catch(Exception e) {
+ fail("Failed testSubscribeAnyProtocol " + e);
+ }
+ }
+
+ @Test
+ public void testSubscribeInvalidProtocol_N() {
+ try {
+ Aitt aitt = new Aitt(appContext, aittId);
+
+ aitt.connect(brokerIp, port);
+ EnumSet<Aitt.Protocol> protocols = EnumSet.noneOf(Aitt.Protocol.class);
+
+ assertThrows(IllegalArgumentException.class, () -> {
+ aitt.subscribe(topic, new Aitt.SubscribeCallback() {
+ @Override
+ public void onMessageReceived(AittMessage message) {
+ String _topic = message.getTopic();
+ byte[] payload = message.getPayload();
+ }
+ },
+ protocols, Aitt.QoS.AT_MOST_ONCE);
+ });
+
+ aitt.disconnect();
+ } catch(Exception e) {
+ fail("Failed testSubscribeAnyProtocol " + e);
+ }
+ }
+
+ @Test
+ public void testSubscribeProtocolSet_P() {
+ try {
+ Aitt aitt = new Aitt(appContext, aittId);
+
+ assertNotNull("Aitt Instance not null", aitt);
+ aitt.connect(brokerIp, port);
+
+ EnumSet<Aitt.Protocol> protocols = EnumSet.of(Aitt.Protocol.MQTT, Aitt.Protocol.TCP);
+ aitt.subscribe(topic, new Aitt.SubscribeCallback() {
+ @Override
+ public void onMessageReceived(AittMessage message) {
+ String _topic = message.getTopic();
+ byte[] payload = message.getPayload();
+ }
+ },
+ protocols, Aitt.QoS.EXACTLY_ONCE);
+
+ aitt.disconnect();
+ } catch(Exception e) {
+ fail("Failed testSubscribeProtocolSet " + e);
+ }
+ }
+
+ @Test
+ public void testUnsubscribe_P() {
+ try {
+ Aitt aitt = new Aitt(appContext, aittId);
+
+ assertNotNull("Aitt Instance not null", aitt);
+ aitt.connect(brokerIp, port);
+
+ aitt.subscribe(topic, new Aitt.SubscribeCallback() {
+ @Override
+ public void onMessageReceived(AittMessage message) {
+ }
+ });
+
+ aitt.unsubscribe(topic);
+ aitt.disconnect();
+ } catch(Exception e) {
+ fail("Failed testUnsubscribe " + e);
+ }
+ }
+
+ @Test
+ public void testUnsubscribeInvalidTopic_N() {
+ try {
+ Aitt aitt = new Aitt(appContext, aittId);
+
+ aitt.connect(brokerIp, port);
+ String _topic = "";
+
+ assertThrows(IllegalArgumentException.class, () -> {
+ aitt.unsubscribe(_topic);
+ });
+
+ aitt.disconnect();
+ } catch(Exception e){
+ fail("Failed testUnsubscribeInvalidTopic " + e);
+ }
+ }
+
+ @Test
+ public void testSetConnectionCallback_P() {
+ try {
+ Aitt aitt = new Aitt(appContext, aittId);
+
+ assertNotNull("Aitt Instance not null", aitt);
+ aitt.setConnectionCallback(new Aitt.ConnectionCallback() {
+ @Override
+ public void onConnected() {}
+
+ @Override
+ public void onDisconnected() {}
+ });
+ aitt.connect(brokerIp, port);
+
+ aitt.disconnect();
+ } catch(Exception e) {
+ fail("Failed testSetConnectionCallback " + e);
+ }
+ }
+
+ @Test
+ public void testSetConnectionCallbackInvalidCallback_N() {
+ try {
+ Aitt aitt = new Aitt(appContext, aittId);
+
+ assertThrows(IllegalArgumentException.class, () -> {
+ aitt.setConnectionCallback(null);
+ });
+
+ aitt.connect(brokerIp, port);
+ aitt.disconnect();
+ } catch(Exception e) {
+ fail("Failed testSetConnectionCallbackInvalidCallback " + e);
+ }
+ }
+
+ @Test
+ public void testSubscribeMultipleCallbacks_P() {
+ try {
+ Aitt aitt = new Aitt(appContext, aittId);
+
+ assertNotNull("Aitt Instance not null", aitt);
+ aitt.connect(brokerIp, port);
+
+ Aitt.SubscribeCallback callback1 = message -> {};
+
+ Aitt.SubscribeCallback callback2 = message -> {};
+
+ aitt.subscribe(topic, callback1);
+ aitt.subscribe(topic, callback2);
+
+ aitt.disconnect();
+ } catch(Exception e) {
+ fail("Failed testSubscribeMultipleCallbacks " + e);
+ }
+ }
+
+ // The test covers different cases of updating the publish table
+ @Test
+ public void testDiscoveryMessageCallbackConnected_P() {
+ try {
+ Aitt aitt = new Aitt(appContext, aittId);
+
+ assertNotNull("Aitt Instance not null", aitt);
+ aitt.connect(brokerIp, port);
+
+ int counter = 1;
+ while (counter < DISCOVERY_MESSAGES_COUNT) {
+ byte[] discoveryMessage = createDiscoveryMessage(counter);
+ messageCallbackMethod.invoke(aitt, JAVA_SPECIFIC_DISCOVERY_TOPIC, (Object) discoveryMessage);
+ counter++;
+ }
+
+ aitt.disconnect();
+ } catch(Exception e) {
+ fail("Failed testDiscoveryMessageCallback " + e);
+ }
+ }
+
+ @Test
+ public void testDiscoveryMessageCallbackDisconnected_P() {
+ try {
+ Aitt aitt = new Aitt(appContext, aittId);
+
+ assertNotNull("Aitt Instance not null", aitt);
+ aitt.connect(brokerIp, port);
+
+ int counter = 1;
+ byte[] discoveryMessage = createDiscoveryMessage(counter);
+ messageCallbackMethod.invoke(aitt, JAVA_SPECIFIC_DISCOVERY_TOPIC, (Object) discoveryMessage);
+
+ counter = 6;
+ byte[] disconnectMessage = createDiscoveryMessage(counter);
+ messageCallbackMethod.invoke(aitt, JAVA_SPECIFIC_DISCOVERY_TOPIC, (Object) disconnectMessage);
+ aitt.disconnect();
+ } catch(Exception e) {
+ fail("Failed testDiscoveryMessageCallback " + e);
+ }
+ }
+
+ @Test
+ public void testDiscoveryMessageCallbackEmptyPayload_P() {
+ try {
+ Aitt aitt = new Aitt(appContext, aittId);
+
+ assertNotNull("Aitt Instance not null", aitt);
+ aitt.connect(brokerIp, port);
+
+ byte[] discoveryMessage = new byte[0];
+ messageCallbackMethod.invoke(aitt, JAVA_SPECIFIC_DISCOVERY_TOPIC, (Object) discoveryMessage);
+
+ aitt.disconnect();
+ } catch(Exception e) {
+ fail("Failed testDiscoveryMessageCallbackEmptyPayload " + e);
+ }
+ }
+
+ @Test
+ public void testSubscribeCallbackVerifyTopic_P() {
+ try {
+ Aitt aitt = new Aitt(appContext, aittId);
+ aitt.connect(brokerIp, port);
+
+ aitt.subscribe(topic, new Aitt.SubscribeCallback() {
+ @Override
+ public void onMessageReceived(AittMessage aittMessage) {
+ String recvTopic = aittMessage.getTopic();
+ assertEquals("Received topic and subscribed topic are equal", recvTopic, topic);
+ }
+ });
+
+ messageCallbackMethod.invoke(aitt, topic, message.getBytes(StandardCharsets.UTF_8));
+
+ aitt.disconnect();
+ } catch(Exception e) {
+ fail("Failed testSubscribeCallback " + e);
+ }
+ }
+
+ @Test
+ public void testSubscribeCallbackVerifyPayload_P() {
+ try {
+ Aitt aitt = new Aitt(appContext, aittId);
+ aitt.connect(brokerIp, port);
+
+ aitt.subscribe(topic, new Aitt.SubscribeCallback() {
+ @Override
+ public void onMessageReceived(AittMessage aittMessage) {
+ String recvMessage = new String(aittMessage.getPayload(), StandardCharsets.UTF_8);
+ assertEquals("Received message and sent message are equal", message, recvMessage);
+ }
+ });
+
+ messageCallbackMethod.invoke(aitt, topic, message.getBytes(StandardCharsets.UTF_8));
+
+ aitt.disconnect();
+ } catch(Exception e) {
+ fail("Failed testSubscribeCallback " + e);
+ }
+ }
+}
diff --git a/android/aitt/src/test/resources/robolectric.properties b/android/aitt/src/test/resources/robolectric.properties
new file mode 100644
index 0000000..932b01b
--- /dev/null
+++ b/android/aitt/src/test/resources/robolectric.properties
@@ -0,0 +1 @@
+sdk=28
diff --git a/android/flatbuffers/build.gradle b/android/flatbuffers/build.gradle
new file mode 100644
index 0000000..ccd0ce7
--- /dev/null
+++ b/android/flatbuffers/build.gradle
@@ -0,0 +1,79 @@
+plugins {
+ id 'com.android.library'
+ id "de.undercouch.download" version "5.0.1"
+}
+
+def thirdPartyDir = new File ("${rootProject.projectDir}/third_party")
+
+def flatbuffersDir = new File("${thirdPartyDir}/flatbuffers-2.0.0")
+
+android {
+ compileSdkVersion 31
+ ndkVersion "21.3.6528147"
+ defaultConfig {
+ minSdkVersion 28
+ targetSdkVersion 31
+
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ externalNativeBuild {
+ cmake {
+ arguments '-DCMAKE_VERBOSE_MAKEFILE=1'
+ arguments '-DCMAKE_INSTALL_PREFIX:PATH=/usr'
+ arguments '-DANDROID_STL=c++_shared'
+ arguments "-DANDROID_NDK_HOME=${System.env.ANDROID_NDK_ROOT}"
+ arguments '-DFLATBUFFERS_BUILD_TESTS=OFF'
+ arguments '-DFLATBUFFERS_BUILD_FLATC=OFF'
+ arguments '-DFLATBUFFERS_BUILD_FLATHASH=OFF'
+ arguments '-DFLATBUFFERS_BUILD_CPP17=ON'
+ arguments '-DFLATBUFFERS_INSTALL=OFF'
+ cppFlags "-std=c++17"
+ abiFilters 'arm64-v8a', 'x86'
+ targets "flatbuffers"
+ }
+ }
+ }
+ buildFeatures {
+ prefab false
+ }
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+ externalNativeBuild {
+ cmake {
+ path "${flatbuffersDir}/CMakeLists.txt"
+ }
+ }
+}
+
+dependencies {
+ testImplementation 'junit:junit:4.+'
+ androidTestImplementation 'androidx.test.ext:junit:1.1.3'
+ androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
+}
+
+task downloadFlatBuffers(type: Download) {
+ doFirst {
+ println("Downloading FlatBuffers")
+ }
+ src "https://github.com/google/flatbuffers/archive/refs/tags/v2.0.0.zip"
+ dest new File(thirdPartyDir, "flatbuffers.zip")
+ onlyIfModified true
+}
+
+task unzipFlatBuffers(type: Copy, dependsOn: downloadFlatBuffers) {
+ doFirst {
+ println("Unzipping FlatBuffers")
+ }
+ from zipTree(downloadFlatBuffers.dest)
+ into thirdPartyDir
+ onlyIf { !flatbuffersDir.exists() }
+}
+
+task wrapper(type: Wrapper) {
+ gradleVersion = '4.1'
+}
+
+preBuild.dependsOn(unzipFlatBuffers)
diff --git a/android/flatbuffers/proguard-rules.pro b/android/flatbuffers/proguard-rules.pro
new file mode 100644
index 0000000..f1b4245
--- /dev/null
+++ b/android/flatbuffers/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
diff --git a/android/flatbuffers/src/main/AndroidManifest.xml b/android/flatbuffers/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..f4119ba
--- /dev/null
+++ b/android/flatbuffers/src/main/AndroidManifest.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.samsung.android.flatbuffers">
+</manifest>
diff --git a/android/modules/webrtc/.gitignore b/android/modules/webrtc/.gitignore
new file mode 100644
index 0000000..796b96d
--- /dev/null
+++ b/android/modules/webrtc/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/android/modules/webrtc/build.gradle b/android/modules/webrtc/build.gradle
new file mode 100644
index 0000000..9764253
--- /dev/null
+++ b/android/modules/webrtc/build.gradle
@@ -0,0 +1,39 @@
+plugins {
+ id 'com.android.library'
+}
+
+android {
+ compileSdkVersion 31
+
+ defaultConfig {
+ minSdkVersion 21
+ targetSdkVersion 31
+ versionCode 1
+ versionName "1.0"
+
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ }
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+}
+
+dependencies {
+ implementation 'androidx.appcompat:appcompat:1.4.1'
+ implementation 'com.google.android.material:material:1.5.0'
+ testImplementation 'junit:junit:4.+'
+ androidTestImplementation 'androidx.test.ext:junit:1.1.3'
+ androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
+ implementation 'org.webrtc:google-webrtc:1.0.32006'
+}
+
+task wrapper(type: Wrapper) {
+ gradleVersion = '4.1'
+}
diff --git a/android/modules/webrtc/proguard-rules.pro b/android/modules/webrtc/proguard-rules.pro
new file mode 100644
index 0000000..f1b4245
--- /dev/null
+++ b/android/modules/webrtc/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
diff --git a/android/modules/webrtc/src/main/AndroidManifest.xml b/android/modules/webrtc/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..7b7ae3f
--- /dev/null
+++ b/android/modules/webrtc/src/main/AndroidManifest.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.samsung.android.modules.webrtc">
+
+ <uses-feature android:glEsVersion="0x00020000" android:required="true" />
+ <uses-permission android:name="android.permission.INTERNET" />
+
+</manifest>
diff --git a/android/modules/webrtc/src/main/java/com/samsung/android/modules/webrtc/WebRTC.java b/android/modules/webrtc/src/main/java/com/samsung/android/modules/webrtc/WebRTC.java
new file mode 100644
index 0000000..ebf6c6e
--- /dev/null
+++ b/android/modules/webrtc/src/main/java/com/samsung/android/modules/webrtc/WebRTC.java
@@ -0,0 +1,698 @@
+/*
+ * Copyright (c) 2022 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.samsung.android.modules.webrtc;
+
+import android.content.Context;
+import android.os.SystemClock;
+import android.util.Log;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.webrtc.CapturerObserver;
+import org.webrtc.DataChannel;
+import org.webrtc.DefaultVideoDecoderFactory;
+import org.webrtc.DefaultVideoEncoderFactory;
+import org.webrtc.EglBase;
+import org.webrtc.IceCandidate;
+import org.webrtc.MediaConstraints;
+import org.webrtc.MediaStream;
+import org.webrtc.MediaStreamTrack;
+import org.webrtc.NV21Buffer;
+import org.webrtc.PeerConnection;
+import org.webrtc.PeerConnectionFactory;
+import org.webrtc.RtpReceiver;
+import org.webrtc.SdpObserver;
+import org.webrtc.SessionDescription;
+import org.webrtc.SurfaceTextureHelper;
+import org.webrtc.VideoCapturer;
+import org.webrtc.VideoDecoderFactory;
+import org.webrtc.VideoEncoderFactory;
+import org.webrtc.VideoFrame;
+import org.webrtc.VideoSink;
+import org.webrtc.VideoSource;
+import org.webrtc.VideoTrack;
+import static org.webrtc.SessionDescription.Type.ANSWER;
+import static org.webrtc.SessionDescription.Type.OFFER;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.Serializable;
+import java.net.Socket;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * WebRTC class to implement webRTC functionalities
+ */
+public class WebRTC {
+ private static final String TAG = "WebRTC";
+ public static final String VIDEO_TRACK_ID = "ARDAMSv0";
+ private static final String CANDIDATE = "candidate";
+ private java.net.Socket socket;
+ private boolean isInitiator;
+ private boolean isChannelReady;
+ private boolean isStarted;
+ private boolean isReciever;
+ private PeerConnection peerConnection;
+ private PeerConnectionFactory factory;
+ private VideoTrack videoTrackFromSource;
+ private ObjectOutputStream outStream;
+ private ObjectInputStream inputStream;
+ private SDPThread sdpThread;
+ private Context appContext;
+ private DataChannel localDataChannel;
+ private FrameVideoCapturer videoCapturer;
+ private ReceiveDataCallback dataCallback;
+ private String recieverIP;
+ private Integer recieverPort;
+
+ /**
+ * WebRTC channels supported - Media channel, data channel
+ */
+ public enum DataType{
+ MESSAGE,
+ VIDEOFRAME,
+ }
+
+ /**
+ * WebRTC constructor to create webRTC instance
+ * @param dataType To decide webRTC channel type
+ * @param appContext Application context creating webRTC instance
+ */
+ public WebRTC(DataType dataType , Context appContext) {
+ this.appContext = appContext;
+ this.isReciever = false;
+ }
+
+ /**
+ * WebRTC constructor to create webRTC instance
+ * @param dataType To decide webRTC channel type
+ * @param appContext Application context creating webRTC instance
+ * @param socket Java server socket for webrtc signalling
+ */
+ WebRTC(DataType dataType , Context appContext , Socket socket) {
+ Log.d(TAG , "InWebRTC Constructor");
+ this.appContext = appContext;
+ this.socket = socket;
+ this.isReciever = true;
+ }
+
+ /**
+ * To create data call-back mechanism
+ * @param cb aitt callback registered to receive a webrtc data
+ */
+ public void registerDataCallback(ReceiveDataCallback cb){
+ this.dataCallback = cb;
+ }
+
+ /**
+ * Method to disconnect the connection from peer
+ */
+ public void disconnect() {
+ if (sdpThread != null) {
+ sdpThread.stop();
+ }
+
+ if (socket != null) {
+ new Thread(() -> {
+ try {
+ sendMessage(false, "bye");
+ socket.close();
+ if (outStream != null) {
+ outStream.close();
+ }
+ if (inputStream != null) {
+ inputStream.close();
+ }
+ } catch (IOException e) {
+ Log.e(TAG, "Error during disconnect", e);
+ }
+ }).start();
+ }
+ }
+
+ /**
+ * Method to establish a socket connection with peer node
+ */
+ public void connect() {
+ initialize();
+ }
+
+ /**
+ * Method to establish communication with peer node
+ * @param recieverIP IP Address of the destination(peer) node
+ * @param recieverPort Port number of the destination(peer) node
+ */
+ public void connect(String recieverIP , Integer recieverPort){
+ this.recieverIP = recieverIP;
+ this.recieverPort = recieverPort;
+ initialize();
+ }
+
+ /**
+ * Method to initialize webRTC APIs while establishing connection
+ */
+ private void initialize(){
+ initializePeerConnectionFactory();
+ initializePeerConnections();
+ if(!isReciever){
+ createVideoTrack();
+ addVideoTrack();
+ }
+ isInitiator = isReciever;
+
+ sdpThread = new SDPThread();
+ new Thread(sdpThread).start();
+ }
+
+ /**
+ * Method to create webRTC offer for sdp negotiation
+ */
+ private void doCall() {
+ MediaConstraints sdpMediaConstraints = new MediaConstraints();
+ sdpMediaConstraints.mandatory.add(new MediaConstraints.KeyValuePair("OfferToReceiveVideo", "true"));
+
+ peerConnection.createOffer(new SimpleSdpObserver() {
+ @Override
+ public void onCreateSuccess(SessionDescription sessionDescription) {
+ Log.d(TAG, "onCreateSuccess: ");
+ peerConnection.setLocalDescription(new SimpleSdpObserver(), sessionDescription);
+ JSONObject message = new JSONObject();
+ try {
+ message.put("type", "offer");
+ message.put("sdp", sessionDescription.description);
+ sendMessage(true , message);
+ } catch (JSONException | IOException e) {
+ Log.e(TAG, "Error during create offer", e);
+ }
+ }
+ }, sdpMediaConstraints);
+ }
+
+ /**
+ * Method to send signalling messages over socket connection
+ * @param isJSON Boolean to check if message is JSON
+ * @param message Data to be sent over webRTC connection
+ * @throws IOException Throws IOException if writing to outStream fails
+ */
+ private void sendMessage(boolean isJSON, Object message) throws IOException {
+ Log.d(TAG, message.toString());
+ if (outStream != null) {
+ if (isJSON) {
+ outStream.writeObject(new Packet((JSONObject) message));
+ } else {
+ outStream.writeObject(new Packet((String) message));
+ }
+ }
+ }
+
+ /**
+ * Class to create proxy video sink
+ */
+ private static class ProxyVideoSink implements VideoSink {
+
+ private ReceiveDataCallback dataCallback;
+
+ /**
+ * ProxyVideoSink constructor to create its instance
+ * @param dataCb DataCall back to be set to self-object
+ */
+ ProxyVideoSink(ReceiveDataCallback dataCb){
+ this.dataCallback = dataCb;
+ }
+
+ /**
+ * Method to send data through data call back
+ * @param frame VideoFrame to be transferred using media channel
+ */
+ @Override
+ synchronized public void onFrame(VideoFrame frame) {
+ byte[] rawFrame = createNV21Data(frame.getBuffer().toI420());
+ dataCallback.pushData(rawFrame);
+ }
+
+ /**
+ * Method used to convert VideoFrame to NV21 data format
+ * @param i420Buffer VideoFrame in I420 buffer format
+ * @return the video frame in NV21 data format
+ */
+ public byte[] createNV21Data(VideoFrame.I420Buffer i420Buffer) {
+ final int width = i420Buffer.getWidth();
+ final int height = i420Buffer.getHeight();
+ final int chromaStride = width;
+ final int chromaWidth = (width + 1) / 2;
+ final int chromaHeight = (height + 1) / 2;
+ final int ySize = width * height;
+ final ByteBuffer nv21Buffer = ByteBuffer.allocateDirect(ySize + chromaStride * chromaHeight);
+ final byte[] nv21Data = nv21Buffer.array();
+ for (int y = 0; y < height; ++y) {
+ for (int x = 0; x < width; ++x) {
+ final byte yValue = i420Buffer.getDataY().get(y * i420Buffer.getStrideY() + x);
+ nv21Data[y * width + x] = yValue;
+ }
+ }
+ for (int y = 0; y < chromaHeight; ++y) {
+ for (int x = 0; x < chromaWidth; ++x) {
+ final byte uValue = i420Buffer.getDataU().get(y * i420Buffer.getStrideU() + x);
+ final byte vValue = i420Buffer.getDataV().get(y * i420Buffer.getStrideV() + x);
+ nv21Data[ySize + y * chromaStride + 2 * x + 0] = vValue;
+ nv21Data[ySize + y * chromaStride + 2 * x + 1] = uValue;
+ }
+ }
+ return nv21Data;
+ }
+ }
+
+ /**
+ * Method to initialize peer connection factory
+ */
+ private void initializePeerConnectionFactory() {
+ EglBase mRootEglBase;
+ mRootEglBase = EglBase.create();
+ VideoEncoderFactory encoderFactory = new DefaultVideoEncoderFactory(mRootEglBase.getEglBaseContext(), true /* enableIntelVp8Encoder */, true);
+ VideoDecoderFactory decoderFactory = new DefaultVideoDecoderFactory(mRootEglBase.getEglBaseContext());
+
+ PeerConnectionFactory.initialize(PeerConnectionFactory.InitializationOptions.builder(appContext).setEnableInternalTracer(true).createInitializationOptions());
+ PeerConnectionFactory.Builder builder = PeerConnectionFactory.builder().setVideoEncoderFactory(encoderFactory).setVideoDecoderFactory(decoderFactory);
+ builder.setOptions(null);
+ factory = builder.createPeerConnectionFactory();
+ }
+
+ /**
+ * Method to create video track
+ */
+ private void createVideoTrack(){
+ videoCapturer = new FrameVideoCapturer();
+ VideoSource videoSource = factory.createVideoSource(false);
+ videoCapturer.initialize(null , null ,videoSource.getCapturerObserver());
+ videoTrackFromSource = factory.createVideoTrack(VIDEO_TRACK_ID, videoSource);
+ videoTrackFromSource.setEnabled(true);
+ }
+
+ /**
+ * Method to initialize peer connections
+ */
+ private void initializePeerConnections() {
+ peerConnection = createPeerConnection(factory);
+ if (peerConnection != null) {
+ localDataChannel = peerConnection.createDataChannel("sendDataChannel", new DataChannel.Init());
+ }
+ }
+
+ /**
+ * Method to add video track
+ */
+ private void addVideoTrack() {
+ MediaStream mediaStream = factory.createLocalMediaStream("ARDAMS");
+ mediaStream.addTrack(videoTrackFromSource);
+ if(peerConnection!=null){
+ peerConnection.addStream(mediaStream);
+ }
+ }
+
+ /**
+ * Method to create peer connection
+ * @param factory Peer connection factory object
+ * @return return factory object
+ */
+ private PeerConnection createPeerConnection(PeerConnectionFactory factory) {
+ PeerConnection.RTCConfiguration rtcConfig = new PeerConnection.RTCConfiguration(new ArrayList<>());
+ MediaConstraints pcConstraints = new MediaConstraints();
+
+ PeerConnection.Observer pcObserver = new PeerConnection.Observer() {
+ @Override
+ public void onSignalingChange(PeerConnection.SignalingState signalingState) {
+ Log.d(TAG, "onSignalingChange: ");
+ }
+
+ @Override
+ public void onIceConnectionChange(PeerConnection.IceConnectionState iceConnectionState) {
+ Log.d(TAG, "onIceConnectionChange: ");
+ }
+
+ @Override
+ public void onIceConnectionReceivingChange(boolean b) {
+ Log.d(TAG, "onIceConnectionReceivingChange: ");
+ }
+
+ @Override
+ public void onIceGatheringChange(PeerConnection.IceGatheringState iceGatheringState) {
+ Log.d(TAG, "onIceGatheringChange: ");
+ }
+
+ @Override
+ public void onIceCandidate(IceCandidate iceCandidate) {
+ Log.d(TAG, "onIceCandidate: ");
+ JSONObject message = new JSONObject();
+ try {
+ message.put("type", CANDIDATE);
+ message.put("label", iceCandidate.sdpMLineIndex);
+ message.put("id", iceCandidate.sdpMid);
+ message.put(CANDIDATE, iceCandidate.sdp);
+ Log.d(TAG, "onIceCandidate: sending candidate " + message);
+ sendMessage(true , message);
+ } catch (JSONException | IOException e) {
+ Log.e(TAG, "Error during onIceCandidate", e);
+ }
+ }
+
+ @Override
+ public void onIceCandidatesRemoved(IceCandidate[] iceCandidates) {
+ Log.d(TAG, "onIceCandidatesRemoved: ");
+ }
+
+ @Override
+ public void onAddStream(MediaStream mediaStream) {
+ Log.d(TAG, "onAddStream: " + mediaStream.videoTracks.size());
+ VideoTrack remoteVideoTrack = mediaStream.videoTracks.get(0);
+ remoteVideoTrack.setEnabled(true);
+ }
+
+ @Override
+ public void onRemoveStream(MediaStream mediaStream) {
+ Log.d(TAG, "onRemoveStream: ");
+ }
+
+ @Override
+ public void onDataChannel(DataChannel dataChannel) {
+ Log.d(TAG, "onDataChannel: ");
+ dataChannel.registerObserver(new DataChannel.Observer() {
+ @Override
+ public void onBufferedAmountChange(long l) {
+ //Keep this callback for future usage
+ Log.d(TAG, "onBufferedAmountChange:");
+ }
+
+ @Override
+ public void onStateChange() {
+ Log.d(TAG, "onStateChange: remote data channel state: " + dataChannel.state().toString());
+ }
+
+ @Override
+ public void onMessage(DataChannel.Buffer buffer) {
+ Log.d(TAG, "onMessage: got message");
+ dataCallback.pushData(readIncomingMessage(buffer.data));
+ }
+ });
+ }
+
+ @Override
+ public void onRenegotiationNeeded() {
+ Log.d(TAG, "onRenegotiationNeeded: ");
+ }
+
+ @Override
+ public void onAddTrack(RtpReceiver rtpReceiver, MediaStream[] mediaStreams) {
+ MediaStreamTrack track = rtpReceiver.track();
+ if (track instanceof VideoTrack && isReciever) {
+ Log.i(TAG, "onAddVideoTrack");
+ VideoTrack remoteVideoTrack = (VideoTrack) track;
+ remoteVideoTrack.setEnabled(true);
+ ProxyVideoSink videoSink = new ProxyVideoSink(dataCallback);
+ remoteVideoTrack.addSink(videoSink);
+ }
+ }
+ };
+ return factory.createPeerConnection(rtcConfig, pcConstraints, pcObserver);
+ }
+
+ /**
+ * Method used to send video data
+ * @param frame Video frame in byte format
+ * @param width width of the video frame
+ * @param height height of the video frame
+ */
+ public void sendVideoData(byte[] frame , int width , int height){
+ videoCapturer.send(frame , width , height);
+ }
+
+ /**
+ * Method to send message data
+ * @param message message to be sent in byte format
+ */
+ public void sendMessageData(byte[] message) {
+ ByteBuffer data = ByteBuffer.wrap(message);
+ localDataChannel.send(new DataChannel.Buffer(data, false));
+ }
+
+ /**
+ * Interface to create data call back mechanism
+ */
+ public interface ReceiveDataCallback{
+ void pushData(byte[] frame);
+ }
+
+ /**
+ * Class packet to create a packet
+ */
+ private static class Packet implements Serializable {
+ boolean isString;
+ String obj;
+ Packet(String s){
+ isString = true;
+ obj = s;
+ }
+
+ Packet(JSONObject json){
+ isString = false;
+ obj = json.toString();
+ }
+ }
+
+ /**
+ * Method to read incoming message and convert it to byte format
+ * @param buffer Message incoming in Byte buffer format
+ * @return returns byteBuffer message in byte format
+ */
+ private byte[] readIncomingMessage(ByteBuffer buffer) {
+ byte[] bytes;
+ if (buffer.hasArray()) {
+ bytes = buffer.array();
+ } else {
+ bytes = new byte[buffer.remaining()];
+ buffer.get(bytes);
+ }
+ return bytes;
+ }
+
+ /**
+ * Class to implement SDP observer
+ */
+ private static class SimpleSdpObserver implements SdpObserver {
+ @Override
+ public void onCreateSuccess(SessionDescription sessionDescription) {
+ //Required for future reference
+ }
+
+ @Override
+ public void onSetSuccess() {
+ Log.d(TAG, "onSetSuccess:");
+ }
+
+ @Override
+ public void onCreateFailure(String s) {
+ Log.d(TAG, "onCreateFailure: Reason = " + s);
+ }
+
+ @Override
+ public void onSetFailure(String s) {
+ Log.d(TAG, "onSetFailure: Reason = " + s);
+ }
+ }
+
+ /**
+ * Class to implement Frame video capturer
+ */
+ private static class FrameVideoCapturer implements VideoCapturer {
+ private CapturerObserver capturerObserver;
+
+ void send(byte[] frame, int width, int height) {
+ long timestampNS = TimeUnit.MILLISECONDS.toNanos(SystemClock.elapsedRealtime());
+ NV21Buffer buffer = new NV21Buffer(frame, width, height, null);
+ VideoFrame videoFrame = new VideoFrame(buffer, 0, timestampNS);
+ this.capturerObserver.onFrameCaptured(videoFrame);
+ videoFrame.release();
+ }
+
+ @Override
+ public void initialize(SurfaceTextureHelper surfaceTextureHelper, Context context, CapturerObserver capturerObserver) {
+ this.capturerObserver = capturerObserver;
+ }
+
+ public void startCapture(int width, int height, int framerate) {
+ //Required for future reference
+ }
+
+ public void stopCapture() throws InterruptedException {
+ //Required for future reference
+ }
+
+ public void changeCaptureFormat(int width, int height, int framerate) {
+ //Required for future reference
+ }
+
+ public void dispose() {
+ //Required for future reference
+ }
+
+ public boolean isScreencast() {
+ return false;
+ }
+ }
+
+ /**
+ * Class to implement SDP thread
+ */
+ private class SDPThread implements Runnable {
+ private volatile boolean isRunning = true;
+
+ @Override
+ public void run() {
+ isChannelReady = true;
+
+ createSocket();
+ invokeSendMessage();
+
+ while (isRunning) {
+ try {
+ Packet recvPacketNew = (Packet) inputStream.readObject();
+ if (recvPacketNew != null) {
+ if (recvPacketNew.isString) {
+ String message = recvPacketNew.obj;
+ checkPacketMessage(message);
+ } else {
+ JSONObject message = new JSONObject(recvPacketNew.obj);
+ Log.d(TAG, "connectToSignallingServer: got message " + message);
+ decodeMessage(message);
+ }
+ }
+ } catch (ClassNotFoundException | JSONException | IOException e) {
+ isRunning = false;
+ Log.e(TAG, "Error during JSON read", e);
+ }
+ }
+ }
+
+ /**
+ * Method to decode message
+ * @param message Message received in JSON object format
+ */
+ private void decodeMessage(JSONObject message) {
+ try {
+ if (message.getString("type").equals("offer")) {
+ Log.d(TAG, "connectToSignallingServer: received an offer " + isInitiator + " " + isStarted);
+ invokeMaybeStart();
+ peerConnection.setRemoteDescription(new SimpleSdpObserver(), new SessionDescription(OFFER, message.getString("sdp")));
+ doAnswer();
+ } else if (message.getString("type").equals("answer") && isStarted) {
+ peerConnection.setRemoteDescription(new SimpleSdpObserver(), new SessionDescription(ANSWER, message.getString("sdp")));
+ } else if (message.getString("type").equals(CANDIDATE) && isStarted) {
+ Log.d(TAG, "connectToSignallingServer: receiving candidates");
+ IceCandidate candidate = new IceCandidate(message.getString("id"), message.getInt("label"), message.getString(CANDIDATE));
+ peerConnection.addIceCandidate(candidate);
+ }
+ } catch (JSONException e) {
+ Log.e(TAG, "Error during message decoding", e);
+ }
+ }
+
+ /**
+ * Method to create SDP answer for a given SDP offer
+ */
+ private void doAnswer() {
+ peerConnection.createAnswer(new SimpleSdpObserver() {
+ @Override
+ public void onCreateSuccess(SessionDescription sessionDescription) {
+ peerConnection.setLocalDescription(new SimpleSdpObserver(), sessionDescription);
+ JSONObject message = new JSONObject();
+ try {
+ message.put("type", "answer");
+ message.put("sdp", sessionDescription.description);
+ sendMessage(true, message);
+ } catch (JSONException | IOException e) {
+ Log.e(TAG, "Error during sdp answer", e);
+ }
+ }
+ }, new MediaConstraints());
+ }
+
+ /**
+ * Method used to create a socket for SDP negotiation
+ */
+ private void createSocket(){
+ try {
+ if(!isReciever){
+ socket = new Socket(recieverIP, recieverPort);
+ }
+ outStream = new ObjectOutputStream(socket.getOutputStream());
+ inputStream = new ObjectInputStream(socket.getInputStream());
+ } catch (Exception e) {
+ Log.e(TAG, "Error during create socket", e);
+ }
+ }
+
+ /**
+ * Method to invoke Signalling handshake message
+ */
+ private void invokeSendMessage(){
+ try {
+ sendMessage(false , "got user media");
+ } catch (Exception e) {
+ Log.e(TAG, "Error during invoke send message", e);
+ }
+ }
+
+ /**
+ * Method to check if the message in received packet is "got user media"
+ */
+ private void checkPacketMessage(String message){
+ if (message.equals("got user media")) {
+ maybeStart();
+ }
+ }
+
+ /**
+ * Method to invoke MaybeStart()
+ */
+ private void invokeMaybeStart(){
+ if (!isInitiator && !isStarted) {
+ maybeStart();
+ }
+ }
+
+ /**
+ * Method to begin SDP negotiation by sending SDP offer to peer
+ */
+ private void maybeStart() {
+ Log.d(TAG, "maybeStart: " + isStarted + " " + isChannelReady);
+ if (!isStarted && isChannelReady) {
+ isStarted = true;
+ if (isInitiator) {
+ doCall();
+ }
+ }
+ }
+
+ /**
+ * Method to stop thread
+ */
+ public void stop() {
+ isRunning = false;
+ }
+ }
+}
diff --git a/android/modules/webrtc/src/main/java/com/samsung/android/modules/webrtc/WebRTCServer.java b/android/modules/webrtc/src/main/java/com/samsung/android/modules/webrtc/WebRTCServer.java
new file mode 100644
index 0000000..6e93ed7
--- /dev/null
+++ b/android/modules/webrtc/src/main/java/com/samsung/android/modules/webrtc/WebRTCServer.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (c) 2022 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.samsung.android.modules.webrtc;
+
+import android.content.Context;
+import android.util.Log;
+
+import java.io.IOException;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Class to implement WebRTC server related functionalities
+ */
+public class WebRTCServer {
+ private static final String TAG = "WebRTCServer";
+ private WebRTC.DataType dataType;
+ private ServerSocket serverSocket = null;
+ private Context appContext;
+ private WebRTC.ReceiveDataCallback dataCallback;
+ private List<WebRTC> connectionList = new ArrayList<>();
+ private ServerThread serverThread = null;
+ private Thread thread = null;
+
+ /**
+ * WebRTCServer constructor to create its instance
+ * @param appContext Application context of the app creating WebRTCServer instance
+ * @param dataType Datatype to create webRTC channel - Media channel or data channel
+ * @param dataCallback Data callback object to create call back mechanism
+ */
+ public WebRTCServer(Context appContext, WebRTC.DataType dataType, WebRTC.ReceiveDataCallback dataCallback){
+ this.appContext = appContext;
+ this.dataType = dataType;
+ this.dataCallback = dataCallback;
+ }
+
+ /**
+ * Method to start WebRTCServer instance
+ * @return Returns Port number on success and -1 on failure
+ */
+ public int start(){
+ try {
+ serverSocket = new ServerSocket(0);
+ } catch (IOException e) {
+ Log.e(TAG, "Error during start", e);
+ return -1;
+ }
+ serverThread = new ServerThread();
+ thread = new Thread(serverThread);
+ thread.start();
+ return serverSocket.getLocalPort();
+ }
+
+ /**
+ * Method to stop running WebRTC server instance
+ */
+ public void stop(){
+ if (serverThread != null) {
+ serverThread.stop();
+ }
+ try {
+ if (serverSocket != null) {
+ serverSocket.close();
+ }
+ } catch (IOException e) {
+ Log.e(TAG, "Error during stop", e);
+ }
+ for(WebRTC web : connectionList){
+ web.disconnect();
+ }
+ }
+
+ /**
+ * Class to implement a server thread
+ */
+ private class ServerThread implements Runnable{
+ private volatile boolean isRunning = true;
+
+ @Override
+ public void run() {
+ while(isRunning){
+ try {
+ Socket socket = serverSocket.accept();
+ WebRTC web = new WebRTC(dataType , appContext , socket);
+ web.connect();
+ web.registerDataCallback(dataCallback);
+ connectionList.add(web);
+ } catch (IOException e) {
+ isRunning = false;
+ Log.e(TAG, "Error during run", e);
+ }
+ }
+ }
+
+ public void stop() {
+ isRunning = false;
+ }
+ }
+}
diff --git a/android/modules/webrtc/src/test/java/com/samsung/android/modules/webrtc/ExampleUnitTest.java b/android/modules/webrtc/src/test/java/com/samsung/android/modules/webrtc/ExampleUnitTest.java
new file mode 100644
index 0000000..1536b3d
--- /dev/null
+++ b/android/modules/webrtc/src/test/java/com/samsung/android/modules/webrtc/ExampleUnitTest.java
@@ -0,0 +1,17 @@
+package com.samsung.android.modules.webrtc;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
+ */
+public class ExampleUnitTest {
+ @Test
+ public void addition_isCorrect() {
+ assertEquals(4, 2 + 2);
+ }
+} \ No newline at end of file
diff --git a/android/mosquitto/build.gradle b/android/mosquitto/build.gradle
new file mode 100644
index 0000000..0b6001a
--- /dev/null
+++ b/android/mosquitto/build.gradle
@@ -0,0 +1,80 @@
+plugins {
+ id 'com.android.library'
+ id "de.undercouch.download" version "5.0.1"
+}
+
+def thirdPartyDir = new File ("${rootProject.projectDir}/third_party")
+
+def mosquittoDir = new File("${thirdPartyDir}/mosquitto-2.0.14")
+
+android {
+ compileSdkVersion 31
+ ndkVersion "21.3.6528147"
+ defaultConfig {
+ minSdkVersion 28
+ targetSdkVersion 31
+
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ externalNativeBuild {
+ cmake {
+ arguments '-DCMAKE_VERBOSE_MAKEFILE=1'
+ arguments '-DCMAKE_INSTALL_PREFIX:PATH=/usr'
+ arguments '-DANDROID_STL=c++_shared'
+ arguments "-DANDROID_NDK_HOME=${System.env.ANDROID_NDK_ROOT}"
+ arguments '-DWITH_STATIC_LIBRARIES=ON'
+ arguments '-DWITH_TLS=OFF'
+ arguments '-DWITH_TLS_PSK=OFF'
+ arguments '-DWITH_CJSON=OFF'
+ arguments '-DWITH_APPS=OFF'
+ arguments '-DDOCUMENTATION=OFF'
+ cppFlags "-std=c++17"
+ abiFilters 'arm64-v8a', 'x86'
+ targets "libmosquitto", "mosquittopp", "libmosquitto_static", "mosquittopp_static"
+ }
+ }
+ }
+ buildFeatures {
+ prefab false
+ }
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+ externalNativeBuild {
+ cmake {
+ path "${mosquittoDir}/CMakeLists.txt"
+ }
+ }
+}
+
+dependencies {
+ testImplementation 'junit:junit:4.+'
+ androidTestImplementation 'androidx.test.ext:junit:1.1.3'
+ androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
+}
+
+task downloadMosquitto(type: Download) {
+ doFirst {
+ println("Downloading Mosquitto")
+ }
+ src "https://github.com/eclipse/mosquitto/archive/refs/tags/v2.0.14.zip"
+ dest new File(thirdPartyDir, "mosquitto-2.0.14.zip")
+ onlyIfModified true
+}
+
+task unzipMosquitto(type: Copy, dependsOn: downloadMosquitto) {
+ doFirst {
+ println("Unzipping Mosquitto")
+ }
+ from zipTree(downloadMosquitto.dest)
+ into thirdPartyDir
+ onlyIf { !mosquittoDir.exists() }
+}
+
+task wrapper(type: Wrapper) {
+ gradleVersion = '4.1'
+}
+
+preBuild.dependsOn(unzipMosquitto)
diff --git a/android/mosquitto/src/main/AndroidManifest.xml b/android/mosquitto/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..458e804
--- /dev/null
+++ b/android/mosquitto/src/main/AndroidManifest.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.samsung.android.mosquitto">
+</manifest>
diff --git a/android/settings.gradle b/android/settings.gradle
new file mode 100644
index 0000000..aa2ce81
--- /dev/null
+++ b/android/settings.gradle
@@ -0,0 +1,4 @@
+include ':aitt'
+include ':flatbuffers'
+include ':mosquitto'
+include ':modules:webrtc'
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000..a8ae137
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,25 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+buildscript {
+ repositories {
+ google()
+ mavenCentral()
+ }
+ dependencies {
+ classpath "com.android.tools.build:gradle:4.2.0"
+
+ // NOTE: Do not place your application dependencies here; they belong
+ // in the individual module build.gradle files
+ }
+}
+
+allprojects {
+ repositories {
+ google()
+ jcenter()
+ mavenCentral()
+ }
+}
+
+task clean(type: Delete) {
+ delete rootProject.buildDir
+}
diff --git a/cmake/aitt_android_flatbuffers.cmake b/cmake/aitt_android_flatbuffers.cmake
new file mode 100644
index 0000000..048d7ec
--- /dev/null
+++ b/cmake/aitt_android_flatbuffers.cmake
@@ -0,0 +1,14 @@
+if(CMAKE_VERSION VERSION_LESS "3.10.0")
+ if(DEFINED AITT_ANDROID_FLATBUFFERS)
+ return()
+ endif()
+ set(AITT_ANDROID_FLATBUFFERS TRUE)
+else(CMAKE_VERSION VERSION_LESS "3.10.0")
+ include_guard(GLOBAL)
+endif(CMAKE_VERSION VERSION_LESS "3.10.0")
+
+include_directories(${PROJECT_ROOT_DIR}/third_party/flatbuffers-2.0.0/include)
+
+link_directories(${PROJECT_ROOT_DIR}/android/flatbuffers/.cxx/cmake/debug/${ANDROID_ABI})
+
+set(FLATBUFFERS_LIBRARY ${PROJECT_ROOT_DIR}/android/flatbuffers/.cxx/cmake/debug/${ANDROID_ABI}/libflatbuffers.a)
diff --git a/cmake/aitt_android_glib.cmake b/cmake/aitt_android_glib.cmake
new file mode 100644
index 0000000..44c6618
--- /dev/null
+++ b/cmake/aitt_android_glib.cmake
@@ -0,0 +1,27 @@
+if(CMAKE_VERSION VERSION_LESS "3.10.0")
+ if(DEFINED AITT_ANDROID_GLIB)
+ return()
+ endif()
+ set(AITT_ANDROID_GLIB TRUE)
+else(CMAKE_VERSION VERSION_LESS "3.10.0")
+ include_guard(GLOBAL)
+endif(CMAKE_VERSION VERSION_LESS "3.10.0")
+
+if(ANDROID_ABI STREQUAL "arm64-v8a")
+ set(GSTREAMER_ABI arm64)
+elseif(ANDROID_ABI STREQUAL "armeabi-v7a")
+ set(GSTREAMER_ABI armv7)
+else(ANDROID_ABI STREQUAL "armeabi-v7a")
+ set(GSTREAMER_ABI ${ANDROID_ABI})
+endif(ANDROID_ABI STREQUAL "arm64-v8a")
+
+include_directories(
+ ${GSTREAMER_ROOT_ANDROID}/${GSTREAMER_ABI}/include/glib-2.0
+ ${GSTREAMER_ROOT_ANDROID}/${GSTREAMER_ABI}/lib/glib-2.0/include
+)
+
+link_directories(${GSTREAMER_ROOT_ANDROID}/${GSTREAMER_ABI}/lib)
+
+set(GLIB_LIBRARIES ${GSTREAMER_ROOT_ANDROID}/${GSTREAMER_ABI}/lib/libglib-2.0.a
+ ${GSTREAMER_ROOT_ANDROID}/${GSTREAMER_ABI}/lib/libiconv.a
+ ${GSTREAMER_ROOT_ANDROID}/${GSTREAMER_ABI}/lib/libintl.a)
diff --git a/cmake/aitt_android_mosquitto.cmake b/cmake/aitt_android_mosquitto.cmake
new file mode 100644
index 0000000..4428534
--- /dev/null
+++ b/cmake/aitt_android_mosquitto.cmake
@@ -0,0 +1,14 @@
+if(CMAKE_VERSION VERSION_LESS "3.10.0")
+ if(DEFINED AITT_ANDROID_MOSQUITTO)
+ return()
+ endif()
+ set(AITT_ANDROID_MOSQUITTO TRUE)
+else(CMAKE_VERSION VERSION_LESS "3.10.0")
+ include_guard(GLOBAL)
+endif(CMAKE_VERSION VERSION_LESS "3.10.0")
+
+include_directories(${PROJECT_ROOT_DIR}/third_party/mosquitto-2.0.14/include)
+
+link_directories(${PROJECT_ROOT_DIR}/android/mosquitto/.cxx/cmake/debug/${ANDROID_ABI}/lib)
+
+set(MOSQUITTO_LIBRARY ${PROJECT_ROOT_DIR}/android/mosquitto/.cxx/cmake/debug/${ANDROID_ABI}/lib/libmosquitto_static.a)
diff --git a/common/AITTEx.cc b/common/AITTEx.cc
new file mode 100755
index 0000000..b99d305
--- /dev/null
+++ b/common/AITTEx.cc
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2021-2022 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "AITTEx.h"
+
+using namespace aitt;
+
+AITTEx::AITTEx(ErrCode code) : err_code(code)
+{
+ err_msg = getErrString();
+}
+
+AITTEx::AITTEx(ErrCode code, const std::string& msg) : err_code(code)
+{
+ err_msg = getErrString() + " : " + msg;
+}
+
+AITTEx::ErrCode AITTEx::getErrCode()
+{
+ return err_code;
+}
+
+std::string AITTEx::getErrString() const
+{
+ switch (err_code) {
+ case INVALID_ARG:
+ return "Invalid Argument";
+ case NO_MEMORY:
+ return "Memory allocation failure";
+ case OPERATION_FAILED:
+ return "Operation failure";
+ case SYSTEM_ERR:
+ return "System failure";
+ case MQTT_ERR:
+ return "MQTT failure";
+ case NO_DATA:
+ return "No data found";
+ default:
+ return "Unknown Error";
+ }
+}
+
+const char* AITTEx::what() const throw()
+{
+ return err_msg.c_str();
+}
+
diff --git a/common/AITTEx.h b/common/AITTEx.h
new file mode 100755
index 0000000..8018c7f
--- /dev/null
+++ b/common/AITTEx.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2021-2022 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+#include <exception>
+#include <string>
+#include <vector>
+
+namespace aitt {
+
+class AITTEx : public std::exception {
+ public:
+ typedef enum {
+ INVALID_ARG,
+ NO_MEMORY,
+ OPERATION_FAILED,
+ SYSTEM_ERR,
+ MQTT_ERR,
+ NO_DATA
+ } ErrCode;
+
+ AITTEx(ErrCode err_code);
+ AITTEx(ErrCode err_code, const std::string& custom_err_msg);
+
+ ErrCode getErrCode();
+ virtual const char* what() const throw() override;
+
+ private:
+ ErrCode err_code;
+ std::string err_msg;
+
+ std::string getErrString() const;
+};
+
+} // namespace aitt
+
diff --git a/common/AittDiscovery.cc b/common/AittDiscovery.cc
new file mode 100644
index 0000000..8f383c0
--- /dev/null
+++ b/common/AittDiscovery.cc
@@ -0,0 +1,197 @@
+/*
+ * Copyright (c) 2021-2022 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "AittDiscovery.h"
+
+#include <flatbuffers/flexbuffers.h>
+
+#include <atomic>
+
+#include "AITTEx.h"
+#include "aitt_internal.h"
+
+namespace aitt {
+
+AittDiscovery::AittDiscovery(const std::string &id)
+ : id_(id), discovery_mq(id + "d", true), callback_handle(nullptr)
+{
+}
+
+void AittDiscovery::Start(const std::string &host, int port, const std::string &username,
+ const std::string &password)
+{
+ RET_IF(callback_handle);
+
+ discovery_mq.SetWillInfo(DISCOVERY_TOPIC_BASE + id_, nullptr, 0, AITT_QOS_EXACTLY_ONCE, true);
+ discovery_mq.Connect(host, port, username, password);
+
+ callback_handle = discovery_mq.Subscribe(DISCOVERY_TOPIC_BASE + "+", DiscoveryMessageCallback,
+ static_cast<void *>(this), AITT_QOS_EXACTLY_ONCE);
+}
+
+void AittDiscovery::Stop()
+{
+ discovery_mq.Publish(DISCOVERY_TOPIC_BASE + id_, nullptr, 0, AITT_QOS_EXACTLY_ONCE, true);
+ discovery_mq.Unsubscribe(callback_handle);
+ callback_handle = nullptr;
+ discovery_mq.Disconnect();
+}
+
+void AittDiscovery::UpdateDiscoveryMsg(AittProtocol protocol, const void *msg, size_t length)
+{
+ auto it = discovery_map.find(protocol);
+ if (it == discovery_map.end())
+ discovery_map.emplace(protocol, DiscoveryBlob(msg, length));
+ else
+ it->second = DiscoveryBlob(msg, length);
+
+ PublishDiscoveryMsg();
+}
+
+int AittDiscovery::AddDiscoveryCB(AittProtocol protocol, const DiscoveryCallback &cb)
+{
+ static std::atomic_int id(0);
+ id++;
+ callbacks.emplace(id, std::make_pair(protocol, cb));
+
+ return id;
+}
+
+void AittDiscovery::RemoveDiscoveryCB(int callback_id)
+{
+ auto it = callbacks.find(callback_id);
+ if (it == callbacks.end()) {
+ ERR("Unknown callback_id(%d)", callback_id);
+ throw AITTEx(AITTEx::INVALID_ARG);
+ }
+ callbacks.erase(it);
+}
+
+void AittDiscovery::DiscoveryMessageCallback(MSG *mq, const std::string &topic, const void *msg,
+ const int szmsg, void *user_data)
+{
+ RET_IF(user_data == nullptr);
+
+ AittDiscovery *discovery = static_cast<AittDiscovery *>(user_data);
+
+ DBG("Called(id = %s, msg = %p:%d)", discovery->id_.c_str(), msg, szmsg);
+
+ size_t end = topic.find("/", DISCOVERY_TOPIC_BASE.length());
+ std::string clientId = topic.substr(DISCOVERY_TOPIC_BASE.length(), end);
+ if (clientId.empty()) {
+ ERR("ClientId is empty");
+ return;
+ }
+
+ if (msg == nullptr) {
+ for (const auto &node : discovery->callbacks) {
+ std::pair<AittProtocol, DiscoveryCallback> cb_info = node.second;
+ cb_info.second(clientId, WILL_LEAVE_NETWORK, nullptr, 0);
+ }
+ return;
+ }
+
+ auto map = flexbuffers::GetRoot(static_cast<const uint8_t *>(msg), szmsg).AsMap();
+ std::string status = map["status"].AsString().c_str();
+
+ auto keys = map.Keys();
+ for (size_t idx = 0; idx < keys.size(); ++idx) {
+ std::string key = keys[idx].AsString().str();
+
+ if (!key.compare("status"))
+ continue;
+
+ auto blob = map[key].AsBlob();
+ for (const auto &node : discovery->callbacks) {
+ std::pair<AittProtocol, DiscoveryCallback> cb_info = node.second;
+ if (cb_info.first == discovery->GetProtocol(key)) {
+ cb_info.second(clientId, status, blob.data(), blob.size());
+ }
+ }
+ }
+}
+
+void AittDiscovery::PublishDiscoveryMsg()
+{
+ flexbuffers::Builder fbb;
+
+ fbb.Map([this, &fbb]() {
+ fbb.String("status", JOIN_NETWORK);
+
+ for (const std::pair<const AittProtocol, const DiscoveryBlob &> &node : discovery_map) {
+ fbb.Key(GetProtocolStr(node.first));
+ fbb.Blob(node.second.data.get(), node.second.len);
+ }
+ });
+
+ fbb.Finish();
+
+ auto buf = fbb.GetBuffer();
+ discovery_mq.Publish(DISCOVERY_TOPIC_BASE + id_, buf.data(), buf.size(), AITT_QOS_EXACTLY_ONCE,
+ true);
+}
+
+const char *AittDiscovery::GetProtocolStr(AittProtocol protocol)
+{
+ switch (protocol) {
+ case AITT_TYPE_MQTT:
+ return "mqtt";
+ case AITT_TYPE_TCP:
+ return "tcp";
+ case AITT_TYPE_WEBRTC:
+ return "webrtc";
+ default:
+ ERR("Unknown protocol(%d)", protocol);
+ }
+
+ return nullptr;
+}
+
+AittProtocol AittDiscovery::GetProtocol(const std::string &protocol_str)
+{
+ if (STR_EQ == protocol_str.compare(GetProtocolStr(AITT_TYPE_MQTT)))
+ return AITT_TYPE_MQTT;
+
+ if (STR_EQ == protocol_str.compare(GetProtocolStr(AITT_TYPE_TCP)))
+ return AITT_TYPE_TCP;
+
+ if (STR_EQ == protocol_str.compare(GetProtocolStr(AITT_TYPE_WEBRTC)))
+ return AITT_TYPE_WEBRTC;
+
+ return AITT_TYPE_UNKNOWN;
+}
+
+AittDiscovery::DiscoveryBlob::DiscoveryBlob(const void *msg, size_t length)
+ : len(length), data(new char[len])
+{
+ memcpy(data.get(), msg, length);
+}
+
+AittDiscovery::DiscoveryBlob::~DiscoveryBlob()
+{
+}
+
+AittDiscovery::DiscoveryBlob::DiscoveryBlob(const DiscoveryBlob &a) : len(a.len), data(a.data)
+{
+}
+
+AittDiscovery::DiscoveryBlob &AittDiscovery::DiscoveryBlob::operator=(const DiscoveryBlob &src)
+{
+ len = src.len;
+ data = src.data;
+ return *this;
+}
+
+} // namespace aitt
diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt
new file mode 100644
index 0000000..53eadd9
--- /dev/null
+++ b/common/CMakeLists.txt
@@ -0,0 +1,12 @@
+FILE(GLOB COMMON_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/*.cc)
+
+ADD_LIBRARY(${AITT_COMMON} SHARED ${COMMON_SRCS})
+TARGET_LINK_LIBRARIES(${AITT_COMMON} ${AITT_NEEDS_LIBRARIES} Threads::Threads)
+TARGET_COMPILE_OPTIONS(${AITT_COMMON} PUBLIC ${AITT_NEEDS_CFLAGS_OTHER} "-fvisibility=default")
+IF(VERSIONING)
+ SET_TARGET_PROPERTIES(${AITT_COMMON} PROPERTIES
+ VERSION ${PROJECT_VERSION}
+ SOVERSION ${PROJECT_VERSION_MAJOR}
+ )
+ENDIF(VERSIONING)
+INSTALL(TARGETS ${AITT_COMMON} DESTINATION ${CMAKE_INSTALL_LIBDIR})
diff --git a/common/MQ.cc b/common/MQ.cc
new file mode 100644
index 0000000..2be4518
--- /dev/null
+++ b/common/MQ.cc
@@ -0,0 +1,427 @@
+/*
+ * Copyright (c) 2021-2022 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "MQ.h"
+
+#include <mqtt_protocol.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <algorithm>
+#include <cerrno>
+#include <stdexcept>
+#include <thread>
+
+#include "AITTEx.h"
+#include "AittTypes.h"
+#include "aitt_internal.h"
+
+namespace aitt {
+
+const std::string MQ::REPLY_SEQUENCE_NUM_KEY = "sequenceNum";
+const std::string MQ::REPLY_IS_END_SEQUENCE_KEY = "isEndSequence";
+thread_local bool MQ::in_callback = false;
+
+MQ::MQ(const std::string &id, bool clear_session)
+ : handle(nullptr),
+ keep_alive(60),
+ subscribers_iterating(false),
+ subscriber_iterator_updated(false),
+ connect_cb(nullptr)
+{
+ do {
+ int ret = mosquitto_lib_init();
+ if (ret != MOSQ_ERR_SUCCESS) {
+ ERR("mosquitto_lib_init() Fail(%s)", mosquitto_strerror(ret));
+ break;
+ }
+
+ handle = mosquitto_new(id.c_str(), clear_session, this);
+ if (handle == nullptr) {
+ ERR("mosquitto_new(%s, %d) Fail", id.c_str(), clear_session);
+ break;
+ }
+
+ ret = mosquitto_int_option(handle, MOSQ_OPT_PROTOCOL_VERSION, MQTT_PROTOCOL_V5);
+ if (ret != MOSQ_ERR_SUCCESS) {
+ ERR("mosquitto_int_option() Fail(%s)", mosquitto_strerror(ret));
+ break;
+ }
+
+ mosquitto_message_v5_callback_set(handle, MessageCallback);
+
+ ret = mosquitto_loop_start(handle);
+ if (ret != MOSQ_ERR_SUCCESS) {
+ ERR("mosquitto_loop_start() Fail(%s)", mosquitto_strerror(ret));
+ break;
+ }
+
+ return;
+ } while (0);
+
+ mosquitto_destroy(handle);
+ mosquitto_lib_cleanup();
+ throw AITTEx(AITTEx::MQTT_ERR, std::string("MQ Constructor Error"));
+}
+
+MQ::~MQ(void)
+{
+ int ret;
+ INFO("Destructor");
+
+ if (mq_connect_thread.joinable())
+ mq_connect_thread.join();
+
+ ret = mosquitto_loop_stop(handle, true);
+ if (ret != MOSQ_ERR_SUCCESS)
+ ERR("mosquitto_loop_stop() Fail(%s)", mosquitto_strerror(ret));
+
+ callback_lock.lock();
+ connect_cb = nullptr;
+ subscribers.clear();
+ callback_lock.unlock();
+
+ mosquitto_destroy(handle);
+
+ ret = mosquitto_lib_cleanup();
+ if (ret != MOSQ_ERR_SUCCESS)
+ ERR("mosquitto_lib_cleanup() Fail(%s)", mosquitto_strerror(ret));
+}
+
+void MQ::SetConnectionCallback(const MQConnectionCallback &cb)
+{
+ std::lock_guard<std::recursive_mutex> lock_from_here(callback_lock);
+ connect_cb = cb;
+
+ if (mq_connect_thread.joinable())
+ mq_connect_thread.join();
+
+ if (in_callback) {
+ // When it's called in the cb, it's blocked by lock. That's why it uses thread.
+ mq_connect_thread = std::thread([&]() { SetConnectionCallbackReal(cb ? true : false); });
+ } else {
+ SetConnectionCallbackReal(cb ? true : false);
+ }
+}
+
+void MQ::SetConnectionCallbackReal(bool is_set)
+{
+ if (is_set) {
+ mosquitto_connect_v5_callback_set(handle, ConnectCallback);
+ mosquitto_disconnect_v5_callback_set(handle, DisconnectCallback);
+ } else {
+ mosquitto_connect_v5_callback_set(handle, nullptr);
+ mosquitto_disconnect_v5_callback_set(handle, nullptr);
+ }
+}
+
+void MQ::ConnectCallback(struct mosquitto *mosq, void *obj, int rc, int flag,
+ const mosquitto_property *props)
+{
+ RET_IF(obj == nullptr);
+ MQ *mq = static_cast<MQ *>(obj);
+
+ INFO("Connected : rc(%d), flag(%d)", rc, flag);
+
+ std::lock_guard<std::recursive_mutex> lock_from_here(mq->callback_lock);
+ in_callback = true;
+ if (mq->connect_cb)
+ mq->connect_cb(AITT_CONNECTED);
+ in_callback = false;
+}
+
+void MQ::DisconnectCallback(struct mosquitto *mosq, void *obj, int rc,
+ const mosquitto_property *props)
+{
+ RET_IF(obj == nullptr);
+ MQ *mq = static_cast<MQ *>(obj);
+
+ INFO("Disconnected : rc(%d)", rc);
+
+ std::lock_guard<std::recursive_mutex> lock_from_here(mq->callback_lock);
+ in_callback = true;
+ if (mq->connect_cb)
+ mq->connect_cb(AITT_DISCONNECTED);
+ in_callback = false;
+}
+
+void MQ::Connect(const std::string &host, int port, const std::string &username,
+ const std::string &password)
+{
+ int ret;
+
+ if (username.empty() == false) {
+ ret = mosquitto_username_pw_set(handle, username.c_str(), password.c_str());
+ if (ret != MOSQ_ERR_SUCCESS) {
+ ERR("mosquitto_username_pw_set(%s, %s) Fail(%s)", username.c_str(), password.c_str(),
+ mosquitto_strerror(ret));
+ throw AITTEx(AITTEx::MQTT_ERR);
+ }
+ }
+
+ ret = mosquitto_connect(handle, host.c_str(), port, keep_alive);
+ if (ret != MOSQ_ERR_SUCCESS) {
+ ERR("mosquitto_connect(%s, %d) Fail(%s)", host.c_str(), port, mosquitto_strerror(ret));
+ throw AITTEx(AITTEx::MQTT_ERR);
+ }
+}
+
+void MQ::SetWillInfo(const std::string &topic, const void *msg, size_t szmsg, int qos, bool retain)
+{
+ int ret = mosquitto_will_set(handle, topic.c_str(), szmsg, msg, qos, retain);
+ if (ret != MOSQ_ERR_SUCCESS) {
+ ERR("mosquitto_will_set(%s) Fail(%s)", topic.c_str(), mosquitto_strerror(ret));
+ throw AITTEx(AITTEx::MQTT_ERR);
+ }
+}
+
+void MQ::Disconnect(void)
+{
+ int ret = mosquitto_disconnect(handle);
+ if (ret != MOSQ_ERR_SUCCESS) {
+ ERR("mosquitto_disconnect() Fail(%s)", mosquitto_strerror(ret));
+ throw AITTEx(AITTEx::MQTT_ERR);
+ }
+
+ mosquitto_will_clear(handle);
+}
+
+void MQ::MessageCallback(mosquitto *handle, void *obj, const mosquitto_message *msg,
+ const mosquitto_property *props)
+{
+ RET_IF(obj == nullptr);
+ MQ *mq = static_cast<MQ *>(obj);
+
+ std::lock_guard<std::recursive_mutex> auto_lock(mq->callback_lock);
+ mq->subscribers_iterating = true;
+ mq->subscriber_iterator = mq->subscribers.begin();
+ while (mq->subscriber_iterator != mq->subscribers.end()) {
+ auto subscribe_data = *(mq->subscriber_iterator);
+ if (nullptr == subscribe_data)
+ ERR("end() is not valid because elements were added.");
+
+ bool result = CompareTopic(subscribe_data->topic.c_str(), msg->topic);
+ if (result)
+ mq->InvokeCallback(msg, props);
+
+ if (!mq->subscriber_iterator_updated)
+ mq->subscriber_iterator++;
+ else
+ mq->subscriber_iterator_updated = false;
+ }
+ mq->subscribers_iterating = false;
+ mq->subscribers.insert(mq->subscribers.end(), mq->new_subscribers.begin(),
+ mq->new_subscribers.end());
+ mq->new_subscribers.clear();
+}
+
+void MQ::InvokeCallback(const mosquitto_message *msg, const mosquitto_property *props)
+{
+ MSG mq_msg;
+ mq_msg.SetTopic(msg->topic);
+ if (props) {
+ const mosquitto_property *prop;
+
+ char *response_topic = nullptr;
+ prop = mosquitto_property_read_string(props, MQTT_PROP_RESPONSE_TOPIC, &response_topic,
+ false);
+ if (prop) {
+ mq_msg.SetResponseTopic(response_topic);
+ free(response_topic);
+ }
+
+ void *correlation = nullptr;
+ uint16_t correlation_size = 0;
+ prop = mosquitto_property_read_binary(props, MQTT_PROP_CORRELATION_DATA, &correlation,
+ &correlation_size, false);
+ if (prop == nullptr || correlation == nullptr)
+ ERR("No Correlation Data");
+
+ mq_msg.SetCorrelation(std::string((char *)correlation, correlation_size));
+ if (correlation)
+ free(correlation);
+
+ char *name = nullptr;
+ char *value = nullptr;
+ prop = mosquitto_property_read_string_pair(props, MQTT_PROP_USER_PROPERTY, &name, &value,
+ false);
+ while (prop) {
+ if (REPLY_SEQUENCE_NUM_KEY == name) {
+ mq_msg.SetSequence(std::stoi(value));
+ } else if (REPLY_IS_END_SEQUENCE_KEY == name) {
+ mq_msg.SetEndSequence(std::stoi(value) == 1);
+ } else {
+ ERR("Unsupported property(%s, %s)", name, value);
+ }
+ free(name);
+ free(value);
+
+ prop = mosquitto_property_read_string_pair(prop, MQTT_PROP_USER_PROPERTY, &name, &value,
+ true);
+ }
+ }
+ in_callback = true;
+ SubscribeData *cb_info = *subscriber_iterator;
+ cb_info->cb(&mq_msg, msg->topic, msg->payload, msg->payloadlen, cb_info->user_data);
+ in_callback = false;
+}
+
+void MQ::Publish(const std::string &topic, const void *data, const size_t datalen, int qos,
+ bool retain)
+{
+ int mid = -1;
+ int ret = mosquitto_publish(handle, &mid, topic.c_str(), datalen, data, qos, retain);
+ if (ret != MOSQ_ERR_SUCCESS) {
+ ERR("mosquitto_publish(%s) Fail(%s)", topic.c_str(), mosquitto_strerror(ret));
+ throw AITTEx(AITTEx::MQTT_ERR);
+ }
+}
+
+void MQ::PublishWithReply(const std::string &topic, const void *data, const size_t datalen, int qos,
+ bool retain, const std::string &reply_topic, const std::string &correlation)
+{
+ int ret;
+ int mid = -1;
+ mosquitto_property *props = nullptr;
+
+ ret = mosquitto_property_add_string(&props, MQTT_PROP_RESPONSE_TOPIC, reply_topic.c_str());
+ if (ret != MOSQ_ERR_SUCCESS) {
+ ERR("mosquitto_property_add_string(response-topic) Fail(%s)", mosquitto_strerror(ret));
+ throw AITTEx(AITTEx::MQTT_ERR);
+ }
+
+ ret = mosquitto_property_add_binary(&props, MQTT_PROP_CORRELATION_DATA, correlation.c_str(),
+ correlation.size());
+ if (ret != MOSQ_ERR_SUCCESS) {
+ ERR("mosquitto_property_add_binary(correlation) Fail(%s)", mosquitto_strerror(ret));
+ throw AITTEx(AITTEx::MQTT_ERR);
+ }
+ ret = mosquitto_publish_v5(handle, &mid, topic.c_str(), datalen, data, qos, retain, props);
+ if (ret != MOSQ_ERR_SUCCESS) {
+ ERR("mosquitto_publish_v5(%s) Fail(%s)", topic.c_str(), mosquitto_strerror(ret));
+ throw AITTEx(AITTEx::MQTT_ERR);
+ }
+}
+
+void MQ::SendReply(MSG *msg, const void *data, const size_t datalen, int qos, bool retain)
+{
+ RET_IF(msg == nullptr);
+
+ int ret;
+ int mId = -1;
+ mosquitto_property *props = nullptr;
+
+ ret = mosquitto_property_add_binary(&props, MQTT_PROP_CORRELATION_DATA,
+ msg->GetCorrelation().c_str(), msg->GetCorrelation().size());
+ if (ret != MOSQ_ERR_SUCCESS) {
+ ERR("mosquitto_property_add_binary(correlation) Fail(%s)", mosquitto_strerror(ret));
+ throw AITTEx(AITTEx::MQTT_ERR);
+ }
+
+ ret = mosquitto_property_add_string_pair(&props, MQTT_PROP_USER_PROPERTY,
+ REPLY_SEQUENCE_NUM_KEY.c_str(), std::to_string(msg->GetSequence()).c_str());
+ if (ret != MOSQ_ERR_SUCCESS) {
+ ERR("mosquitto_property_add_string_pair(squenceNum) Fail(%s)", mosquitto_strerror(ret));
+ throw AITTEx(AITTEx::MQTT_ERR);
+ }
+
+ ret = mosquitto_property_add_string_pair(&props, MQTT_PROP_USER_PROPERTY,
+ REPLY_IS_END_SEQUENCE_KEY.c_str(), std::to_string(msg->IsEndSequence()).c_str());
+ if (ret != MOSQ_ERR_SUCCESS) {
+ ERR("mosquitto_property_add_string_pair(IsEndSequence) Fail(%s)", mosquitto_strerror(ret));
+ throw AITTEx(AITTEx::MQTT_ERR);
+ }
+
+ ret = mosquitto_publish_v5(handle, &mId, msg->GetResponseTopic().c_str(), datalen, data, qos,
+ retain, props);
+ if (ret != MOSQ_ERR_SUCCESS) {
+ ERR("mosquitto_publish_v5(%s) Fail(%s)", msg->GetResponseTopic().c_str(),
+ mosquitto_strerror(ret));
+ throw AITTEx(AITTEx::MQTT_ERR);
+ }
+}
+
+void *MQ::Subscribe(const std::string &topic, const SubscribeCallback &cb, void *user_data, int qos)
+{
+ int mid = -1;
+ int ret = mosquitto_subscribe(handle, &mid, topic.c_str(), qos);
+ if (ret != MOSQ_ERR_SUCCESS) {
+ ERR("mosquitto_subscribe(%s) Fail(%s)", topic.c_str(), mosquitto_strerror(ret));
+ throw AITTEx(AITTEx::MQTT_ERR);
+ }
+
+ std::lock_guard<std::recursive_mutex> lock_from_here(callback_lock);
+ SubscribeData *data = new SubscribeData(topic, cb, user_data);
+ if (subscribers_iterating)
+ new_subscribers.push_back(data);
+ else
+ subscribers.push_back(data);
+
+ return static_cast<void *>(data);
+}
+
+void *MQ::Unsubscribe(void *sub_handle)
+{
+ std::lock_guard<std::recursive_mutex> auto_lock(callback_lock);
+ auto it = std::find(subscribers.begin(), subscribers.end(),
+ static_cast<SubscribeData *>(sub_handle));
+
+ if (it == subscribers.end()) {
+ ERR("No Subscription(%p)", sub_handle);
+ throw AITTEx(AITTEx::NO_DATA);
+ }
+
+ SubscribeData *data = static_cast<SubscribeData *>(sub_handle);
+
+ if (subscriber_iterator == it) {
+ subscriber_iterator = subscribers.erase(it);
+ subscriber_iterator_updated = true;
+ } else {
+ subscribers.erase(it);
+ }
+
+ void *user_data = data->user_data;
+ std::string topic = data->topic;
+ delete data;
+
+ int mid = -1;
+ int ret = mosquitto_unsubscribe(handle, &mid, topic.c_str());
+ if (ret != MOSQ_ERR_SUCCESS) {
+ ERR("mosquitto_unsubscribe(%s) Fail(%d)", topic.c_str(), ret);
+ throw AITTEx(AITTEx::MQTT_ERR);
+ }
+
+ return user_data;
+}
+
+bool MQ::CompareTopic(const std::string &left, const std::string &right)
+{
+ bool result = false;
+ int ret = mosquitto_topic_matches_sub(left.c_str(), right.c_str(), &result);
+ if (ret != MOSQ_ERR_SUCCESS) {
+ ERR("mosquitto_topic_matches_sub(%s, %s) Fail(%s)", left.c_str(), right.c_str(),
+ mosquitto_strerror(ret));
+ throw AITTEx(AITTEx::MQTT_ERR);
+ }
+ return result;
+}
+
+MQ::SubscribeData::SubscribeData(const std::string &in_topic, const SubscribeCallback &in_cb,
+ void *in_user_data)
+ : topic(in_topic), cb(in_cb), user_data(in_user_data)
+{
+}
+
+} // namespace aitt
diff --git a/common/MQ.h b/common/MQ.h
new file mode 100644
index 0000000..3da6097
--- /dev/null
+++ b/common/MQ.h
@@ -0,0 +1,93 @@
+/*
+ * Copyright (c) 2021-2022 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+#include <mosquitto.h>
+
+#include <functional>
+#include <map>
+#include <mutex>
+#include <string>
+#include <thread>
+#include <utility>
+#include <vector>
+
+#include "MSG.h"
+
+#define MQTT_LOCALHOST "127.0.0.1"
+#define MQTT_PORT 1883
+
+namespace aitt {
+
+class MQ {
+ public:
+ using SubscribeCallback = std::function<void(MSG *msg, const std::string &topic,
+ const void *data, const size_t datalen, void *user_data)>;
+ using MQConnectionCallback = std::function<void(int)>;
+
+ explicit MQ(const std::string &id, bool clear_session = false);
+ virtual ~MQ(void);
+
+ static bool CompareTopic(const std::string &left, const std::string &right);
+
+ void SetConnectionCallback(const MQConnectionCallback &cb);
+ void Connect(const std::string &host, int port, const std::string &username,
+ const std::string &password);
+ void SetWillInfo(const std::string &topic, const void *msg, size_t szmsg, int qos, bool retain);
+ void Disconnect(void);
+ void Publish(const std::string &topic, const void *data, const size_t datalen, int qos = 0,
+ bool retain = false);
+ void PublishWithReply(const std::string &topic, const void *data, const size_t datalen, int qos,
+ bool retain, const std::string &reply_topic, const std::string &correlation);
+ void SendReply(MSG *msg, const void *data, const size_t datalen, int qos, bool retain);
+ void *Subscribe(const std::string &topic, const SubscribeCallback &cb,
+ void *user_data = nullptr, int qos = 0);
+ void *Unsubscribe(void *handle);
+
+ private:
+ struct SubscribeData {
+ SubscribeData(const std::string &topic, const SubscribeCallback &cb, void *user_data);
+ std::string topic;
+ SubscribeCallback cb;
+ void *user_data;
+ };
+
+ static void ConnectCallback(mosquitto *mosq, void *obj, int rc, int flag,
+ const mosquitto_property *props);
+ static void DisconnectCallback(mosquitto *mosq, void *obj, int rc,
+ const mosquitto_property *props);
+ static void MessageCallback(mosquitto *, void *, const mosquitto_message *,
+ const mosquitto_property *);
+ void InvokeCallback(const mosquitto_message *msg, const mosquitto_property *props);
+ void SetConnectionCallbackReal(bool is_set);
+
+ static const std::string REPLY_SEQUENCE_NUM_KEY;
+ static const std::string REPLY_IS_END_SEQUENCE_KEY;
+ thread_local static bool in_callback;
+
+ mosquitto *handle;
+ const int keep_alive;
+ std::vector<SubscribeData *> subscribers;
+ bool subscribers_iterating;
+ std::vector<SubscribeData *> new_subscribers;
+ std::vector<SubscribeData *>::iterator subscriber_iterator;
+ bool subscriber_iterator_updated;
+ std::recursive_mutex callback_lock;
+ MQConnectionCallback connect_cb;
+ std::thread mq_connect_thread;
+};
+
+} // namespace aitt
diff --git a/common/MSG.cc b/common/MSG.cc
new file mode 100644
index 0000000..46eb8e3
--- /dev/null
+++ b/common/MSG.cc
@@ -0,0 +1,98 @@
+/*
+ * Copyright (c) 2021-2022 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "MSG.h"
+
+namespace aitt {
+MSG::MSG() : sequence(0), end_sequence(true), id_(nullptr), protocols_(AITT_TYPE_MQTT)
+{
+}
+
+void MSG::SetID(AittSubscribeID id)
+{
+ id_ = id;
+}
+
+AittSubscribeID MSG::GetID()
+{
+ return id_;
+}
+
+void MSG::SetTopic(const std::string& topic)
+{
+ topic_ = topic;
+}
+
+const std::string& MSG::GetTopic()
+{
+ return topic_;
+}
+
+void MSG::SetCorrelation(const std::string& correlation)
+{
+ correlation_ = correlation;
+}
+
+const std::string& MSG::GetCorrelation()
+{
+ return correlation_;
+}
+
+void MSG::SetResponseTopic(const std::string& replyTopic)
+{
+ reply_topic_ = replyTopic;
+}
+
+const std::string& MSG::GetResponseTopic()
+{
+ return reply_topic_;
+}
+
+void MSG::SetSequence(int num)
+{
+ sequence = num;
+}
+
+void MSG::IncreaseSequence()
+{
+ sequence++;
+}
+
+int MSG::GetSequence()
+{
+ return sequence;
+}
+
+void MSG::SetEndSequence(bool end)
+{
+ end_sequence = end;
+}
+
+bool MSG::IsEndSequence()
+{
+ return end_sequence;
+}
+
+void MSG::SetProtocols(AittProtocol protocols)
+{
+ protocols_ = protocols;
+}
+
+AittProtocol MSG::GetProtocols()
+{
+ return protocols_;
+}
+
+} // namespace aitt
diff --git a/common/MainLoopHandler.cc b/common/MainLoopHandler.cc
new file mode 100644
index 0000000..747a6bc
--- /dev/null
+++ b/common/MainLoopHandler.cc
@@ -0,0 +1,187 @@
+/*
+ * Copyright (c) 2022 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "MainLoopHandler.h"
+
+#include <glib.h>
+
+#include "aitt_internal.h"
+
+namespace aitt {
+
+MainLoopHandler::MainLoopHandler()
+{
+ GMainContext *ctx = g_main_context_new();
+ if (ctx == nullptr)
+ throw std::runtime_error("Failed to create a context");
+
+ loop = g_main_loop_new(ctx, FALSE);
+ if (loop == nullptr) {
+ g_main_context_unref(ctx);
+ throw std::runtime_error("Failed to create a loop");
+ }
+ g_main_context_unref(ctx);
+}
+
+MainLoopHandler::~MainLoopHandler()
+{
+ g_main_loop_unref(loop);
+}
+
+void MainLoopHandler::Run()
+{
+ g_main_loop_run(loop);
+}
+
+bool MainLoopHandler::Quit()
+{
+ if (g_main_loop_is_running(loop) == FALSE) {
+ ERR("main loop is not running");
+ return false;
+ }
+
+ g_main_loop_quit(loop);
+ return true;
+}
+
+void MainLoopHandler::AddWatch(int fd, const mainLoopCB &cb, MainLoopData *user_data)
+{
+ MainLoopCbData *cb_data = new MainLoopCbData();
+ GMainContext *ctx = g_main_loop_get_context(loop);
+ cb_data->ctx = ctx;
+ cb_data->cb = cb;
+ cb_data->data = user_data;
+ cb_data->fd = fd;
+
+ GIOChannel *channel = g_io_channel_unix_new(fd);
+ GSource *source = g_io_create_watch(channel, (GIOCondition)(G_IO_IN | G_IO_HUP | G_IO_ERR));
+ g_source_set_callback(source, (GSourceFunc)EventHandler, cb_data, DestroyNotify);
+
+ g_source_attach(source, ctx);
+
+ g_source_unref(source);
+
+ callback_table_lock.lock();
+ callback_table.insert(CallbackMap::value_type(fd, std::make_pair(source, cb_data)));
+ callback_table_lock.unlock();
+}
+
+MainLoopHandler::MainLoopData *MainLoopHandler::RemoveWatch(int fd)
+{
+ GSource *source;
+ MainLoopData *user_data = nullptr;
+
+ {
+ std::lock_guard<std::mutex> autoLock(callback_table_lock);
+ auto it = callback_table.find(fd);
+ if (it == callback_table.end())
+ return user_data;
+ source = it->second.first;
+ user_data = it->second.second->data;
+ callback_table.erase(it);
+ }
+
+ g_source_destroy(source);
+ return user_data;
+}
+
+unsigned int MainLoopHandler::AddTimeout(int interval, const mainLoopCB &cb, MainLoopData *data)
+{
+ MainLoopCbData *cb_data = new MainLoopCbData();
+ GMainContext *ctx = g_main_loop_get_context(loop);
+ cb_data->ctx = ctx;
+ cb_data->cb = cb;
+ cb_data->data = data;
+
+ GSource *source = g_timeout_source_new(interval);
+ g_source_set_callback(source, IdlerHandler, cb_data, DestroyNotify);
+ unsigned int id = g_source_attach(source, cb_data->ctx);
+ g_source_unref(source);
+
+ return id;
+}
+
+void MainLoopHandler::RemoveTimeout(unsigned int id)
+{
+ GSource *source;
+ source = g_main_context_find_source_by_id(g_main_loop_get_context(loop), id);
+ if (source)
+ g_source_destroy(source);
+}
+
+void MainLoopHandler::AddIdle(MainLoopHandler *handle, const mainLoopCB &cb,
+ MainLoopData *user_data)
+{
+ RET_IF(handle == nullptr);
+
+ MainLoopCbData *cb_data = new MainLoopCbData();
+ cb_data->cb = cb;
+ cb_data->data = user_data;
+ cb_data->ctx = g_main_loop_get_context(handle->loop);
+
+ AddIdle(cb_data, DestroyNotify);
+}
+
+void MainLoopHandler::AddIdle(MainLoopCbData *cb_data, GDestroyNotify destroy)
+{
+ RET_IF(cb_data->ctx == nullptr);
+
+ GSource *source = g_idle_source_new();
+ g_source_set_priority(source, G_PRIORITY_HIGH);
+ g_source_set_callback(source, IdlerHandler, cb_data, destroy);
+ g_source_attach(source, cb_data->ctx);
+ g_source_unref(source);
+}
+
+gboolean MainLoopHandler::IdlerHandler(gpointer user_data)
+{
+ RETV_IF(user_data == nullptr, FALSE);
+
+ MainLoopCbData *cb_data = static_cast<MainLoopCbData *>(user_data);
+
+ cb_data->cb(cb_data->result, cb_data->fd, cb_data->data);
+
+ return FALSE;
+}
+
+gboolean MainLoopHandler::EventHandler(GIOChannel *src, GIOCondition condition, gpointer user_data)
+{
+ RETV_IF(user_data == nullptr, FALSE);
+
+ int ret = TRUE;
+ MainLoopCbData *cb_data = static_cast<MainLoopCbData *>(user_data);
+
+ if ((G_IO_HUP | G_IO_ERR) & condition) {
+ ERR("Connection Error(%d)", condition);
+ cb_data->result = (G_IO_HUP & condition) ? HANGUP : ERROR;
+ ret = FALSE;
+ }
+
+ cb_data->cb(cb_data->result, cb_data->fd, cb_data->data);
+
+ return ret;
+}
+
+void MainLoopHandler::DestroyNotify(gpointer data)
+{
+ MainLoopCbData *cb_data = static_cast<MainLoopCbData *>(data);
+ delete cb_data;
+}
+
+MainLoopHandler::MainLoopCbData::MainLoopCbData() : result(OK), fd(-1)
+{
+}
+
+} // namespace aitt
diff --git a/common/MainLoopHandler.h b/common/MainLoopHandler.h
new file mode 100644
index 0000000..42737b1
--- /dev/null
+++ b/common/MainLoopHandler.h
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2022 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+#include <AittTypes.h>
+#include <glib.h>
+
+#include <functional>
+#include <map>
+#include <mutex>
+
+namespace aitt {
+
+class MainLoopHandler {
+ public:
+ enum MainLoopResult {
+ OK,
+ ERROR,
+ REMOVED,
+ HANGUP,
+ };
+ struct MainLoopData {
+ virtual ~MainLoopData() = default;
+ };
+ using mainLoopCB = std::function<void(MainLoopResult result, int fd, MainLoopData *data)>;
+
+ MainLoopHandler();
+ ~MainLoopHandler();
+
+ static void AddIdle(MainLoopHandler *handle, const mainLoopCB &cb, MainLoopData *user_data);
+
+ void Run();
+ bool Quit();
+ void AddWatch(int fd, const mainLoopCB &cb, MainLoopData *user_data);
+ MainLoopData *RemoveWatch(int fd);
+ unsigned int AddTimeout(int interval, const mainLoopCB &cb, MainLoopData *user_data);
+ void RemoveTimeout(unsigned int id);
+
+ private:
+ struct MainLoopCbData {
+ MainLoopCbData();
+ mainLoopCB cb;
+ MainLoopData *data;
+ MainLoopResult result;
+ int fd;
+ GMainContext *ctx;
+ };
+ using CallbackMap = std::map<int, std::pair<GSource *, MainLoopCbData *>>;
+
+ static void AddIdle(MainLoopCbData *, GDestroyNotify);
+ static gboolean IdlerHandler(gpointer user_data);
+ static gboolean EventHandler(GIOChannel *src, GIOCondition cond, gpointer user_data);
+ static void DestroyNotify(gpointer data);
+
+ GMainLoop *loop;
+ CallbackMap callback_table;
+ std::mutex callback_table_lock;
+};
+} // namespace aitt
diff --git a/common/aitt_internal.h b/common/aitt_internal.h
new file mode 100644
index 0000000..feef42b
--- /dev/null
+++ b/common/aitt_internal.h
@@ -0,0 +1,94 @@
+/*
+ * Copyright (c) 2021-2022 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+#include <libgen.h>
+#include <sys/syscall.h>
+#include <unistd.h>
+
+#include <cassert>
+#include <cerrno>
+#include <cstring>
+
+#include "aitt_internal_definitions.h"
+#include "aitt_platform.h"
+
+#if defined(SYS_gettid)
+#define GETTID() syscall(SYS_gettid)
+#else // SYS_gettid
+#define GETTID() 0lu
+#endif // SYS_gettid
+
+#define AITT_ERRMSG_LEN 80
+
+#if (_POSIX_C_SOURCE >= 200112L) && !_GNU_SOURCE
+#define AITT_STRERROR_R(errno, buf, buflen) \
+ do { \
+ int ret = strerror_r(errno, buf, buflen); \
+ if (ret != 0) { \
+ assert(ret == 0 && "strerror_r failed"); \
+ } \
+ } while (0)
+#else
+#define AITT_STRERROR_R(errno, buf, buflen) \
+ do { \
+ const char *errstr = strerror_r(errno, buf, buflen); \
+ if (errstr == nullptr) { \
+ assert(errstr != nullptr && "strerror_r failed"); \
+ } \
+ } while (0)
+#endif
+
+#ifdef _LOG_WITH_TIMESTAMP
+#include "aitt_internal_profiling.h"
+#else
+#define DBG(fmt, ...) PLATFORM_LOGD("[%lu] " fmt, GETTID(), ##__VA_ARGS__)
+
+#define INFO(fmt, ...) PLATFORM_LOGI("[%lu] " fmt, GETTID(), ##__VA_ARGS__)
+#define ERR(fmt, ...) PLATFORM_LOGE("[%lu] \033[31m" fmt "\033[0m", GETTID(), ##__VA_ARGS__)
+#define ERR_CODE(_aitt_errno, fmt, ...) \
+ do { \
+ char errMsg[AITT_ERRMSG_LEN] = {'\0'}; \
+ int _errno = (_aitt_errno); \
+ AITT_STRERROR_R(_errno, errMsg, sizeof(errMsg)); \
+ PLATFORM_LOGE("[%lu] (%d:%s) \033[31m" fmt "\033[0m", GETTID(), _errno, errMsg, \
+ ##__VA_ARGS__); \
+ } while (0)
+#endif
+
+#define RET_IF(expr) \
+ do { \
+ if (expr) { \
+ ERR("(%s)", #expr); \
+ return; \
+ } \
+ } while (0)
+
+#define RETV_IF(expr, val) \
+ do { \
+ if (expr) { \
+ ERR("(%s)", #expr); \
+ return (val); \
+ } \
+ } while (0)
+
+#define RETVM_IF(expr, val, fmt, arg...) \
+ do { \
+ if (expr) { \
+ ERR(fmt, ##arg); \
+ return (val); \
+ } \
+ } while (0)
diff --git a/common/aitt_internal_definitions.h b/common/aitt_internal_definitions.h
new file mode 100644
index 0000000..10cde41
--- /dev/null
+++ b/common/aitt_internal_definitions.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2022 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+#ifdef API
+#undef API
+#endif
+#define API __attribute__((visibility("default")))
+
+#define STR_EQ 0
+
+#define AITT_MANAGED_TOPIC_PREFIX "/v1/custom/aitt/"
+#define DISCOVERY_TOPIC_BASE std::string(AITT_MANAGED_TOPIC_PREFIX "discovery/")
+#define RESPONSE_POSTFIX "_AittRe_"
+
+// Specification MQTT-4.7.3-3
+#define AITT_TOPIC_NAME_MAX 65535
+
+// Specification MQTT-1.5.5
+#define AITT_PAYLOAD_MAX 268435455
diff --git a/common/aitt_internal_profiling.h b/common/aitt_internal_profiling.h
new file mode 100644
index 0000000..11fcd47
--- /dev/null
+++ b/common/aitt_internal_profiling.h
@@ -0,0 +1,134 @@
+/*
+ * Copyright (c) 2022 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+#include <sys/time.h>
+
+// Increase this if you need more space for profiling
+#define AITT_PROFILE_ID_MAX 8
+
+struct __aitt__tls__ {
+ struct timeval last_timestamp;
+ struct timeval profile_timestamp[AITT_PROFILE_ID_MAX];
+ char profile_idx; // Max to 255
+ char initialized; // Max to 255, but we only use 0 or 1
+};
+
+extern __thread struct __aitt__tls__ __aitt;
+
+#define InitAITT() \
+ do { \
+ if (__aitt.initialized == 0) { \
+ __aitt.initialized = 1; \
+ if (gettimeofday(&__aitt.last_timestamp, NULL) < 0) { \
+ assert(!"gettimeofday failed"); \
+ } \
+ } \
+ } while (0)
+
+#define PROFILE_MARK() \
+ do { \
+ if (__aitt.profile_idx < AITT_PROFILE_ID_MAX) { \
+ if (gettimeofday(__aitt.profile_timestamp + __aitt.profile_idx, NULL) < 0) { \
+ assert(!"gettimeofday failed"); \
+ } \
+ ++__aitt.profile_idx; \
+ } else { \
+ ERR("Unable to mark a profile point: %d\n", __aitt.profile_idx); \
+ } \
+ } while (0)
+
+#define PROFILE_ESTIMATE(tres) \
+ do { \
+ struct timeval tv; \
+ struct timeval res; \
+ if (gettimeofday(&tv, NULL) < 0) { \
+ assert(!"gettimeofday failed"); \
+ } \
+ --__aitt.profile_idx; \
+ timersub(&tv, __aitt.profile_timestamp + __aitt.profile_idx, &res); \
+ (tres) = static_cast<double>(res.tv_sec) + static_cast<double>(res.tv_usec) / 1000000.0f; \
+ } while (0)
+
+#define PROFILE_RESET(count) \
+ do { \
+ __aitt.profile_idx -= (count); \
+ } while (0)
+
+#define DBG(fmt, ...) \
+ do { \
+ struct timeval tv; \
+ struct timeval res; \
+ InitAITT(); \
+ if (gettimeofday(&tv, NULL) < 0) { \
+ assert(!"gettimeofday failed"); \
+ } \
+ timersub(&tv, &__aitt.last_timestamp, &res); \
+ PLATFORM_LOGD("[%lu] %lu.%.6lu(%lu.%.6lu) " fmt, GETTID(), tv.tv_sec, tv.tv_usec, \
+ res.tv_sec, res.tv_usec, ##__VA_ARGS__); \
+ __aitt.last_timestamp.tv_sec = tv.tv_sec; \
+ __aitt.last_timestamp.tv_usec = tv.tv_usec; \
+ } while (0)
+
+#define INFO(fmt, ...) \
+ do { \
+ struct timeval tv; \
+ struct timeval res; \
+ InitAITT(); \
+ if (gettimeofday(&tv, NULL) < 0) { \
+ assert(!"gettimeofday failed"); \
+ } \
+ timersub(&tv, &__aitt.last_timestamp, &res); \
+ PLATFORM_LOGI("[%lu] %lu.%.6lu(%lu.%.6lu) " fmt, GETTID(), tv.tv_sec, tv.tv_usec, \
+ res.tv_sec, res.tv_usec, ##__VA_ARGS__); \
+ __aitt.last_timestamp.tv_sec = tv.tv_sec; \
+ __aitt.last_timestamp.tv_usec = tv.tv_usec; \
+ } while (0)
+
+#define ERR(fmt, ...) \
+ do { \
+ struct timeval tv; \
+ struct timeval res; \
+ InitAITT(); \
+ if (gettimeofday(&tv, NULL) < 0) { \
+ assert(!"gettimeofday failed"); \
+ } \
+ timersub(&tv, &__aitt.last_timestamp, &res); \
+ PLATFORM_LOGE("[%lu] %lu.%.6lu(%lu.%.6lu) \033[31m" fmt "\033[0m", GETTID(), tv.tv_sec, \
+ tv.tv_usec, res.tv_sec, res.tv_usec, ##__VA_ARGS__); \
+ __aitt.last_timestamp.tv_sec = tv.tv_sec; \
+ __aitt.last_timestamp.tv_usec = tv.tv_usec; \
+ } while (0)
+
+#define ERR_CODE(_aitt_errno, fmt, ...) \
+ do { \
+ struct timeval tv; \
+ struct timeval res; \
+ char errMsg[AITT_ERRMSG_LEN] = {'\0'}; \
+ int _errno = (_aitt_errno); \
+ \
+ AITT_STRERROR_R(_errno, errMsg, sizeof(errMsg)); \
+ \
+ InitAITT(); \
+ if (gettimeofday(&tv, NULL) < 0) { \
+ assert(!"gettimeofday failed"); \
+ } \
+ timersub(&tv, &__aitt.last_timestamp, &res); \
+ PLATFORM_LOGE("[%lu] %lu.%.6lu(%lu.%.6lu) (%d:%s) \033[31m" fmt "\033[0m", GETTID(), \
+ tv.tv_sec, tv.tv_usec, res.tv_sec, res.tv_usec, _errno, errMsg, ##__VA_ARGS__); \
+ __aitt.last_timestamp.tv_sec = tv.tv_sec; \
+ __aitt.last_timestamp.tv_usec = tv.tv_usec; \
+ } while (0)
diff --git a/common/aitt_platform.h b/common/aitt_platform.h
new file mode 100644
index 0000000..dd13d4d
--- /dev/null
+++ b/common/aitt_platform.h
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2021-2022 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+#ifndef LOG_TAG
+#define LOG_TAG "AITT"
+#endif
+
+#define LOG_RED "\033[31m"
+#define LOG_GREEN "\033[32m"
+#define LOG_BROWN "\033[33m"
+#define LOG_BLUE "\033[34m"
+#define LOG_END "\033[0m"
+
+#if defined(PLATFORM) && !defined(LOG_STDOUT)
+
+#define STR(x) #x
+#define PURE(x) x
+#define PLATFORM_HEADER(x) STR(x)
+#include PLATFORM_HEADER(PLATFORM PURE(/) PURE(aitt_platform.h))
+
+#else // PLATFORM
+
+#include <libgen.h>
+#include <stdio.h>
+
+#define __FILENAME__ basename((char *)(__FILE__))
+#define PLATFORM_LOGD(fmt, ...) \
+ fprintf(stdout, LOG_BROWN "[%s]%s(%s:%d)" LOG_END fmt "\n", LOG_TAG, __func__, __FILENAME__, \
+ __LINE__, ##__VA_ARGS__)
+#define PLATFORM_LOGI(fmt, ...) \
+ fprintf(stdout, LOG_GREEN "[%s]%s(%s:%d)" LOG_END fmt "\n", LOG_TAG, __func__, __FILENAME__, \
+ __LINE__, ##__VA_ARGS__)
+#define PLATFORM_LOGE(fmt, ...) \
+ fprintf(stderr, LOG_RED "[%s]%s(%s:%d)" LOG_END fmt "\n", LOG_TAG, __func__, __FILENAME__, \
+ __LINE__, ##__VA_ARGS__)
+
+#endif // PLATFORM
diff --git a/common/tizen/aitt_platform.h b/common/tizen/aitt_platform.h
new file mode 100644
index 0000000..2a898a5
--- /dev/null
+++ b/common/tizen/aitt_platform.h
@@ -0,0 +1,22 @@
+/*
+ * Copyright (c) 2021-2022 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+#include <dlog/dlog.h>
+
+#define PLATFORM_LOGD(fmt, ...) LOGD(fmt, ##__VA_ARGS__)
+#define PLATFORM_LOGI(fmt, ...) LOGI(fmt, ##__VA_ARGS__)
+#define PLATFORM_LOGE(fmt, ...) LOGE(fmt, ##__VA_ARGS__)
diff --git a/debian/aitt-dev.install b/debian/aitt-dev.install
new file mode 100644
index 0000000..7354e42
--- /dev/null
+++ b/debian/aitt-dev.install
@@ -0,0 +1,2 @@
+/usr/include/aitt/*.h
+/usr/lib/*/pkgconfig/*.pc
diff --git a/debian/aitt-plugins.install b/debian/aitt-plugins.install
new file mode 100644
index 0000000..38722db
--- /dev/null
+++ b/debian/aitt-plugins.install
@@ -0,0 +1 @@
+/usr/lib/*/libaitt-transport*.so*
diff --git a/debian/aitt.install b/debian/aitt.install
new file mode 100644
index 0000000..ccdd217
--- /dev/null
+++ b/debian/aitt.install
@@ -0,0 +1,2 @@
+/usr/lib/*/libaitt*.so*
+/usr/bin/*
diff --git a/debian/changelog b/debian/changelog
new file mode 100644
index 0000000..5dc0313
--- /dev/null
+++ b/debian/changelog
@@ -0,0 +1,5 @@
+aitt (0.0.1) unstable; urgency=medium
+
+ * Initialize the debian packaging scripts
+
+ -- Sungjae Park <nicesj.park@samsung.com> Tue, 23 Nov 2021 14:27:00 +0900
diff --git a/debian/compat b/debian/compat
new file mode 100644
index 0000000..ec63514
--- /dev/null
+++ b/debian/compat
@@ -0,0 +1 @@
+9
diff --git a/debian/control b/debian/control
new file mode 100644
index 0000000..c4c06ab
--- /dev/null
+++ b/debian/control
@@ -0,0 +1,30 @@
+Source: aitt
+Section: libs
+Priority: optional
+Maintainer: Semun Lee <semun.lee@samsung.com>
+Build-Depends: gcc-9 | gcc-8 | gcc-7 | gcc-6 | gcc-5 (>=5.4),
+ cmake, debhelper (>=9), libmosquitto-dev, lcov, libgmock-dev, libflatbuffers-dev, libglib2.0-dev
+Standards-Version: 0.0.1
+
+Package: aitt
+Architecture: any
+Multi-Arch: same
+Depends: ${shlibs:Depends}, ${misc:Depends}
+Description: AI Telemetry Transport based on MQTT
+ AITT is a Framework which transfers data of AI service.
+ It makes distributed AI Inference possible.
+
+Package: aitt-plugins
+Architecture: any
+Multi-Arch: same
+Depends: ${shlibs:Depends}, ${misc:Depends}
+Description: Plugin Libraries for AITT P2P transport
+ The aitt-plugins package contains basic plugin libraries for AITT P2P transport.
+
+Package: aitt-dev
+Architecture: any
+Multi-Arch: same
+Depends: aitt
+Description: AITT development package
+ The aitt-dev package contains libraries and header files for
+ developing programs that use %{name}
diff --git a/debian/copyright b/debian/copyright
new file mode 100644
index 0000000..deb2e6f
--- /dev/null
+++ b/debian/copyright
@@ -0,0 +1,3 @@
+Files: *
+License: Apache-2.0
+Copyright(C) Samsung Electonics 2021
diff --git a/debian/rules b/debian/rules
new file mode 100755
index 0000000..cbfdc90
--- /dev/null
+++ b/debian/rules
@@ -0,0 +1,71 @@
+#!/usr/bin/make -f
+# See debhelper(7) (uncomment to enable)
+# output every command that modifies files on the build system.
+#export DH_VERBOSE = 1
+
+# see FEATURE AREAS in dpkg-buildflags(1)
+#export DEB_BUILD_MAINT_OPTIONS = hardening=+all
+
+# see ENVIRONMENT in dpkg-buildflags(1)
+# package maintainers to append CFLAGS
+#export DEB_CFLAGS_MAINT_APPEND = -Wall -pedantic
+# package maintainers to append LDFLAGS
+#export DEB_LDFLAGS_MAINT_APPEND = -Wl,--as-needed
+
+ROOT_DIR:=$(shell pwd)
+export DEB_HOST_MULTIARCH ?= $(shell dpkg-architecture -qDEB_HOST_MULTIARCH)
+export DEB_HOST_ARCH ?= $(shell dpkg-architecture -qDEB_HOST_ARCH)
+export BUILDDIR=build
+export AITT_SOURCE_ROOT_PATH=$(ROOT_DIR)
+export AITT_BUILD_ROOT_PATH=${AITT_SOURCE_ROOT_PATH}/${BUILDDIR}
+export COMMIT_ID=$(shell git rev-parse --short HEAD)
+
+export TEST ?= 1
+export COVERAGE ?= 0
+
+%:
+ dh $@ --parallel --buildsystem=cmake
+
+.PHONY: override_dh_auto_clean
+override_dh_auto_clean:
+ rm -rf ${AITT_BUILD_ROOT_PATH}
+
+.PHONY: override_dh_auto_configure
+override_dh_auto_configure:
+ mkdir -p ${AITT_BUILD_ROOT_PATH}; \
+ cd ${AITT_BUILD_ROOT_PATH}; \
+ cmake .. \
+ -DCMAKE_INSTALL_PREFIX:PATH=/usr \
+ -DCMAKE_VERBOSE_MAKEFILE:BOOL=OFF \
+ -DBUILD_TESTING:BOOL=${TEST} \
+ -DCOVERAGE_TEST:BOOL=${COVERAGE}; \
+ cd -
+
+.PHONY: override_dh_auto_build
+override_dh_auto_build:
+ make -C ${AITT_BUILD_ROOT_PATH}
+
+.PHONY: override_dh_shlibdeps
+override_dh_shlibdeps:
+ dh_shlibdeps --dpkg-shlibdeps-params=--ignore-missing-info
+
+.PHONY: override_dh_auto_test
+override_dh_auto_test:
+ cd ${AITT_BUILD_ROOT_PATH}; \
+ ctest --output-on-failure --timeout 100
+
+ if [ ${TEST} -ne 0 -a ${COVERAGE} -ne 0 ]; then \
+ lcov -c --ignore-errors graph --no-external -b . -d . -o aitt_gcov.info; \
+ genhtml aitt_gcov.info -o out --legend --show-details; \
+ fi
+.PHONY: override_dh_link
+override_dh_link:
+
+.PHONY: override_dh_auto_install
+override_dh_auto_install:
+ DESTDIR=$(CURDIR)/debian/tmp make -C ${AITT_BUILD_ROOT_PATH} install
+
+.PHONY: override_dh_install
+override_dh_install:
+ dh_install --sourcedir=debian/tmp
+ dh_missing --fail-missing
diff --git a/gradle.properties b/gradle.properties
new file mode 100644
index 0000000..d5e8af3
--- /dev/null
+++ b/gradle.properties
@@ -0,0 +1,21 @@
+# Project-wide Gradle settings.
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
+# AndroidX package structure to make it clearer which packages are bundled with the
+# Android operating system, and which are packaged with your app"s APK
+# https://developer.android.com/topic/libraries/support-library/androidx-rn
+android.useAndroidX=true
+# Enables namespacing of each library's R class so that its R class includes only the
+# resources declared in the library itself and none from the library's dependencies,
+# thereby reducing the size of the R class for that library
+android.nonTransitiveRClass=true
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..c6f3907
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Thu Feb 24 14:17:33 IST 2022
+distributionBase=GRADLE_USER_HOME
+distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-bin.zip
+distributionPath=wrapper/dists
+zipStorePath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
diff --git a/gradlew b/gradlew
new file mode 100644
index 0000000..744e882
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,185 @@
+#!/usr/bin/env sh
+
+#
+# Copyright 2015 the original author or authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+ echo "$*"
+}
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MSYS* | MINGW* )
+ msys=true
+ ;;
+ NONSTOP* )
+ nonstop=true
+ ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
+ JAVACMD=`cygpath --unix "$JAVACMD"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=`expr $i + 1`
+ done
+ case $i in
+ 0) set -- ;;
+ 1) set -- "$args0" ;;
+ 2) set -- "$args0" "$args1" ;;
+ 3) set -- "$args0" "$args1" "$args2" ;;
+ 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Escape application args
+save () {
+ for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+ echo " "
+}
+APP_ARGS=`save "$@"`
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+exec "$JAVACMD" "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 0000000..107acd3
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,89 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/include/AITT.h b/include/AITT.h
new file mode 100644
index 0000000..aeeacd8
--- /dev/null
+++ b/include/AITT.h
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 2021-2022 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+#include <AittTypes.h>
+#include <MSG.h>
+
+#include <functional>
+#include <memory>
+#include <string>
+
+#define AITT_LOCALHOST "127.0.0.1"
+#define AITT_PORT 1883
+
+namespace aitt {
+
+class API AITT {
+ public:
+ using SubscribeCallback =
+ std::function<void(MSG *msg, const void *data, const size_t datalen, void *user_data)>;
+ using ConnectionCallback = std::function<void(AITT &, int, void *user_data)>;
+
+ explicit AITT(const std::string &id, const std::string &ip_addr, bool clear_session = false);
+ virtual ~AITT(void);
+
+ void SetWillInfo(const std::string &topic, const void *data, const size_t datalen, AittQoS qos,
+ bool retain);
+ void SetConnectionCallback(ConnectionCallback cb, void *user_data = nullptr);
+ void Connect(const std::string &host = AITT_LOCALHOST, int port = AITT_PORT,
+ const std::string &username = std::string(), const std::string &password = std::string());
+ void Disconnect(void);
+
+ void Publish(const std::string &topic, const void *data, const size_t datalen,
+ AittProtocol protocols = AITT_TYPE_MQTT, AittQoS qos = AITT_QOS_AT_MOST_ONCE,
+ bool retain = false);
+ int PublishWithReply(const std::string &topic, const void *data, const size_t datalen,
+ AittProtocol protocol, AittQoS qos, bool retain, const SubscribeCallback &cb,
+ void *cbdata, const std::string &correlation);
+
+ int PublishWithReplySync(const std::string &topic, const void *data, const size_t datalen,
+ AittProtocol protocol, AittQoS qos, bool retain, const SubscribeCallback &cb,
+ void *cbdata, const std::string &correlation, int timeout_ms = 0);
+
+ AittSubscribeID Subscribe(const std::string &topic, const SubscribeCallback &cb,
+ void *cbdata = nullptr, AittProtocol protocol = AITT_TYPE_MQTT,
+ AittQoS qos = AITT_QOS_AT_MOST_ONCE);
+ void *Unsubscribe(AittSubscribeID handle);
+
+ void SendReply(MSG *msg, const void *data, const size_t datalen, bool end = true);
+
+ // NOTE:
+ // Provide utility functions to developers who only be able to access the AITT class
+ static bool CompareTopic(const std::string &left, const std::string &right);
+
+ private:
+ class Impl;
+ std::unique_ptr<Impl> pImpl;
+};
+
+} // namespace aitt
diff --git a/include/AittDiscovery.h b/include/AittDiscovery.h
new file mode 100644
index 0000000..f7e3f46
--- /dev/null
+++ b/include/AittDiscovery.h
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2022 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+#include "MQ.h"
+
+namespace aitt {
+
+class AittDiscovery {
+ public:
+ static constexpr const char *WILL_LEAVE_NETWORK = "disconnected";
+ static constexpr const char *JOIN_NETWORK = "connected";
+
+ using DiscoveryCallback = std::function<void(const std::string &clientId,
+ const std::string &status, const void *msg, const int szmsg)>;
+
+ explicit AittDiscovery(const std::string &id);
+ void Start(const std::string &host, int port, const std::string &username,
+ const std::string &password);
+ void Stop();
+ void UpdateDiscoveryMsg(AittProtocol protocol, const void *msg, size_t length);
+ int AddDiscoveryCB(AittProtocol protocol, const DiscoveryCallback &cb);
+ void RemoveDiscoveryCB(int callback_id);
+
+ private:
+ struct DiscoveryBlob {
+ explicit DiscoveryBlob(const void *msg, size_t length);
+ ~DiscoveryBlob();
+ DiscoveryBlob(const DiscoveryBlob &src);
+ DiscoveryBlob &operator=(const DiscoveryBlob &src);
+
+ size_t len;
+ std::shared_ptr<char> data;
+ };
+
+ static void DiscoveryMessageCallback(MSG *mq, const std::string &topic, const void *msg,
+ const int szmsg, void *user_data);
+ void PublishDiscoveryMsg();
+ const char *GetProtocolStr(AittProtocol protocol);
+ AittProtocol GetProtocol(const std::string &protocol_str);
+
+ std::string id_;
+ MQ discovery_mq;
+ void *callback_handle;
+ std::map<AittProtocol, DiscoveryBlob> discovery_map;
+ std::map<int, std::pair<AittProtocol, DiscoveryCallback>> callbacks;
+};
+
+// Discovery Message (flexbuffers)
+// map {
+// "status": "connected",
+// "tcp": Blob Data for tcp Module,
+// "webrtc": Blob Data for tcp Module,
+// }
+
+} // namespace aitt
diff --git a/include/AittTransport.h b/include/AittTransport.h
new file mode 100644
index 0000000..6523b2e
--- /dev/null
+++ b/include/AittTransport.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2021-2022 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+#include <AittDiscovery.h>
+#include <AittTypes.h>
+
+#include <functional>
+#include <string>
+
+namespace aitt {
+
+class AittTransport {
+ public:
+ typedef void *(*ModuleEntry)(const char *ip, AittDiscovery &discovery);
+ using SubscribeCallback = std::function<void(const std::string &topic, const void *msg,
+ const size_t szmsg, void *cbdata, const std::string &correlation)>;
+
+ static constexpr const char *const MODULE_ENTRY_NAME = "aitt_module_entry";
+
+ explicit AittTransport(AittDiscovery &discovery) : discovery(discovery) {}
+ virtual ~AittTransport(void) = default;
+
+ virtual void Publish(const std::string &topic, const void *data, const size_t datalen,
+ const std::string &correlation, AittQoS qos = AITT_QOS_AT_MOST_ONCE,
+ bool retain = false) = 0;
+
+ virtual void Publish(const std::string &topic, const void *data, const size_t datalen,
+ AittQoS qos = AITT_QOS_AT_MOST_ONCE, bool retain = false) = 0;
+
+ virtual void *Subscribe(const std::string &topic, const SubscribeCallback &cb,
+ void *cbdata = nullptr, AittQoS qos = AITT_QOS_AT_MOST_ONCE) = 0;
+ virtual void *Subscribe(const std::string &topic, const SubscribeCallback &cb, const void *data,
+ const size_t datalen, void *cbdata = nullptr, AittQoS qos = AITT_QOS_AT_MOST_ONCE) = 0;
+
+ virtual void *Unsubscribe(void *handle) = 0;
+
+ protected:
+ aitt::AittDiscovery &discovery;
+};
+
+} // namespace aitt
diff --git a/include/AittTypes.h b/include/AittTypes.h
new file mode 100644
index 0000000..1770922
--- /dev/null
+++ b/include/AittTypes.h
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2021-2022 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+#define API __attribute__((visibility("default")))
+
+typedef void* AittSubscribeID;
+
+enum AittProtocol {
+ AITT_TYPE_UNKNOWN = 0,
+ AITT_TYPE_MQTT = (0x1 << 0), // Publish message through the MQTT
+ AITT_TYPE_TCP = (0x1 << 1), // Publish message to peers using the TCP
+ AITT_TYPE_WEBRTC = (0x1 << 2), // Publish message to peers using the WEBRTC
+};
+
+// AittQoS only works with the AITT_TYPE_MQTT
+enum AittQoS {
+ AITT_QOS_AT_MOST_ONCE = 0, // Fire and forget
+ AITT_QOS_AT_LEAST_ONCE = 1, // Receiver is able to receive multiple times
+ AITT_QOS_EXACTLY_ONCE = 2, // Receiver only receives exactly once
+};
+
+enum AittConnectionState {
+ AITT_DISCONNECTED = 0, // The connection is disconnected.
+ AITT_CONNECTED = 1, // A connection was successfully established to the mqtt broker.
+};
+
+#ifdef TIZEN
+#include <tizen.h>
+#define TIZEN_ERROR_AITT -0x04020000
+#else
+#include <errno.h>
+
+#define TIZEN_ERROR_NONE 0
+#define TIZEN_ERROR_INVALID_PARAMETER -EINVAL
+#define TIZEN_ERROR_PERMISSION_DENIED -EACCES
+#define TIZEN_ERROR_OUT_OF_MEMORY -ENOMEM
+#define TIZEN_ERROR_TIMED_OUT (-1073741824LL + 1)
+#define TIZEN_ERROR_NOT_SUPPORTED (-1073741824LL + 2)
+#define TIZEN_ERROR_AITT -0x04020000
+#endif
+
+enum AittError {
+ AITT_ERROR_NONE = TIZEN_ERROR_NONE, /**< On Success */
+ AITT_ERROR_INVALID_PARAMETER = TIZEN_ERROR_INVALID_PARAMETER, /**< Invalid parameter */
+ AITT_ERROR_PERMISSION_DENIED = TIZEN_ERROR_PERMISSION_DENIED, /**< Permission denied */
+ AITT_ERROR_OUT_OF_MEMORY = TIZEN_ERROR_OUT_OF_MEMORY, /**< Out of memory */
+ AITT_ERROR_TIMED_OUT = TIZEN_ERROR_TIMED_OUT, /**< Time out */
+ AITT_ERROR_NOT_SUPPORTED = TIZEN_ERROR_NOT_SUPPORTED, /**< Not supported */
+ AITT_ERROR_UNKNOWN = TIZEN_ERROR_AITT | 0x01, /**< Unknown Error */
+ AITT_ERROR_SYSTEM = TIZEN_ERROR_AITT | 0x02, /**< System errors */
+ AITT_ERROR_NOT_READY = TIZEN_ERROR_AITT | 0x03, /**< System errors */
+};
diff --git a/include/MSG.h b/include/MSG.h
new file mode 100644
index 0000000..85ecc76
--- /dev/null
+++ b/include/MSG.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2021-2022 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+#include <string>
+
+#include <AittTypes.h>
+
+namespace aitt {
+class API MSG {
+ public:
+ MSG();
+
+ void SetID(AittSubscribeID id);
+ AittSubscribeID GetID();
+ void SetTopic(const std::string &topic);
+ const std::string &GetTopic();
+ void SetCorrelation(const std::string &correlation);
+ const std::string &GetCorrelation();
+ void SetResponseTopic(const std::string &reply_topic);
+ const std::string &GetResponseTopic();
+ void SetSequence(int num);
+ void IncreaseSequence();
+ int GetSequence();
+ void SetEndSequence(bool end);
+ bool IsEndSequence();
+ void SetProtocols(AittProtocol protocols);
+ AittProtocol GetProtocols();
+
+ protected:
+ std::string topic_;
+ std::string correlation_;
+ std::string reply_topic_;
+ int sequence;
+ bool end_sequence;
+ AittSubscribeID id_;
+ AittProtocol protocols_;
+};
+} // namespace aitt
diff --git a/include/aitt_c.h b/include/aitt_c.h
new file mode 100644
index 0000000..24ffce3
--- /dev/null
+++ b/include/aitt_c.h
@@ -0,0 +1,366 @@
+/*
+ * Copyright (c) 2022 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+#include <stddef.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <AittTypes.h>
+/**
+ * @addtogroup CAPI_AITT_MODULE
+ * @{
+ */
+
+/**
+ * @brief @a aitt_h is an opaque data structure to represent AITT service handle.
+ * @since_tizen 7.0
+ */
+typedef struct aitt_handle *aitt_h;
+
+/**
+ * @brief @a aitt_msg_h is an opaque data structure to represent AITT message handle.
+ * @since_tizen 7.0
+ * @see aitt_sub_fn
+ * @see aitt_msg_get_topic()
+ */
+typedef void *aitt_msg_h;
+
+/**
+ * @brief @a aitt_sub_h is an opaque data structure to represent AITT subscribe ID.
+ * @since_tizen 7.0
+ * @see aitt_subscribe(), aitt_unsubscribe()
+ */
+typedef AittSubscribeID aitt_sub_h;
+
+/**
+ * @brief Enumeration for protocol.
+ * @since_tizen 7.0
+ */
+typedef enum AittProtocol aitt_protocol_e;
+
+/**
+ * @brief Enumeration for MQTT QoS.
+ * It only works with the AITT_TYPE_MQTT
+ * @since_tizen 7.0
+ */
+typedef enum AittQoS aitt_qos_e;
+
+/**
+ * @brief Enumeration for AITT error code.
+ * @since_tizen 7.0
+ */
+typedef enum AittError aitt_error_e;
+
+/**
+ * @brief Specify the type of function passed to aitt_subscribe().
+ * @details When the aitt get message, it is called, immediately.
+ * @since_tizen 7.0
+ * @param[in] msg_handle aitt message handle. The handle has topic name and so on. @c aitt_msg_h
+ * @param[in] msg pointer to the data received
+ * @param[in] msg_len the size of the @c msg (bytes)
+ * @param[in] user_data The user data to pass to the function
+ *
+ * @pre The callback must be registered using aitt_subscribe(), and aitt_subscribe_full()
+ *
+ * @see aitt_subscribe()
+ * @see aitt_subscribe_full()
+ */
+typedef void (
+ *aitt_sub_fn)(aitt_msg_h msg_handle, const void *msg, size_t msg_len, void *user_data);
+
+/**
+ * @brief Create a new AITT service instance.
+ * @detail If id is NULL or empty string, id will be generated automatically.
+ * If my_ip is NULL or empty string, my_ip will be set as 127.0.0.1.
+ * @since_tizen 7.0
+ * @privlevel public
+ * @param[in] id Unique identifier in local network
+ * @param[in] my_ip Own device ip address for connecting by others
+ * @return @c handle of AITT service
+ * otherwise NULL value on failure
+ * @see aitt_destroy()
+ */
+aitt_h aitt_new(const char *id, const char *my_ip);
+
+/**
+ * @brief Enumeration for option.
+ * @since_tizen 7.0
+ */
+typedef enum {
+ AITT_OPT_UNKNOWN, /**< Unknown */
+} aitt_option_e;
+
+/**
+ * @brief Set the contents of a @c handle related with @c option to @c value
+ * @detail The @c value can be NULL for removing the content
+ * @since_tizen 7.0
+ * @privlevel public
+ * @param[in] handle Handle of AITT service
+ * @param[in] option value of @a aitt_option_e.
+ * @return @c 0 on success
+ * otherwise a negative error value
+ * @retval #AITT_ERROR_NONE Success
+ * @retval #AITT_ERROR_INVALID_PARAMETER Invalid parameter
+ * @retval #AITT_ERROR_SYSTEM System errors
+ *
+ * @see aitt_get_option()
+ */
+int aitt_set_option(aitt_h handle, aitt_option_e option, const char *value);
+
+/**
+ * @brief Returns the string value of a @c handle related with @c option
+ * @since_tizen 7.0
+ * @privlevel public
+ * @param[in] handle Handle of AITT service
+ * @param[in] option value of @a aitt_option_e.
+ * @return @c value related with @c option
+ * otherwise NULL value
+ *
+ * @see aitt_set_option()
+ */
+const char *aitt_get_option(aitt_h handle, aitt_option_e option);
+
+/**
+ * @brief Configure will information for a aitt instance.
+ * @detail By default, clients do not have a will. This must be called before calling aitt_connect()
+ * @since_tizen 7.0
+ * @privlevel public
+ * @param[in] handle Handle of AITT service
+ * @param[in] topic the topic on which to publish the will.
+ * @param[in] msg pointer to the data to send.
+ * @param[in] msg_len the size of the @c msg (bytes). Valid values are between 0 and 268,435,455.
+ * @param[in] qos integer value 0, 1 or 2 indicating the Quality of Service.
+ * @param[in] retain set to true to make the will a retained message.
+ * @return @c 0 on success
+ * otherwise a negative error value
+ * @retval #AITT_ERROR_NONE Success
+ * @retval #AITT_ERROR_INVALID_PARAMETER Invalid parameter
+ * @retval #AITT_ERROR_SYSTEM System errors
+ *
+ * @see aitt_connect()
+ */
+int aitt_will_set(aitt_h handle, const char *topic, const void *msg, const size_t msg_len,
+ aitt_qos_e qos, bool retain);
+
+/**
+ * @brief Release memory of the AITT service instance.
+ * @since_tizen 7.0
+ * @privlevel public
+ * @param[in] handle Handle of AITT service;
+ * @see aitt_new()
+ */
+void aitt_destroy(aitt_h handle);
+
+/**
+ * @brief Connect to mqtt broker.
+ * @since_tizen 7.0
+ * @privlevel public
+ * @param[in] handle Handle of AITT service
+ * @param[in] broker_ip IP address of the broker to connect to
+ * @param[in] port the network port to connect to. Usually 1883.
+ * @return @c 0 on success
+ * otherwise a negative error value
+ * @retval #AITT_ERROR_NONE Success
+ * @retval #AITT_ERROR_INVALID_PARAMETER Invalid parameter
+ * @retval #AITT_ERROR_SYSTEM System errors
+ */
+int aitt_connect(aitt_h handle, const char *broker_ip, int port);
+
+/**
+ * @brief Connect to mqtt broker as aitt_connect(), but takes username and password.
+ * @since_tizen 7.0
+ * @privlevel public
+ * @param[in] handle Handle of AITT service
+ * @param[in] broker_ip IP address of the broker to connect to
+ * @param[in] port the network port to connect to. Usually 1883.
+ * @param[in] username the username to send as a string, or NULL to disable authentication
+ * @param[in] password the password to send as a string
+ * @return @c 0 on success
+ * otherwise a negative error value
+ * @retval #AITT_ERROR_NONE Success
+ * @retval #AITT_ERROR_INVALID_PARAMETER Invalid parameter
+ * @retval #AITT_ERROR_SYSTEM System errors
+ */
+int aitt_connect_full(aitt_h handle, const char *broker_ip, int port, const char *username,
+ const char *password);
+
+/**
+ * @brief Disconnect from the broker.
+ * @since_tizen 7.0
+ * @privlevel public
+ * @param[in] handle Handle of AITT service
+ * @return @c 0 on success
+ * otherwise a negative error value
+ * @retval #AITT_ERROR_NONE Success
+ * @retval #AITT_ERROR_INVALID_PARAMETER Invalid parameter
+ * @retval #AITT_ERROR_SYSTEM System errors
+ */
+int aitt_disconnect(aitt_h handle);
+
+/**
+ * @brief Publish a message on a given topic using MQTT, QoS0(At most once).
+ * @since_tizen 7.0
+ * @privlevel public
+ * @param[in] handle Handle of AITT service
+ * @param[in] topic null terminated string of the topic to publish to.
+ * @param[in] msg pointer to the data to send.
+ * @param[in] msg_len the size of the @c msg (bytes). Valid values are between 0 and 268,435,455.
+ * @return @c 0 on success
+ * otherwise a negative error value
+ * @retval #AITT_ERROR_NONE Success
+ * @retval #AITT_ERROR_INVALID_PARAMETER Invalid parameter
+ * @retval #AITT_ERROR_SYSTEM System errors
+ */
+int aitt_publish(aitt_h handle, const char *topic, const void *msg, const size_t msg_len);
+
+/**
+ * @brief Publish a message on a given topic as aitt_publish(), but takes protocols and qos.
+ * @since_tizen 7.0
+ * @privlevel public
+ * @param[in] handle Handle of AITT service
+ * @param[in] topic null terminated string of the topic to publish to.
+ * @param[in] msg pointer to the data to send.
+ * @param[in] msg_len the size of the @c msg (bytes). Valid values are between 0 and 268,435,455.
+ * @param[in] protocols value of @a aitt_protocol_e. The value can be bitwise-or'd.
+ * @param[in] qos integer value 0, 1 or 2 indicating the Quality of Service.
+ * @return @c 0 on success
+ * otherwise a negative error value
+ * @retval #AITT_ERROR_NONE Success
+ * @retval #AITT_ERROR_INVALID_PARAMETER Invalid parameter
+ * @retval #AITT_ERROR_SYSTEM System errors
+ */
+int aitt_publish_full(aitt_h handle, const char *topic, const void *msg, const size_t msg_len,
+ int protocols, aitt_qos_e qos);
+
+/**
+ * @brief Publish a message on a given topic as aitt_publish_full(),
+ * but takes reply topic and callback for the reply.
+ * @since_tizen 7.0
+ * @privlevel public
+ * @param[in] handle Handle of AITT service
+ * @param[in] topic null terminated string of the topic to publish to.
+ * @param[in] msg pointer to the data to send.
+ * @param[in] msg_len the size of the @c msg (bytes). Valid values are between 0 and 268,435,455.
+ * @param[in] protocols value of @a aitt_protocol_e. The value can be bitwise-or'd.
+ * @param[in] qos integer value 0, 1 or 2 indicating the Quality of Service.
+ * @param[in] correlation value indicating the Correlation.
+ * @param[in] cb The callback function to invoke
+ * @param[in] user_data The user data to pass to the function
+ * @return @c 0 on success
+ * otherwise a negative error value
+ * @retval #AITT_ERROR_NONE Success
+ * @retval #AITT_ERROR_INVALID_PARAMETER Invalid parameter
+ * @retval #AITT_ERROR_SYSTEM System errors
+ */
+int aitt_publish_with_reply(aitt_h handle, const char *topic, const void *msg, const size_t msg_len,
+ aitt_protocol_e protocols, aitt_qos_e qos, const char *correlation, aitt_sub_fn cb,
+ void *user_data);
+
+/**
+ * @brief Send reply message to regarding topic.
+ * @since_tizen 7.0
+ * @privlevel public
+ * @param[in] handle Handle of AITT service
+ * @param[in] msg_handle Handle of published message(to reply).
+ * @param[in] reply pointer to the data to send.
+ * @param[in] reply_len the size of the @c reply (bytes).
+ * Valid values are between 0 and 268,435,455.
+ * @param[in] end boolean value indicating the reply message is end or not.
+ * @return @c 0 on success
+ * otherwise a negative error value
+ * @retval #AITT_ERROR_NONE Success
+ * @retval #AITT_ERROR_INVALID_PARAMETER Invalid parameter
+ * @retval #AITT_ERROR_SYSTEM System errors
+ */
+int aitt_send_reply(aitt_h handle, aitt_msg_h msg_handle, const void *reply, const size_t reply_len,
+ bool end);
+
+/**
+ * @brief Get topic name from @c handle
+ * @since_tizen 7.0
+ * @privlevel public
+ * @param[in] handle aitt message handle
+ * @return topic name on success otherwise NULL value on failure
+ */
+const char *aitt_msg_get_topic(aitt_msg_h handle);
+
+/**
+ * @brief Subscribe to a topic on MQTT with QoS0(at most once).
+ * @details Sets a function to be called when the aitt get messages related with @c topic
+ * @since_tizen 7.0
+ * @privlevel public
+ * @param[in] handle Handle of AITT service
+ * @param[in] topic null terminated string of the topic to subscribe to.
+ * @param[in] cb The callback function to invoke
+ * @param[in] user_data The user data to pass to the function
+ * @param[out] sub_handle Handle of subscribed topic
+ * @return @c 0 on success
+ * otherwise a negative error value
+ * @retval #AITT_ERROR_NONE Success
+ * @retval #AITT_ERROR_INVALID_PARAMETER Invalid parameter
+ * @retval #AITT_ERROR_SYSTEM System errors
+ */
+int aitt_subscribe(aitt_h handle, const char *topic, aitt_sub_fn cb, void *user_data,
+ aitt_sub_h *sub_handle);
+
+/**
+ * @brief Subscribe to a topic, but takes protocols and qos.
+ * @details Sets a function to be called when the aitt get messages related with @c topic
+ * @since_tizen 7.0
+ * @privlevel public
+ * @param[in] handle Handle of AITT service
+ * @param[in] topic null terminated string of the topic to subscribe to.
+ * @param[in] cb The callback function to invoke
+ * @param[in] user_data The user data to pass to the function
+ * @param[in] protocols value of @a aitt_protocol_e.
+ * @param[in] qos integer value 0, 1 or 2 indicating the Quality of Service.
+ * @param[out] sub_handle Handle of subscribed topic
+ * @return @c 0 on success
+ * otherwise a negative error value
+ * @retval #AITT_ERROR_NONE Success
+ * @retval #AITT_ERROR_INVALID_PARAMETER Invalid parameter
+ * @retval #AITT_ERROR_SYSTEM System errors
+ */
+int aitt_subscribe_full(aitt_h handle, const char *topic, aitt_sub_fn cb, void *user_data,
+ aitt_protocol_e protocols, aitt_qos_e qos, aitt_sub_h *sub_handle);
+
+/**
+ * @brief Unsubscribe from a topic.
+ * @details Removes the subscription of changes with given ID.
+ * @since_tizen 7.0
+ * @privlevel public
+ * @param[in] handle Handle of AITT service
+ * @param[in] sub_handle Handle of subscribed topic
+ * @return @c 0 on success
+ * otherwise a negative error value
+ * @retval #AITT_ERROR_NONE Success
+ * @retval #AITT_ERROR_INVALID_PARAMETER Invalid parameter
+ * @retval #AITT_ERROR_SYSTEM System errors
+ *
+ * @see aitt_subscribe(), aitt_subscribe_full()
+ */
+int aitt_unsubscribe(aitt_h handle, aitt_sub_h sub_handle);
+
+/**
+ * @}
+ */
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/mock/MQMockTest.h b/mock/MQMockTest.h
new file mode 100644
index 0000000..307640b
--- /dev/null
+++ b/mock/MQMockTest.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2021-2022 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+#include <gmock/gmock.h>
+
+#include "MQTTMock.h"
+
+class MQMockTest : public ::testing::Test {
+ protected:
+ void SetUp() override { mqttMock = new MQTTMock; }
+
+ void TearDown() override
+ {
+ delete mqttMock;
+ mqttMock = nullptr;
+ }
+
+ public:
+ static MQTTMock &GetMock(void) { return *mqttMock; }
+
+ private:
+ static MQTTMock *mqttMock;
+};
diff --git a/mock/MQTTMock.h b/mock/MQTTMock.h
new file mode 100644
index 0000000..10d484e
--- /dev/null
+++ b/mock/MQTTMock.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2021-2022 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+#include <gmock/gmock.h>
+
+class MQTTMock {
+ public:
+ MQTTMock(void) = default;
+ virtual ~MQTTMock(void) = default;
+
+ MOCK_METHOD0(mosquitto_lib_init, int(void));
+ MOCK_METHOD0(mosquitto_lib_cleanup, int(void));
+ MOCK_METHOD3(mosquitto_new, struct mosquitto *(const char *id, bool clean_session, void *obj));
+ MOCK_METHOD3(mosquitto_int_option, int(struct mosquitto *mosq, int option, int value));
+ MOCK_METHOD1(mosquitto_destroy, void(struct mosquitto *mosq));
+ MOCK_METHOD3(mosquitto_username_pw_set,
+ int(struct mosquitto *mosq, const char *username, const char *password));
+ MOCK_METHOD6(mosquitto_will_set, int(struct mosquitto *mosq, const char *topic, int payloadlen,
+ const void *payload, int qos, bool retain));
+ MOCK_METHOD1(mosquitto_will_clear, int(struct mosquitto *mosq));
+ MOCK_METHOD4(mosquitto_connect,
+ int(struct mosquitto *mosq, const char *host, int port, int keepalive));
+ MOCK_METHOD1(mosquitto_disconnect, int(struct mosquitto *mosq));
+ MOCK_METHOD7(mosquitto_publish,
+ int(struct mosquitto *mosq, int *mid, const char *topic, int payloadlen,
+ const void *payload, int qos, bool retain));
+ MOCK_METHOD4(mosquitto_subscribe,
+ int(struct mosquitto *mosq, int *mid, const char *sub, int qos));
+ MOCK_METHOD3(mosquitto_unsubscribe, int(struct mosquitto *mosq, int *mid, const char *sub));
+ MOCK_METHOD1(mosquitto_loop_start, int(struct mosquitto *mosq));
+ MOCK_METHOD2(mosquitto_loop_stop, int(struct mosquitto *mosq, bool force));
+ MOCK_METHOD2(mosquitto_message_v5_callback_set,
+ void(struct mosquitto *mosq,
+ void (*on_message)(struct mosquitto *, void *, const struct mosquitto_message *,
+ const struct mqtt5__property *)));
+};
diff --git a/mock/mosquitto.cc b/mock/mosquitto.cc
new file mode 100644
index 0000000..5ea4f1b
--- /dev/null
+++ b/mock/mosquitto.cc
@@ -0,0 +1,110 @@
+/*
+ * Copyright (c) 2021-2022 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <cstring>
+
+#include "MQMockTest.h"
+#include "MQTTMock.h"
+
+MQTTMock *MQMockTest::mqttMock = nullptr;
+
+extern "C" {
+
+int mosquitto_lib_init(void)
+{
+ return MQMockTest::GetMock().mosquitto_lib_init();
+}
+
+int mosquitto_lib_cleanup(void)
+{
+ return MQMockTest::GetMock().mosquitto_lib_cleanup();
+}
+
+struct mosquitto *mosquitto_new(const char *id, bool clean_session, void *obj)
+{
+ return MQMockTest::GetMock().mosquitto_new(id, clean_session, obj);
+}
+
+int mosquitto_int_option(struct mosquitto *mosq, int option, int value)
+{
+ return MQMockTest::GetMock().mosquitto_int_option(mosq, option, value);
+}
+
+void mosquitto_destroy(struct mosquitto *mosq)
+{
+ return MQMockTest::GetMock().mosquitto_destroy(mosq);
+}
+
+int mosquitto_username_pw_set(struct mosquitto *mosq, const char *username, const char *password)
+{
+ return MQMockTest::GetMock().mosquitto_username_pw_set(mosq, username, password);
+}
+
+int mosquitto_will_set(struct mosquitto *mosq, const char *topic, int payloadlen,
+ const void *payload, int qos, bool retain)
+{
+ return MQMockTest::GetMock().mosquitto_will_set(mosq, topic, payloadlen, payload, qos, retain);
+}
+
+int mosquitto_will_clear(struct mosquitto *mosq)
+{
+ return MQMockTest::GetMock().mosquitto_will_clear(mosq);
+}
+
+int mosquitto_connect(struct mosquitto *mosq, const char *host, int port, int keepalive)
+{
+ return MQMockTest::GetMock().mosquitto_connect(mosq, host, port, keepalive);
+}
+
+int mosquitto_disconnect(struct mosquitto *mosq)
+{
+ return MQMockTest::GetMock().mosquitto_disconnect(mosq);
+}
+
+int mosquitto_publish(struct mosquitto *mosq, int *mid, const char *topic, int payloadlen,
+ const void *payload, int qos, bool retain)
+{
+ return MQMockTest::GetMock().mosquitto_publish(mosq, mid, topic, payloadlen, payload, qos,
+ retain);
+}
+
+int mosquitto_subscribe(struct mosquitto *mosq, int *mid, const char *sub, int qos)
+{
+ return MQMockTest::GetMock().mosquitto_subscribe(mosq, mid, sub, qos);
+}
+
+int mosquitto_unsubscribe(struct mosquitto *mosq, int *mid, const char *sub)
+{
+ return MQMockTest::GetMock().mosquitto_unsubscribe(mosq, mid, sub);
+}
+
+int mosquitto_loop_start(struct mosquitto *mosq)
+{
+ return MQMockTest::GetMock().mosquitto_loop_start(mosq);
+}
+
+int mosquitto_loop_stop(struct mosquitto *mosq, bool force)
+{
+ return MQMockTest::GetMock().mosquitto_loop_stop(mosq, force);
+}
+
+void mosquitto_message_v5_callback_set(struct mosquitto *mosq,
+ void (*on_message)(struct mosquitto *, void *, const struct mosquitto_message *,
+ const struct mqtt5__property *))
+{
+ return MQMockTest::GetMock().mosquitto_message_v5_callback_set(mosq, on_message);
+}
+
+} // extern "C"
diff --git a/modules/main.cc b/modules/main.cc
new file mode 100644
index 0000000..86e9c36
--- /dev/null
+++ b/modules/main.cc
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2021-2022 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <assert.h>
+
+#include "Module.h"
+#include "aitt_internal_definitions.h"
+
+extern "C" {
+
+// Function name Should be same with aitt::AittTransport::MODULE_ENTRY_NAME
+API void *aitt_module_entry(const char *ip, AittDiscovery &discovery)
+{
+ assert(!strcmp(__func__, aitt::AittTransport::MODULE_ENTRY_NAME)
+ && "Entry point name is not matched");
+
+ std::string ip_address(ip);
+ Module *module = new Module(ip_address, discovery);
+
+ AittTransport *tModule = dynamic_cast<AittTransport *>(module);
+ // NOTE:
+ // validate that the module creates valid object (which inherits AittTransport)
+ assert(tModule && "Transport Module is not created");
+
+ return tModule;
+}
+
+} // extern "C"
diff --git a/modules/tcp/CMakeLists.txt b/modules/tcp/CMakeLists.txt
new file mode 100644
index 0000000..edac2fd
--- /dev/null
+++ b/modules/tcp/CMakeLists.txt
@@ -0,0 +1,14 @@
+SET(AITT_TCP aitt-transport-tcp)
+
+INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR})
+
+ADD_LIBRARY(TCP_OBJ OBJECT TCP.cc TCPServer.cc)
+ADD_LIBRARY(${AITT_TCP} SHARED ../main.cc Module.cc $<TARGET_OBJECTS:TCP_OBJ>)
+TARGET_LINK_LIBRARIES(${AITT_TCP} ${AITT_TCP_NEEDS_LIBRARIES} Threads::Threads ${AITT_COMMON})
+
+INSTALL(TARGETS ${AITT_TCP} DESTINATION ${CMAKE_INSTALL_LIBDIR})
+
+IF(BUILD_TESTING)
+ ADD_SUBDIRECTORY(samples)
+ ADD_SUBDIRECTORY(tests)
+ENDIF(BUILD_TESTING)
diff --git a/modules/tcp/Module.cc b/modules/tcp/Module.cc
new file mode 100644
index 0000000..bc50d7d
--- /dev/null
+++ b/modules/tcp/Module.cc
@@ -0,0 +1,513 @@
+/*
+ * Copyright (c) 2021-2022 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "Module.h"
+
+#include <MQ.h>
+#include <flatbuffers/flexbuffers.h>
+#include <unistd.h>
+
+#include "aitt_internal.h"
+
+/*
+ * P2P Data Packet Definition
+ * TopicLength: 4 bytes
+ * TopicString: $TopicLength
+ */
+
+Module::Module(const std::string &ip, AittDiscovery &discovery) : AittTransport(discovery), ip(ip)
+{
+ aittThread = std::thread(&Module::ThreadMain, this);
+
+ discovery_cb = discovery.AddDiscoveryCB(AITT_TYPE_TCP,
+ std::bind(&Module::DiscoveryMessageCallback, this, std::placeholders::_1,
+ std::placeholders::_2, std::placeholders::_3, std::placeholders::_4));
+ DBG("Discovery Callback : %p, %d", this, discovery_cb);
+}
+
+Module::~Module(void)
+{
+ discovery.RemoveDiscoveryCB(discovery_cb);
+
+ while (main_loop.Quit() == false) {
+ // wait when called before the thread has completely created.
+ usleep(1000);
+ }
+
+ if (aittThread.joinable())
+ aittThread.join();
+}
+
+void Module::ThreadMain(void)
+{
+ pthread_setname_np(pthread_self(), "TCPWorkerLoop");
+ main_loop.Run();
+}
+
+void Module::Publish(const std::string &topic, const void *data, const size_t datalen,
+ const std::string &correlation, AittQoS qos, bool retain)
+{
+ // NOTE:
+ // Iterate discovered service table
+ // PublishMap
+ // map {
+ // "/customTopic/faceRecog": map {
+ // "$clientId": map {
+ // 11234: $handle,
+ //
+ // ...
+ //
+ // 21234: nullptr,
+ // },
+ // },
+ // }
+ std::lock_guard<std::mutex> auto_lock_publish(publishTableLock);
+ for (PublishMap::iterator it = publishTable.begin(); it != publishTable.end(); ++it) {
+ // NOTE:
+ // Find entries that have matched with the given topic
+ if (!aitt::MQ::CompareTopic(it->first, topic))
+ continue;
+
+ // NOTE:
+ // Iterate all hosts
+ for (HostMap::iterator hostIt = it->second.begin(); hostIt != it->second.end(); ++hostIt) {
+ // Iterate all ports,
+ // the current implementation only be able to have the ZERO or a SINGLE entry
+ // hostIt->first // clientId
+ for (PortMap::iterator portIt = hostIt->second.begin(); portIt != hostIt->second.end();
+ ++portIt) {
+ // portIt->first // port
+ // portIt->second // handle
+ if (!portIt->second) {
+ std::string host;
+ {
+ ClientMap::iterator clientIt;
+ std::lock_guard<std::mutex> auto_lock_client(clientTableLock);
+
+ clientIt = clientTable.find(hostIt->first);
+ if (clientIt != clientTable.end())
+ host = clientIt->second;
+
+ // NOTE:
+ // otherwise, it is a critical error
+ // The broken clientTable or subscribeTable
+ }
+
+ std::unique_ptr<TCP> client(std::make_unique<TCP>(host, portIt->first));
+
+ // TODO:
+ // If the client gets disconnected,
+ // This channel entry must be cleared
+ // In order to do that,
+ // There should be an observer to monitor
+ // each connections and manipulate
+ // the discovered service table
+ portIt->second = std::move(client);
+ }
+
+ if (!portIt->second) {
+ ERR("Failed to create a new client instance");
+ continue;
+ }
+
+ SendTopic(topic, portIt);
+ SendPayload(datalen, portIt, data);
+ }
+ } // connectionEntries
+ } // publishTable
+}
+
+void Module::SendTopic(const std::string &topic, Module::PortMap::iterator &portIt)
+{
+ uint32_t topicLen = topic.length();
+ size_t szData = sizeof(topicLen);
+ portIt->second->Send(static_cast<void *>(&topicLen), szData);
+ szData = topicLen;
+ portIt->second->Send(static_cast<const void *>(topic.c_str()), szData);
+}
+
+void Module::SendPayload(const size_t &datalen, Module::PortMap::iterator &portIt, const void *data)
+{
+ uint32_t sendsize = datalen;
+ size_t szsize = sizeof(sendsize);
+
+ try {
+ if (0 == datalen) {
+ // distinguish between connection problems and zero-size messages
+ INFO("Send zero-size Message");
+ sendsize = UINT32_MAX;
+ }
+ portIt->second->Send(static_cast<void *>(&sendsize), szsize);
+
+ int msgSize = datalen;
+ while (0 < msgSize) {
+ size_t sentSize = msgSize;
+ char *dataIdx = (char *)data + (sendsize - msgSize);
+ portIt->second->Send(dataIdx, sentSize);
+ if (sentSize > 0) {
+ msgSize -= sentSize;
+ }
+ }
+ } catch (std::exception &e) {
+ ERR("An exception(%s) occurs during Send().", e.what());
+ }
+}
+
+void Module::Publish(const std::string &topic, const void *data, const size_t datalen, AittQoS qos,
+ bool retain)
+{
+ Publish(topic, data, datalen, std::string(), qos, retain);
+}
+
+void *Module::Subscribe(const std::string &topic, const AittTransport::SubscribeCallback &cb,
+ void *cbdata, AittQoS qos)
+{
+ std::unique_ptr<TCP::Server> tcpServer;
+
+ unsigned short port = 0;
+ tcpServer = std::make_unique<TCP::Server>("0.0.0.0", port);
+ TCPServerData *listen_info = new TCPServerData;
+ listen_info->impl = this;
+ listen_info->cb = cb;
+ listen_info->cbdata = cbdata;
+ listen_info->topic = topic;
+ auto handle = tcpServer->GetHandle();
+
+ main_loop.AddWatch(handle, AcceptConnection, listen_info);
+
+ // 서비스 테이블에 토픽을 키워드로 프로토콜을 등록한다.
+ {
+ std::lock_guard<std::mutex> autoLock(subscribeTableLock);
+ subscribeTable.insert(SubscribeMap::value_type(topic, std::move(tcpServer)));
+ UpdateDiscoveryMsg();
+ }
+
+ return reinterpret_cast<void *>(handle);
+}
+
+void *Module::Subscribe(const std::string &topic, const AittTransport::SubscribeCallback &cb,
+ const void *data, const size_t datalen, void *cbdata, AittQoS qos)
+{
+ return nullptr;
+}
+
+void *Module::Unsubscribe(void *handlePtr)
+{
+ int handle = static_cast<int>(reinterpret_cast<intptr_t>(handlePtr));
+ TCPServerData *listen_info = dynamic_cast<TCPServerData *>(main_loop.RemoveWatch(handle));
+ if (!listen_info)
+ return nullptr;
+
+ {
+ std::lock_guard<std::mutex> autoLock(subscribeTableLock);
+ auto it = subscribeTable.find(listen_info->topic);
+ if (it == subscribeTable.end())
+ throw std::runtime_error("Service is not registered: " + listen_info->topic);
+
+ subscribeTable.erase(it);
+
+ UpdateDiscoveryMsg();
+ }
+
+ void *cbdata = listen_info->cbdata;
+ listen_info->client_lock.lock();
+ for (auto fd : listen_info->client_list) {
+ TCPData *connect_info = dynamic_cast<TCPData *>(main_loop.RemoveWatch(fd));
+ delete connect_info;
+ }
+ listen_info->client_list.clear();
+ listen_info->client_lock.unlock();
+ delete listen_info;
+
+ return cbdata;
+}
+
+void Module::DiscoveryMessageCallback(const std::string &clientId, const std::string &status,
+ const void *msg, const int szmsg)
+{
+ // NOTE:
+ // Iterate discovered service table
+ // PublishMap
+ // map {
+ // "/customTopic/faceRecog": map {
+ // "clientId.uniq.abcd.123": map {
+ // 11234: pair {
+ // "protocol": 1,
+ // "handle": nullptr,
+ // },
+ //
+ // ...
+ //
+ // 21234: pair {
+ // "protocol": 2,
+ // "handle": nullptr,
+ // }
+ // },
+ // },
+ // }
+
+ if (!status.compare(AittDiscovery::WILL_LEAVE_NETWORK)) {
+ {
+ std::lock_guard<std::mutex> autoLock(clientTableLock);
+ // Delete from the { clientId : Host } mapping table
+ clientTable.erase(clientId);
+ }
+
+ {
+ // NOTE:
+ // Iterate all topics in the publishTable holds discovered client information
+ std::lock_guard<std::mutex> autoLock(publishTableLock);
+ for (auto it = publishTable.begin(); it != publishTable.end(); ++it)
+ it->second.erase(clientId);
+ }
+ return;
+ }
+
+ // serviceMessage (flexbuffers)
+ // map {
+ // "host": "192.168.1.11",
+ // "$topic": port,
+ // }
+ auto map = flexbuffers::GetRoot(static_cast<const uint8_t *>(msg), szmsg).AsMap();
+ std::string host = map["host"].AsString().c_str();
+
+ // NOTE:
+ // Update the clientTable
+ {
+ std::lock_guard<std::mutex> autoLock(clientTableLock);
+ auto clientIt = clientTable.find(clientId);
+ if (clientIt == clientTable.end())
+ clientTable.insert(ClientMap::value_type(clientId, host));
+ else if (clientIt->second.compare(host))
+ clientIt->second = host;
+ }
+
+ auto topics = map.Keys();
+ for (size_t idx = 0; idx < topics.size(); ++idx) {
+ std::string topic = topics[idx].AsString().c_str();
+
+ if (!topic.compare("host"))
+ continue;
+
+ auto port = map[topic].AsUInt16();
+
+ {
+ std::lock_guard<std::mutex> autoLock(publishTableLock);
+ UpdatePublishTable(topic, clientId, port);
+ }
+ }
+}
+
+void Module::UpdateDiscoveryMsg()
+{
+ flexbuffers::Builder fbb;
+ // flexbuffers
+ // {
+ // "host": "127.0.0.1",
+ // "/customTopic/aitt/faceRecog": $port,
+ // "/customTopic/aitt/ASR": 102020,
+ //
+ // ...
+ //
+ // "/customTopic/aitt/+": 20123,
+ // }
+ fbb.Map([this, &fbb]() {
+ fbb.String("host", ip);
+
+ // SubscribeTable
+ // map {
+ // "/customTopic/mytopic": $serverHandle,
+ // ...
+ // }
+ for (auto it = subscribeTable.begin(); it != subscribeTable.end(); ++it) {
+ if (it->second)
+ fbb.UInt(it->first.c_str(), it->second->GetPort());
+ else
+ fbb.UInt(it->first.c_str(), 0); // this is an error case
+ }
+ });
+ fbb.Finish();
+
+ auto buf = fbb.GetBuffer();
+ discovery.UpdateDiscoveryMsg(AITT_TYPE_TCP, buf.data(), buf.size());
+}
+
+void Module::ReceiveData(MainLoopHandler::MainLoopResult result, int handle,
+ MainLoopHandler::MainLoopData *user_data)
+{
+ TCPData *connect_info = dynamic_cast<TCPData *>(user_data);
+ RET_IF(connect_info == nullptr);
+ TCPServerData *parent_info = connect_info->parent;
+ RET_IF(parent_info == nullptr);
+ Module *impl = parent_info->impl;
+ RET_IF(impl == nullptr);
+
+ if (result == MainLoopHandler::HANGUP) {
+ ERR("Disconnected");
+ return impl->HandleClientDisconnect(handle);
+ }
+
+ uint32_t szmsg = 0;
+ size_t szdata = sizeof(szmsg);
+ char *msg = nullptr;
+ std::string topic;
+
+ try {
+ topic = impl->GetTopicName(connect_info);
+ if (topic.empty()) {
+ ERR("Unknown Topic");
+ return impl->HandleClientDisconnect(handle);
+ }
+
+ connect_info->client->Recv(static_cast<void *>(&szmsg), szdata);
+ if (szmsg == 0) {
+ ERR("Disconnected");
+ return impl->HandleClientDisconnect(handle);
+ }
+
+ if (UINT32_MAX == szmsg) {
+ // distinguish between connection problems and zero-size messages
+ INFO("Got zero-size Message");
+ szmsg = 0;
+ }
+
+ msg = static_cast<char *>(malloc(szmsg));
+ int msgSize = szmsg;
+ while (0 < msgSize) {
+ size_t receivedSize = msgSize;
+ connect_info->client->Recv(static_cast<void *>(msg + (szmsg - msgSize)), receivedSize);
+ if (receivedSize > 0) {
+ msgSize -= receivedSize;
+ }
+ }
+ } catch (std::exception &e) {
+ ERR("An exception(%s) occurs during Recv()", e.what());
+ }
+
+ std::string correlation;
+ // TODO:
+ // Correlation data (string) should be filled
+
+ parent_info->cb(topic, msg, szmsg, parent_info->cbdata, correlation);
+ free(msg);
+}
+
+void Module::HandleClientDisconnect(int handle)
+{
+ TCPData *connect_info = dynamic_cast<TCPData *>(main_loop.RemoveWatch(handle));
+ if (connect_info == nullptr) {
+ ERR("No watch data");
+ return;
+ }
+ connect_info->parent->client_lock.lock();
+ auto it = std::find(connect_info->parent->client_list.begin(),
+ connect_info->parent->client_list.end(), handle);
+ connect_info->parent->client_list.erase(it);
+ connect_info->parent->client_lock.unlock();
+
+ delete connect_info;
+}
+
+std::string Module::GetTopicName(Module::TCPData *connect_info)
+{
+ uint32_t topic_len = 0;
+ size_t data_size = sizeof(topic_len);
+ connect_info->client->Recv(static_cast<void *>(&topic_len), data_size);
+
+ if (AITT_TOPIC_NAME_MAX < topic_len) {
+ ERR("Invalid topic name length(%d)", topic_len);
+ return std::string();
+ }
+
+ char data[topic_len];
+ data_size = topic_len;
+ connect_info->client->Recv(data, data_size);
+ if (data_size != topic_len)
+ ERR("Recv() Fail");
+
+ return std::string(data, data_size);
+}
+
+void Module::AcceptConnection(MainLoopHandler::MainLoopResult result, int handle,
+ MainLoopHandler::MainLoopData *user_data)
+{
+ // TODO:
+ // Update the discovery map
+ std::unique_ptr<TCP> client;
+
+ TCPServerData *listen_info = dynamic_cast<TCPServerData *>(user_data);
+ Module *impl = listen_info->impl;
+ {
+ std::lock_guard<std::mutex> autoLock(impl->subscribeTableLock);
+
+ auto clientIt = impl->subscribeTable.find(listen_info->topic);
+ if (clientIt == impl->subscribeTable.end())
+ return;
+
+ client = clientIt->second->AcceptPeer();
+ }
+
+ if (client == nullptr) {
+ ERR("Unable to accept a peer"); // NOTE: FATAL ERROR
+ return;
+ }
+
+ int cHandle = client->GetHandle();
+ listen_info->client_list.push_back(cHandle);
+
+ TCPData *ecd = new TCPData;
+ ecd->parent = listen_info;
+ ecd->client = std::move(client);
+
+ impl->main_loop.AddWatch(cHandle, ReceiveData, ecd);
+}
+
+void Module::UpdatePublishTable(const std::string &topic, const std::string &clientId,
+ unsigned short port)
+{
+ auto topicIt = publishTable.find(topic);
+ if (topicIt == publishTable.end()) {
+ PortMap portMap;
+ portMap.insert(PortMap::value_type(port, nullptr));
+ HostMap hostMap;
+ hostMap.insert(HostMap::value_type(clientId, std::move(portMap)));
+ publishTable.insert(PublishMap::value_type(topic, std::move(hostMap)));
+ return;
+ }
+
+ auto hostIt = topicIt->second.find(clientId);
+ if (hostIt == topicIt->second.end()) {
+ PortMap portMap;
+ portMap.insert(PortMap::value_type(port, nullptr));
+ topicIt->second.insert(HostMap::value_type(clientId, std::move(portMap)));
+ return;
+ }
+
+ // NOTE:
+ // The current implementation only has a single port entry
+ // therefore, if the hostIt is not empty, there is the previous connection
+ if (!hostIt->second.empty()) {
+ auto portIt = hostIt->second.begin();
+
+ if (portIt->first == port)
+ return; // nothing changed. keep the current handle
+
+ // otherwise, delete the connection handle
+ // to make a new connection with the new port
+ hostIt->second.clear();
+ }
+
+ hostIt->second.insert(PortMap::value_type(port, nullptr));
+}
diff --git a/modules/tcp/Module.h b/modules/tcp/Module.h
new file mode 100644
index 0000000..4011980
--- /dev/null
+++ b/modules/tcp/Module.h
@@ -0,0 +1,135 @@
+/*
+ * Copyright (c) 2021-2022 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+#include <AittTransport.h>
+#include <MainLoopHandler.h>
+
+#include <map>
+#include <memory>
+#include <mutex>
+#include <string>
+#include <thread>
+
+#include "TCPServer.h"
+
+using AittTransport = aitt::AittTransport;
+using MainLoopHandler = aitt::MainLoopHandler;
+using AittDiscovery = aitt::AittDiscovery;
+
+class Module : public AittTransport {
+ public:
+ explicit Module(const std::string &ip, AittDiscovery &discovery);
+ virtual ~Module(void);
+
+ void Publish(const std::string &topic, const void *data, const size_t datalen,
+ const std::string &correlation, AittQoS qos = AITT_QOS_AT_MOST_ONCE,
+ bool retain = false) override;
+
+ void Publish(const std::string &topic, const void *data, const size_t datalen,
+ AittQoS qos = AITT_QOS_AT_MOST_ONCE, bool retain = false) override;
+
+ void *Subscribe(const std::string &topic, const SubscribeCallback &cb, void *cbdata = nullptr,
+ AittQoS qos = AITT_QOS_AT_MOST_ONCE) override;
+
+ void *Subscribe(const std::string &topic, const SubscribeCallback &cb, const void *data,
+ const size_t datalen, void *cbdata = nullptr,
+ AittQoS qos = AITT_QOS_AT_MOST_ONCE) override;
+ void *Unsubscribe(void *handle) override;
+
+ private:
+ struct TCPServerData : public MainLoopHandler::MainLoopData {
+ Module *impl;
+ SubscribeCallback cb;
+ void *cbdata;
+ std::string topic;
+ std::vector<int> client_list;
+ std::mutex client_lock;
+ };
+
+ struct TCPData : public MainLoopHandler::MainLoopData {
+ TCPServerData *parent;
+ std::unique_ptr<TCP> client;
+ };
+
+ // SubscribeTable
+ // map {
+ // "/customTopic/mytopic": $serverHandle,
+ // ...
+ // }
+ using SubscribeMap = std::map<std::string, std::unique_ptr<TCP::Server>>;
+
+ // ClientTable
+ // map {
+ // $clientId: $host,
+ // "client.uniqId.123": "192.168.1.11"
+ // ...
+ // }
+ using ClientMap = std::map<std::string /* id */, std::string /* host */>;
+
+ // NOTE:
+ // There could be multiple clientIds for the single host
+ // If several applications are run on the same device, each applicaion will get unique client
+ // Ids therefore we have to keep in mind that the clientId is not 1:1 matched for the IPAddress.
+
+ // PublishTable
+ // map {
+ // "/customTopic/faceRecog": map {
+ // $clientId: map {
+ // 11234: $clientHandle,
+ //
+ // ...
+ //
+ // 21234: $clientHandle,
+ // },
+ // },
+ // }
+ //
+ // NOTE:
+ // TCP handle should be the unique_ptr, so if we delete the entry from the map,
+ // the handle must be released automatically
+ // in order to make the handle "unique_ptr", it should be a class object not the "void *"
+ using PortMap = std::map<unsigned short /* port */, std::unique_ptr<TCP>>;
+ using HostMap = std::map<std::string /* clientId */, PortMap>;
+ using PublishMap = std::map<std::string /* topic */, HostMap>;
+
+ static void AcceptConnection(MainLoopHandler::MainLoopResult result, int handle,
+ MainLoopHandler::MainLoopData *watchData);
+ void DiscoveryMessageCallback(const std::string &clientId, const std::string &status,
+ const void *msg, const int szmsg);
+ void UpdateDiscoveryMsg();
+ static void ReceiveData(MainLoopHandler::MainLoopResult result, int handle,
+ MainLoopHandler::MainLoopData *watchData);
+ void HandleClientDisconnect(int handle);
+ std::string GetTopicName(TCPData *connect_info);
+ void ThreadMain(void);
+ void SendPayload(const size_t &datalen, Module::PortMap::iterator &portIt, const void *data);
+ void SendTopic(const std::string &topic, Module::PortMap::iterator &portIt);
+
+ void UpdatePublishTable(const std::string &topic, const std::string &host, unsigned short port);
+
+ MainLoopHandler main_loop;
+ std::thread aittThread;
+ std::string ip;
+ int discovery_cb;
+
+ PublishMap publishTable;
+ std::mutex publishTableLock;
+ SubscribeMap subscribeTable;
+ std::mutex subscribeTableLock;
+ ClientMap clientTable;
+ std::mutex clientTableLock;
+};
diff --git a/modules/tcp/TCP.cc b/modules/tcp/TCP.cc
new file mode 100644
index 0000000..3b6751e
--- /dev/null
+++ b/modules/tcp/TCP.cc
@@ -0,0 +1,157 @@
+/*
+ * Copyright (c) 2021-2022 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "TCP.h"
+
+#include <arpa/inet.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <cstdlib>
+#include <cstring>
+#include <stdexcept>
+
+#include "aitt_internal.h"
+
+TCP::TCP(const std::string &host, unsigned short port) : handle(-1), addrlen(0), addr(nullptr)
+{
+ int ret = 0;
+
+ do {
+ if (port == 0) {
+ ret = EINVAL;
+ break;
+ }
+
+ handle = socket(AF_INET, SOCK_STREAM | SOCK_CLOEXEC, 0);
+ if (handle < 0) {
+ ERR("socket() Fail()");
+ break;
+ }
+
+ addrlen = sizeof(sockaddr_in);
+ addr = static_cast<sockaddr *>(calloc(1, addrlen));
+ if (!addr) {
+ ERR("calloc() Fail()");
+ break;
+ }
+
+ sockaddr_in *inet_addr = reinterpret_cast<sockaddr_in *>(addr);
+ if (!inet_pton(AF_INET, host.c_str(), &inet_addr->sin_addr)) {
+ ret = EINVAL;
+ break;
+ }
+
+ inet_addr->sin_port = htons(port);
+ inet_addr->sin_family = AF_INET;
+
+ ret = connect(handle, addr, addrlen);
+ if (ret < 0) {
+ ERR("connect() Fail(%s, %d)", host.c_str(), port);
+ break;
+ }
+
+ SetupOptions();
+ return;
+ } while (0);
+
+ if (ret <= 0)
+ ret = errno;
+
+ free(addr);
+ if (handle >= 0 && close(handle) < 0)
+ ERR_CODE(errno, "close");
+ throw std::runtime_error(strerror(ret));
+}
+
+TCP::TCP(int handle, sockaddr *addr, socklen_t szAddr) : handle(handle), addrlen(szAddr), addr(addr)
+{
+ SetupOptions();
+}
+
+TCP::~TCP(void)
+{
+ if (handle < 0)
+ return;
+
+ free(addr);
+ if (close(handle) < 0)
+ ERR_CODE(errno, "close");
+}
+
+void TCP::SetupOptions(void)
+{
+ int on = 1;
+
+ int ret = setsockopt(handle, IPPROTO_IP, TCP_NODELAY, &on, sizeof(on));
+ if (ret < 0) {
+ ERR_CODE(errno, "delay option setting failed");
+ }
+}
+
+void TCP::Send(const void *data, size_t &szData)
+{
+ int ret = send(handle, data, szData, 0);
+ if (ret < 0) {
+ ERR("Fail to send data, handle = %d, size = %zu", handle, szData);
+ throw std::runtime_error(strerror(errno));
+ }
+
+ szData = ret;
+}
+
+void TCP::Recv(void *data, size_t &szData)
+{
+ int ret = recv(handle, data, szData, 0);
+ if (ret < 0) {
+ ERR("Fail to recv data, handle = %d, size = %zu", handle, szData);
+ throw std::runtime_error(strerror(errno));
+ }
+
+ szData = ret;
+}
+
+int TCP::GetHandle(void)
+{
+ return handle;
+}
+
+void TCP::GetPeerInfo(std::string &host, unsigned short &port)
+{
+ char address[INET_ADDRSTRLEN] = {
+ 0,
+ };
+
+ if (!inet_ntop(AF_INET, &reinterpret_cast<sockaddr_in *>(this->addr)->sin_addr, address,
+ sizeof(address)))
+ throw std::runtime_error(strerror(errno));
+
+ port = ntohs(reinterpret_cast<sockaddr_in *>(this->addr)->sin_port);
+ host = address;
+}
+
+unsigned short TCP::GetPort(void)
+{
+ sockaddr_in addr;
+ socklen_t addrlen = sizeof(addr);
+
+ if (getsockname(handle, reinterpret_cast<sockaddr *>(&addr), &addrlen) < 0)
+ throw std::runtime_error(strerror(errno));
+
+ return ntohs(addr.sin_port);
+}
diff --git a/modules/tcp/TCP.h b/modules/tcp/TCP.h
new file mode 100644
index 0000000..535819c
--- /dev/null
+++ b/modules/tcp/TCP.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2021-2022 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+#include <sys/socket.h>
+#include <sys/types.h> /* See NOTES */
+
+#include <string>
+
+class TCP {
+ public:
+ class Server;
+
+ TCP(const std::string &host, unsigned short port);
+ virtual ~TCP(void);
+
+ void Send(const void *data, size_t &szData);
+ void Recv(void *data, size_t &szData);
+ int GetHandle(void);
+ unsigned short GetPort(void);
+ void GetPeerInfo(std::string &host, unsigned short &port);
+
+ private:
+ TCP(int handle, sockaddr *addr, socklen_t addrlen);
+ void SetupOptions(void);
+
+ int handle;
+ socklen_t addrlen;
+ sockaddr *addr;
+};
diff --git a/modules/tcp/TCPServer.cc b/modules/tcp/TCPServer.cc
new file mode 100644
index 0000000..55f8511
--- /dev/null
+++ b/modules/tcp/TCPServer.cc
@@ -0,0 +1,132 @@
+/*
+ * Copyright (c) 2021-2022 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "TCPServer.h"
+
+#include <arpa/inet.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <cstdlib>
+#include <stdexcept>
+
+#include "aitt_internal.h"
+
+#define BACKLOG 10 // Accept only 10 simultaneously connections by default
+
+TCP::Server::Server(const std::string &host, unsigned short &port)
+ : handle(-1), addr(nullptr), addrlen(0)
+{
+ int ret = 0;
+
+ do {
+ handle = socket(AF_INET, SOCK_STREAM | SOCK_CLOEXEC, 0);
+ if (handle < 0)
+ break;
+
+ addrlen = sizeof(sockaddr_in);
+ addr = static_cast<sockaddr *>(calloc(1, sizeof(sockaddr_in)));
+ if (!addr)
+ break;
+
+ sockaddr_in *inet_addr = reinterpret_cast<sockaddr_in *>(addr);
+ if (!inet_pton(AF_INET, host.c_str(), &inet_addr->sin_addr)) {
+ ret = EINVAL;
+ break;
+ }
+
+ inet_addr->sin_port = htons(port);
+ inet_addr->sin_family = AF_INET;
+
+ int on = 1;
+ ret = setsockopt(handle, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
+ if (ret < 0)
+ break;
+
+ ret = bind(handle, addr, addrlen);
+ if (ret < 0)
+ break;
+
+ if (!port) {
+ if (getsockname(handle, addr, &addrlen) < 0)
+ break;
+ port = ntohs(inet_addr->sin_port);
+ }
+
+ ret = listen(handle, BACKLOG);
+ if (ret < 0)
+ break;
+
+ return;
+ } while (0);
+
+ if (ret <= 0)
+ ret = errno;
+
+ free(addr);
+
+ if (handle >= 0 && close(handle) < 0)
+ ERR_CODE(errno, "close");
+
+ throw std::runtime_error(strerror(ret));
+}
+
+TCP::Server::~Server(void)
+{
+ if (handle < 0)
+ return;
+
+ free(addr);
+ if (close(handle) < 0)
+ ERR_CODE(errno, "close");
+}
+
+std::unique_ptr<TCP> TCP::Server::AcceptPeer(void)
+{
+ sockaddr *peerAddr;
+ socklen_t szAddr = sizeof(sockaddr_in);
+ int peerHandle;
+
+ peerAddr = static_cast<sockaddr *>(calloc(1, szAddr));
+ if (!peerAddr)
+ throw std::runtime_error(strerror(errno));
+
+ peerHandle = accept(handle, peerAddr, &szAddr);
+ if (peerHandle < 0) {
+ free(peerAddr);
+ throw std::runtime_error(strerror(errno));
+ }
+
+ return std::unique_ptr<TCP>(new TCP(peerHandle, peerAddr, szAddr));
+}
+
+int TCP::Server::GetHandle(void)
+{
+ return handle;
+}
+
+unsigned short TCP::Server::GetPort(void)
+{
+ sockaddr_in addr;
+ socklen_t addrlen = sizeof(addr);
+
+ if (getsockname(handle, reinterpret_cast<sockaddr *>(&addr), &addrlen) < 0)
+ throw std::runtime_error(strerror(errno));
+
+ return ntohs(addr.sin_port);
+}
diff --git a/modules/tcp/TCPServer.h b/modules/tcp/TCPServer.h
new file mode 100644
index 0000000..3c82bc6
--- /dev/null
+++ b/modules/tcp/TCPServer.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2021-2022 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+#include <memory>
+#include <string>
+
+#include "TCP.h"
+
+class TCP::Server {
+ public:
+ Server(const std::string &host, unsigned short &port);
+ virtual ~Server(void);
+
+ std::unique_ptr<TCP> AcceptPeer(void);
+
+ int GetHandle(void);
+ unsigned short GetPort(void);
+
+ private:
+ int handle;
+ sockaddr *addr;
+ socklen_t addrlen;
+};
diff --git a/modules/tcp/samples/CMakeLists.txt b/modules/tcp/samples/CMakeLists.txt
new file mode 100644
index 0000000..8fd1b4b
--- /dev/null
+++ b/modules/tcp/samples/CMakeLists.txt
@@ -0,0 +1,3 @@
+ADD_EXECUTABLE("aitt_tcp_test" tcp_test.cc $<TARGET_OBJECTS:TCP_OBJ>)
+TARGET_LINK_LIBRARIES("aitt_tcp_test" ${PROJECT_NAME} Threads::Threads ${AITT_NEEDS_LIBRARIES})
+INSTALL(TARGETS "aitt_tcp_test" DESTINATION ${AITT_TEST_BINDIR})
diff --git a/modules/tcp/samples/tcp_test.cc b/modules/tcp/samples/tcp_test.cc
new file mode 100644
index 0000000..d319e27
--- /dev/null
+++ b/modules/tcp/samples/tcp_test.cc
@@ -0,0 +1,235 @@
+/*
+ * Copyright (c) 2021-2022 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <TCP.h>
+#include <TCPServer.h>
+#include <getopt.h>
+#include <glib.h>
+
+#include <functional>
+#include <iostream>
+#include <memory>
+#include <string>
+
+//#define _LOG_WITH_TIMESTAMP
+#include "aitt_internal.h"
+#ifdef _LOG_WITH_TIMESTAMP
+__thread __aitt__tls__ __aitt;
+#endif
+
+#define HELLO_STRING "hello"
+#define BYE_STRING "bye"
+#define SEND_INTERVAL 1000
+
+class AittTcpSample {
+ public:
+ AittTcpSample(const std::string &host, unsigned short &port)
+ : server(std::make_unique<TCP::Server>(host, port))
+ {
+ }
+ virtual ~AittTcpSample(void) {}
+
+ std::unique_ptr<TCP::Server> server;
+};
+
+int main(int argc, char *argv[])
+{
+ const option opts[] = {
+ {
+ .name = "server",
+ .has_arg = 0,
+ .flag = nullptr,
+ .val = 's',
+ },
+ {
+ .name = "host",
+ .has_arg = 1,
+ .flag = nullptr,
+ .val = 'h',
+ },
+ {
+ .name = "port",
+ .has_arg = 1,
+ .flag = nullptr,
+ .val = 'p',
+ },
+ };
+ int c;
+ int idx;
+ bool isServer = false;
+ std::string host = "127.0.0.1";
+ unsigned short port = 0;
+
+ while ((c = getopt_long(argc, argv, "sh:up:", opts, &idx)) != -1) {
+ switch (c) {
+ case 's':
+ isServer = true;
+ break;
+ case 'h':
+ host = optarg;
+ break;
+ case 'p':
+ port = std::stoi(optarg);
+ break;
+ default:
+ break;
+ }
+ }
+
+ INFO("Host[%s] port[%u]", host.c_str(), port);
+
+ struct EventData {
+ GSource source_;
+ GPollFD fd;
+ AittTcpSample *sample;
+ };
+
+ guint timeoutId = 0;
+ GSource *src = nullptr;
+ EventData *ed = nullptr;
+
+ GMainLoop *mainLoop = g_main_loop_new(nullptr, FALSE);
+ if (!mainLoop) {
+ ERR("Failed to create a main loop");
+ return 1;
+ }
+
+ // Handling the server/client events
+ if (isServer) {
+ GSourceFuncs srcs = {
+ [](GSource *src, gint *timeout) -> gboolean {
+ *timeout = 1;
+ return FALSE;
+ },
+ [](GSource *src) -> gboolean {
+ EventData *ed = reinterpret_cast<EventData *>(src);
+ RETV_IF(ed == nullptr, FALSE);
+
+ if ((ed->fd.revents & G_IO_IN) == G_IO_IN)
+ return TRUE;
+ if ((ed->fd.revents & G_IO_ERR) == G_IO_ERR)
+ return TRUE;
+
+ return FALSE;
+ },
+ [](GSource *src, GSourceFunc callback, gpointer user_data) -> gboolean {
+ EventData *ed = reinterpret_cast<EventData *>(src);
+ RETV_IF(ed == nullptr, FALSE);
+
+ if ((ed->fd.revents & G_IO_ERR) == G_IO_ERR) {
+ ERR("Error!");
+ return FALSE;
+ }
+
+ std::unique_ptr<TCP> peer = ed->sample->server->AcceptPeer();
+
+ INFO("Assigned port: %u, %u", ed->sample->server->GetPort(), peer->GetPort());
+ std::string peerHost;
+ unsigned short peerPort = 0;
+ peer->GetPeerInfo(peerHost, peerPort);
+ INFO("Peer Info: %s %u", peerHost.c_str(), peerPort);
+
+ char buffer[10];
+ void *ptr = static_cast<void *>(buffer);
+ size_t szData = sizeof(HELLO_STRING);
+ peer->Recv(ptr, szData);
+ INFO("Gots[%s]", buffer);
+
+ szData = sizeof(BYE_STRING);
+ peer->Send(BYE_STRING, szData);
+ INFO("Reply to client[%s]", BYE_STRING);
+
+ return TRUE;
+ },
+ nullptr,
+ };
+
+ src = g_source_new(&srcs, sizeof(EventData));
+ if (!src) {
+ g_main_loop_unref(mainLoop);
+ ERR("g_source_new failed");
+ return 1;
+ }
+
+ ed = reinterpret_cast<EventData *>(src);
+
+ try {
+ ed->sample = new AittTcpSample(host, port);
+ } catch (std::exception &e) {
+ ERR("new: %s", e.what());
+ g_source_unref(src);
+ g_main_loop_unref(mainLoop);
+ return 1;
+ }
+
+ INFO("host: %s, port: %u", host.c_str(), port);
+
+ ed->fd.fd = ed->sample->server->GetHandle();
+ ed->fd.events = G_IO_IN | G_IO_ERR;
+ g_source_add_poll(src, &ed->fd);
+ guint id = g_source_attach(src, g_main_loop_get_context(mainLoop));
+ g_source_unref(src);
+ if (id == 0) {
+ delete ed->sample;
+ g_source_destroy(src);
+ g_main_loop_unref(mainLoop);
+ return 1;
+ }
+ } else {
+ static struct Main {
+ const std::string &host;
+ unsigned short port;
+ } main_data = {
+ .host = host,
+ .port = port,
+ };
+ // Now the server is ready.
+ // Let's create a new client and communicate with the server within every
+ // SEND_INTERTVAL
+ timeoutId = g_timeout_add(
+ SEND_INTERVAL,
+ [](gpointer data) -> gboolean {
+ Main *ctx = static_cast<Main *>(data);
+ std::unique_ptr<TCP> client(std::make_unique<TCP>(ctx->host, ctx->port));
+
+ INFO("Assigned client port: %u", client->GetPort());
+
+ INFO("Send[%s]", HELLO_STRING);
+ size_t szBuffer = sizeof(HELLO_STRING);
+ client->Send(HELLO_STRING, szBuffer);
+
+ char buffer[10];
+ void *ptr = static_cast<void *>(buffer);
+ szBuffer = sizeof(BYE_STRING);
+ client->Recv(ptr, szBuffer);
+ INFO("Replied with[%s]", buffer);
+
+ // Send oneshot message, and disconnect from the server
+ return TRUE;
+ },
+ &main_data);
+ }
+
+ g_main_loop_run(mainLoop);
+
+ if (src) {
+ delete ed->sample;
+ g_source_destroy(src);
+ }
+ if (timeoutId)
+ g_source_remove(timeoutId);
+ g_main_loop_unref(mainLoop);
+ return 0;
+}
diff --git a/modules/tcp/tests/CMakeLists.txt b/modules/tcp/tests/CMakeLists.txt
new file mode 100644
index 0000000..bf1adf1
--- /dev/null
+++ b/modules/tcp/tests/CMakeLists.txt
@@ -0,0 +1,19 @@
+PKG_CHECK_MODULES(UT_NEEDS REQUIRED gmock_main)
+INCLUDE_DIRECTORIES(${UT_NEEDS_INCLUDE_DIRS})
+LINK_DIRECTORIES(${UT_NEEDS_LIBRARY_DIRS})
+
+SET(AITT_TCP_UT ${PROJECT_NAME}_tcp_ut)
+
+SET(AITT_TCP_UT_SRC TCP_test.cc TCPServer_test.cc)
+
+ADD_EXECUTABLE(${AITT_TCP_UT} ${AITT_TCP_UT_SRC} $<TARGET_OBJECTS:TCP_OBJ>)
+TARGET_LINK_LIBRARIES(${AITT_TCP_UT} ${UT_NEEDS_LIBRARIES} Threads::Threads ${AITT_NEEDS_LIBRARIES})
+INSTALL(TARGETS ${AITT_TCP_UT} DESTINATION ${AITT_TEST_BINDIR})
+
+ADD_TEST(
+ NAME
+ ${AITT_TCP_UT}
+ COMMAND
+ ${CMAKE_COMMAND} -E env
+ ${CMAKE_CURRENT_BINARY_DIR}/${AITT_TCP_UT} --gtest_filter=*_Anytime
+)
diff --git a/modules/tcp/tests/TCPServer_test.cc b/modules/tcp/tests/TCPServer_test.cc
new file mode 100644
index 0000000..e8b48b1
--- /dev/null
+++ b/modules/tcp/tests/TCPServer_test.cc
@@ -0,0 +1,121 @@
+/*
+ * Copyright (c) 2021-2022 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "../TCPServer.h"
+
+#include <gtest/gtest.h>
+
+#include <condition_variable>
+#include <cstring>
+#include <memory>
+#include <mutex>
+#include <thread>
+
+#define TEST_SERVER_ADDRESS "127.0.0.1"
+#define TEST_SERVER_INVALID_ADDRESS "287.0.0.1"
+#define TEST_SERVER_PORT 8123
+#define TEST_SERVER_AVAILABLE_PORT 0
+
+TEST(TCPServer, Positive_Create_Anytime)
+{
+ unsigned short port = TEST_SERVER_PORT;
+ std::unique_ptr<TCP::Server> tcp(std::make_unique<TCP::Server>(TEST_SERVER_ADDRESS, port));
+ ASSERT_NE(tcp, nullptr);
+}
+
+TEST(TCPServer, Negative_Create_Anytime)
+{
+ try {
+ unsigned short port = TEST_SERVER_PORT;
+
+ std::unique_ptr<TCP::Server> tcp(
+ std::make_unique<TCP::Server>(TEST_SERVER_INVALID_ADDRESS, port));
+ ASSERT_EQ(tcp, nullptr);
+ } catch (std::exception &e) {
+ ASSERT_STREQ(e.what(), strerror(EINVAL));
+ }
+}
+
+TEST(TCPServer, Positive_Create_AutoPort_Anytime)
+{
+ unsigned short port = TEST_SERVER_AVAILABLE_PORT;
+ std::unique_ptr<TCP::Server> tcp(std::make_unique<TCP::Server>(TEST_SERVER_ADDRESS, port));
+ ASSERT_NE(tcp, nullptr);
+ ASSERT_NE(port, 0);
+}
+
+TEST(TCPServer, Positive_GetPort_Anytime)
+{
+ unsigned short port = TEST_SERVER_PORT;
+ std::unique_ptr<TCP::Server> tcp(std::make_unique<TCP::Server>(TEST_SERVER_ADDRESS, port));
+ ASSERT_NE(tcp, nullptr);
+ ASSERT_EQ(tcp->GetPort(), TEST_SERVER_PORT);
+}
+
+TEST(TCPServer, Positive_GetHandle_Anytime)
+{
+ unsigned short port = TEST_SERVER_PORT;
+ std::unique_ptr<TCP::Server> tcp(std::make_unique<TCP::Server>(TEST_SERVER_ADDRESS, port));
+ ASSERT_NE(tcp, nullptr);
+ ASSERT_GE(tcp->GetHandle(), 0);
+}
+
+TEST(TCPServer, Positive_GetPort_AutoPort_Anytime)
+{
+ unsigned short port = TEST_SERVER_AVAILABLE_PORT;
+ std::unique_ptr<TCP::Server> tcp(std::make_unique<TCP::Server>(TEST_SERVER_ADDRESS, port));
+ ASSERT_NE(tcp, nullptr);
+ ASSERT_EQ(tcp->GetPort(), port);
+}
+
+TEST(TCPServer, Positive_AcceptPeer_Anytime)
+{
+ std::mutex m;
+ std::condition_variable ready_cv;
+ std::condition_variable connected_cv;
+ bool ready = false;
+ bool connected = false;
+
+ unsigned short serverPort = TEST_SERVER_PORT;
+ std::thread serverThread(
+ [serverPort, &m, &ready, &connected, &ready_cv, &connected_cv](void) mutable -> void {
+ std::unique_ptr<TCP::Server> tcp(
+ std::make_unique<TCP::Server>(TEST_SERVER_ADDRESS, serverPort));
+ {
+ std::lock_guard<std::mutex> lk(m);
+ ready = true;
+ }
+ ready_cv.notify_one();
+
+ std::unique_ptr<TCP> peer = tcp->AcceptPeer();
+ {
+ std::lock_guard<std::mutex> lk(m);
+ connected = !!peer;
+ }
+ connected_cv.notify_one();
+ });
+
+ {
+ std::unique_lock<std::mutex> lk(m);
+ ready_cv.wait(lk, [&ready] { return ready; });
+ std::unique_ptr<TCP> tcp(std::make_unique<TCP>(TEST_SERVER_ADDRESS, serverPort));
+ connected_cv.wait(lk, [&connected] { return connected; });
+ }
+
+ serverThread.join();
+
+ ASSERT_EQ(ready, true);
+ ASSERT_EQ(connected, true);
+}
diff --git a/modules/tcp/tests/TCP_test.cc b/modules/tcp/tests/TCP_test.cc
new file mode 100644
index 0000000..604bd23
--- /dev/null
+++ b/modules/tcp/tests/TCP_test.cc
@@ -0,0 +1,149 @@
+/*
+ * Copyright (c) 2021-2022 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <gtest/gtest.h>
+
+#include <condition_variable>
+#include <cstring>
+#include <functional>
+#include <memory>
+#include <mutex>
+#include <thread>
+
+#include "TCPServer.h"
+
+#define TEST_SERVER_ADDRESS "127.0.0.1"
+#define TEST_SERVER_INVALID_ADDRESS "287.0.0.1"
+#define TEST_SERVER_PORT 8123
+#define TEST_SERVER_AVAILABLE_PORT 0
+#define TEST_BUFFER_SIZE 256
+#define TEST_BUFFER_HELLO "Hello World"
+#define TEST_BUFFER_BYE "Good Bye"
+
+class TCPTest : public testing::Test {
+ protected:
+ void SetUp() override
+ {
+ ready = false;
+ serverPort = TEST_SERVER_PORT;
+ customTest = [](void) {};
+
+ clientThread = std::thread([this](void) mutable -> void {
+ std::unique_lock<std::mutex> lk(m);
+ ready_cv.wait(lk, [this] { return ready; });
+ client = std::make_unique<TCP>(TEST_SERVER_ADDRESS, serverPort);
+
+ customTest();
+ });
+ }
+
+ void RunServer(void)
+ {
+ tcp = std::make_unique<TCP::Server>(TEST_SERVER_ADDRESS, serverPort);
+ {
+ std::lock_guard<std::mutex> lk(m);
+ ready = true;
+ }
+ ready_cv.notify_one();
+
+ peer = tcp->AcceptPeer();
+ }
+
+ void TearDown() override { clientThread.join(); }
+
+ protected:
+ std::mutex m;
+ std::condition_variable ready_cv;
+ bool ready;
+ unsigned short serverPort;
+ std::thread clientThread;
+ std::unique_ptr<TCP::Server> tcp;
+ std::unique_ptr<TCP> peer;
+ std::unique_ptr<TCP> client;
+ std::function<void(void)> customTest;
+};
+
+TEST(TCP, Negative_Create_InvalidPort_Anytime)
+{
+ try {
+ std::unique_ptr<TCP> tcp(
+ std::make_unique<TCP>(TEST_SERVER_ADDRESS, TEST_SERVER_AVAILABLE_PORT));
+ ASSERT_EQ(tcp, nullptr);
+ } catch (std::exception &e) {
+ ASSERT_STREQ(e.what(), strerror(EINVAL));
+ }
+}
+
+TEST(TCP, Negative_Create_InvalidAddress_Anytime)
+{
+ try {
+ std::unique_ptr<TCP> tcp(
+ std::make_unique<TCP>(TEST_SERVER_INVALID_ADDRESS, TEST_SERVER_PORT));
+ ASSERT_EQ(tcp, nullptr);
+ } catch (std::exception &e) {
+ ASSERT_STREQ(e.what(), strerror(EINVAL));
+ }
+}
+
+TEST_F(TCPTest, Positive_GetPeerInfo_Anytime)
+{
+ std::string peerHost;
+ unsigned short peerPort = 0;
+
+ RunServer();
+
+ peer->GetPeerInfo(peerHost, peerPort);
+ ASSERT_STREQ(peerHost.c_str(), TEST_SERVER_ADDRESS);
+ ASSERT_GT(peerPort, 0);
+}
+
+TEST_F(TCPTest, Positive_GetHandle_Anytime)
+{
+ RunServer();
+ int handle = peer->GetHandle();
+ ASSERT_GE(handle, 0);
+}
+
+TEST_F(TCPTest, Positive_GetPort_Anytime)
+{
+ RunServer();
+ unsigned short port = peer->GetPort();
+ ASSERT_GT(port, 0);
+}
+
+TEST_F(TCPTest, Positive_SendRecv_Anytime)
+{
+ char helloBuffer[TEST_BUFFER_SIZE];
+ char byeBuffer[TEST_BUFFER_SIZE];
+
+ customTest = [this, &helloBuffer](void) mutable -> void {
+ size_t szData = sizeof(helloBuffer);
+ client->Recv(static_cast<void *>(helloBuffer), szData);
+
+ szData = sizeof(TEST_BUFFER_BYE);
+ client->Send(TEST_BUFFER_BYE, szData);
+ };
+
+ RunServer();
+
+ size_t szMsg = sizeof(TEST_BUFFER_HELLO);
+ peer->Send(TEST_BUFFER_HELLO, szMsg);
+
+ szMsg = sizeof(byeBuffer);
+ peer->Recv(static_cast<void *>(byeBuffer), szMsg);
+
+ ASSERT_STREQ(helloBuffer, TEST_BUFFER_HELLO);
+ ASSERT_STREQ(byeBuffer, TEST_BUFFER_BYE);
+}
diff --git a/modules/webrtc/CMakeLists.txt b/modules/webrtc/CMakeLists.txt
new file mode 100644
index 0000000..9452b2b
--- /dev/null
+++ b/modules/webrtc/CMakeLists.txt
@@ -0,0 +1,24 @@
+SET(AITT_WEBRTC aitt-transport-webrtc)
+
+INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR})
+
+PKG_CHECK_MODULES(AITT_WEBRTC_NEEDS REQUIRED
+ capi-media-camera
+ capi-media-webrtc
+ json-glib-1.0
+)
+INCLUDE_DIRECTORIES(${AITT_WEBRTC_NEEDS_INCLUDE_DIRS})
+LINK_DIRECTORIES(${AITT_WEBRTC_NEEDS_LIBRARY_DIRS})
+
+FILE(GLOB AITT_WEBRTC_SRC *.cc)
+list(REMOVE_ITEM AITT_WEBRTC_SRC ${CMAKE_CURRENT_SOURCE_DIR}/Module.cc)
+ADD_LIBRARY(WEBRTC_OBJ OBJECT ${AITT_WEBRTC_SRC})
+ADD_LIBRARY(${AITT_WEBRTC} SHARED $<TARGET_OBJECTS:WEBRTC_OBJ> ../main.cc Module.cc)
+TARGET_LINK_LIBRARIES(${AITT_WEBRTC} ${AITT_WEBRTC_NEEDS_LIBRARIES} ${AITT_COMMON})
+TARGET_COMPILE_OPTIONS(${AITT_WEBRTC} PUBLIC ${AITT_WEBRTC_NEEDS_CFLAGS_OTHER})
+
+INSTALL(TARGETS ${AITT_WEBRTC} DESTINATION ${CMAKE_INSTALL_LIBDIR})
+
+IF(BUILD_TESTING)
+ ADD_SUBDIRECTORY(tests)
+ENDIF(BUILD_TESTING)
diff --git a/modules/webrtc/CameraHandler.cc b/modules/webrtc/CameraHandler.cc
new file mode 100644
index 0000000..c3fc8ec
--- /dev/null
+++ b/modules/webrtc/CameraHandler.cc
@@ -0,0 +1,170 @@
+/*
+ * Copyright (c) 2022 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "CameraHandler.h"
+
+#include "aitt_internal.h"
+
+#define RETURN_DEFINED_NAME_AS_STRING(defined_constant) \
+ case defined_constant: \
+ return #defined_constant;
+
+CameraHandler::~CameraHandler(void)
+{
+ if (handle_) {
+ camera_state_e state = CAMERA_STATE_NONE;
+
+ int ret = camera_get_state(handle_, &state);
+ if (ret != CAMERA_ERROR_NONE) {
+ ERR("camera_get_state() Fail(%s)", ErrorToString(ret));
+ }
+
+ if (state == CAMERA_STATE_PREVIEW) {
+ INFO("CameraHandler preview is not stopped (stop)");
+ ret = camera_stop_preview(handle_);
+ if (ret != CAMERA_ERROR_NONE) {
+ ERR("camera_stop_preview() Fail(%s)", ErrorToString(ret));
+ }
+ }
+ }
+
+ if (handle_)
+ camera_destroy(handle_);
+}
+
+int CameraHandler::Init(const MediaPacketPreviewCallback &preview_cb, void *user_data)
+{
+ int ret = camera_create(CAMERA_DEVICE_CAMERA0, &handle_);
+ if (ret != CAMERA_ERROR_NONE) {
+ ERR("camera_create() Fail(%s)", ErrorToString(ret));
+ return -1;
+ }
+ SettingCamera(preview_cb, user_data);
+
+ return 0;
+}
+
+void CameraHandler::SettingCamera(const MediaPacketPreviewCallback &preview_cb, void *user_data)
+{
+ int ret = camera_set_media_packet_preview_cb(handle_, CameraPreviewCB, this);
+ if (ret != CAMERA_ERROR_NONE) {
+ ERR("camera_set_media_packet_preview_cb() Fail(%s)", ErrorToString(ret));
+ return;
+ }
+ media_packet_preview_cb_ = preview_cb;
+ user_data_ = user_data;
+}
+
+void CameraHandler::Deinit(void)
+{
+ if (!handle_) {
+ ERR("Handler is nullptr");
+ return;
+ }
+
+ is_started_ = false;
+ media_packet_preview_cb_ = nullptr;
+ user_data_ = nullptr;
+}
+
+int CameraHandler::StartPreview(void)
+{
+ camera_state_e state;
+ int ret = camera_get_state(handle_, &state);
+ if (ret != CAMERA_ERROR_NONE) {
+ ERR("camera_get_state() Fail(%s)", ErrorToString(ret));
+ return -1;
+ }
+
+ if (state == CAMERA_STATE_PREVIEW) {
+ INFO("Preview is already started");
+ is_started_ = true;
+ return 0;
+ }
+
+ ret = camera_start_preview(handle_);
+ if (ret != CAMERA_ERROR_NONE) {
+ ERR("camera_start_preview() Fail(%s)", ErrorToString(ret));
+ return -1;
+ }
+
+ is_started_ = true;
+
+ return 0;
+}
+
+int CameraHandler::StopPreview(void)
+{
+ RETV_IF(handle_ == nullptr, -1);
+ is_started_ = false;
+
+ return 0;
+}
+
+void CameraHandler::CameraPreviewCB(media_packet_h media_packet, void *user_data)
+{
+ auto camera_handler = static_cast<CameraHandler *>(user_data);
+ if (!camera_handler) {
+ ERR("Invalid user_data");
+ return;
+ }
+
+ if (!camera_handler->is_started_) {
+ ERR("Preveiw is not started yet");
+ return;
+ }
+
+ if (!camera_handler->media_packet_preview_cb_) {
+ ERR("Preveiw cb is not set");
+ return;
+ }
+
+ camera_handler->media_packet_preview_cb_(media_packet, camera_handler->user_data_);
+}
+
+const char *CameraHandler::ErrorToString(const int error)
+{
+ switch (error) {
+ RETURN_DEFINED_NAME_AS_STRING(CAMERA_ERROR_NONE)
+ RETURN_DEFINED_NAME_AS_STRING(CAMERA_ERROR_INVALID_PARAMETER)
+ RETURN_DEFINED_NAME_AS_STRING(CAMERA_ERROR_INVALID_STATE)
+ RETURN_DEFINED_NAME_AS_STRING(CAMERA_ERROR_OUT_OF_MEMORY)
+ RETURN_DEFINED_NAME_AS_STRING(CAMERA_ERROR_DEVICE)
+ RETURN_DEFINED_NAME_AS_STRING(CAMERA_ERROR_INVALID_OPERATION)
+ RETURN_DEFINED_NAME_AS_STRING(CAMERA_ERROR_SECURITY_RESTRICTED)
+ RETURN_DEFINED_NAME_AS_STRING(CAMERA_ERROR_DEVICE_BUSY)
+ RETURN_DEFINED_NAME_AS_STRING(CAMERA_ERROR_DEVICE_NOT_FOUND)
+ RETURN_DEFINED_NAME_AS_STRING(CAMERA_ERROR_ESD)
+ RETURN_DEFINED_NAME_AS_STRING(CAMERA_ERROR_PERMISSION_DENIED)
+ RETURN_DEFINED_NAME_AS_STRING(CAMERA_ERROR_NOT_SUPPORTED)
+ RETURN_DEFINED_NAME_AS_STRING(CAMERA_ERROR_RESOURCE_CONFLICT)
+ RETURN_DEFINED_NAME_AS_STRING(CAMERA_ERROR_SERVICE_DISCONNECTED)
+ }
+
+ return "Unknown error";
+}
+
+const char *CameraHandler::StateToString(const camera_state_e state)
+{
+ switch (state) {
+ RETURN_DEFINED_NAME_AS_STRING(CAMERA_STATE_NONE)
+ RETURN_DEFINED_NAME_AS_STRING(CAMERA_STATE_CREATED)
+ RETURN_DEFINED_NAME_AS_STRING(CAMERA_STATE_PREVIEW)
+ RETURN_DEFINED_NAME_AS_STRING(CAMERA_STATE_CAPTURING)
+ RETURN_DEFINED_NAME_AS_STRING(CAMERA_STATE_CAPTURED)
+ }
+
+ return "Unknown state";
+}
diff --git a/modules/webrtc/CameraHandler.h b/modules/webrtc/CameraHandler.h
new file mode 100644
index 0000000..5c44828
--- /dev/null
+++ b/modules/webrtc/CameraHandler.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2022 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+/*
+ * Copyright (c) 2022 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+#include <camera.h>
+
+#include <functional>
+
+class CameraHandler {
+ public:
+ using MediaPacketPreviewCallback = std::function<void(media_packet_h, void *)>;
+
+ ~CameraHandler();
+ int Init(const MediaPacketPreviewCallback &preview_cb, void *user_data);
+ void Deinit(void);
+ int StartPreview(void);
+ int StopPreview(void);
+
+ static const char *ErrorToString(const int error);
+ static const char *StateToString(const camera_state_e state);
+
+ private:
+ void SettingCamera(const MediaPacketPreviewCallback &preview_cb, void *user_data);
+ static void CameraPreviewCB(media_packet_h media_packet, void *user_data);
+
+ camera_h handle_;
+ bool is_started_;
+ MediaPacketPreviewCallback media_packet_preview_cb_;
+ void *user_data_;
+};
diff --git a/modules/webrtc/Config.h b/modules/webrtc/Config.h
new file mode 100644
index 0000000..63dbd4b
--- /dev/null
+++ b/modules/webrtc/Config.h
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2022 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <string>
+
+class Config {
+ public:
+ Config() : disable_ssl_(false), broker_port_(0), user_data_length_(0) {};
+ Config(const std::string &id, const std::string &broker_ip, int broker_port,
+ const std::string &room_id, const std::string &source_id = std::string(""))
+ : local_id_(id),
+ room_id_(room_id),
+ source_id_(source_id),
+ disable_ssl_(false),
+ broker_ip_(broker_ip),
+ broker_port_(broker_port),
+ user_data_length_(0){};
+ std::string GetLocalId(void) const { return local_id_; };
+ void SetLocalId(const std::string &local_id) { local_id_ = local_id; };
+ std::string GetRoomId(void) const { return room_id_; };
+ void SetRoomId(const std::string &room_id) { room_id_ = room_id; };
+ std::string GetSourceId(void) const { return source_id_; };
+ void SetSourceId(const std::string &source_id) { source_id_ = source_id; };
+ void SetSignalingServerUrl(const std::string &signaling_server_url)
+ {
+ signaling_server_url_ = signaling_server_url;
+ };
+ std::string GetSignalingServerUrl(void) const { return signaling_server_url_; };
+ void SetDisableSSl(bool disable_ssl) { disable_ssl_ = disable_ssl; };
+ bool GetDisableSSl(void) const { return disable_ssl_; };
+ std::string GetBrokerIp(void) const { return broker_ip_; };
+ void SetBrokerIp(const std::string &broker_ip) { broker_ip_ = broker_ip; };
+ int GetBrokerPort(void) const { return broker_port_; };
+ void SetBrokerPort(int port) { broker_port_ = port; };
+ unsigned int GetUserDataLength(void) const { return user_data_length_; };
+ void SetUserDataLength(unsigned int user_data_length) { user_data_length_ = user_data_length; };
+
+ private:
+ std::string local_id_;
+ std::string room_id_;
+ std::string source_id_;
+ std::string signaling_server_url_;
+ bool disable_ssl_;
+ std::string broker_ip_;
+ int broker_port_;
+ unsigned int user_data_length_;
+};
diff --git a/modules/webrtc/IfaceServer.h b/modules/webrtc/IfaceServer.h
new file mode 100644
index 0000000..ad6d36d
--- /dev/null
+++ b/modules/webrtc/IfaceServer.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2022 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <functional>
+#include <vector>
+
+class IfaceServer {
+ public:
+ enum class ConnectionState {
+ Disconnected,
+ Connecting,
+ Connected,
+ Registering,
+ Registered,
+ };
+
+ virtual ~IfaceServer(){};
+ virtual void SetConnectionStateChangedCb(
+ std::function<void(ConnectionState)> connection_state_changed_cb) = 0;
+ virtual void UnsetConnectionStateChangedCb(void) = 0;
+ virtual int Connect(void) = 0;
+ virtual int Disconnect(void) = 0;
+ virtual bool IsConnected(void) = 0;
+ virtual int SendMessage(const std::string &peer_id, const std::string &message) = 0;
+};
diff --git a/modules/webrtc/Module.cc b/modules/webrtc/Module.cc
new file mode 100644
index 0000000..3c9e4f8
--- /dev/null
+++ b/modules/webrtc/Module.cc
@@ -0,0 +1,127 @@
+/*
+ * Copyright (c) 2022 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "Module.h"
+
+#include <flatbuffers/flexbuffers.h>
+
+#include "Config.h"
+#include "aitt_internal.h"
+
+Module::Module(const std::string &ip, AittDiscovery &discovery) : AittTransport(discovery)
+{
+}
+
+Module::~Module(void)
+{
+}
+
+void Module::Publish(const std::string &topic, const void *data, const size_t datalen,
+ const std::string &correlation, AittQoS qos, bool retain)
+{
+ // TODO
+}
+
+void Module::Publish(const std::string &topic, const void *data, const size_t datalen, AittQoS qos,
+ bool retain)
+{
+ std::lock_guard<std::mutex> publish_table_lock(publish_table_lock_);
+
+ auto config = BuildConfigFromFb(data, datalen);
+ if (config.GetUserDataLength()) {
+ publish_table_[topic] =
+ std::make_shared<PublishStream>(topic, BuildConfigFromFb(data, datalen));
+
+ publish_table_[topic]->Start();
+ } else {
+ auto publish_table_itr = publish_table_.find(topic);
+ if (publish_table_itr == publish_table_.end()) {
+ ERR("%s not found", topic.c_str());
+ return;
+ }
+ auto publish_stream = publish_table_itr->second;
+ publish_stream->Stop();
+ publish_table_.erase(publish_table_itr);
+ }
+}
+
+void *Module::Subscribe(const std::string &topic, const AittTransport::SubscribeCallback &cb,
+ void *cbdata, AittQoS qos)
+{
+ return nullptr;
+}
+
+void *Module::Subscribe(const std::string &topic, const AittTransport::SubscribeCallback &cb,
+ const void *data, const size_t datalen, void *cbdata, AittQoS qos)
+{
+ std::lock_guard<std::mutex> subscribe_table_lock(subscribe_table_lock_);
+
+ subscribe_table_[topic] =
+ std::make_shared<SubscribeStream>(topic, BuildConfigFromFb(data, datalen));
+
+ subscribe_table_[topic]->Start(qos == AITT_QOS_EXACTLY_ONCE, cbdata);
+
+ return subscribe_table_[topic].get();
+}
+
+Config Module::BuildConfigFromFb(const void *data, const size_t data_size)
+{
+ Config config;
+ auto webrtc_configs =
+ flexbuffers::GetRoot(static_cast<const uint8_t *>(data), data_size).AsMap();
+ auto webrtc_config_keys = webrtc_configs.Keys();
+ for (size_t idx = 0; idx < webrtc_config_keys.size(); ++idx) {
+ std::string key = webrtc_config_keys[idx].AsString().c_str();
+
+ if (key.compare("Id") == 0)
+ config.SetLocalId(webrtc_configs[key].AsString().c_str());
+ else if (key.compare("RoomId") == 0)
+ config.SetRoomId(webrtc_configs[key].AsString().c_str());
+ else if (key.compare("SourceId") == 0)
+ config.SetSourceId(webrtc_configs[key].AsString().c_str());
+ else if (key.compare("BrokerIp") == 0)
+ config.SetBrokerIp(webrtc_configs[key].AsString().c_str());
+ else if (key.compare("BrokerPort") == 0)
+ config.SetBrokerPort(webrtc_configs[key].AsInt32());
+ else if (key.compare("UserDataLength") == 0)
+ config.SetUserDataLength(webrtc_configs[key].AsUInt32());
+ else {
+ printf("Not supported key name: %s\n", key.c_str());
+ }
+ }
+
+ return config;
+}
+
+void *Module::Unsubscribe(void *handlePtr)
+{
+ void *ret = nullptr;
+ std::string topic;
+ std::lock_guard<std::mutex> subscribe_table_lock(subscribe_table_lock_);
+ for (auto itr = subscribe_table_.begin(); itr != subscribe_table_.end(); ++itr) {
+ if (itr->second.get() == handlePtr) {
+ auto topic = itr->first;
+ break;
+ }
+ }
+
+ if (topic.size() != 0) {
+ ret = subscribe_table_[topic]->Stop();
+ subscribe_table_.erase(topic);
+ }
+
+ return ret;
+}
diff --git a/modules/webrtc/Module.h b/modules/webrtc/Module.h
new file mode 100644
index 0000000..ca31eb8
--- /dev/null
+++ b/modules/webrtc/Module.h
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2022 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <AittTransport.h>
+#include <MainLoopHandler.h>
+
+#include <map>
+#include <memory>
+#include <mutex>
+#include <set>
+#include <string>
+#include <thread>
+
+#include "PublishStream.h"
+#include "SubscribeStream.h"
+
+using AittTransport = aitt::AittTransport;
+using MainLoopHandler = aitt::MainLoopHandler;
+using AittDiscovery = aitt::AittDiscovery;
+
+class Module : public AittTransport {
+ public:
+ explicit Module(const std::string &ip, AittDiscovery &discovery);
+ virtual ~Module(void);
+
+ // TODO: How about regarding topic as service name?
+ void Publish(const std::string &topic, const void *data, const size_t datalen,
+ const std::string &correlation, AittQoS qos = AITT_QOS_AT_MOST_ONCE,
+ bool retain = false) override;
+
+ void Publish(const std::string &topic, const void *data, const size_t datalen,
+ AittQoS qos = AITT_QOS_AT_MOST_ONCE, bool retain = false) override;
+
+ // TODO: How about regarding topic as service name?
+ void *Subscribe(const std::string &topic, const AittTransport::SubscribeCallback &cb,
+ void *cbdata = nullptr, AittQoS qos = AITT_QOS_AT_MOST_ONCE) override;
+
+ void *Subscribe(const std::string &topic, const AittTransport::SubscribeCallback &cb,
+ const void *data, const size_t datalen, void *cbdata = nullptr,
+ AittQoS qos = AITT_QOS_AT_MOST_ONCE) override;
+
+ void *Unsubscribe(void *handle) override;
+
+ private:
+ Config BuildConfigFromFb(const void *data, const size_t data_size);
+
+ std::map<std::string, std::shared_ptr<PublishStream>> publish_table_;
+ std::mutex publish_table_lock_;
+ std::map<std::string, std::shared_ptr<SubscribeStream>> subscribe_table_;
+ std::mutex subscribe_table_lock_;
+};
diff --git a/modules/webrtc/MqttServer.cc b/modules/webrtc/MqttServer.cc
new file mode 100644
index 0000000..70a07ed
--- /dev/null
+++ b/modules/webrtc/MqttServer.cc
@@ -0,0 +1,289 @@
+/*
+ * Copyright (c) 2022 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "MqttServer.h"
+
+#include "aitt_internal.h"
+
+#define MQTT_HANDLER_MSG_QOS 1
+#define MQTT_HANDLER_MGMT_QOS 2
+
+MqttServer::MqttServer(const Config &config) : mq(config.GetLocalId(), true)
+{
+ broker_ip_ = config.GetBrokerIp();
+ broker_port_ = config.GetBrokerPort();
+ id_ = config.GetLocalId();
+ room_id_ = config.GetRoomId();
+ source_id_ = config.GetSourceId();
+ is_publisher_ = (id_ == source_id_);
+
+ DBG("ID[%s] BROKER IP[%s] BROKER PORT [%d] ROOM[%s] %s", id_.c_str(), broker_ip_.c_str(),
+ broker_port_, room_id_.c_str(), is_publisher_ ? "Publisher" : "Subscriber");
+
+ mq.SetConnectionCallback(std::bind(&MqttServer::ConnectCallBack, this, std::placeholders::_1));
+}
+
+MqttServer::~MqttServer()
+{
+ // Prevent to call below callbacks after destructoring
+ connection_state_changed_cb_ = nullptr;
+ room_message_arrived_cb_ = nullptr;
+}
+
+void MqttServer::SetConnectionState(ConnectionState state)
+{
+ connection_state_ = state;
+ if (connection_state_changed_cb_)
+ connection_state_changed_cb_(state);
+}
+
+void MqttServer::ConnectCallBack(int status)
+{
+ if (status == AITT_CONNECTED)
+ OnConnect();
+ else
+ OnDisconnect();
+}
+
+void MqttServer::OnConnect()
+{
+ INFO("Connected to signalling server");
+
+ // Sometimes it seems that broker is silently disconnected/reconnected
+ if (GetConnectionState() != ConnectionState::Connecting) {
+ ERR("Invalid status");
+ return;
+ }
+
+ SetConnectionState(ConnectionState::Connected);
+ SetConnectionState(ConnectionState::Registering);
+ try {
+ RegisterWithServer();
+ } catch (const std::runtime_error &e) {
+ ERR("%s", e.what());
+ SetConnectionState(ConnectionState::Connected);
+ }
+}
+
+void MqttServer::OnDisconnect()
+{
+ INFO("mosquitto disconnected");
+
+ SetConnectionState(ConnectionState::Disconnected);
+ // TODO
+}
+
+void MqttServer::RegisterWithServer(void)
+{
+ if (connection_state_ != IfaceServer::ConnectionState::Registering) {
+ ERR("Invaild status(%d)", (int)connection_state_);
+ throw std::runtime_error("Invalid status");
+ }
+
+ // Notify Who is source?
+ std::string source_topic = room_id_ + std::string("/source");
+ if (is_publisher_) {
+ mq.Publish(source_topic, id_.c_str(), id_.size(), AITT_QOS_EXACTLY_ONCE, true);
+ SetConnectionState(ConnectionState::Registered);
+ } else {
+ mq.Subscribe(source_topic,
+ std::bind(&MqttServer::HandleSourceTopic, this, std::placeholders::_1,
+ std::placeholders::_2, std::placeholders::_3, std::placeholders::_4,
+ std::placeholders::_5),
+ nullptr, AITT_QOS_EXACTLY_ONCE);
+ }
+}
+
+void MqttServer::HandleSourceTopic(aitt::MSG *msg, const std::string &topic, const void *data,
+ const size_t datalen, void *user_data)
+{
+ INFO("Source topic");
+ if (connection_state_ != IfaceServer::ConnectionState::Registering) {
+ ERR("Invaild status(%d)", (int)connection_state_);
+ return;
+ }
+
+ if (is_publisher_) {
+ ERR("Ignore");
+ } else {
+ std::string message(static_cast<const char *>(data), datalen);
+ INFO("Set source ID %s", message.c_str());
+ SetSourceId(message);
+ SetConnectionState(ConnectionState::Registered);
+ }
+}
+
+bool MqttServer::IsConnected(void)
+{
+ INFO("%s", __func__);
+
+ return connection_state_ == IfaceServer::ConnectionState::Registered;
+}
+
+int MqttServer::Connect(void)
+{
+ std::string will_message = std::string("ROOM_PEER_LEFT ") + id_;
+ mq.SetWillInfo(room_id_, will_message.c_str(), will_message.size(), AITT_QOS_EXACTLY_ONCE,
+ false);
+
+ SetConnectionState(ConnectionState::Connecting);
+ mq.Connect(broker_ip_, broker_port_, std::string(), std::string());
+
+ return 0;
+}
+
+int MqttServer::Disconnect(void)
+{
+ if (is_publisher_) {
+ INFO("remove retained");
+ std::string source_topic = room_id_ + std::string("/source");
+ mq.Publish(source_topic, nullptr, 0, AITT_QOS_AT_LEAST_ONCE, true);
+ }
+
+ std::string left_message = std::string("ROOM_PEER_LEFT ") + id_;
+ mq.Publish(room_id_, left_message.c_str(), left_message.size(), AITT_QOS_AT_LEAST_ONCE, false);
+
+ mq.Disconnect();
+
+ room_id_ = std::string("");
+
+ SetConnectionState(ConnectionState::Disconnected);
+ return 0;
+}
+
+int MqttServer::SendMessage(const std::string &peer_id, const std::string &msg)
+{
+ if (room_id_.empty()) {
+ ERR("Invaild status");
+ return -1;
+ }
+ if (peer_id.size() == 0 || msg.size() == 0) {
+ ERR("Invalid parameter");
+ return -1;
+ }
+
+ std::string receiver_topic = room_id_ + std::string("/") + peer_id;
+ std::string server_formatted_msg = "ROOM_PEER_MSG " + id_ + " " + msg;
+ mq.Publish(receiver_topic, server_formatted_msg.c_str(), server_formatted_msg.size(),
+ AITT_QOS_AT_LEAST_ONCE);
+
+ return 0;
+}
+
+std::string MqttServer::GetConnectionStateStr(ConnectionState state)
+{
+ std::string state_str;
+ switch (state) {
+ case IfaceServer::ConnectionState::Disconnected: {
+ state_str = std::string("Disconnected");
+ break;
+ }
+ case IfaceServer::ConnectionState::Connecting: {
+ state_str = std::string("Connecting");
+ break;
+ }
+ case IfaceServer::ConnectionState::Connected: {
+ state_str = std::string("Connected");
+ break;
+ }
+ case IfaceServer::ConnectionState::Registering: {
+ state_str = std::string("Registering");
+ break;
+ }
+ case IfaceServer::ConnectionState::Registered: {
+ state_str = std::string("Registered");
+ break;
+ }
+ }
+
+ return state_str;
+}
+
+void MqttServer::JoinRoom(const std::string &room_id)
+{
+ if (room_id.empty() || room_id != room_id_) {
+ ERR("Invaild room id");
+ throw std::runtime_error(std::string("Invalid room_id"));
+ }
+
+ // Subscribe PEER_JOIN PEER_LEFT
+ mq.Subscribe(room_id_,
+ std::bind(&MqttServer::HandleRoomTopic, this, std::placeholders::_1,
+ std::placeholders::_2, std::placeholders::_3, std::placeholders::_4,
+ std::placeholders::_5),
+ nullptr, AITT_QOS_EXACTLY_ONCE);
+
+ // Subscribe PEER_MSG
+ std::string receiving_topic = room_id + std::string("/") + id_;
+ mq.Subscribe(receiving_topic,
+ std::bind(&MqttServer::HandleMessageTopic, this, std::placeholders::_1,
+ std::placeholders::_2, std::placeholders::_3, std::placeholders::_4,
+ std::placeholders::_5),
+ nullptr, AITT_QOS_AT_LEAST_ONCE);
+
+ INFO("Subscribe room topics");
+
+ if (!is_publisher_) {
+ std::string join_message = std::string("ROOM_PEER_JOINED ") + id_;
+ mq.Publish(room_id_, join_message.c_str(), join_message.size(), AITT_QOS_EXACTLY_ONCE);
+ }
+}
+
+void MqttServer::HandleRoomTopic(aitt::MSG *msg, const std::string &topic, const void *data,
+ const size_t datalen, void *user_data)
+{
+ std::string message(static_cast<const char *>(data), datalen);
+ INFO("Room topic(%s, %s)", topic.c_str(), message.c_str());
+
+ std::string peer_id;
+ if (message.compare(0, 16, "ROOM_PEER_JOINED") == 0) {
+ peer_id = message.substr(17, std::string::npos);
+ } else if (message.compare(0, 14, "ROOM_PEER_LEFT") == 0) {
+ peer_id = message.substr(15, std::string::npos);
+ } else {
+ ERR("Invalid type of Room message %s", message.c_str());
+ return;
+ }
+
+ if (peer_id == id_) {
+ ERR("ignore");
+ return;
+ }
+
+ if (is_publisher_) {
+ if (room_message_arrived_cb_)
+ room_message_arrived_cb_(message);
+ } else {
+ // TODO: ADHOC, will handle this by room
+ if (peer_id != source_id_) {
+ ERR("peer(%s) is Not source(%s)", peer_id.c_str(), source_id_.c_str());
+ return;
+ }
+
+ if (room_message_arrived_cb_)
+ room_message_arrived_cb_(message);
+ }
+}
+
+void MqttServer::HandleMessageTopic(aitt::MSG *msg, const std::string &topic, const void *data,
+ const size_t datalen, void *user_data)
+{
+ INFO("Message topic");
+ std::string message(static_cast<const char *>(data), datalen);
+
+ if (room_message_arrived_cb_)
+ room_message_arrived_cb_(message);
+}
diff --git a/modules/webrtc/MqttServer.h b/modules/webrtc/MqttServer.h
new file mode 100644
index 0000000..7f93192
--- /dev/null
+++ b/modules/webrtc/MqttServer.h
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) 2022 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <MQ.h>
+
+#include "Config.h"
+#include "IfaceServer.h"
+
+class MqttServer : public IfaceServer {
+ public:
+ explicit MqttServer(const Config &config);
+ virtual ~MqttServer();
+
+ void SetConnectionStateChangedCb(
+ std::function<void(ConnectionState)> connection_state_changed_cb) override
+ {
+ connection_state_changed_cb_ = connection_state_changed_cb;
+ };
+ void UnsetConnectionStateChangedCb(void) override { connection_state_changed_cb_ = nullptr; };
+
+ bool IsConnected(void) override;
+ int Connect(void) override;
+ int Disconnect(void) override;
+ int SendMessage(const std::string &peer_id, const std::string &msg) override;
+
+ static std::string GetConnectionStateStr(ConnectionState state);
+ void RegisterWithServer(void);
+ void JoinRoom(const std::string &room_id);
+ void SetConnectionState(ConnectionState state);
+ ConnectionState GetConnectionState(void) const { return connection_state_; };
+ std::string GetId(void) const { return id_; };
+ std::string GetSourceId(void) const { return source_id_; };
+ void SetSourceId(const std::string &source_id) { source_id_ = source_id; };
+
+ void SetRoomMessageArrivedCb(std::function<void(const std::string &)> room_message_arrived_cb)
+ {
+ room_message_arrived_cb_ = room_message_arrived_cb;
+ };
+ void UnsetRoomMessageArrivedCb(void) { room_message_arrived_cb_ = nullptr; }
+
+ private:
+ static void MessageCallback(mosquitto *handle, void *mqtt_server, const mosquitto_message *msg,
+ const mosquitto_property *props);
+ void OnConnect();
+ void OnDisconnect();
+ void ConnectCallBack(int status);
+ void HandleRoomTopic(aitt::MSG *msg, const std::string &topic, const void *data,
+ const size_t datalen, void *user_data);
+ void HandleSourceTopic(aitt::MSG *msg, const std::string &topic, const void *data,
+ const size_t datalen, void *user_data);
+ void HandleMessageTopic(aitt::MSG *msg, const std::string &topic, const void *data,
+ const size_t datalen, void *user_data);
+
+ std::string broker_ip_;
+ int broker_port_;
+ std::string id_;
+ std::string room_id_;
+ std::string source_id_;
+ bool is_publisher_;
+ aitt::MQ mq;
+
+ ConnectionState connection_state_;
+ std::function<void(ConnectionState)> connection_state_changed_cb_;
+ std::function<void(const std::string &)> room_message_arrived_cb_;
+};
diff --git a/modules/webrtc/PublishStream.cc b/modules/webrtc/PublishStream.cc
new file mode 100644
index 0000000..f93ecea
--- /dev/null
+++ b/modules/webrtc/PublishStream.cc
@@ -0,0 +1,210 @@
+/*
+ * Copyright (c) 2022 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "PublishStream.h"
+
+#include <sys/time.h>
+
+#include "WebRtcEventHandler.h"
+#include "aitt_internal.h"
+
+PublishStream::~PublishStream()
+{
+ // TODO: clear resources
+}
+
+void PublishStream::Start(void)
+{
+ PrepareStream();
+ SetSignalingServerCallbacks();
+ SetRoomCallbacks();
+}
+
+void PublishStream::PrepareStream(void)
+{
+ std::lock_guard<std::mutex> prepared_stream_lock(prepared_stream_lock_);
+ prepared_stream_ = std::make_shared<WebRtcStream>();
+ prepared_stream_->Create(true, false);
+ prepared_stream_->AttachCameraSource();
+ auto on_stream_state_changed_prepared_cb =
+ std::bind(OnStreamStateChangedPrepared, std::placeholders::_1, std::ref(*this));
+ prepared_stream_->GetEventHandler().SetOnStateChangedCb(on_stream_state_changed_prepared_cb);
+ prepared_stream_->Start();
+}
+
+void PublishStream::OnStreamStateChangedPrepared(WebRtcState::Stream state, PublishStream &stream)
+{
+ ERR("%s", __func__);
+ if (state == WebRtcState::Stream::NEGOTIATING) {
+ auto on_offer_created_prepared_cb =
+ std::bind(OnOfferCreatedPrepared, std::placeholders::_1, std::ref(stream));
+ stream.prepared_stream_->CreateOfferAsync(on_offer_created_prepared_cb);
+ }
+}
+
+void PublishStream::OnOfferCreatedPrepared(std::string sdp, PublishStream &stream)
+{
+ ERR("%s", __func__);
+
+ stream.prepared_stream_->SetPreparedLocalDescription(sdp);
+ stream.prepared_stream_->SetLocalDescription(sdp);
+ try {
+ stream.server_->Connect();
+ } catch (const std::exception &e) {
+ ERR("Failed to start Publish stream %s", e.what());
+ }
+}
+
+void PublishStream::SetSignalingServerCallbacks(void)
+{
+ auto on_signaling_server_connection_state_changed =
+ std::bind(OnSignalingServerConnectionStateChanged, std::placeholders::_1,
+ std::ref(*room_), std::ref(*server_));
+
+ server_->SetConnectionStateChangedCb(on_signaling_server_connection_state_changed);
+
+ auto on_room_message_arrived =
+ std::bind(OnRoomMessageArrived, std::placeholders::_1, std::ref(*room_));
+
+ server_->SetRoomMessageArrivedCb(on_room_message_arrived);
+}
+
+void PublishStream::OnSignalingServerConnectionStateChanged(IfaceServer::ConnectionState state,
+ WebRtcRoom &room, MqttServer &server)
+{
+ DBG("current state [%s]", MqttServer::GetConnectionStateStr(state).c_str());
+
+ if (state == IfaceServer::ConnectionState::Disconnected) {
+ ; // TODO: what to do when server is disconnected?
+ } else if (state == IfaceServer::ConnectionState::Registered) {
+ server.JoinRoom(room.getId());
+ }
+}
+
+void PublishStream::OnRoomMessageArrived(const std::string &message, WebRtcRoom &room)
+{
+ room.handleMessage(message);
+}
+
+void PublishStream::SetRoomCallbacks()
+{
+ auto on_room_joined = std::bind(OnRoomJoined, std::ref(*this));
+
+ room_->SetRoomJoinedCb(on_room_joined);
+
+ auto on_peer_joined = std::bind(OnPeerJoined, std::placeholders::_1, std::ref(*this));
+ room_->SetPeerJoinedCb(on_peer_joined);
+
+ auto on_peer_left = std::bind(OnPeerLeft, std::placeholders::_1, std::ref(*room_));
+ room_->SetPeerLeftCb(on_peer_left);
+}
+
+void PublishStream::OnRoomJoined(PublishStream &publish_stream)
+{
+ // TODO: Notify Room Joined?
+ DBG("%s on %p", __func__, &publish_stream);
+}
+
+void PublishStream::OnPeerJoined(const std::string &peer_id, PublishStream &publish_stream)
+{
+ DBG("%s [%s]", __func__, peer_id.c_str());
+ if (!publish_stream.room_->AddPeer(peer_id)) {
+ ERR("Failed to add peer");
+ return;
+ }
+
+ try {
+ WebRtcPeer &peer = publish_stream.room_->GetPeer(peer_id);
+
+ std::unique_lock<std::mutex> prepared_stream_lock(publish_stream.prepared_stream_lock_);
+ auto prepared_stream = publish_stream.prepared_stream_;
+ publish_stream.prepared_stream_ = nullptr;
+ prepared_stream_lock.unlock();
+
+ try {
+ peer.SetWebRtcStream(prepared_stream);
+ publish_stream.SetWebRtcStreamCallbacks(peer);
+ publish_stream.server_->SendMessage(peer.getId(),
+ peer.GetWebRtcStream()->GetPreparedLocalDescription());
+ prepared_stream->SetPreparedLocalDescription("");
+ } catch (std::exception &e) {
+ ERR("Failed to start stream for peer %s", e.what());
+ }
+ // TODO why we can't prepare more sources?
+
+ } catch (std::exception &e) {
+ ERR("Wired %s", e.what());
+ }
+}
+
+void PublishStream::SetWebRtcStreamCallbacks(WebRtcPeer &peer)
+{
+ // TODO: set more webrtc callbacks
+ WebRtcEventHandler event_handlers;
+ auto on_stream_state_changed_cb = std::bind(OnStreamStateChanged, std::placeholders::_1,
+ std::ref(peer), std::ref(*server_));
+ event_handlers.SetOnStateChangedCb(on_stream_state_changed_cb);
+
+ auto on_signaling_state_notify_cb = std::bind(OnSignalingStateNotify, std::placeholders::_1,
+ std::ref(peer), std::ref(*server_));
+ event_handlers.SetOnSignalingStateNotifyCb(on_signaling_state_notify_cb);
+
+ auto on_ice_connection_state_notify = std::bind(OnIceConnectionStateNotify,
+ std::placeholders::_1, std::ref(peer), std::ref(*server_));
+ event_handlers.SetOnIceConnectionStateNotifyCb(on_ice_connection_state_notify);
+
+ peer.GetWebRtcStream()->SetEventHandler(event_handlers);
+}
+
+void PublishStream::OnStreamStateChanged(WebRtcState::Stream state, WebRtcPeer &peer,
+ MqttServer &server)
+{
+ ERR("%s for %s", __func__, peer.getId().c_str());
+}
+
+void PublishStream::OnSignalingStateNotify(WebRtcState::Signaling state, WebRtcPeer &peer,
+ MqttServer &server)
+{
+ ERR("Singaling State: %s", WebRtcState::SignalingToStr(state).c_str());
+ if (state == WebRtcState::Signaling::STABLE) {
+ auto ice_candidates = peer.GetWebRtcStream()->GetIceCandidates();
+ for (const auto &candidate : ice_candidates)
+ server.SendMessage(peer.getId(), candidate);
+ }
+}
+
+void PublishStream::OnIceConnectionStateNotify(WebRtcState::IceConnection state, WebRtcPeer &peer,
+ MqttServer &server)
+{
+ ERR("IceConnection State: %s", WebRtcState::IceConnectionToStr(state).c_str());
+}
+
+void PublishStream::OnPeerLeft(const std::string &peer_id, WebRtcRoom &room)
+{
+ DBG("%s [%s]", __func__, peer_id.c_str());
+ if (!room.RemovePeer(peer_id))
+ ERR("Failed to remove peer");
+}
+
+void PublishStream::Stop(void)
+{
+ try {
+ server_->Disconnect();
+ } catch (const std::exception &e) {
+ ERR("Failed to disconnect server %s", e.what());
+ }
+
+ room_->ClearPeers();
+}
diff --git a/modules/webrtc/PublishStream.h b/modules/webrtc/PublishStream.h
new file mode 100644
index 0000000..1805528
--- /dev/null
+++ b/modules/webrtc/PublishStream.h
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2022 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <memory>
+#include <mutex>
+#include <string>
+
+#include "Config.h"
+#include "MqttServer.h"
+#include "WebRtcRoom.h"
+#include "WebRtcStream.h"
+
+class PublishStream {
+ // TODO: Notify & get status
+ public:
+ PublishStream() = delete;
+ PublishStream(const std::string &topic, const Config &config)
+ : topic_(topic),
+ config_(config),
+ server_(std::make_shared<MqttServer>(config)),
+ room_(std::make_shared<WebRtcRoom>(config.GetRoomId())),
+ prepared_stream_(nullptr){};
+ ~PublishStream();
+
+ void Start(void);
+ void Stop(void);
+ void SetSignalingServerCallbacks(void);
+ void SetRoomCallbacks(void);
+ void SetWebRtcStreamCallbacks(WebRtcPeer &peer);
+ void PrepareStream(void);
+
+ private:
+ static void OnStreamStateChangedPrepared(WebRtcState::Stream state, PublishStream &stream);
+ static void OnOfferCreatedPrepared(std::string sdp, PublishStream &stream);
+ static void OnSignalingServerConnectionStateChanged(IfaceServer::ConnectionState state,
+ WebRtcRoom &room, MqttServer &server);
+ static void OnRoomMessageArrived(const std::string &message, WebRtcRoom &room);
+ static void OnRoomJoined(PublishStream &publish_stream);
+ static void OnPeerJoined(const std::string &peer_id, PublishStream &publish_stream);
+ static void OnPeerLeft(const std::string &peer_id, WebRtcRoom &room);
+ static void OnStreamStateChanged(WebRtcState::Stream state, WebRtcPeer &peer,
+ MqttServer &server);
+
+ static void OnSignalingStateNotify(WebRtcState::Signaling state, WebRtcPeer &peer,
+ MqttServer &server);
+ static void OnIceConnectionStateNotify(WebRtcState::IceConnection state, WebRtcPeer &peer,
+ MqttServer &server);
+
+ private:
+ std::string topic_;
+ Config config_;
+ std::shared_ptr<MqttServer> server_;
+ std::shared_ptr<WebRtcRoom> room_;
+ std::mutex prepared_stream_lock_;
+ std::shared_ptr<WebRtcStream> prepared_stream_;
+};
diff --git a/modules/webrtc/SubscribeStream.cc b/modules/webrtc/SubscribeStream.cc
new file mode 100644
index 0000000..841cfa6
--- /dev/null
+++ b/modules/webrtc/SubscribeStream.cc
@@ -0,0 +1,202 @@
+/*
+ * Copyright (c) 2022 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "SubscribeStream.h"
+
+#include "WebRtcEventHandler.h"
+#include "aitt_internal.h"
+
+SubscribeStream::~SubscribeStream()
+{
+ // TODO Clear resources
+}
+
+void SubscribeStream::Start(bool need_display, void *display_object)
+{
+ display_object_ = display_object;
+ is_track_added_ = need_display;
+ SetSignalingServerCallbacks();
+ SetRoomCallbacks();
+ try {
+ server_->Connect();
+ } catch (const std::exception &e) {
+ ERR("Failed to start Subscribe stream %s", e.what());
+ }
+}
+
+void SubscribeStream::SetSignalingServerCallbacks(void)
+{
+ auto on_signaling_server_connection_state_changed =
+ std::bind(OnSignalingServerConnectionStateChanged, std::placeholders::_1,
+ std::ref(*room_), std::ref(*server_));
+
+ server_->SetConnectionStateChangedCb(on_signaling_server_connection_state_changed);
+
+ auto on_room_message_arrived =
+ std::bind(OnRoomMessageArrived, std::placeholders::_1, std::ref(*room_));
+
+ server_->SetRoomMessageArrivedCb(on_room_message_arrived);
+}
+
+void SubscribeStream::OnSignalingServerConnectionStateChanged(IfaceServer::ConnectionState state,
+ WebRtcRoom &room, MqttServer &server)
+{
+ // TODO VD doesn't show DBG level log
+ ERR("current state [%s]", MqttServer::GetConnectionStateStr(state).c_str());
+
+ if (state == IfaceServer::ConnectionState::Disconnected) {
+ ; // TODO: what to do when server is disconnected?
+ } else if (state == IfaceServer::ConnectionState::Registered) {
+ if (server.GetSourceId().size() != 0)
+ room.SetSourceId(server.GetSourceId());
+ server.JoinRoom(room.getId());
+ }
+}
+
+void SubscribeStream::OnRoomMessageArrived(const std::string &message, WebRtcRoom &room)
+{
+ room.handleMessage(message);
+}
+
+void SubscribeStream::SetRoomCallbacks(void)
+{
+ auto on_room_joined = std::bind(OnRoomJoined, std::ref(*this));
+
+ room_->SetRoomJoinedCb(on_room_joined);
+
+ auto on_peer_joined = std::bind(OnPeerJoined, std::placeholders::_1, std::ref(*this));
+ room_->SetPeerJoinedCb(on_peer_joined);
+
+ auto on_peer_left = std::bind(OnPeerLeft, std::placeholders::_1, std::ref(*room_));
+ room_->SetPeerLeftCb(on_peer_left);
+}
+
+void SubscribeStream::OnRoomJoined(SubscribeStream &subscribe_stream)
+{
+ // TODO: Notify Room Joined?
+ ERR("%s on %p", __func__, &subscribe_stream);
+}
+
+void SubscribeStream::OnPeerJoined(const std::string &peer_id, SubscribeStream &subscribe_stream)
+{
+ ERR("%s [%s]", __func__, peer_id.c_str());
+
+ if (peer_id.compare(subscribe_stream.room_->GetSourceId()) != 0) {
+ ERR("is not matched to source ID, ignored");
+ return;
+ }
+
+ if (!subscribe_stream.room_->AddPeer(peer_id)) {
+ ERR("Failed to add peer");
+ return;
+ }
+
+ try {
+ WebRtcPeer &peer = subscribe_stream.room_->GetPeer(peer_id);
+
+ auto webrtc_subscribe_stream = peer.GetWebRtcStream();
+ webrtc_subscribe_stream->Create(false, subscribe_stream.is_track_added_);
+ webrtc_subscribe_stream->Start();
+ subscribe_stream.SetWebRtcStreamCallbacks(peer);
+ } catch (std::out_of_range &e) {
+ ERR("Wired %s", e.what());
+ }
+}
+
+void SubscribeStream::SetWebRtcStreamCallbacks(WebRtcPeer &peer)
+{
+ WebRtcEventHandler event_handlers;
+
+ auto on_signaling_state_notify = std::bind(OnSignalingStateNotify, std::placeholders::_1,
+ std::ref(peer), std::ref(*server_));
+ event_handlers.SetOnSignalingStateNotifyCb(on_signaling_state_notify);
+
+ auto on_ice_connection_state_notify = std::bind(OnIceConnectionStateNotify,
+ std::placeholders::_1, std::ref(peer), std::ref(*server_));
+ event_handlers.SetOnIceConnectionStateNotifyCb(on_ice_connection_state_notify);
+
+ auto on_encoded_frame = std::bind(OnEncodedFrame, std::ref(peer));
+ event_handlers.SetOnEncodedFrameCb(on_encoded_frame);
+
+ auto on_track_added =
+ std::bind(OnTrackAdded, std::placeholders::_1, display_object_, std::ref(peer));
+ event_handlers.SetOnTrakAddedCb(on_track_added);
+
+ peer.GetWebRtcStream()->SetEventHandler(event_handlers);
+}
+
+void SubscribeStream::OnSignalingStateNotify(WebRtcState::Signaling state, WebRtcPeer &peer,
+ MqttServer &server)
+{
+ ERR("Singaling State: %s", WebRtcState::SignalingToStr(state).c_str());
+ if (state == WebRtcState::Signaling::HAVE_REMOTE_OFFER) {
+ auto on_answer_created_cb =
+ std::bind(OnAnswerCreated, std::placeholders::_1, std::ref(peer), std::ref(server));
+ peer.GetWebRtcStream()->CreateAnswerAsync(on_answer_created_cb);
+ }
+}
+
+void SubscribeStream::OnIceConnectionStateNotify(WebRtcState::IceConnection state, WebRtcPeer &peer,
+ MqttServer &server)
+{
+ ERR("IceConnection State: %s", WebRtcState::IceConnectionToStr(state).c_str());
+ if (state == WebRtcState::IceConnection::CHECKING) {
+ auto ice_candidates = peer.GetWebRtcStream()->GetIceCandidates();
+ for (const auto &candidate : ice_candidates)
+ server.SendMessage(peer.getId(), candidate);
+ }
+}
+
+void SubscribeStream::OnAnswerCreated(std::string sdp, WebRtcPeer &peer, MqttServer &server)
+{
+ server.SendMessage(peer.getId(), sdp);
+ peer.GetWebRtcStream()->SetLocalDescription(sdp);
+}
+
+void SubscribeStream::OnEncodedFrame(WebRtcPeer &peer)
+{
+ // TODO
+}
+
+void SubscribeStream::OnTrackAdded(unsigned int id, void *display_object, WebRtcPeer &peer)
+{
+ peer.GetWebRtcStream()->SetDisplayObject(id, display_object);
+}
+
+void SubscribeStream::OnPeerLeft(const std::string &peer_id, WebRtcRoom &room)
+{
+ /*TODO
+ ERR("%s [%s]", __func__, peer_id.c_str());
+ if (peer_id.compare(room.getSourceId()) != 0) {
+ ERR("is not matched to source ID, ignored");
+ return;
+ }
+ */
+ if (!room.RemovePeer(peer_id))
+ ERR("Failed to remove peer");
+}
+
+void *SubscribeStream::Stop(void)
+{
+ try {
+ server_->Disconnect();
+ } catch (const std::exception &e) {
+ ERR("Failed to disconnect server %s", e.what());
+ }
+
+ room_->ClearPeers();
+
+ return display_object_;
+}
diff --git a/modules/webrtc/SubscribeStream.h b/modules/webrtc/SubscribeStream.h
new file mode 100644
index 0000000..c8853f6
--- /dev/null
+++ b/modules/webrtc/SubscribeStream.h
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2022 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <memory>
+#include <mutex>
+
+#include "Config.h"
+#include "MqttServer.h"
+#include "WebRtcRoom.h"
+#include "WebRtcStream.h"
+
+class SubscribeStream {
+ public:
+ SubscribeStream() = delete;
+ SubscribeStream(const std::string &topic, const Config &config)
+ : topic_(topic),
+ config_(config),
+ server_(std::make_shared<MqttServer>(config)),
+ room_(std::make_shared<WebRtcRoom>(config.GetRoomId())),
+ is_track_added_(false),
+ display_object_(nullptr){};
+ ~SubscribeStream();
+
+ // TODO what will be final form of callback
+ void Start(bool need_display, void *display_object);
+ void *Stop(void);
+ void SetSignalingServerCallbacks(void);
+ void SetRoomCallbacks(void);
+ void SetWebRtcStreamCallbacks(WebRtcPeer &peer);
+
+ private:
+ static void OnSignalingServerConnectionStateChanged(IfaceServer::ConnectionState state,
+ WebRtcRoom &room, MqttServer &server);
+ static void OnRoomMessageArrived(const std::string &message, WebRtcRoom &room);
+ static void OnRoomJoined(SubscribeStream &subscribe_stream);
+ static void OnPeerJoined(const std::string &peer_id, SubscribeStream &subscribe_stream);
+ static void OnPeerLeft(const std::string &peer_id, WebRtcRoom &room);
+ static void OnSignalingStateNotify(WebRtcState::Signaling state, WebRtcPeer &peer,
+ MqttServer &server);
+ static void OnIceConnectionStateNotify(WebRtcState::IceConnection state, WebRtcPeer &peer,
+ MqttServer &server);
+ static void OnAnswerCreated(std::string sdp, WebRtcPeer &peer, MqttServer &server);
+ static void OnEncodedFrame(WebRtcPeer &peer);
+ static void OnTrackAdded(unsigned int id, void *dispaly_object, WebRtcPeer &peer);
+
+ private:
+ std::string topic_;
+ Config config_;
+ std::shared_ptr<MqttServer> server_;
+ std::shared_ptr<WebRtcRoom> room_;
+ bool is_track_added_;
+ void *display_object_;
+};
diff --git a/modules/webrtc/WebRtcEventHandler.h b/modules/webrtc/WebRtcEventHandler.h
new file mode 100644
index 0000000..c922672
--- /dev/null
+++ b/modules/webrtc/WebRtcEventHandler.h
@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) 2022 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <functional>
+#include <string>
+
+#include "WebRtcState.h"
+
+class WebRtcEventHandler {
+ public:
+ // TODO Add error and state callbacks
+ void SetOnStateChangedCb(std::function<void(WebRtcState::Stream)> on_state_changed_cb)
+ {
+ on_state_changed_cb_ = on_state_changed_cb;
+ };
+ void CallOnStateChangedCb(WebRtcState::Stream state) const
+ {
+ if (on_state_changed_cb_)
+ on_state_changed_cb_(state);
+ };
+ void UnsetOnStateChangedCb(void) { on_state_changed_cb_ = nullptr; };
+
+ void SetOnSignalingStateNotifyCb(
+ std::function<void(WebRtcState::Signaling)> on_signaling_state_notify_cb)
+ {
+ on_signaling_state_notify_cb_ = on_signaling_state_notify_cb;
+ };
+ void CallOnSignalingStateNotifyCb(WebRtcState::Signaling state) const
+ {
+ if (on_signaling_state_notify_cb_)
+ on_signaling_state_notify_cb_(state);
+ };
+ void UnsetOnSignalingStateNotifyCb(void) { on_signaling_state_notify_cb_ = nullptr; };
+
+ void SetOnIceConnectionStateNotifyCb(std::function<void(WebRtcState::IceConnection)> on_ice_connection_state_notify_cb)
+ {
+ on_ice_connection_state_notify_cb_ = on_ice_connection_state_notify_cb;
+ };
+ void CallOnIceConnectionStateNotifyCb(WebRtcState::IceConnection state) const
+ {
+ if (on_ice_connection_state_notify_cb_)
+ on_ice_connection_state_notify_cb_(state);
+ };
+ void UnsetOnIceConnectionStateNotifyeCb(void) { on_ice_connection_state_notify_cb_ = nullptr; };
+
+ void SetOnEncodedFrameCb(std::function<void(void)> on_encoded_frame_cb)
+ {
+ on_encoded_frame_cb_ = on_encoded_frame_cb;
+ };
+ void CallOnEncodedFrameCb(void) const
+ {
+ if (on_encoded_frame_cb_)
+ on_encoded_frame_cb_();
+ };
+ void UnsetEncodedFrameCb(void) { on_encoded_frame_cb_ = nullptr; };
+
+ void SetOnTrakAddedCb(std::function<void(unsigned int id)> on_track_added_cb)
+ {
+ on_track_added_cb_ = on_track_added_cb;
+ };
+ void CallOnTrakAddedCb(unsigned int id) const
+ {
+ if (on_track_added_cb_)
+ on_track_added_cb_(id);
+ };
+ void UnsetTrackAddedCb(void) { on_track_added_cb_ = nullptr; };
+
+ private:
+ std::function<void(void)> on_negotiation_needed_cb_;
+ std::function<void(WebRtcState::Stream)> on_state_changed_cb_;
+ std::function<void(WebRtcState::Signaling)> on_signaling_state_notify_cb_;
+ std::function<void(WebRtcState::IceConnection)> on_ice_connection_state_notify_cb_;
+ std::function<void(void)> on_encoded_frame_cb_;
+ std::function<void(unsigned int id)> on_track_added_cb_;
+};
diff --git a/modules/webrtc/WebRtcMessage.cc b/modules/webrtc/WebRtcMessage.cc
new file mode 100644
index 0000000..3d9af79
--- /dev/null
+++ b/modules/webrtc/WebRtcMessage.cc
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2022 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <json-glib/json-glib.h>
+
+#include "aitt_internal.h"
+
+#include "WebRtcMessage.h"
+
+WebRtcMessage::Type WebRtcMessage::getMessageType(const std::string &message)
+{
+ WebRtcMessage::Type type = WebRtcMessage::Type::UNKNOWN;
+ JsonParser *parser = json_parser_new();
+ if (!json_parser_load_from_data(parser, message.c_str(), -1, NULL)) {
+ DBG("Unknown message '%s', ignoring", message.c_str());
+ g_object_unref(parser);
+ return type;
+ }
+
+ JsonNode *root = json_parser_get_root(parser);
+ if (!JSON_NODE_HOLDS_OBJECT(root)) {
+ DBG("Unknown json message '%s', ignoring", message.c_str());
+ g_object_unref(parser);
+ return type;
+ }
+
+ JsonObject *object = json_node_get_object(root);
+ /* Check type of JSON message */
+
+ if (json_object_has_member(object, "sdp")) {
+ type = WebRtcMessage::Type::SDP;
+ } else if (json_object_has_member(object, "ice")) {
+ type = WebRtcMessage::Type::ICE;
+ } else {
+ DBG("%s:UNKNOWN", __func__);
+ }
+
+ g_object_unref(parser);
+ return type;
+}
diff --git a/modules/webrtc/WebRtcMessage.h b/modules/webrtc/WebRtcMessage.h
new file mode 100644
index 0000000..6057a22
--- /dev/null
+++ b/modules/webrtc/WebRtcMessage.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2022 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <string>
+
+class WebRtcMessage {
+ public:
+ enum class Type {
+ SDP,
+ ICE,
+ UNKNOWN,
+ };
+ static WebRtcMessage::Type getMessageType(const std::string &message);
+};
diff --git a/modules/webrtc/WebRtcPeer.cc b/modules/webrtc/WebRtcPeer.cc
new file mode 100644
index 0000000..119f6e4
--- /dev/null
+++ b/modules/webrtc/WebRtcPeer.cc
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2022 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "WebRtcPeer.h"
+
+#include "WebRtcMessage.h"
+#include "aitt_internal.h"
+
+WebRtcPeer::WebRtcPeer(const std::string &peer_id)
+ : local_id_(peer_id), webrtc_stream_(std::make_shared<WebRtcStream>())
+{
+ DBG("%s", __func__);
+}
+
+WebRtcPeer::~WebRtcPeer()
+{
+ webrtc_stream_ = nullptr;
+ DBG("%s removed", local_id_.c_str());
+}
+
+std::shared_ptr<WebRtcStream> WebRtcPeer::GetWebRtcStream(void) const
+{
+ return webrtc_stream_;
+}
+
+void WebRtcPeer::SetWebRtcStream(std::shared_ptr<WebRtcStream> webrtc_stream)
+{
+ webrtc_stream_ = webrtc_stream;
+}
+
+std::string WebRtcPeer::getId(void) const
+{
+ return local_id_;
+}
+
+void WebRtcPeer::HandleMessage(const std::string &message)
+{
+ WebRtcMessage::Type type = WebRtcMessage::getMessageType(message);
+ if (type == WebRtcMessage::Type::SDP)
+ webrtc_stream_->SetRemoteDescription(message);
+ else if (type == WebRtcMessage::Type::ICE)
+ webrtc_stream_->AddIceCandidateFromMessage(message);
+ else
+ DBG("%s can't handle %s", __func__, message.c_str());
+}
diff --git a/modules/webrtc/WebRtcPeer.h b/modules/webrtc/WebRtcPeer.h
new file mode 100644
index 0000000..1ccb4e9
--- /dev/null
+++ b/modules/webrtc/WebRtcPeer.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2022 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <memory>
+#include <string>
+
+#include "WebRtcStream.h"
+
+class WebRtcPeer {
+ public:
+ explicit WebRtcPeer(const std::string &peer_id);
+ ~WebRtcPeer();
+
+ std::shared_ptr<WebRtcStream> GetWebRtcStream(void) const;
+ void SetWebRtcStream(std::shared_ptr<WebRtcStream> webrtc_stream);
+ std::string getId(void) const;
+ void HandleMessage(const std::string &message);
+
+ private:
+ std::string local_id_;
+ std::shared_ptr<WebRtcStream> webrtc_stream_;
+};
diff --git a/modules/webrtc/WebRtcRoom.cc b/modules/webrtc/WebRtcRoom.cc
new file mode 100644
index 0000000..781b72b
--- /dev/null
+++ b/modules/webrtc/WebRtcRoom.cc
@@ -0,0 +1,141 @@
+/*
+ * Copyright (c) 2022 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "WebRtcRoom.h"
+
+#include <sstream>
+#include <stdexcept>
+
+#include "aitt_internal.h"
+
+WebRtcRoom::~WebRtcRoom()
+{
+ //TODO How about removing handling webrtc_stream part from Room?
+ for (auto pair : peers_) {
+ auto peer = pair.second;
+ auto webrtc_stream = peer->GetWebRtcStream();
+ webrtc_stream->Stop();
+ webrtc_stream->Destroy();
+ }
+}
+
+static std::vector<std::string> split(const std::string &line, char delimiter)
+{
+ std::vector<std::string> result;
+ std::stringstream input(line);
+
+ std::string buffer;
+ while (getline(input, buffer, delimiter)) {
+ result.push_back(buffer);
+ }
+
+ return result;
+}
+
+void WebRtcRoom::handleMessage(const std::string &msg)
+{
+ if (msg.compare("ROOM_OK ") == 0)
+ CallRoomJoinedCb();
+ else if (msg.compare(0, 8, "ROOM_OK ") == 0) {
+ CallRoomJoinedCb();
+ HandleRoomJoinedWithPeerList(msg.substr(8, std::string::npos));
+ } else if (msg.compare(0, 16, "ROOM_PEER_JOINED") == 0) {
+ CallPeerJoinedCb(msg.substr(17, std::string::npos));
+ } else if (msg.compare(0, 14, "ROOM_PEER_LEFT") == 0) {
+ CallPeerLeftCb(msg.substr(15, std::string::npos));
+ } else if (msg.compare(0, 13, "ROOM_PEER_MSG") == 0) {
+ HandlePeerMessage(msg.substr(14, std::string::npos));
+ } else {
+ DBG("Not defined");
+ }
+
+ return;
+}
+
+void WebRtcRoom::HandleRoomJoinedWithPeerList(const std::string &peer_list)
+{
+ auto peer_ids = split(peer_list, ' ');
+ for (const auto &peer_id : peer_ids) {
+ CallPeerJoinedCb(peer_id);
+ }
+}
+
+void WebRtcRoom::HandlePeerMessage(const std::string &msg)
+{
+ std::size_t pos = msg.find(' ');
+ if (pos == std::string::npos) {
+ DBG("%s can't handle %s", __func__, msg.c_str());
+ return;
+ }
+
+ auto peer_id = msg.substr(0, pos);
+ auto itr = peers_.find(peer_id);
+ if (itr == peers_.end()) {
+ ERR("%s is not in peer list", peer_id.c_str());
+ //Opening backdoor here for source. What'll be crisis for this?
+ CallPeerJoinedCb(peer_id);
+ itr = peers_.find(peer_id);
+ RET_IF(itr == peers_.end());
+ }
+
+ itr->second->HandleMessage(msg.substr(pos + 1, std::string::npos));
+}
+
+bool WebRtcRoom::AddPeer(const std::string &peer_id)
+{
+ auto peer = std::make_shared<WebRtcPeer>(peer_id);
+ auto ret = peers_.insert(std::make_pair(peer_id, peer));
+
+ return ret.second;
+}
+
+bool WebRtcRoom::RemovePeer(const std::string &peer_id)
+{
+ auto itr = peers_.find(peer_id);
+ if (itr == peers_.end()) {
+ DBG("There's no such peer");
+ return false;
+ }
+ auto peer = itr->second;
+
+ //TODO How about removing handling webrtc_stream part from Room?
+ auto webrtc_stream = peer->GetWebRtcStream();
+ webrtc_stream->Stop();
+ webrtc_stream->Destroy();
+
+ return peers_.erase(peer_id) == 1;
+}
+
+WebRtcPeer &WebRtcRoom::GetPeer(const std::string &peer_id)
+{
+ auto itr = peers_.find(peer_id);
+ if (itr == peers_.end())
+ throw std::out_of_range("There's no such peer");
+
+ return *itr->second;
+}
+
+void WebRtcRoom::ClearPeers(void)
+{
+ //TODO How about removing handling webrtc_stream part from Room?
+ for (auto pair : peers_) {
+ auto peer = pair.second;
+ auto webrtc_stream = peer->GetWebRtcStream();
+ webrtc_stream->Stop();
+ webrtc_stream->Destroy();
+ }
+ peers_.clear();
+}
diff --git a/modules/webrtc/WebRtcRoom.h b/modules/webrtc/WebRtcRoom.h
new file mode 100644
index 0000000..fabeb1e
--- /dev/null
+++ b/modules/webrtc/WebRtcRoom.h
@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) 2022 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <functional>
+#include <map>
+#include <memory>
+#include <string>
+
+#include "WebRtcPeer.h"
+
+class WebRtcRoom {
+ public:
+ enum class State {
+ JOINNING,
+ JOINED,
+ };
+ WebRtcRoom() = delete;
+ WebRtcRoom(const std::string &room_id) : id_(room_id){};
+ ~WebRtcRoom();
+ void setRoomState(State current) { state_ = current; }
+ State getRoomState(void) const { return state_; };
+ void handleMessage(const std::string &msg);
+ bool AddPeer(const std::string &peer_id);
+ bool RemovePeer(const std::string &peer_id);
+ void ClearPeers(void);
+ // You need to handle out_of_range exception if there's no matching peer;
+ WebRtcPeer &GetPeer(const std::string &peer_id);
+ std::string getId(void) const { return id_; };
+ void SetSourceId(const std::string &source_id) { source_id_ = source_id; };
+ std::string GetSourceId(void) const { return source_id_; };
+
+ void SetRoomJoinedCb(std::function<void(void)> on_room_joined_cb)
+ {
+ on_room_joined_cb_ = on_room_joined_cb;
+ };
+ void CallRoomJoinedCb(void) const
+ {
+ if (on_room_joined_cb_)
+ on_room_joined_cb_();
+ };
+ void UnsetRoomJoinedCb(void) { on_room_joined_cb_ = nullptr; };
+ void SetPeerJoinedCb(std::function<void(const std::string &peer_id)> on_peer_joined_cb)
+ {
+ on_peer_joined_cb_ = on_peer_joined_cb;
+ };
+ void CallPeerJoinedCb(const std::string &peer_id) const
+ {
+ if (on_peer_joined_cb_)
+ on_peer_joined_cb_(peer_id);
+ };
+ void UnsetPeerJoinedCb(void) { on_peer_joined_cb_ = nullptr; };
+ void SetPeerLeftCb(std::function<void(const std::string &peer_id)> on_peer_left_cb)
+ {
+ on_peer_left_cb_ = on_peer_left_cb;
+ };
+ void CallPeerLeftCb(const std::string &peer_id) const
+ {
+ if (on_peer_left_cb_)
+ on_peer_left_cb_(peer_id);
+ };
+ void UnsetPeerLeftCb(void) { on_peer_left_cb_ = nullptr; };
+
+ private:
+ void HandleRoomJoinedWithPeerList(const std::string &peer_list);
+ void HandlePeerMessage(const std::string &msg);
+
+ private:
+ std::string id_;
+ std::string source_id_;
+ std::map<std::string, std::shared_ptr<WebRtcPeer>> peers_;
+ State state_;
+ std::function<void(void)> on_room_joined_cb_;
+ std::function<void(const std::string &peer_id)> on_peer_joined_cb_;
+ std::function<void(const std::string &peer_id)> on_peer_left_cb_;
+};
diff --git a/modules/webrtc/WebRtcState.cc b/modules/webrtc/WebRtcState.cc
new file mode 100644
index 0000000..437460d
--- /dev/null
+++ b/modules/webrtc/WebRtcState.cc
@@ -0,0 +1,187 @@
+/*
+ * Copyright (c) 2022 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "WebRtcState.h"
+
+WebRtcState::Stream WebRtcState::ToStreamState(webrtc_state_e state)
+{
+ switch (state) {
+ case WEBRTC_STATE_IDLE: {
+ return Stream::IDLE;
+ }
+ case WEBRTC_STATE_NEGOTIATING: {
+ return Stream::NEGOTIATING;
+ }
+ case WEBRTC_STATE_PLAYING: {
+ return Stream::PLAYING;
+ }
+ }
+ return Stream::IDLE;
+}
+
+std::string WebRtcState::StreamToStr(WebRtcState::Stream state)
+{
+ switch (state) {
+ case Stream::IDLE: {
+ return std::string("IDLE");
+ }
+ case Stream::NEGOTIATING: {
+ return std::string("NEGOTIATING");
+ }
+ case Stream::PLAYING: {
+ return std::string("PLAYING");
+ }
+ }
+ return std::string("");
+}
+
+WebRtcState::Signaling WebRtcState::ToSignalingState(webrtc_signaling_state_e state)
+{
+ switch (state) {
+ case WEBRTC_SIGNALING_STATE_STABLE: {
+ return Signaling::STABLE;
+ }
+ case WEBRTC_SIGNALING_STATE_HAVE_LOCAL_OFFER: {
+ return Signaling::HAVE_LOCAL_OFFER;
+ }
+ case WEBRTC_SIGNALING_STATE_HAVE_REMOTE_OFFER: {
+ return Signaling::HAVE_REMOTE_OFFER;
+ }
+ case WEBRTC_SIGNALING_STATE_HAVE_LOCAL_PRANSWER: {
+ return Signaling::HAVE_LOCAL_PRANSWER;
+ }
+ case WEBRTC_SIGNALING_STATE_HAVE_REMOTE_PRANSWER: {
+ return Signaling::HAVE_REMOTE_PRANSWER;
+ }
+ case WEBRTC_SIGNALING_STATE_CLOSED: {
+ return Signaling::CLOSED;
+ }
+ }
+ return Signaling::STABLE;
+}
+
+std::string WebRtcState::SignalingToStr(WebRtcState::Signaling state)
+{
+ switch (state) {
+ case (WebRtcState::Signaling::STABLE): {
+ return std::string("STABLE");
+ }
+ case (WebRtcState::Signaling::CLOSED): {
+ return std::string("CLOSED");
+ }
+ case (WebRtcState::Signaling::HAVE_LOCAL_OFFER): {
+ return std::string("HAVE_LOCAL_OFFER");
+ }
+ case (WebRtcState::Signaling::HAVE_REMOTE_OFFER): {
+ return std::string("HAVE_REMOTE_OFFER");
+ }
+ case (WebRtcState::Signaling::HAVE_LOCAL_PRANSWER): {
+ return std::string("HAVE_LOCAL_PRANSWER");
+ }
+ case (WebRtcState::Signaling::HAVE_REMOTE_PRANSWER): {
+ return std::string("HAVE_REMOTE_PRANSWER");
+ }
+ }
+ return std::string("");
+}
+
+WebRtcState::IceGathering WebRtcState::ToIceGatheringState(webrtc_ice_gathering_state_e state)
+{
+ switch (state) {
+ case WEBRTC_ICE_GATHERING_STATE_COMPLETE: {
+ return IceGathering::COMPLETE;
+ }
+ case WEBRTC_ICE_GATHERING_STATE_GATHERING: {
+ return IceGathering::GATHERING;
+ }
+ case WEBRTC_ICE_GATHERING_STATE_NEW: {
+ return IceGathering::NEW;
+ }
+ }
+ return IceGathering::NEW;
+}
+
+std::string WebRtcState::IceGatheringToStr(WebRtcState::IceGathering state)
+{
+ switch (state) {
+ case (WebRtcState::IceGathering::NEW): {
+ return std::string("NEW");
+ }
+ case (WebRtcState::IceGathering::GATHERING): {
+ return std::string("GATHERING");
+ }
+ case (WebRtcState::IceGathering::COMPLETE): {
+ return std::string("COMPLETE");
+ }
+ }
+ return std::string("");
+}
+
+WebRtcState::IceConnection WebRtcState::ToIceConnectionState(webrtc_ice_connection_state_e state)
+{
+ switch (state) {
+ case WEBRTC_ICE_CONNECTION_STATE_CHECKING: {
+ return IceConnection::CHECKING;
+ }
+ case WEBRTC_ICE_CONNECTION_STATE_CLOSED: {
+ return IceConnection::CLOSED;
+ }
+ case WEBRTC_ICE_CONNECTION_STATE_COMPLETED: {
+ return IceConnection::COMPLETED;
+ }
+ case WEBRTC_ICE_CONNECTION_STATE_CONNECTED: {
+ return IceConnection::CONNECTED;
+ }
+ case WEBRTC_ICE_CONNECTION_STATE_DISCONNECTED: {
+ return IceConnection::DISCONNECTED;
+ }
+ case WEBRTC_ICE_CONNECTION_STATE_FAILED: {
+ return IceConnection::FAILED;
+ }
+ case WEBRTC_ICE_CONNECTION_STATE_NEW: {
+ return IceConnection::NEW;
+ }
+ }
+ return IceConnection::NEW;
+}
+
+std::string WebRtcState::IceConnectionToStr(WebRtcState::IceConnection state)
+{
+ switch (state) {
+ case (WebRtcState::IceConnection::NEW): {
+ return std::string("NEW");
+ }
+ case (WebRtcState::IceConnection::CHECKING): {
+ return std::string("CHECKING");
+ }
+ case (WebRtcState::IceConnection::CONNECTED): {
+ return std::string("CONNECTED");
+ }
+ case (WebRtcState::IceConnection::COMPLETED): {
+ return std::string("COMPLETED");
+ }
+ case (WebRtcState::IceConnection::FAILED): {
+ return std::string("FAILED");
+ }
+ case (WebRtcState::IceConnection::DISCONNECTED): {
+ return std::string("DISCONNECTED");
+ }
+ case (WebRtcState::IceConnection::CLOSED): {
+ return std::string("CLOSED");
+ }
+ }
+ return std::string("");
+}
diff --git a/modules/webrtc/WebRtcState.h b/modules/webrtc/WebRtcState.h
new file mode 100644
index 0000000..c6ad8d0
--- /dev/null
+++ b/modules/webrtc/WebRtcState.h
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2022 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <functional>
+#include <string>
+
+#include <webrtc.h>
+
+class WebRtcState {
+ public:
+ enum class Stream {
+ IDLE,
+ NEGOTIATING,
+ PLAYING,
+ };
+
+ enum class PeerConnection {
+ NEW,
+ CONNECTING,
+ CONNECTED,
+ DISCONNECTED,
+ FAILED,
+ CLOSED,
+ };
+
+ enum class Signaling {
+ STABLE,
+ CLOSED,
+ HAVE_LOCAL_OFFER,
+ HAVE_REMOTE_OFFER,
+ HAVE_LOCAL_PRANSWER,
+ HAVE_REMOTE_PRANSWER,
+ };
+
+ enum class IceGathering {
+ NEW,
+ GATHERING,
+ COMPLETE,
+ };
+
+ enum class IceConnection {
+ NEW,
+ CHECKING,
+ CONNECTED,
+ COMPLETED,
+ FAILED,
+ DISCONNECTED,
+ CLOSED,
+ };
+
+ public:
+ static Stream ToStreamState(webrtc_state_e state);
+ static std::string StreamToStr(WebRtcState::Stream state);
+ static Signaling ToSignalingState(webrtc_signaling_state_e state);
+ static std::string SignalingToStr(WebRtcState::Signaling state);
+ static IceGathering ToIceGatheringState(webrtc_ice_gathering_state_e state);
+ static std::string IceGatheringToStr(WebRtcState::IceGathering state);
+ static IceConnection ToIceConnectionState(webrtc_ice_connection_state_e state);
+ static std::string IceConnectionToStr(WebRtcState::IceConnection state);
+};
diff --git a/modules/webrtc/WebRtcStream.cc b/modules/webrtc/WebRtcStream.cc
new file mode 100644
index 0000000..ef717b0
--- /dev/null
+++ b/modules/webrtc/WebRtcStream.cc
@@ -0,0 +1,414 @@
+/*
+ * Copyright (c) 2022 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "WebRtcStream.h"
+
+#include "aitt_internal.h"
+
+WebRtcStream::~WebRtcStream()
+{
+ Destroy();
+ DBG("%s", __func__);
+}
+
+bool WebRtcStream::Create(bool is_source, bool need_display)
+{
+ if (webrtc_handle_) {
+ ERR("Already created %p", webrtc_handle_);
+ return false;
+ }
+
+ auto ret = webrtc_create(&webrtc_handle_);
+ if (ret != WEBRTC_ERROR_NONE) {
+ ERR("Failed to create webrtc handle");
+ return false;
+ }
+ AttachSignals(is_source, need_display);
+
+ return true;
+}
+
+void WebRtcStream::Destroy(void)
+{
+ if (!webrtc_handle_) {
+ ERR("WebRTC handle is not created");
+ return;
+ }
+ auto stop_ret = webrtc_stop(webrtc_handle_);
+ if (stop_ret != WEBRTC_ERROR_NONE)
+ ERR("Failed to stop webrtc handle");
+
+ auto ret = webrtc_destroy(webrtc_handle_);
+ if (ret != WEBRTC_ERROR_NONE)
+ ERR("Failed to destroy webrtc handle");
+ webrtc_handle_ = nullptr;
+}
+
+bool WebRtcStream::Start(void)
+{
+ if (!webrtc_handle_) {
+ ERR("WebRTC handle is not created");
+ return false;
+ }
+ if (camera_handler_)
+ camera_handler_->StartPreview();
+
+ auto ret = webrtc_start(webrtc_handle_);
+ if (ret != WEBRTC_ERROR_NONE)
+ ERR("Failed to start webrtc handle");
+
+ return ret == WEBRTC_ERROR_NONE;
+}
+
+bool WebRtcStream::Stop(void)
+{
+ if (!webrtc_handle_) {
+ ERR("WebRTC handle is not created");
+ return false;
+ }
+ if (camera_handler_)
+ camera_handler_->StopPreview();
+
+ auto ret = webrtc_stop(webrtc_handle_);
+ if (ret != WEBRTC_ERROR_NONE)
+ ERR("Failed to stop webrtc handle");
+
+ return ret == WEBRTC_ERROR_NONE;
+}
+
+bool WebRtcStream::AttachCameraSource(void)
+{
+ if (!webrtc_handle_) {
+ ERR("WebRTC handle is not created");
+ return false;
+ }
+
+ if (source_id_) {
+ ERR("source already attached");
+ return false;
+ }
+
+ auto ret =
+ webrtc_add_media_source(webrtc_handle_, WEBRTC_MEDIA_SOURCE_TYPE_CAMERA, &source_id_);
+ if (ret != WEBRTC_ERROR_NONE)
+ ERR("Failed to add media source");
+
+ return ret == WEBRTC_ERROR_NONE;
+}
+
+bool WebRtcStream::AttachCameraPreviewSource(void)
+{
+ if (!webrtc_handle_) {
+ ERR("WebRTC handle is not created");
+ return false;
+ }
+
+ if (source_id_) {
+ ERR("source already attached");
+ return false;
+ }
+
+ camera_handler_ = std::make_unique<CameraHandler>();
+ camera_handler_->Init(OnMediaPacketPreview, this);
+
+ auto ret = webrtc_add_media_source(webrtc_handle_, WEBRTC_MEDIA_SOURCE_TYPE_MEDIA_PACKET,
+ &source_id_);
+ if (ret != WEBRTC_ERROR_NONE)
+ ERR("Failed to add media source");
+
+ return ret == WEBRTC_ERROR_NONE;
+}
+
+void WebRtcStream::OnMediaPacketPreview(media_packet_h media_packet, void *user_data)
+{
+ ERR("%s", __func__);
+ auto webrtc_stream = static_cast<WebRtcStream *>(user_data);
+ RET_IF(webrtc_stream == nullptr);
+
+ if (webrtc_stream->is_source_overflow_) {
+ return;
+ }
+ if (webrtc_media_packet_source_push_packet(webrtc_stream->webrtc_handle_,
+ webrtc_stream->source_id_, media_packet)
+ != WEBRTC_ERROR_NONE) {
+ media_packet_destroy(media_packet);
+ }
+}
+
+bool WebRtcStream::DetachCameraSource(void)
+{
+ if (!webrtc_handle_) {
+ ERR("WebRTC handle is not created");
+ return false;
+ }
+
+ if (!source_id_) {
+ ERR("Camera source is not attached");
+ return false;
+ }
+
+ camera_handler_ = nullptr;
+
+ auto ret = webrtc_remove_media_source(webrtc_handle_, source_id_);
+ if (ret != WEBRTC_ERROR_NONE)
+ ERR("Failed to remove media source");
+
+ return ret == WEBRTC_ERROR_NONE;
+}
+
+void WebRtcStream::SetDisplayObject(unsigned int id, void *object)
+{
+ if (!webrtc_handle_) {
+ ERR("WebRTC handle is not created");
+ return;
+ }
+
+ if (!object) {
+ ERR("Object is not specified");
+ return;
+ }
+
+ webrtc_set_display(webrtc_handle_, id, WEBRTC_DISPLAY_TYPE_EVAS, object);
+}
+
+bool WebRtcStream::CreateOfferAsync(std::function<void(std::string)> on_created_cb)
+{
+ if (!webrtc_handle_) {
+ ERR("WebRTC handle is not created");
+ return false;
+ }
+ on_offer_created_cb_ = on_created_cb;
+ auto ret = webrtc_create_offer_async(webrtc_handle_, NULL, OnOfferCreated, this);
+ if (ret != WEBRTC_ERROR_NONE)
+ ERR("Failed to create offer async");
+
+ return ret == WEBRTC_ERROR_NONE;
+}
+
+void WebRtcStream::OnOfferCreated(webrtc_h webrtc, const char *description, void *user_data)
+{
+ RET_IF(!user_data);
+
+ WebRtcStream *webrtc_stream = static_cast<WebRtcStream *>(user_data);
+
+ if (webrtc_stream->on_offer_created_cb_)
+ webrtc_stream->on_offer_created_cb_(std::string(description));
+}
+
+bool WebRtcStream::CreateAnswerAsync(std::function<void(std::string)> on_created_cb)
+{
+ if (!webrtc_handle_) {
+ ERR("WebRTC handle is not created");
+ return false;
+ }
+ on_answer_created_cb_ = on_created_cb;
+ auto ret = webrtc_create_answer_async(webrtc_handle_, NULL, OnAnswerCreated, this);
+ if (ret != WEBRTC_ERROR_NONE)
+ ERR("Failed to create answer async");
+
+ return ret == WEBRTC_ERROR_NONE;
+}
+
+void WebRtcStream::OnAnswerCreated(webrtc_h webrtc, const char *description, void *user_data)
+{
+ if (!user_data)
+ return;
+
+ WebRtcStream *webrtc_stream = static_cast<WebRtcStream *>(user_data);
+ if (webrtc_stream->on_answer_created_cb_)
+ webrtc_stream->on_answer_created_cb_(std::string(description));
+}
+
+bool WebRtcStream::SetLocalDescription(const std::string &description)
+{
+ if (!webrtc_handle_) {
+ ERR("WebRTC handle is not created");
+ return false;
+ }
+ auto ret = webrtc_set_local_description(webrtc_handle_, description.c_str());
+ if (ret != WEBRTC_ERROR_NONE)
+ ERR("Failed to set local description");
+
+ return ret == WEBRTC_ERROR_NONE;
+}
+
+bool WebRtcStream::SetRemoteDescription(const std::string &description)
+{
+ if (!webrtc_handle_) {
+ ERR("WebRTC handle is not created");
+ return false;
+ }
+
+ webrtc_state_e state;
+ auto get_state_ret = webrtc_get_state(webrtc_handle_, &state);
+ if (get_state_ret != WEBRTC_ERROR_NONE) {
+ ERR("Failed to get state");
+ return false;
+ }
+
+ if (state != WEBRTC_STATE_NEGOTIATING) {
+ remote_description_ = description;
+ ERR("Invalid state, will be registred at NEGOTIATING state");
+ return true;
+ }
+
+ auto ret = webrtc_set_remote_description(webrtc_handle_, description.c_str());
+ if (ret != WEBRTC_ERROR_NONE)
+ ERR("Failed to set remote description");
+
+ return ret == WEBRTC_ERROR_NONE;
+}
+
+bool WebRtcStream::AddIceCandidateFromMessage(const std::string &ice_message)
+{
+ ERR("%s", __func__);
+ if (!webrtc_handle_) {
+ ERR("WebRTC handle is not created");
+ return false;
+ }
+ auto ret = webrtc_add_ice_candidate(webrtc_handle_, ice_message.c_str());
+ if (ret != WEBRTC_ERROR_NONE)
+ ERR("Failed to set add ice candidate");
+
+ return ret == WEBRTC_ERROR_NONE;
+}
+
+void WebRtcStream::AttachSignals(bool is_source, bool need_display)
+{
+ if (!webrtc_handle_) {
+ ERR("WebRTC handle is not created");
+ return;
+ }
+
+ int ret = WEBRTC_ERROR_NONE;
+ // TODO: ADHOC TV profile doesn't show DBG level log
+ ret = webrtc_set_error_cb(webrtc_handle_, OnError, this);
+ DBG("webrtc_set_error_cb %s", ret == WEBRTC_ERROR_NONE ? "Succeeded" : "failed");
+ ret = webrtc_set_state_changed_cb(webrtc_handle_, OnStateChanged, this);
+ DBG("webrtc_set_state_changed_cb %s", ret == WEBRTC_ERROR_NONE ? "Succeeded" : "failed");
+ ret = webrtc_set_signaling_state_change_cb(webrtc_handle_, OnSignalingStateChanged, this);
+ DBG("webrtc_set_signaling_state_change_cb %s",
+ ret == WEBRTC_ERROR_NONE ? "Succeeded" : "failed");
+ ret = webrtc_set_ice_connection_state_change_cb(webrtc_handle_, OnIceConnectionStateChanged,
+ this);
+ DBG("webrtc_set_ice_connection_state_change_cb %s",
+ ret == WEBRTC_ERROR_NONE ? "Succeeded" : "failed");
+ ret = webrtc_set_ice_candidate_cb(webrtc_handle_, OnIceCandiate, this);
+ DBG("webrtc_set_ice_candidate_cb %s", ret == WEBRTC_ERROR_NONE ? "Succeeded" : "failed");
+
+ if (!is_source && !need_display) {
+ ret = webrtc_set_encoded_video_frame_cb(webrtc_handle_, OnEncodedFrame, this);
+ ERR("webrtc_set_encoded_video_frame_cb %s",
+ ret == WEBRTC_ERROR_NONE ? "Succeeded" : "failed");
+ }
+
+ if (!is_source && need_display) {
+ ret = webrtc_set_track_added_cb(webrtc_handle_, OnTrackAdded, this);
+ ERR("webrtc_set_track_added_cb %s", ret == WEBRTC_ERROR_NONE ? "Succeeded" : "failed");
+ }
+
+ ret = webrtc_media_packet_source_set_buffer_state_changed_cb(webrtc_handle_, source_id_,
+ OnMediaPacketBufferStateChanged, this);
+ DBG("webrtc_media_packet_source_set_buffer_state_changed_cb %s",
+ ret == WEBRTC_ERROR_NONE ? "Succeeded" : "failed");
+
+ return;
+}
+
+void WebRtcStream::OnError(webrtc_h webrtc, webrtc_error_e error, webrtc_state_e state,
+ void *user_data)
+{
+ // TODO
+ ERR("%s", __func__);
+}
+
+void WebRtcStream::OnStateChanged(webrtc_h webrtc, webrtc_state_e previous, webrtc_state_e current,
+ void *user_data)
+{
+ ERR("%s", __func__);
+ auto webrtc_stream = static_cast<WebRtcStream *>(user_data);
+ RET_IF(webrtc_stream == nullptr);
+
+ if (current == WEBRTC_STATE_NEGOTIATING && webrtc_stream->remote_description_.size() != 0) {
+ ERR("received remote description exists");
+ auto ret = webrtc_set_remote_description(webrtc_stream->webrtc_handle_,
+ webrtc_stream->remote_description_.c_str());
+ if (ret != WEBRTC_ERROR_NONE)
+ ERR("Failed to set remote description");
+ webrtc_stream->remote_description_ = std::string();
+ }
+ webrtc_stream->GetEventHandler().CallOnStateChangedCb(WebRtcState::ToStreamState(current));
+}
+
+void WebRtcStream::OnSignalingStateChanged(webrtc_h webrtc, webrtc_signaling_state_e state,
+ void *user_data)
+{
+ ERR("%s", __func__);
+ auto webrtc_stream = static_cast<WebRtcStream *>(user_data);
+ RET_IF(webrtc_stream == nullptr);
+ webrtc_stream->GetEventHandler().CallOnSignalingStateNotifyCb(
+ WebRtcState::ToSignalingState(state));
+}
+
+void WebRtcStream::OnIceConnectionStateChanged(webrtc_h webrtc, webrtc_ice_connection_state_e state,
+ void *user_data)
+{
+ ERR("%s %d", __func__, state);
+ auto webrtc_stream = static_cast<WebRtcStream *>(user_data);
+ RET_IF(webrtc_stream == nullptr);
+
+ webrtc_stream->GetEventHandler().CallOnIceConnectionStateNotifyCb(
+ WebRtcState::ToIceConnectionState(state));
+}
+
+void WebRtcStream::OnIceCandiate(webrtc_h webrtc, const char *candidate, void *user_data)
+{
+ ERR("%s", __func__);
+ auto webrtc_stream = static_cast<WebRtcStream *>(user_data);
+ webrtc_stream->ice_candidates_.push_back(candidate);
+}
+
+void WebRtcStream::OnEncodedFrame(webrtc_h webrtc, webrtc_media_type_e type, unsigned int track_id,
+ media_packet_h packet, void *user_data)
+{
+ ERR("%s", __func__);
+ // TODO
+}
+
+void WebRtcStream::OnTrackAdded(webrtc_h webrtc, webrtc_media_type_e type, unsigned int id,
+ void *user_data)
+{
+ // type AUDIO(0), VIDEO(1)
+ INFO("Added Track : id(%d), type(%s)", id, type ? "Video" : "Audio");
+
+ ERR("%s", __func__);
+ auto webrtc_stream = static_cast<WebRtcStream *>(user_data);
+ RET_IF(webrtc_stream == nullptr);
+
+ if (type == WEBRTC_MEDIA_TYPE_VIDEO)
+ webrtc_stream->GetEventHandler().CallOnTrakAddedCb(id);
+}
+
+void WebRtcStream::OnMediaPacketBufferStateChanged(unsigned int source_id,
+ webrtc_media_packet_source_buffer_state_e state, void *user_data)
+{
+ ERR("%s", __func__);
+ auto webrtc_stream = static_cast<WebRtcStream *>(user_data);
+ RET_IF(webrtc_stream == nullptr);
+
+ webrtc_stream->is_source_overflow_ =
+ (state == WEBRTC_MEDIA_PACKET_SOURCE_BUFFER_STATE_OVERFLOW);
+}
diff --git a/modules/webrtc/WebRtcStream.h b/modules/webrtc/WebRtcStream.h
new file mode 100644
index 0000000..755c1ae
--- /dev/null
+++ b/modules/webrtc/WebRtcStream.h
@@ -0,0 +1,107 @@
+/*
+ * Copyright (c) 2022 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <functional>
+#include <list>
+#include <memory>
+#include <mutex>
+#include <string>
+
+// TODO: webrtc.h is very heavy header file.
+// I think we need to decide whether to include this or not
+#include <webrtc.h>
+
+#include "CameraHandler.h"
+#include "WebRtcEventHandler.h"
+
+class WebRtcStream {
+ public:
+ ~WebRtcStream();
+ bool Create(bool is_source, bool need_display);
+ void Destroy(void);
+ bool Start(void);
+ bool Stop(void);
+ bool AttachCameraSource(void);
+ bool AttachCameraPreviewSource(void);
+ static void OnMediaPacketPreview(media_packet_h media_packet, void *user_data);
+ bool DetachCameraSource(void);
+ void SetDisplayObject(unsigned int id, void *object);
+ void AttachSignals(bool is_source, bool need_display);
+ // Cautions : Event handler is not a pointer. So, change event_handle after Set Event handler
+ // doesn't affect event handler which is included int WebRtcStream
+ void SetEventHandler(WebRtcEventHandler event_handler) { event_handler_ = event_handler; };
+ WebRtcEventHandler &GetEventHandler(void) { return event_handler_; };
+
+ bool CreateOfferAsync(std::function<void(std::string)> on_created_cb);
+ void CallOnOfferCreatedCb(std::string offer)
+ {
+ if (on_offer_created_cb_)
+ on_offer_created_cb_(offer);
+ }
+ bool CreateAnswerAsync(std::function<void(std::string)> on_created_cb);
+ void CallOnAnswerCreatedCb(std::string answer)
+ {
+ if (on_answer_created_cb_)
+ on_answer_created_cb_(answer);
+ }
+ void SetPreparedLocalDescription(const std::string &description)
+ {
+ local_description_ = description;
+ };
+ std::string GetPreparedLocalDescription(void) const { return local_description_; };
+
+ bool SetLocalDescription(const std::string &description);
+ bool SetRemoteDescription(const std::string &description);
+
+ bool AddIceCandidateFromMessage(const std::string &ice_message);
+ const std::vector<std::string> &GetIceCandidates() const { return ice_candidates_; };
+
+ std::string GetRemoteDescription(void) const { return remote_description_; };
+
+ private:
+ static void OnOfferCreated(webrtc_h webrtc, const char *description, void *user_data);
+ static void OnAnswerCreated(webrtc_h webrtc, const char *description, void *user_data);
+ static void OnError(webrtc_h webrtc, webrtc_error_e error, webrtc_state_e state,
+ void *user_data);
+ static void OnStateChanged(webrtc_h webrtc, webrtc_state_e previous, webrtc_state_e current,
+ void *user_data);
+ static void OnSignalingStateChanged(webrtc_h webrtc, webrtc_signaling_state_e state,
+ void *user_data);
+ static void OnIceConnectionStateChanged(webrtc_h webrtc, webrtc_ice_connection_state_e state,
+ void *user_data);
+ static void OnIceCandiate(webrtc_h webrtc, const char *candidate, void *user_data);
+ static void OnEncodedFrame(webrtc_h webrtc, webrtc_media_type_e type, unsigned int track_id,
+ media_packet_h packet, void *user_data);
+ static void OnTrackAdded(webrtc_h webrtc, webrtc_media_type_e type, unsigned int id,
+ void *user_data);
+ static void OnMediaPacketBufferStateChanged(unsigned int source_id,
+ webrtc_media_packet_source_buffer_state_e state, void *user_data);
+
+ private:
+ webrtc_h webrtc_handle_;
+ std::shared_ptr<CameraHandler> camera_handler_;
+ // DO we need to make is_source_overflow_ as atomic?
+ bool is_source_overflow_;
+ unsigned int source_id_;
+ std::string local_description_;
+ std::string remote_description_;
+ std::vector<std::string> ice_candidates_;
+ std::function<void(std::string)> on_offer_created_cb_;
+ std::function<void(std::string)> on_answer_created_cb_;
+ WebRtcEventHandler event_handler_;
+};
diff --git a/modules/webrtc/tests/CMakeLists.txt b/modules/webrtc/tests/CMakeLists.txt
new file mode 100644
index 0000000..a1dd90f
--- /dev/null
+++ b/modules/webrtc/tests/CMakeLists.txt
@@ -0,0 +1,21 @@
+INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}/..)
+
+PKG_CHECK_MODULES(UT_NEEDS REQUIRED gmock_main)
+INCLUDE_DIRECTORIES(${UT_NEEDS_INCLUDE_DIRS})
+LINK_DIRECTORIES(${UT_NEEDS_LIBRARY_DIRS})
+
+SET(AITT_WEBRTC_UT ${PROJECT_NAME}_webrtc_ut)
+SET(AITT_WEBRTC_UT_SRC WEBRTC_test.cc)
+
+ADD_EXECUTABLE(${AITT_WEBRTC_UT} ${AITT_WEBRTC_UT_SRC} $<TARGET_OBJECTS:WEBRTC_OBJ>)
+TARGET_LINK_LIBRARIES(${AITT_WEBRTC_UT} ${UT_NEEDS_LIBRARIES} ${AITT_WEBRTC_NEEDS_LIBRARIES} ${AITT_COMMON})
+INSTALL(TARGETS ${AITT_WEBRTC_UT} DESTINATION ${AITT_TEST_BINDIR})
+
+ADD_TEST(
+ NAME
+ ${AITT_WEBRTC_UT}
+ COMMAND
+ ${CMAKE_COMMAND} -E env
+ LD_LIBRARY_PATH=../../../common:$ENV{LD_LIBRARY_PATH}
+ ${CMAKE_CURRENT_BINARY_DIR}/${AITT_WEBRTC_UT} --gtest_filter=*_Anytime
+)
diff --git a/modules/webrtc/tests/MockPublishStream.h b/modules/webrtc/tests/MockPublishStream.h
new file mode 100644
index 0000000..ced285e
--- /dev/null
+++ b/modules/webrtc/tests/MockPublishStream.h
@@ -0,0 +1,103 @@
+/*
+ * Copyright (c) 2022 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <memory>
+#include <string>
+
+#include "Config.h"
+#include "MqttServer.h"
+#include "WebRtcRoom.h"
+
+class MockPublishStream {
+ // TODO: Notify & get status
+ public:
+ MockPublishStream() = delete;
+ MockPublishStream(const std::string &topic, const Config &config)
+ : topic_(topic), config_(config), server_(std::make_shared<MqttServer>(config))
+ {
+ config.SetSourceId(config.GetLocalId());
+ config.SetRoomId(config::MQTT_ROOM_PREFIX() + topic);
+ room_ = std::make_shared<WebRtcRoom>(config.GetRoomId());
+ };
+ ~MockPublishStream();
+
+ void Start(void)
+ {
+ SetSignalingServerCallbacks();
+ SetRoomCallbacks();
+ server_->Connect();
+ }
+ void Stop(void){
+ // TODO
+ };
+
+ private:
+ void SetSignalingServerCallbacks(void)
+ {
+ auto on_signaling_server_connection_state_changed =
+ std::bind(OnSignalingServerConnectionStateChanged, std::placeholders::_1,
+ std::ref(*room_), std::ref(*server_));
+
+ server_->SetConnectionStateChangedCb(on_signaling_server_connection_state_changed);
+
+ auto on_room_message_arrived =
+ std::bind(OnRoomMessageArrived, std::placeholders::_1, std::ref(*room_));
+
+ server_->SetRoomMessageArrivedCb(on_room_message_arrived);
+ };
+
+ void SetRoomCallbacks(void)
+ {
+ auto on_peer_joined =
+ std::bind(OnPeerJoined, std::placeholders::_1, std::ref(*server_), std::ref(*room_));
+ room_->SetPeerJoinedCb(on_peer_joined);
+
+ auto on_peer_left = std::bind(OnPeerLeft, std::placeholders::_1, std::ref(*room_));
+ room_->SetPeerLeftCb(on_peer_left);
+ };
+
+ static void OnSignalingServerConnectionStateChanged(IfaceServer::ConnectionState state,
+ WebRtcRoom &room, MqttServer &server)
+ {
+ DBG("current state [%s]", SignalingServer::GetConnectionStateStr(state).c_str());
+
+ if (state == IfaceServer::ConnectionState::Registered)
+ server.JoinRoom(room.getId());
+ };
+
+ static void OnRoomMessageArrived(const std::string &message, WebRtcRoom &room)
+ {
+ room.handleMessage(message);
+ };
+
+ static void OnPeerJoined(const std::string &peer_id, MqttServer &server, WebRtcRoom &room)
+ {
+ DBG("%s [%s]", __func__, peer_id.c_str());
+ };
+
+ static void OnPeerLeft(const std::string &peer_id, WebRtcRoom &room)
+ {
+ DBG("%s [%s]", __func__, peer_id.c_str());
+ };
+
+ private:
+ std::string topic_;
+ config config_;
+ std::shared_ptr<MqttServer> server_;
+ std::shared_ptr<WebRtcRoom> room_;
+};
diff --git a/modules/webrtc/tests/MockSubscribeStream.h b/modules/webrtc/tests/MockSubscribeStream.h
new file mode 100644
index 0000000..ea2736d
--- /dev/null
+++ b/modules/webrtc/tests/MockSubscribeStream.h
@@ -0,0 +1,100 @@
+/*
+ * Copyright (c) 2022 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <memory>
+
+#include "Config.h"
+#include "MqttServer.h"
+
+class MockSubscribeStream {
+ public:
+ MockSubscribeStream() = delete;
+ MockSubscribeStream(const std::string &topic, const Config &config)
+ : topic_(topic), config_(config), server_(std::make_shared<SignalingServer>(config))
+ {
+ config.SetRoomId(config::MQTT_ROOM_PREFIX() + topic);
+ room_ = std::make_shared<WebRtcRoom>(config.GetRoomId());
+ };
+ ~MockSubscribeStream(){};
+
+ void Start(void)
+ {
+ SetSignalingServerCallbacks();
+ SetRoomCallbacks();
+ server_->Connect();
+ };
+
+ void Stop(void){
+ //TODO
+ };
+
+ private:
+ void SetSignalingServerCallbacks(void)
+ {
+ auto on_signaling_server_connection_state_changed =
+ std::bind(OnSignalingServerConnectionStateChanged, std::placeholders::_1,
+ std::ref(*room_), std::ref(*server_));
+
+ server_->SetConnectionStateChangedCb(on_signaling_server_connection_state_changed);
+
+ auto on_room_message_arrived =
+ std::bind(OnRoomMessageArrived, std::placeholders::_1, std::ref(*room_));
+
+ server_->SetRoomMessageArrivedCb(on_room_message_arrived);
+ };
+
+ void SetRoomCallbacks(void)
+ {
+ auto on_peer_joined =
+ std::bind(OnPeerJoined, std::placeholders::_1, std::ref(*server_), std::ref(*room_));
+ room_->SetPeerJoinedCb(on_peer_joined);
+
+ auto on_peer_left = std::bind(OnPeerLeft, std::placeholders::_1, std::ref(*room_));
+ room_->SetPeerLeftCb(on_peer_left);
+ };
+
+ static void OnSignalingServerConnectionStateChanged(IfaceServer::ConnectionState state,
+ WebRtcRoom &room, MqttServer &server)
+ {
+ DBG("current state [%s]", SignalingServer::GetConnectionStateStr(state).c_str());
+
+ if (state == IfaceServer::ConnectionState::Registered)
+ server.JoinRoom(room.getId());
+ };
+
+ static void OnRoomMessageArrived(const std::string &message, WebRtcRoom &room)
+ {
+ room.handleMessage(message);
+ };
+
+ static void OnPeerJoined(const std::string &peer_id, MqttServer &server, WebRtcRoom &room)
+ {
+ DBG("%s [%s]", __func__, peer_id.c_str());
+ };
+
+ static void OnPeerLeft(const std::string &peer_id, WebRtcRoom &room)
+ {
+ DBG("%s [%s]", __func__, peer_id.c_str());
+ };
+
+ private:
+ std::string topic_;
+ Config config_;
+ std::shared_ptr<MqttServer> server_;
+ std::shared_ptr<WebRtcRoom> room_;
+};
diff --git a/modules/webrtc/tests/WEBRTC_test.cc b/modules/webrtc/tests/WEBRTC_test.cc
new file mode 100644
index 0000000..5ee42c1
--- /dev/null
+++ b/modules/webrtc/tests/WEBRTC_test.cc
@@ -0,0 +1,598 @@
+/*
+ * Copyright (c) 2021-2022 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <glib.h>
+#include <gtest/gtest.h>
+
+#include <chrono>
+#include <set>
+#include <thread>
+
+#include "AITTEx.h"
+#include "Config.h"
+#include "MqttServer.h"
+#include "aitt_internal.h"
+
+#define DEFAULT_BROKER_IP "127.0.0.1"
+#define DEFAULT_BROKER_PORT 1883
+
+#define DEFAULT_WEBRTC_SRC_ID "webrtc_src"
+#define DEFAULT_FIRST_SINK_ID "webrtc_first_sink"
+#define DEFAULT_SECOND_SINK_ID "webrtc_second_sink"
+#define DEFAULT_ROOM_ID AITT_MANAGED_TOPIC_PREFIX "webrtc/room/Room.webrtc"
+
+class MqttServerTest : public testing::Test {
+ protected:
+ void SetUp() override
+ {
+ webrtc_src_config_ = Config(DEFAULT_WEBRTC_SRC_ID, DEFAULT_BROKER_IP, DEFAULT_BROKER_PORT,
+ DEFAULT_ROOM_ID, DEFAULT_WEBRTC_SRC_ID);
+ webrtc_first_sink_config_ = Config(DEFAULT_FIRST_SINK_ID, DEFAULT_BROKER_IP,
+ DEFAULT_BROKER_PORT, DEFAULT_ROOM_ID);
+ webrtc_second_sink_config_ = Config(DEFAULT_SECOND_SINK_ID, DEFAULT_BROKER_IP,
+ DEFAULT_BROKER_PORT, DEFAULT_ROOM_ID);
+
+ loop_ = g_main_loop_new(nullptr, FALSE);
+ }
+
+ void TearDown() override { g_main_loop_unref(loop_); }
+
+ protected:
+ Config webrtc_src_config_;
+ Config webrtc_first_sink_config_;
+ Config webrtc_second_sink_config_;
+ GMainLoop *loop_;
+};
+static void onConnectionStateChanged(IfaceServer::ConnectionState state, MqttServer &server,
+ GMainLoop *loop)
+{
+ if (state == IfaceServer::ConnectionState::Registered) {
+ EXPECT_EQ(server.IsConnected(), true) << "should return Connected";
+ g_main_loop_quit(loop);
+ }
+}
+
+TEST_F(MqttServerTest, Positive_Connect_Anytime)
+{
+ try {
+ MqttServer server(webrtc_src_config_);
+ EXPECT_EQ(server.IsConnected(), false) << "Should return not connected";
+
+ auto on_connection_state_changed =
+ std::bind(onConnectionStateChanged, std::placeholders::_1, std::ref(server), loop_);
+ server.SetConnectionStateChangedCb(on_connection_state_changed);
+
+ server.Connect();
+
+ g_main_loop_run(loop_);
+
+ server.UnsetConnectionStateChangedCb();
+ server.Disconnect();
+ } catch (...) {
+ FAIL() << "Expected No throw";
+ }
+}
+static int Positive_Connect_Src_Sinks_Anytime_connect_count;
+static void onConnectionStateChangedPositive_Connect_Src_Sinks_Anytime(
+ IfaceServer::ConnectionState state, MqttServer &server, GMainLoop *loop)
+{
+ if (state == IfaceServer::ConnectionState::Registered) {
+ EXPECT_EQ(server.IsConnected(), true) << "should return Connected";
+ ++Positive_Connect_Src_Sinks_Anytime_connect_count;
+ if (Positive_Connect_Src_Sinks_Anytime_connect_count == 3) {
+ g_main_loop_quit(loop);
+ }
+ }
+}
+
+TEST_F(MqttServerTest, Positive_Connect_Src_Sinks_Anytime)
+{
+ try {
+ Positive_Connect_Src_Sinks_Anytime_connect_count = 0;
+ MqttServer src_server(webrtc_src_config_);
+ EXPECT_EQ(src_server.IsConnected(), false) << "Should return not connected";
+
+ auto on_src_connection_state_changed =
+ std::bind(onConnectionStateChangedPositive_Connect_Src_Sinks_Anytime,
+ std::placeholders::_1, std::ref(src_server), loop_);
+ src_server.SetConnectionStateChangedCb(on_src_connection_state_changed);
+
+ src_server.Connect();
+
+ MqttServer first_sink_server(webrtc_first_sink_config_);
+ EXPECT_EQ(first_sink_server.IsConnected(), false) << "Should return not connected";
+
+ auto on_first_sink_connection_state_changed =
+ std::bind(onConnectionStateChangedPositive_Connect_Src_Sinks_Anytime,
+ std::placeholders::_1, std::ref(first_sink_server), loop_);
+ first_sink_server.SetConnectionStateChangedCb(on_first_sink_connection_state_changed);
+
+ first_sink_server.Connect();
+
+ MqttServer second_sink_server(webrtc_second_sink_config_);
+ EXPECT_EQ(second_sink_server.IsConnected(), false) << "Should return not connected";
+
+ auto on_second_sink_connection_state_changed =
+ std::bind(onConnectionStateChangedPositive_Connect_Src_Sinks_Anytime,
+ std::placeholders::_1, std::ref(second_sink_server), loop_);
+ second_sink_server.SetConnectionStateChangedCb(on_second_sink_connection_state_changed);
+
+ second_sink_server.Connect();
+
+ g_main_loop_run(loop_);
+
+ src_server.UnsetConnectionStateChangedCb();
+ first_sink_server.UnsetConnectionStateChangedCb();
+ second_sink_server.UnsetConnectionStateChangedCb();
+ src_server.Disconnect();
+ first_sink_server.Disconnect();
+ second_sink_server.Disconnect();
+ } catch (...) {
+ FAIL() << "Expected No throw";
+ }
+}
+
+TEST_F(MqttServerTest, Negative_Disconnect_Anytime)
+{
+ EXPECT_THROW(
+ {
+ try {
+ MqttServer server(webrtc_src_config_);
+ EXPECT_EQ(server.IsConnected(), false) << "Should return not connected";
+
+ server.Disconnect();
+
+ g_main_loop_run(loop_);
+ } catch (const aitt::AITTEx &e) {
+ // and this tests that it has the correct message
+ throw;
+ }
+ },
+ aitt::AITTEx);
+}
+
+TEST_F(MqttServerTest, Positive_Disconnect_Anytime)
+{
+ try {
+ MqttServer server(webrtc_src_config_);
+ EXPECT_EQ(server.IsConnected(), false);
+
+ auto on_connection_state_changed =
+ std::bind(onConnectionStateChanged, std::placeholders::_1, std::ref(server), loop_);
+ server.SetConnectionStateChangedCb(on_connection_state_changed);
+
+ server.Connect();
+
+ g_main_loop_run(loop_);
+
+ server.UnsetConnectionStateChangedCb();
+ server.Disconnect();
+
+ EXPECT_EQ(server.IsConnected(), false) << "Should return not connected";
+ } catch (...) {
+ FAIL() << "Expected No throw";
+ }
+}
+
+TEST_F(MqttServerTest, Negative_Register_Anytime)
+{
+ EXPECT_THROW(
+ {
+ try {
+ MqttServer server(webrtc_src_config_);
+ EXPECT_EQ(server.IsConnected(), false) << "Should return not connected";
+
+ server.RegisterWithServer();
+ } catch (const std::runtime_error &e) {
+ // and this tests that it has the correct message
+ throw;
+ }
+ },
+ std::runtime_error);
+}
+
+TEST_F(MqttServerTest, Negative_JoinRoom_Invalid_Parameter_Anytime)
+{
+ EXPECT_THROW(
+ {
+ try {
+ MqttServer server(webrtc_src_config_);
+ EXPECT_EQ(server.IsConnected(), false) << "Should return not connected";
+
+ server.JoinRoom(std::string("InvalidRoomId"));
+
+ } catch (const std::runtime_error &e) {
+ // and this tests that it has the correct message
+ throw;
+ }
+ },
+ std::runtime_error);
+}
+
+static void joinRoomOnRegisteredQuit(IfaceServer::ConnectionState state, MqttServer &server,
+ GMainLoop *loop)
+{
+ if (state != IfaceServer::ConnectionState::Registered) {
+ return;
+ }
+
+ EXPECT_EQ(server.IsConnected(), true) << "should return Connected";
+ try {
+ server.JoinRoom(DEFAULT_ROOM_ID);
+ g_main_loop_quit(loop);
+ } catch (...) {
+ FAIL() << "Expected No throw";
+ }
+}
+
+TEST_F(MqttServerTest, Positive_JoinRoom_Anytime)
+{
+ try {
+ MqttServer server(webrtc_src_config_);
+ EXPECT_EQ(server.IsConnected(), false) << "Should return not connected";
+
+ auto join_room_on_registered =
+ std::bind(joinRoomOnRegisteredQuit, std::placeholders::_1, std::ref(server), loop_);
+ server.SetConnectionStateChangedCb(join_room_on_registered);
+
+ server.Connect();
+
+ g_main_loop_run(loop_);
+
+ server.UnsetConnectionStateChangedCb();
+ server.Disconnect();
+ } catch (...) {
+ FAIL() << "Expected No throw";
+ }
+}
+
+static void joinRoomOnRegistered(IfaceServer::ConnectionState state, MqttServer &server)
+{
+ if (state != IfaceServer::ConnectionState::Registered) {
+ return;
+ }
+
+ EXPECT_EQ(server.IsConnected(), true) << "should return Connected";
+ try {
+ server.JoinRoom(DEFAULT_ROOM_ID);
+ } catch (...) {
+ FAIL() << "Expected No throw";
+ }
+}
+
+static void onSrcMessage(const std::string &msg, MqttServer &server, GMainLoop *loop)
+{
+ if (msg.compare(0, 16, "ROOM_PEER_JOINED") == 0) {
+ std::string peer_id = msg.substr(17, std::string::npos);
+ EXPECT_EQ(peer_id.compare(std::string(DEFAULT_FIRST_SINK_ID)), 0)
+ << "Not expected peer" << peer_id;
+
+ } else if (msg.compare(0, 14, "ROOM_PEER_LEFT") == 0) {
+ std::string peer_id = msg.substr(15, std::string::npos);
+ EXPECT_EQ(peer_id.compare(std::string(DEFAULT_FIRST_SINK_ID)), 0)
+ << "Not expected peer" << peer_id;
+ g_main_loop_quit(loop);
+ } else {
+ FAIL() << "Invalid type of Room message " << msg;
+ }
+}
+
+static void onSinkMessage(const std::string &msg, MqttServer &server, GMainLoop *loop)
+{
+ if (msg.compare(0, 16, "ROOM_PEER_JOINED") == 0) {
+ std::string peer_id = msg.substr(17, std::string::npos);
+ EXPECT_EQ(peer_id.compare(std::string(DEFAULT_WEBRTC_SRC_ID)), 0)
+ << "Not expected peer" << peer_id;
+ server.Disconnect();
+ } else {
+ FAIL() << "Invalid type of Room message " << msg;
+ }
+}
+
+TEST_F(MqttServerTest, Positive_src_sink)
+{
+ try {
+ MqttServer src_server(webrtc_src_config_);
+ auto join_room_on_registered_src =
+ std::bind(joinRoomOnRegistered, std::placeholders::_1, std::ref(src_server));
+ src_server.SetConnectionStateChangedCb(join_room_on_registered_src);
+
+ auto on_src_message =
+ std::bind(onSrcMessage, std::placeholders::_1, std::ref(src_server), loop_);
+ src_server.SetRoomMessageArrivedCb(on_src_message);
+ src_server.Connect();
+
+ MqttServer sink_server(webrtc_first_sink_config_);
+ auto join_room_on_registered_sink =
+ std::bind(joinRoomOnRegistered, std::placeholders::_1, std::ref(sink_server));
+ sink_server.SetConnectionStateChangedCb(join_room_on_registered_sink);
+
+ auto on_sink_message =
+ std::bind(onSinkMessage, std::placeholders::_1, std::ref(sink_server), loop_);
+ sink_server.SetRoomMessageArrivedCb(on_sink_message);
+
+ sink_server.Connect();
+
+ g_main_loop_run(loop_);
+
+ src_server.UnsetConnectionStateChangedCb();
+ sink_server.UnsetConnectionStateChangedCb();
+ src_server.Disconnect();
+ } catch (...) {
+ FAIL() << "Expected No throw";
+ }
+}
+
+TEST_F(MqttServerTest, Positive_sink_src)
+{
+ try {
+ MqttServer sink_server(webrtc_first_sink_config_);
+ auto join_room_on_registered_sink =
+ std::bind(joinRoomOnRegistered, std::placeholders::_1, std::ref(sink_server));
+ sink_server.SetConnectionStateChangedCb(join_room_on_registered_sink);
+
+ auto on_sink_message =
+ std::bind(onSinkMessage, std::placeholders::_1, std::ref(sink_server), loop_);
+ sink_server.SetRoomMessageArrivedCb(on_sink_message);
+
+ sink_server.Connect();
+
+ MqttServer src_server(webrtc_src_config_);
+ auto join_room_on_registered_src =
+ std::bind(joinRoomOnRegistered, std::placeholders::_1, std::ref(src_server));
+ src_server.SetConnectionStateChangedCb(join_room_on_registered_src);
+
+ auto on_src_message =
+ std::bind(onSrcMessage, std::placeholders::_1, std::ref(src_server), loop_);
+ src_server.SetRoomMessageArrivedCb(on_src_message);
+ src_server.Connect();
+
+ g_main_loop_run(loop_);
+
+ src_server.UnsetConnectionStateChangedCb();
+ sink_server.UnsetConnectionStateChangedCb();
+ src_server.Disconnect();
+ } catch (...) {
+ FAIL() << "Expected No throw";
+ }
+}
+
+static void onSrcMessageDisconnect(const std::string &msg, MqttServer &server, GMainLoop *loop)
+{
+ if (msg.compare(0, 16, "ROOM_PEER_JOINED") == 0) {
+ std::string peer_id = msg.substr(17, std::string::npos);
+ EXPECT_EQ(peer_id.compare(std::string(DEFAULT_FIRST_SINK_ID)), 0)
+ << "Not expected peer" << peer_id;
+ server.Disconnect();
+
+ } else {
+ FAIL() << "Invalid type of Room message " << msg;
+ }
+}
+
+static void onSinkMessageDisconnect(const std::string &msg, MqttServer &server, GMainLoop *loop)
+{
+ if (msg.compare(0, 16, "ROOM_PEER_JOINED") == 0) {
+ std::string peer_id = msg.substr(17, std::string::npos);
+ EXPECT_EQ(peer_id.compare(std::string(DEFAULT_WEBRTC_SRC_ID)), 0)
+ << "Not expected peer" << peer_id;
+ } else if (msg.compare(0, 14, "ROOM_PEER_LEFT") == 0) {
+ std::string peer_id = msg.substr(15, std::string::npos);
+ EXPECT_EQ(peer_id.compare(std::string(DEFAULT_WEBRTC_SRC_ID)), 0)
+ << "Not expected peer" << peer_id;
+ g_main_loop_quit(loop);
+ } else {
+ FAIL() << "Invalid type of Room message " << msg;
+ }
+}
+
+TEST_F(MqttServerTest, Positive_src_sink_disconnect_src_first_Anytime)
+{
+ try {
+ MqttServer src_server(webrtc_src_config_);
+ auto join_room_on_registered_src =
+ std::bind(joinRoomOnRegistered, std::placeholders::_1, std::ref(src_server));
+ src_server.SetConnectionStateChangedCb(join_room_on_registered_src);
+
+ auto on_src_message =
+ std::bind(onSrcMessageDisconnect, std::placeholders::_1, std::ref(src_server), loop_);
+ src_server.SetRoomMessageArrivedCb(on_src_message);
+ src_server.Connect();
+
+ MqttServer sink_server(webrtc_first_sink_config_);
+ auto join_room_on_registered_sink =
+ std::bind(joinRoomOnRegistered, std::placeholders::_1, std::ref(sink_server));
+ sink_server.SetConnectionStateChangedCb(join_room_on_registered_sink);
+
+ auto on_sink_message = std::bind(onSinkMessageDisconnect, std::placeholders::_1,
+ std::ref(sink_server), loop_);
+ sink_server.SetRoomMessageArrivedCb(on_sink_message);
+
+ sink_server.Connect();
+
+ g_main_loop_run(loop_);
+
+ src_server.UnsetConnectionStateChangedCb();
+ sink_server.UnsetConnectionStateChangedCb();
+ sink_server.Disconnect();
+ } catch (...) {
+ FAIL() << "Expected No throw";
+ }
+}
+
+TEST_F(MqttServerTest, Positive_sink_src_disconnect_src_first_Anytime)
+{
+ try {
+ MqttServer sink_server(webrtc_first_sink_config_);
+ auto join_room_on_registered_sink =
+ std::bind(joinRoomOnRegistered, std::placeholders::_1, std::ref(sink_server));
+ sink_server.SetConnectionStateChangedCb(join_room_on_registered_sink);
+
+ auto on_sink_message = std::bind(onSinkMessageDisconnect, std::placeholders::_1,
+ std::ref(sink_server), loop_);
+ sink_server.SetRoomMessageArrivedCb(on_sink_message);
+
+ sink_server.Connect();
+
+ MqttServer src_server(webrtc_src_config_);
+ auto join_room_on_registered_src =
+ std::bind(joinRoomOnRegistered, std::placeholders::_1, std::ref(src_server));
+ src_server.SetConnectionStateChangedCb(join_room_on_registered_src);
+
+ auto on_src_message =
+ std::bind(onSrcMessageDisconnect, std::placeholders::_1, std::ref(src_server), loop_);
+ src_server.SetRoomMessageArrivedCb(on_src_message);
+ src_server.Connect();
+
+ g_main_loop_run(loop_);
+
+ src_server.UnsetConnectionStateChangedCb();
+ sink_server.UnsetConnectionStateChangedCb();
+ sink_server.Disconnect();
+ } catch (...) {
+ FAIL() << "Expected No throw";
+ }
+}
+
+static int handled_sink;
+static int expected_sink;
+
+std::set<std::string> sink_set;
+
+static void onSrcMessageThreeWay(const std::string &msg, MqttServer &server, GMainLoop *loop)
+{
+ if (msg.compare(0, 16, "ROOM_PEER_JOINED") == 0) {
+ auto peer_id = msg.substr(17, std::string::npos);
+ sink_set.insert(peer_id);
+ server.SendMessage(peer_id, "Three");
+
+ } else if (msg.compare(0, 14, "ROOM_PEER_LEFT") == 0) {
+ auto peer_id = msg.substr(15, std::string::npos);
+
+ if (sink_set.find(peer_id) != sink_set.end())
+ sink_set.erase(peer_id);
+
+ if (sink_set.size() == 0 && handled_sink == expected_sink)
+ g_main_loop_quit(loop);
+
+ } else if (msg.compare(0, 13, "ROOM_PEER_MSG") == 0) {
+ auto peer_msg = msg.substr(14, std::string::npos);
+ std::size_t pos = peer_msg.find(' ');
+ if (pos == std::string::npos)
+ FAIL() << "Invalid type of peer message" << msg;
+
+ auto peer_id = peer_msg.substr(0, pos);
+ auto received_msg = peer_msg.substr(pos + 1, std::string::npos);
+
+ if (received_msg.compare("Way") == 0) {
+ server.SendMessage(peer_id, "HandShake");
+ ++handled_sink;
+ } else
+ FAIL() << "Can't understand message" << received_msg;
+
+ } else {
+ FAIL() << "Invalid type of Room message " << msg;
+ }
+}
+
+static void onSinkMessageThreeWay(const std::string &msg, MqttServer &server)
+{
+ if (msg.compare(0, 16, "ROOM_PEER_JOINED") == 0) {
+ auto peer_id = msg.substr(17, std::string::npos);
+
+ EXPECT_EQ(peer_id.compare(std::string(DEFAULT_WEBRTC_SRC_ID)), 0)
+ << "Not expected peer" << peer_id;
+
+ } else if (msg.compare(0, 14, "ROOM_PEER_LEFT") == 0) {
+ auto peer_id = msg.substr(15, std::string::npos);
+
+ EXPECT_EQ(peer_id.compare(std::string(DEFAULT_WEBRTC_SRC_ID)), 0)
+ << "Not expected peer" << peer_id;
+
+ server.Disconnect();
+
+ } else if (msg.compare(0, 13, "ROOM_PEER_MSG") == 0) {
+ auto peer_msg = msg.substr(14, std::string::npos);
+ std::size_t pos = peer_msg.find(' ');
+ if (pos == std::string::npos)
+ FAIL() << "Invalid type of peer message" << msg;
+
+ auto peer_id = peer_msg.substr(0, pos);
+ auto received_msg = peer_msg.substr(pos + 1, std::string::npos);
+
+ EXPECT_EQ(peer_id.compare(std::string(DEFAULT_WEBRTC_SRC_ID)), 0)
+ << "Not expected peer " << peer_id;
+
+ if (received_msg.compare("Three") == 0)
+ server.SendMessage(peer_id, "Way");
+ else if (received_msg.compare("HandShake") == 0)
+ server.Disconnect();
+ else
+ FAIL() << "Can't understand message" << received_msg;
+ } else {
+ FAIL() << "Invalid type of Room message " << msg;
+ }
+}
+
+TEST_F(MqttServerTest, Positive_SendMessageThreeWay_Src_Sinks1_Anytime)
+{
+ try {
+ handled_sink = 0;
+ expected_sink = 2;
+ MqttServer src_server(webrtc_src_config_);
+
+ auto join_room_on_registered_src =
+ std::bind(joinRoomOnRegistered, std::placeholders::_1, std::ref(src_server));
+ src_server.SetConnectionStateChangedCb(join_room_on_registered_src);
+
+ auto on_src_message =
+ std::bind(onSrcMessageThreeWay, std::placeholders::_1, std::ref(src_server), loop_);
+ src_server.SetRoomMessageArrivedCb(on_src_message);
+ src_server.Connect();
+
+ MqttServer first_sink_server(webrtc_first_sink_config_);
+
+ auto join_room_on_registered_first_sink =
+ std::bind(joinRoomOnRegistered, std::placeholders::_1, std::ref(first_sink_server));
+ first_sink_server.SetConnectionStateChangedCb(join_room_on_registered_first_sink);
+
+ auto on_first_sink_message =
+ std::bind(onSinkMessageThreeWay, std::placeholders::_1, std::ref(first_sink_server));
+ first_sink_server.SetRoomMessageArrivedCb(on_first_sink_message);
+ first_sink_server.Connect();
+
+ MqttServer second_sink_server(webrtc_second_sink_config_);
+
+ auto join_room_on_registered_second_sink =
+ std::bind(joinRoomOnRegistered, std::placeholders::_1, std::ref(second_sink_server));
+ second_sink_server.SetConnectionStateChangedCb(join_room_on_registered_second_sink);
+
+ auto on_second_sink_message =
+ std::bind(onSinkMessageThreeWay, std::placeholders::_1, std::ref(second_sink_server));
+ second_sink_server.SetRoomMessageArrivedCb(on_second_sink_message);
+
+ second_sink_server.Connect();
+
+ g_main_loop_run(loop_);
+
+ src_server.UnsetConnectionStateChangedCb();
+ first_sink_server.UnsetConnectionStateChangedCb();
+ second_sink_server.UnsetConnectionStateChangedCb();
+ src_server.Disconnect();
+ } catch (...) {
+ FAIL() << "Expected No throw";
+ }
+}
diff --git a/packaging/aitt.manifest b/packaging/aitt.manifest
new file mode 100644
index 0000000..a76fdba
--- /dev/null
+++ b/packaging/aitt.manifest
@@ -0,0 +1,5 @@
+<manifest>
+ <request>
+ <domain name="_" />
+ </request>
+</manifest>
diff --git a/packaging/aitt.spec b/packaging/aitt.spec
new file mode 100644
index 0000000..50aaa78
--- /dev/null
+++ b/packaging/aitt.spec
@@ -0,0 +1,104 @@
+Name: aitt
+Version: 0.0.1
+Release: 0
+Summary: AI Telemetry Transport based on MQTT
+
+Group: Machine Learning / ML Framework
+License: Apache-2.0
+Source0: %{name}-%{version}.tar.gz
+Source1001: %{name}.manifest
+
+%{!?stdoutlog: %global stdoutlog 0}
+%{!?test: %global test 1}
+%{!?gcov: %global gcov 0}
+
+BuildRequires: cmake
+BuildRequires: pkgconfig(dlog)
+BuildRequires: pkgconfig(flatbuffers)
+BuildRequires: pkgconfig(glib-2.0)
+BuildRequires: pkgconfig(libmosquitto)
+BuildRequires: pkgconfig(gmock_main)
+BuildRequires: pkgconfig(capi-media-tool)
+BuildRequires: pkgconfig(capi-media-sound-manager)
+BuildRequires: pkgconfig(bundle)
+BuildRequires: elementary-tizen
+BuildRequires: pkgconfig(capi-media-webrtc)
+BuildRequires: pkgconfig(capi-media-camera)
+BuildRequires: pkgconfig(json-glib-1.0)
+%if 0%{gcov}
+BuildRequires: lcov
+%endif
+
+%description
+AITT is a Framework which transfers data of AI service.
+It makes distributed AI Inference possible.
+
+%package plugins
+Summary: Plugin Libraries for AITT P2P transport
+Group: Machine Learning / ML Framework
+Requires: %{name} = %{version}
+
+%description plugins
+The %{name}-plugins package contains basic plugin libraries for AITT P2P transport.
+
+%package devel
+Summary: AITT development package
+Group: Development/Libraries
+Requires: %{name} = %{version}-%{release}
+
+%description devel
+The %{name}-devel package contains libraries and header files for
+developing programs that use %{name}.
+
+%prep
+%setup -q
+cp %{SOURCE1001} .
+
+%build
+%cmake . \
+ -DLOG_STDOUT:BOOL=%{stdoutlog} \
+ -DPLATFORM="tizen" \
+ -DVERSIONING:BOOL=OFF \
+ -DWITH_WEBRTC:BOOL=ON \
+ -DCMAKE_INSTALL_PREFIX:PATH=%{_prefix} \
+ -DCMAKE_VERBOSE_MAKEFILE=OFF \
+ -DBUILD_TESTING:BOOL=%{test} \
+ -DCOVERAGE_TEST:BOOL=%{gcov}
+
+%__make %{?_smp_mflags}
+
+%install
+%make_install
+
+%check
+ctest --output-on-failure --timeout 30 || true
+
+%if 0%{test} && 0%{gcov}
+# Extract coverage information
+lcov -c --ignore-errors graph --no-external -b . -d . -o %{name}_gcov.info
+genhtml %{name}_gcov.info -o out --legend --show-details
+%endif
+
+%post -p /sbin/ldconfig
+
+%postun -p /sbin/ldconfig
+
+%files
+%manifest %{name}.manifest
+%if 0%{test}
+%{_bindir}/*
+%endif
+%{_libdir}/lib%{name}*.so*
+%license LICENSE.APLv2
+
+%files plugins
+%manifest %{name}.manifest
+%{_libdir}/lib%{name}-transport*.so*
+%license LICENSE.APLv2
+
+%files devel
+%{_includedir}/*
+%{_libdir}/pkgconfig/*.pc
+
+%clean
+rm -rf %{buildroot}
diff --git a/settings.gradle b/settings.gradle
new file mode 100644
index 0000000..a8e5154
--- /dev/null
+++ b/settings.gradle
@@ -0,0 +1,4 @@
+include ':android:aitt'
+include ':android:flatbuffers'
+include ':android:mosquitto'
+include ':android:modules:webrtc'
diff --git a/src/AITT.cc b/src/AITT.cc
new file mode 100644
index 0000000..c50332d
--- /dev/null
+++ b/src/AITT.cc
@@ -0,0 +1,137 @@
+/*
+ * Copyright (c) 2021-2022 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <memory>
+#include <random>
+
+#include "AITTImpl.h"
+#include "aitt_internal.h"
+
+namespace aitt {
+
+AITT::AITT(const std::string &id, const std::string &ip_addr, bool clear_session)
+{
+ std::string valid_id = id;
+ std::string valid_ip = ip_addr;
+
+ if (id.empty()) {
+ const char character_set[] =
+ "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
+ std::mt19937 random_gen{std::random_device{}()};
+ std::uniform_int_distribution<std::string::size_type> gen(0, 61);
+ char name[16];
+ for (size_t i = 0; i < sizeof(name); i++) {
+ name[i] = character_set[gen(random_gen)];
+ }
+ valid_id = "aitt-" + std::string(name, sizeof(name) - 1);
+ DBG("Generated name = %s", valid_id.c_str());
+ }
+
+ if (ip_addr.empty())
+ valid_ip = "127.0.0.1";
+
+ pImpl = std::make_unique<AITT::Impl>(*this, valid_id, valid_ip, clear_session);
+}
+
+AITT::~AITT(void)
+{
+}
+
+void AITT::SetWillInfo(const std::string &topic, const void *data, const size_t datalen,
+ AittQoS qos, bool retain)
+{
+ return pImpl->SetWillInfo(topic, data, datalen, qos, retain);
+}
+
+void AITT::SetConnectionCallback(ConnectionCallback cb, void *user_data)
+{
+ return pImpl->SetConnectionCallback(cb, user_data);
+}
+
+void AITT::Connect(const std::string &host, int port, const std::string &username,
+ const std::string &password)
+{
+ return pImpl->Connect(host, port, username, password);
+}
+
+void AITT::Disconnect(void)
+{
+ return pImpl->Disconnect();
+}
+
+void AITT::Publish(const std::string &topic, const void *data, const size_t datalen,
+ AittProtocol protocols, AittQoS qos, bool retain)
+{
+ if (AITT_PAYLOAD_MAX < datalen) {
+ ERR("Invalid Size(%zu)", datalen);
+ throw std::runtime_error("Invalid Size");
+ }
+
+ return pImpl->Publish(topic, data, datalen, protocols, qos, retain);
+}
+
+int AITT::PublishWithReply(const std::string &topic, const void *data, const size_t datalen,
+ AittProtocol protocol, AittQoS qos, bool retain, const SubscribeCallback &cb, void *cbdata,
+ const std::string &correlation)
+{
+ if (AITT_PAYLOAD_MAX < datalen) {
+ ERR("Invalid Size(%zu)", datalen);
+ throw std::runtime_error("Invalid Size");
+ }
+
+ return pImpl->PublishWithReply(topic, data, datalen, protocol, qos, retain, cb, cbdata,
+ correlation);
+}
+
+int AITT::PublishWithReplySync(const std::string &topic, const void *data, const size_t datalen,
+ AittProtocol protocol, AittQoS qos, bool retain, const SubscribeCallback &cb, void *cbdata,
+ const std::string &correlation, int timeout_ms)
+{
+ if (AITT_PAYLOAD_MAX < datalen) {
+ ERR("Invalid Size(%zu)", datalen);
+ throw std::runtime_error("Invalid Size");
+ }
+
+ return pImpl->PublishWithReplySync(topic, data, datalen, protocol, qos, retain, cb, cbdata,
+ correlation, timeout_ms);
+}
+
+AittSubscribeID AITT::Subscribe(const std::string &topic, const SubscribeCallback &cb, void *cbdata,
+ AittProtocol protocols, AittQoS qos)
+{
+ return pImpl->Subscribe(topic, cb, cbdata, protocols, qos);
+}
+
+void *AITT::Unsubscribe(AittSubscribeID handle)
+{
+ return pImpl->Unsubscribe(handle);
+}
+
+void AITT::SendReply(MSG *msg, const void *data, size_t datalen, bool end)
+{
+ if (AITT_PAYLOAD_MAX < datalen) {
+ ERR("Invalid Size(%zu)", datalen);
+ throw std::runtime_error("Invalid Size");
+ }
+
+ return pImpl->SendReply(msg, data, datalen, end);
+}
+
+bool AITT::CompareTopic(const std::string &left, const std::string &right)
+{
+ return MQ::CompareTopic(left, right);
+}
+
+} // namespace aitt
diff --git a/src/AITTImpl.cc b/src/AITTImpl.cc
new file mode 100644
index 0000000..b851625
--- /dev/null
+++ b/src/AITTImpl.cc
@@ -0,0 +1,437 @@
+/*
+ * Copyright (c) 2021-2022 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "AITTImpl.h"
+
+#include <flatbuffers/flexbuffers.h>
+
+#include <cerrno>
+#include <cstring>
+#include <functional>
+#include <memory>
+#include <stdexcept>
+
+#include "aitt_internal.h"
+
+#define WEBRTC_ROOM_ID_PREFIX std::string(AITT_MANAGED_TOPIC_PREFIX "webrtc/room/Room.webrtc")
+#define WEBRTC_ID_POSTFIX std::string("_for_webrtc")
+
+namespace aitt {
+
+AITT::Impl::Impl(AITT &parent, const std::string &id, const std::string &ipAddr, bool clearSession)
+ : public_api(parent),
+ id_(id),
+ mq(id, clearSession),
+ discovery(id),
+ reply_id(0),
+ modules(ipAddr)
+{
+ // TODO:
+ // Validate ipAddr
+
+ aittThread = std::thread(&AITT::Impl::ThreadMain, this);
+}
+
+AITT::Impl::~Impl(void)
+{
+ if (false == mqtt_broker_ip_.empty())
+ Disconnect();
+
+ while (main_loop.Quit() == false) {
+ // wait when called before the thread has completely created.
+ usleep(1000); // 1millisecond
+ }
+
+ if (aittThread.joinable())
+ aittThread.join();
+}
+
+void AITT::Impl::ThreadMain(void)
+{
+ pthread_setname_np(pthread_self(), "AITTWorkerLoop");
+ main_loop.Run();
+}
+
+void AITT::Impl::SetWillInfo(const std::string &topic, const void *data, const size_t datalen,
+ AittQoS qos, bool retain)
+{
+ mq.SetWillInfo(topic, data, datalen, qos, retain);
+}
+
+void AITT::Impl::SetConnectionCallback(ConnectionCallback cb, void *user_data)
+{
+ if (cb)
+ mq.SetConnectionCallback(
+ std::bind(&Impl::ConnectionCB, this, cb, user_data, std::placeholders::_1));
+ else
+ mq.SetConnectionCallback(nullptr);
+}
+
+void AITT::Impl::ConnectionCB(ConnectionCallback cb, void *user_data, int status)
+{
+ RET_IF(cb == nullptr);
+
+ cb(public_api, status, user_data);
+}
+
+void AITT::Impl::Connect(const std::string &host, int port, const std::string &username,
+ const std::string &password)
+{
+ modules.Init(discovery);
+
+ discovery.Start(host, port, username, password);
+ mq.Connect(host, port, username, password);
+
+ mqtt_broker_ip_ = host;
+ mqtt_broker_port_ = port;
+}
+
+void AITT::Impl::Disconnect(void)
+{
+ UnsubscribeAll();
+
+ mqtt_broker_ip_.clear();
+ mqtt_broker_port_ = -1;
+
+ mq.Disconnect();
+ discovery.Stop();
+}
+
+void AITT::Impl::UnsubscribeAll()
+{
+ std::unique_lock<std::mutex> lock(subscribed_list_mutex_);
+
+ for (auto subscribe_info : subscribed_list) {
+ switch (subscribe_info->first) {
+ case AITT_TYPE_MQTT:
+ mq.Unsubscribe(subscribe_info->second);
+ break;
+
+ case AITT_TYPE_TCP:
+ case AITT_TYPE_WEBRTC:
+ modules.GetInstance(subscribe_info->first)->Unsubscribe(subscribe_info->second);
+ break;
+
+ default:
+ ERR("Unknown AittProtocol(%d)", subscribe_info->first);
+ break;
+ }
+
+ delete subscribe_info;
+ }
+ subscribed_list.clear();
+}
+
+void AITT::Impl::ConfigureTransportModule(const std::string &key, const std::string &value,
+ AittProtocol protocols)
+{
+}
+
+void AITT::Impl::Publish(const std::string &topic, const void *data, const size_t datalen,
+ AittProtocol protocols, AittQoS qos, bool retain)
+{
+ if ((protocols & AITT_TYPE_MQTT) == AITT_TYPE_MQTT)
+ mq.Publish(topic, data, datalen, qos, retain);
+
+ // NOTE:
+ // Invoke the publish method of the specified transport module
+ if ((protocols & AITT_TYPE_TCP) == AITT_TYPE_TCP) {
+ auto tcpModule = modules.GetInstance(AITT_TYPE_TCP);
+ tcpModule->Publish(topic, data, datalen, qos, retain);
+ }
+ if ((protocols & AITT_TYPE_WEBRTC) == AITT_TYPE_WEBRTC) {
+ PublishWebRtc(topic, data, datalen, qos, retain);
+ }
+}
+
+void AITT::Impl::PublishWebRtc(const std::string &topic, const void *data, const size_t datalen,
+ AittQoS qos, bool retain)
+{
+ auto webrtcModule = modules.GetInstance(AITT_TYPE_WEBRTC);
+ flexbuffers::Builder fbb;
+ fbb.Map([=, &fbb]() {
+ fbb.String("Id", id_ + WEBRTC_ID_POSTFIX);
+ fbb.String("BrokerIp", mqtt_broker_ip_);
+ fbb.Int("BrokerPort", mqtt_broker_port_);
+ fbb.String("RoomId", WEBRTC_ROOM_ID_PREFIX + topic);
+ fbb.String("SourceId", id_ + WEBRTC_ID_POSTFIX);
+ // TODO pass user data to WEBRTC module
+ fbb.UInt("UserDataLength", datalen);
+ });
+ fbb.Finish();
+ auto buf = fbb.GetBuffer();
+ webrtcModule->Publish(topic, buf.data(), buf.size(), qos, retain);
+}
+
+AittSubscribeID AITT::Impl::Subscribe(const std::string &topic, const AITT::SubscribeCallback &cb,
+ void *user_data, AittProtocol protocol, AittQoS qos)
+{
+ SubscribeInfo *info = new SubscribeInfo();
+ info->first = protocol;
+
+ void *subscribe_handle;
+ switch (protocol) {
+ case AITT_TYPE_MQTT:
+ subscribe_handle = SubscribeMQ(info, &main_loop, topic, cb, user_data, qos);
+ break;
+ case AITT_TYPE_TCP:
+ subscribe_handle = SubscribeTCP(info, topic, cb, user_data, qos);
+ break;
+ case AITT_TYPE_WEBRTC:
+ subscribe_handle = SubscribeWebRtc(info, topic, cb, user_data, qos);
+ break;
+ default:
+ ERR("Unknown AittProtocol(%d)", protocol);
+ delete info;
+ throw std::runtime_error("Unknown AittProtocol");
+ }
+ info->second = subscribe_handle;
+ {
+ std::unique_lock<std::mutex> lock(subscribed_list_mutex_);
+ subscribed_list.push_back(info);
+ }
+
+ INFO("Subscribe topic(%s) : %p", topic.c_str(), info);
+ return reinterpret_cast<AittSubscribeID>(info);
+}
+
+AittSubscribeID AITT::Impl::SubscribeMQ(SubscribeInfo *handle, MainLoopHandler *loop_handle,
+ const std::string &topic, const SubscribeCallback &cb, void *user_data, AittQoS qos)
+{
+ return mq.Subscribe(
+ topic,
+ [this, handle, loop_handle, cb](MSG *msg, const std::string &topic, const void *data,
+ const size_t datalen, void *mq_user_data) {
+ void *delivery = malloc(datalen);
+ if (delivery)
+ memcpy(delivery, data, datalen);
+
+ msg->SetID(handle);
+ auto idler_cb =
+ std::bind(&Impl::DetachedCB, this, cb, *msg, delivery, datalen, mq_user_data,
+ std::placeholders::_1, std::placeholders::_2, std::placeholders::_3);
+ MainLoopHandler::AddIdle(loop_handle, idler_cb, nullptr);
+ },
+ user_data, qos);
+}
+
+void AITT::Impl::DetachedCB(SubscribeCallback cb, MSG msg, void *data, const size_t datalen,
+ void *user_data, MainLoopHandler::MainLoopResult result, int fd,
+ MainLoopHandler::MainLoopData *loop_data)
+{
+ RET_IF(cb == nullptr);
+
+ cb(&msg, data, datalen, user_data);
+
+ free(data);
+}
+
+void *AITT::Impl::Unsubscribe(AittSubscribeID subscribe_id)
+{
+ INFO("[%s] %p", __func__, subscribe_id);
+ SubscribeInfo *info = reinterpret_cast<SubscribeInfo *>(subscribe_id);
+
+ std::unique_lock<std::mutex> lock(subscribed_list_mutex_);
+
+ auto it = std::find(subscribed_list.begin(), subscribed_list.end(), info);
+ if (it == subscribed_list.end()) {
+ ERR("Unknown subscribe_id(%p)", subscribe_id);
+ throw std::runtime_error("subscribe_id");
+ }
+
+ void *user_data = nullptr;
+ SubscribeInfo *found_info = *it;
+ switch (found_info->first) {
+ case AITT_TYPE_MQTT:
+ user_data = mq.Unsubscribe(found_info->second);
+ break;
+ case AITT_TYPE_TCP: {
+ auto tcpModule = modules.GetInstance(AITT_TYPE_TCP);
+ user_data = tcpModule->Unsubscribe(found_info->second);
+ break;
+ }
+ case AITT_TYPE_WEBRTC: {
+ auto webrtcModule = modules.GetInstance(AITT_TYPE_WEBRTC);
+ user_data = webrtcModule->Unsubscribe(found_info->second);
+ break;
+ }
+ default:
+ ERR("Unknown AittProtocol(%d)", found_info->first);
+ break;
+ }
+
+ subscribed_list.erase(it);
+ delete info;
+
+ return user_data;
+}
+
+int AITT::Impl::PublishWithReply(const std::string &topic, const void *data, const size_t datalen,
+ AittProtocol protocol, AittQoS qos, bool retain, const SubscribeCallback &cb, void *user_data,
+ const std::string &correlation)
+{
+ std::string replyTopic = topic + RESPONSE_POSTFIX + std::to_string(reply_id++);
+
+ if (protocol != AITT_TYPE_MQTT)
+ return -1; // not yet support
+
+ Subscribe(
+ replyTopic,
+ [this, cb](MSG *sub_msg, const void *sub_data, const size_t sub_datalen,
+ void *sub_cbdata) {
+ if (sub_msg->IsEndSequence()) {
+ try {
+ Unsubscribe(sub_msg->GetID());
+ } catch (std::runtime_error &e) {
+ ERR("Unsubscribe() Fail(%s)", e.what());
+ }
+ }
+ cb(sub_msg, sub_data, sub_datalen, sub_cbdata);
+ },
+ user_data, protocol, qos);
+
+ mq.PublishWithReply(topic, data, datalen, qos, false, replyTopic, correlation);
+ return 0;
+}
+
+int AITT::Impl::PublishWithReplySync(const std::string &topic, const void *data,
+ const size_t datalen, AittProtocol protocol, AittQoS qos, bool retain,
+ const SubscribeCallback &cb, void *user_data, const std::string &correlation, int timeout_ms)
+{
+ std::string replyTopic = topic + RESPONSE_POSTFIX + std::to_string(reply_id++);
+
+ if (protocol != AITT_TYPE_MQTT)
+ return -1; // not yet support
+
+ SubscribeInfo *info = new SubscribeInfo();
+ info->first = protocol;
+
+ void *subscribe_handle;
+ MainLoopHandler sync_loop;
+ unsigned int timeout_id = 0;
+ bool is_timeout = false;
+
+ subscribe_handle = SubscribeMQ(
+ info, &sync_loop, replyTopic,
+ [&](MSG *sub_msg, const void *sub_data, const size_t sub_datalen, void *sub_cbdata) {
+ if (sub_msg->IsEndSequence()) {
+ try {
+ Unsubscribe(sub_msg->GetID());
+ } catch (std::runtime_error &e) {
+ ERR("Unsubscribe() Fail(%s)", e.what());
+ }
+ sync_loop.Quit();
+ } else {
+ if (timeout_id) {
+ sync_loop.RemoveTimeout(timeout_id);
+ HandleTimeout(timeout_ms, timeout_id, sync_loop, is_timeout);
+ }
+ }
+ cb(sub_msg, sub_data, sub_datalen, sub_cbdata);
+ },
+ user_data, qos);
+ info->second = subscribe_handle;
+ {
+ std::unique_lock<std::mutex> lock(subscribed_list_mutex_);
+ subscribed_list.push_back(info);
+ }
+
+ mq.PublishWithReply(topic, data, datalen, qos, false, replyTopic, correlation);
+ if (timeout_ms)
+ HandleTimeout(timeout_ms, timeout_id, sync_loop, is_timeout);
+
+ sync_loop.Run();
+
+ if (is_timeout)
+ return AITT_ERROR_TIMED_OUT;
+ return 0;
+}
+
+void AITT::Impl::HandleTimeout(int timeout_ms, unsigned int &timeout_id,
+ aitt::MainLoopHandler &sync_loop, bool &is_timeout)
+{
+ timeout_id = sync_loop.AddTimeout(
+ timeout_ms,
+ [&, timeout_ms](MainLoopHandler::MainLoopResult result, int fd,
+ MainLoopHandler::MainLoopData *data) {
+ ERR("PublishWithReplySync() timeout(%d)", timeout_ms);
+ sync_loop.Quit();
+ is_timeout = true;
+ },
+ nullptr);
+}
+
+void AITT::Impl::SendReply(MSG *msg, const void *data, const int datalen, bool end)
+{
+ RET_IF(msg == nullptr);
+
+ if ((msg->GetProtocols() & AITT_TYPE_MQTT) != AITT_TYPE_MQTT)
+ return; // not yet support
+
+ if (end == false || msg->GetSequence())
+ msg->IncreaseSequence();
+ msg->SetEndSequence(end);
+
+ mq.SendReply(msg, data, datalen, AITT_QOS_AT_MOST_ONCE, false);
+}
+
+void *AITT::Impl::SubscribeTCP(SubscribeInfo *handle, const std::string &topic,
+ const SubscribeCallback &cb, void *user_data, AittQoS qos)
+{
+ auto tcpModule = modules.GetInstance(AITT_TYPE_TCP);
+ return tcpModule->Subscribe(
+ topic,
+ [handle, cb](const std::string &topic, const void *data, const size_t datalen,
+ void *user_data, const std::string &correlation) -> void {
+ MSG msg;
+ msg.SetID(handle);
+ msg.SetTopic(topic);
+ msg.SetCorrelation(correlation);
+ msg.SetProtocols(AITT_TYPE_TCP);
+
+ return cb(&msg, data, datalen, user_data);
+ },
+ user_data, qos);
+}
+
+void *AITT::Impl::SubscribeWebRtc(SubscribeInfo *handle, const std::string &topic,
+ const SubscribeCallback &cb, void *user_data, AittQoS qos)
+{
+ auto webrtc_module = modules.GetInstance(AITT_TYPE_WEBRTC);
+ flexbuffers::Builder fbb;
+ fbb.Map([=, &fbb]() {
+ fbb.String("Id", id_ + WEBRTC_ID_POSTFIX);
+ fbb.String("BrokerIp", mqtt_broker_ip_);
+ fbb.String("RoomId", WEBRTC_ROOM_ID_PREFIX + topic);
+ fbb.Int("BrokerPort", mqtt_broker_port_);
+ });
+ fbb.Finish();
+ auto buf = fbb.GetBuffer();
+
+ return webrtc_module->Subscribe(
+ topic,
+ [handle, cb](const std::string &topic, const void *data, const size_t datalen,
+ void *user_data, const std::string &correlation) -> void {
+ MSG msg;
+ msg.SetID(handle);
+ msg.SetTopic(topic);
+ msg.SetCorrelation(correlation);
+ msg.SetProtocols(AITT_TYPE_WEBRTC);
+
+ return cb(&msg, data, datalen, user_data);
+ },
+ buf.data(), buf.size(), user_data, qos);
+}
+} // namespace aitt
diff --git a/src/AITTImpl.h b/src/AITTImpl.h
new file mode 100644
index 0000000..08bec4a
--- /dev/null
+++ b/src/AITTImpl.h
@@ -0,0 +1,103 @@
+/*
+ * Copyright (c) 2021-2022 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+#include <flatbuffers/flexbuffers.h>
+
+#include <map>
+#include <memory>
+#include <mutex>
+#include <string>
+#include <thread>
+#include <utility>
+
+#include "AITT.h"
+#include "AittDiscovery.h"
+#include "MQ.h"
+#include "MainLoopHandler.h"
+#include "TransportModuleLoader.h"
+
+namespace aitt {
+
+class AITT::Impl {
+ public:
+ Impl(AITT &parent, const std::string &id, const std::string &ipAddr, bool clearSession);
+ virtual ~Impl(void);
+
+ void SetWillInfo(const std::string &topic, const void *data, const size_t datalen, AittQoS qos,
+ bool retain);
+ void SetConnectionCallback(ConnectionCallback cb, void *user_data);
+ void Connect(const std::string &host, int port, const std::string &username,
+ const std::string &password);
+ void Disconnect(void);
+
+ void ConfigureTransportModule(const std::string &key, const std::string &value,
+ AittProtocol protocols);
+
+ void Publish(const std::string &topic, const void *data, const size_t datalen,
+ AittProtocol protocols, AittQoS qos, bool retain);
+
+ int PublishWithReply(const std::string &topic, const void *data, const size_t datalen,
+ AittProtocol protocol, AittQoS qos, bool retain, const AITT::SubscribeCallback &cb,
+ void *cbdata, const std::string &correlation);
+
+ int PublishWithReplySync(const std::string &topic, const void *data, const size_t datalen,
+ AittProtocol protocol, AittQoS qos, bool retain, const SubscribeCallback &cb,
+ void *cbdata, const std::string &correlation, int timeout_ms);
+
+ AittSubscribeID Subscribe(const std::string &topic, const AITT::SubscribeCallback &cb,
+ void *cbdata, AittProtocol protocols, AittQoS qos);
+
+ void *Unsubscribe(AittSubscribeID handle);
+
+ void SendReply(MSG *msg, const void *data, const int datalen, bool end);
+
+ private:
+ using Blob = std::pair<const void *, int>;
+ using SubscribeInfo = std::pair<AittProtocol, void *>;
+
+ void ConnectionCB(ConnectionCallback cb, void *user_data, int status);
+ AittSubscribeID SubscribeMQ(SubscribeInfo *info, MainLoopHandler *loop_handle,
+ const std::string &topic, const SubscribeCallback &cb, void *cbdata, AittQoS qos);
+ void DetachedCB(SubscribeCallback cb, MSG mq_msg, void *data, const size_t datalen,
+ void *cbdata, MainLoopHandler::MainLoopResult result, int fd,
+ MainLoopHandler::MainLoopData *loop_data);
+ void *SubscribeTCP(SubscribeInfo *, const std::string &topic, const SubscribeCallback &cb,
+ void *cbdata, AittQoS qos);
+ void *SubscribeWebRtc(SubscribeInfo *, const std::string &topic, const SubscribeCallback &cb,
+ void *cbdata, AittQoS qos);
+ void HandleTimeout(int timeout_ms, unsigned int &timeout_id, aitt::MainLoopHandler &sync_loop,
+ bool &is_timeout);
+ void PublishWebRtc(const std::string &topic, const void *data, const size_t datalen,
+ AittQoS qos, bool retain);
+ void UnsubscribeAll();
+
+ AITT &public_api;
+ std::string id_;
+ std::string mqtt_broker_ip_;
+ int mqtt_broker_port_;
+ MQ mq;
+ AittDiscovery discovery;
+ unsigned short reply_id;
+ TransportModuleLoader modules;
+ MainLoopHandler main_loop;
+ void ThreadMain(void);
+ std::thread aittThread;
+ std::vector<SubscribeInfo *> subscribed_list;
+ std::mutex subscribed_list_mutex_;
+};
+
+} // namespace aitt
diff --git a/src/TransportModuleLoader.cc b/src/TransportModuleLoader.cc
new file mode 100644
index 0000000..a953ac9
--- /dev/null
+++ b/src/TransportModuleLoader.cc
@@ -0,0 +1,105 @@
+/*
+ * Copyright (c) 2021-2022 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "TransportModuleLoader.h"
+
+#include <dlfcn.h>
+
+#include "AITTEx.h"
+#include "aitt_internal.h"
+
+namespace aitt {
+
+TransportModuleLoader::TransportModuleLoader(const std::string &ip) : ip(ip)
+{
+}
+
+std::string TransportModuleLoader::GetModuleFilename(AittProtocol protocol)
+{
+ // TODO:
+ // We are able to generate the module name by a particular syntax,
+ // It could be introduced later when we have several modules.
+ if (protocol == AITT_TYPE_TCP)
+ return "libaitt-transport-tcp.so";
+ if (protocol == AITT_TYPE_WEBRTC)
+ return "libaitt-transport-webrtc.so";
+
+ return std::string();
+}
+
+int TransportModuleLoader::LoadModule(AittProtocol protocol, AittDiscovery &discovery)
+{
+ std::string filename = GetModuleFilename(protocol);
+
+ Handler handle(dlopen(filename.c_str(), RTLD_LAZY | RTLD_LOCAL),
+ [](const void *handle) -> void {
+ if (dlclose(const_cast<void *>(handle)))
+ ERR("dlclose: %s", dlerror());
+ });
+ if (handle == nullptr) {
+ ERR("dlopen: %s", dlerror());
+ return -1;
+ }
+
+ AittTransport::ModuleEntry get_instance_fn = reinterpret_cast<AittTransport::ModuleEntry>(
+ dlsym(handle.get(), AittTransport::MODULE_ENTRY_NAME));
+ if (get_instance_fn == nullptr) {
+ ERR("dlsym: %s", dlerror());
+ return -1;
+ }
+
+ std::shared_ptr<AittTransport> instance(
+ static_cast<AittTransport *>(get_instance_fn(ip.c_str(), discovery)),
+ [](const AittTransport *instance) -> void { delete instance; });
+ if (instance == nullptr) {
+ ERR("Failed to create a new instance");
+ return -1;
+ }
+
+ module_table.emplace(protocol, std::make_pair(std::move(handle), instance));
+
+ return 0;
+}
+
+void TransportModuleLoader::Init(AittDiscovery &discovery)
+{
+ std::lock_guard<std::mutex> lock_from_here(module_lock);
+ if (LoadModule(AITT_TYPE_TCP, discovery) < 0) {
+ ERR("LoadModule(AITT_TYPE_TCP) Fail");
+ }
+
+#ifdef WITH_WEBRTC
+ if (LoadModule(AITT_TYPE_WEBRTC, discovery) < 0) {
+ ERR("LoadModule(AITT_TYPE_WEBRTC) Fail");
+ }
+#endif // WITH_WEBRTC
+}
+
+std::shared_ptr<AittTransport> TransportModuleLoader::GetInstance(AittProtocol protocol)
+{
+ std::lock_guard<std::mutex> lock_from_here(module_lock);
+
+ auto item = module_table.find(protocol);
+ if (item == module_table.end()) {
+ ERR("Not Initialized");
+ // throw AITTEx(AITTEx::NO_DATA, "Not Initialized");
+ return nullptr;
+ }
+
+ return item->second.second;
+}
+
+} // namespace aitt
diff --git a/src/TransportModuleLoader.h b/src/TransportModuleLoader.h
new file mode 100644
index 0000000..576c97e
--- /dev/null
+++ b/src/TransportModuleLoader.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2021-2022 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+#include <AITT.h>
+#include <AittTransport.h>
+
+#include <map>
+#include <memory>
+#include <mutex>
+#include <string>
+
+#include "TransportModuleLoader.h"
+
+namespace aitt {
+
+class TransportModuleLoader {
+ public:
+ explicit TransportModuleLoader(const std::string &ip);
+ virtual ~TransportModuleLoader() = default;
+
+ void Init(AittDiscovery &discovery);
+ std::shared_ptr<AittTransport> GetInstance(AittProtocol protocol);
+
+ private:
+ using Handler = std::unique_ptr<void, void (*)(const void *)>;
+ using ModuleMap = std::map<AittProtocol, std::pair<Handler, std::shared_ptr<AittTransport>>>;
+
+ std::string GetModuleFilename(AittProtocol protocol);
+ int LoadModule(AittProtocol protocol, AittDiscovery &discovery);
+
+ ModuleMap module_table;
+ std::mutex module_lock;
+ std::string ip;
+};
+
+} // namespace aitt
diff --git a/src/aitt_c.cc b/src/aitt_c.cc
new file mode 100644
index 0000000..1135353
--- /dev/null
+++ b/src/aitt_c.cc
@@ -0,0 +1,282 @@
+/*
+ * Copyright (c) 2022 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "aitt_c.h"
+
+#include <arpa/inet.h>
+#include <stdlib.h>
+
+#include "AITT.h"
+#include "aitt_internal.h"
+
+using namespace aitt;
+
+struct aitt_handle {
+ aitt_handle() : aitt(nullptr) {}
+ AITT *aitt;
+ bool connected;
+};
+
+API aitt_h aitt_new(const char *id, const char *my_ip)
+{
+ aitt_h handle = nullptr;
+ try {
+ std::string valid_id;
+ std::string valid_ip;
+
+ if (id)
+ valid_id = id;
+
+ if (my_ip)
+ valid_ip = my_ip;
+
+ DBG("id(%s), ip(%s)", valid_id.c_str(), valid_ip.c_str());
+
+ handle = new aitt_handle();
+ handle->aitt = new AITT(valid_id, valid_ip, true);
+ handle->connected = false;
+ } catch (std::exception &e) {
+ ERR("new() Fail(%s)", e.what());
+ return nullptr;
+ }
+
+ return handle;
+}
+
+API int aitt_set_option(aitt_h handle, aitt_option_e option, const char *value)
+{
+ RETV_IF(handle == nullptr, AITT_ERROR_INVALID_PARAMETER);
+
+ switch (option) {
+ case AITT_OPT_UNKNOWN:
+ try {
+ // something to do
+ } catch (std::exception &e) {
+ ERR("string() Fail(%s)", e.what());
+ return AITT_ERROR_SYSTEM;
+ }
+ break;
+ default:
+ ERR("Unknown option(%d)", option);
+ return AITT_ERROR_INVALID_PARAMETER;
+ }
+
+ return AITT_ERROR_NONE;
+}
+
+API const char *aitt_get_option(aitt_h handle, aitt_option_e option)
+{
+ RETV_IF(handle == nullptr, nullptr);
+
+ switch (option) {
+ case AITT_OPT_UNKNOWN:
+ return "Unknown";
+ default:
+ ERR("Unknown option(%d)", option);
+ }
+
+ return nullptr;
+}
+
+API int aitt_will_set(aitt_h handle, const char *topic, const void *msg, const size_t msg_len,
+ aitt_qos_e qos, bool retain)
+{
+ RETV_IF(handle == nullptr, AITT_ERROR_INVALID_PARAMETER);
+
+ try {
+ handle->aitt->SetWillInfo(topic, msg, msg_len, qos, retain);
+ } catch (std::exception &e) {
+ ERR("SetWillInfo(%s, %zu) Fail(%s)", topic, msg_len, e.what());
+ return AITT_ERROR_SYSTEM;
+ }
+ return AITT_ERROR_NONE;
+}
+
+API void aitt_destroy(aitt_h handle)
+{
+ if (handle == nullptr) {
+ ERR("handle is NULL");
+ return;
+ }
+
+ try {
+ delete handle->aitt;
+ delete handle;
+ } catch (std::exception &e) {
+ ERR("delete() Fail(%s)", e.what());
+ }
+}
+
+static bool is_valid_ip(const char *ip)
+{
+ RETV_IF(ip == nullptr, false);
+
+ struct sockaddr_in sa;
+ if (inet_pton(AF_INET, ip, &(sa.sin_addr)) <= 0)
+ return false;
+
+ return true;
+}
+
+API int aitt_connect(aitt_h handle, const char *broker_ip, int port)
+{
+ return aitt_connect_full(handle, broker_ip, port, NULL, NULL);
+}
+
+API int aitt_connect_full(aitt_h handle, const char *broker_ip, int port, const char *username,
+ const char *password)
+{
+ RETV_IF(handle == nullptr, AITT_ERROR_INVALID_PARAMETER);
+ RETVM_IF(is_valid_ip(broker_ip) == false, AITT_ERROR_INVALID_PARAMETER, "Invalid IP(%s)",
+ broker_ip);
+
+ try {
+ handle->aitt->Connect(broker_ip, port, username ? username : std::string(),
+ password ? password : std::string());
+ } catch (std::exception &e) {
+ ERR("Connect(%s, %d) Fail(%s)", broker_ip, port, e.what());
+ return AITT_ERROR_SYSTEM;
+ }
+
+ handle->connected = true;
+ return AITT_ERROR_NONE;
+}
+
+API int aitt_disconnect(aitt_h handle)
+{
+ RETV_IF(handle == nullptr, AITT_ERROR_INVALID_PARAMETER);
+ RETV_IF(handle->aitt == nullptr, AITT_ERROR_INVALID_PARAMETER);
+ RETV_IF(handle->connected == false, AITT_ERROR_NOT_READY);
+
+ try {
+ handle->aitt->Disconnect();
+ } catch (std::exception &e) {
+ ERR("Disconnect() Fail(%s)", e.what());
+ return AITT_ERROR_SYSTEM;
+ }
+ return AITT_ERROR_NONE;
+}
+
+API int aitt_publish(aitt_h handle, const char *topic, const void *msg, const size_t msg_len)
+{
+ return aitt_publish_full(handle, topic, msg, msg_len, AITT_TYPE_MQTT, AITT_QOS_AT_MOST_ONCE);
+}
+
+API int aitt_publish_full(aitt_h handle, const char *topic, const void *msg, const size_t msg_len,
+ int protocols, aitt_qos_e qos)
+{
+ RETV_IF(handle == nullptr, AITT_ERROR_INVALID_PARAMETER);
+ RETV_IF(handle->aitt == nullptr, AITT_ERROR_INVALID_PARAMETER);
+ RETV_IF(handle->connected == false, AITT_ERROR_NOT_READY);
+ RETV_IF(topic == nullptr, AITT_ERROR_INVALID_PARAMETER);
+ RETV_IF(msg == nullptr, AITT_ERROR_INVALID_PARAMETER);
+
+ try {
+ handle->aitt->Publish(topic, msg, msg_len, AITT_TYPE_MQTT);
+ } catch (std::exception &e) {
+ ERR("Publish(topic:%s, msg_len:%zu) Fail(%s)", topic, msg_len, e.what());
+ return AITT_ERROR_SYSTEM;
+ }
+
+ return AITT_ERROR_NONE;
+}
+
+API int aitt_publish_with_reply(aitt_h handle, const char *topic, const void *msg,
+ const size_t msg_len, aitt_protocol_e protocols, aitt_qos_e qos, const char *correlation,
+ aitt_sub_fn cb, void *user_data)
+{
+ RETV_IF(handle == nullptr, AITT_ERROR_INVALID_PARAMETER);
+ RETV_IF(handle->aitt == nullptr, AITT_ERROR_INVALID_PARAMETER);
+ RETV_IF(handle->connected == false, AITT_ERROR_NOT_READY);
+ RETV_IF(topic == nullptr, AITT_ERROR_INVALID_PARAMETER);
+ RETV_IF(msg == nullptr, AITT_ERROR_INVALID_PARAMETER);
+ RETV_IF(cb == nullptr, AITT_ERROR_INVALID_PARAMETER);
+
+ try {
+ // TODO: handle protocols, qos
+ handle->aitt->PublishWithReply(topic, msg, msg_len, protocols, AITT_QOS_AT_MOST_ONCE, false,
+ cb, user_data, std::string(correlation));
+ } catch (std::exception &e) {
+ ERR("PublishWithReply(%s) Fail(%s)", topic, e.what());
+ return AITT_ERROR_SYSTEM;
+ }
+ return AITT_ERROR_NONE;
+}
+
+API int aitt_send_reply(aitt_h handle, aitt_msg_h msg_handle, const void *reply,
+ const size_t reply_len, bool end)
+{
+ try {
+ aitt::MSG *msg = static_cast<aitt::MSG *>(msg_handle);
+
+ handle->aitt->SendReply(msg, reply, reply_len, end);
+ } catch (std::exception &e) {
+ ERR("SendReply Fail(%s)", e.what());
+ return AITT_ERROR_SYSTEM;
+ }
+ return AITT_ERROR_NONE;
+}
+
+API int aitt_subscribe(aitt_h handle, const char *topic, aitt_sub_fn cb, void *user_data,
+ aitt_sub_h *sub_handle)
+{
+ return aitt_subscribe_full(handle, topic, cb, user_data, AITT_TYPE_MQTT, AITT_QOS_AT_MOST_ONCE,
+ sub_handle);
+}
+
+API int aitt_subscribe_full(aitt_h handle, const char *topic, aitt_sub_fn cb, void *user_data,
+ aitt_protocol_e protocol, aitt_qos_e qos, aitt_sub_h *sub_handle)
+{
+ RETV_IF(handle == nullptr, AITT_ERROR_INVALID_PARAMETER);
+ RETV_IF(handle->aitt == nullptr, AITT_ERROR_INVALID_PARAMETER);
+ RETV_IF(handle->connected == false, AITT_ERROR_NOT_READY);
+ RETV_IF(topic == nullptr, AITT_ERROR_INVALID_PARAMETER);
+ RETV_IF(cb == nullptr, AITT_ERROR_INVALID_PARAMETER);
+ RETV_IF(sub_handle == nullptr, AITT_ERROR_INVALID_PARAMETER);
+
+ try {
+ // TODO: handle protocols, qos
+ *sub_handle =
+ handle->aitt->Subscribe(topic, cb, user_data, static_cast<AittProtocol>(protocol));
+ } catch (std::exception &e) {
+ ERR("Subscribe(%s) Fail(%s)", topic, e.what());
+ return AITT_ERROR_SYSTEM;
+ }
+ return AITT_ERROR_NONE;
+}
+
+API int aitt_unsubscribe(aitt_h handle, aitt_sub_h sub_handle)
+{
+ RETV_IF(handle == nullptr, AITT_ERROR_INVALID_PARAMETER);
+ RETV_IF(handle->aitt == nullptr, AITT_ERROR_INVALID_PARAMETER);
+ RETV_IF(handle->connected == false, AITT_ERROR_NOT_READY);
+ RETV_IF(sub_handle == nullptr, AITT_ERROR_INVALID_PARAMETER);
+
+ try {
+ handle->aitt->Unsubscribe(static_cast<AittSubscribeID>(sub_handle));
+ } catch (std::exception &e) {
+ ERR("Unsubscribe(%p) Fail(%s)", sub_handle, e.what());
+ return AITT_ERROR_SYSTEM;
+ }
+ return AITT_ERROR_NONE;
+}
+
+API const char *aitt_msg_get_topic(aitt_msg_h handle)
+{
+ RETV_IF(handle == nullptr, nullptr);
+
+ MSG *msg = reinterpret_cast<MSG *>(handle);
+ return msg->GetTopic().c_str();
+}
diff --git a/tests/AITT_TCP_test.cc b/tests/AITT_TCP_test.cc
new file mode 100644
index 0000000..bd28993
--- /dev/null
+++ b/tests/AITT_TCP_test.cc
@@ -0,0 +1,112 @@
+/*
+ * Copyright (c) 2021-2022 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <glib.h>
+#include <gtest/gtest.h>
+
+#include <thread>
+
+#include "AITT.h"
+#include "aitt_internal.h"
+#include "aitt_tests.h"
+
+using AITT = aitt::AITT;
+
+class AITTTCPTest : public testing::Test, public AittTests {
+ protected:
+ void SetUp() override { Init(); }
+ void TearDown() override { Deinit(); }
+};
+
+TEST_F(AITTTCPTest, TCP_Wildcards1_Anytime)
+{
+ try {
+ char dump_msg[204800];
+
+ AITT aitt(clientId, LOCAL_IP);
+ aitt.Connect();
+
+ aitt.Subscribe(
+ "test/#",
+ [&](aitt::MSG *handle, const void *msg, const size_t szmsg, void *cbdata) -> void {
+ AITTTCPTest *test = static_cast<AITTTCPTest *>(cbdata);
+ INFO("Got Message(Topic:%s, size:%zu)", handle->GetTopic().c_str(), szmsg);
+ static int cnt = 0;
+ ++cnt;
+ if (cnt == 3)
+ test->ToggleReady();
+ },
+ static_cast<void *>(this), AITT_TYPE_TCP);
+
+ // Wait a few seconds until the AITT client gets a server list (discover devices)
+ DBG("Sleep %d secs", SLEEP_MS);
+ sleep(SLEEP_MS);
+
+ aitt.Publish("test/step1/value1", dump_msg, 12, AITT_TYPE_TCP);
+ aitt.Publish("test/step2/value1", dump_msg, 1600, AITT_TYPE_TCP);
+ aitt.Publish("test/step2/value1", dump_msg, 1600, AITT_TYPE_TCP);
+
+ g_timeout_add(10, AittTests::ReadyCheck, static_cast<AittTests *>(this));
+
+ IterateEventLoop();
+
+ ASSERT_TRUE(ready);
+ } catch (std::exception &e) {
+ FAIL() << "Unexpected exception: " << e.what();
+ }
+}
+
+TEST_F(AITTTCPTest, TCP_Wildcards2_Anytime)
+{
+ try {
+ char dump_msg[204800];
+
+ AITT aitt(clientId, LOCAL_IP);
+ aitt.Connect();
+
+ aitt.Subscribe(
+ "test/+",
+ [&](aitt::MSG *handle, const void *msg, const size_t szmsg, void *cbdata) -> void {
+ AITTTCPTest *test = static_cast<AITTTCPTest *>(cbdata);
+ INFO("Got Message(Topic:%s, size:%zu)", handle->GetTopic().c_str(), szmsg);
+ static int cnt = 0;
+ ++cnt;
+
+ std::stringstream ss;
+ ss << "test/value" << cnt;
+ EXPECT_EQ(ss.str(), handle->GetTopic());
+
+ if (cnt == 3)
+ test->ToggleReady();
+ },
+ static_cast<void *>(this), AITT_TYPE_TCP);
+
+ // Wait a few seconds until the AITT client gets a server list (discover devices)
+ DBG("Sleep %d secs", SLEEP_MS);
+ sleep(SLEEP_MS);
+
+ aitt.Publish("test/value1", dump_msg, 12, AITT_TYPE_TCP);
+ aitt.Publish("test/value2", dump_msg, 1600, AITT_TYPE_TCP);
+ aitt.Publish("test/value3", dump_msg, 1600, AITT_TYPE_TCP);
+
+ g_timeout_add(10, AittTests::ReadyCheck, static_cast<AittTests *>(this));
+
+ IterateEventLoop();
+
+ ASSERT_TRUE(ready);
+ } catch (std::exception &e) {
+ FAIL() << "Unexpected exception: " << e.what();
+ }
+}
diff --git a/tests/AITT_manualtest.cc b/tests/AITT_manualtest.cc
new file mode 100644
index 0000000..cea0fcf
--- /dev/null
+++ b/tests/AITT_manualtest.cc
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 2021-2022 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "AITT.h"
+
+#include <glib.h>
+#include <gtest/gtest.h>
+
+#include "aitt_internal.h"
+#include "aitt_tests.h"
+
+using AITT = aitt::AITT;
+
+class AITTManualTest : public testing::Test, public AittTests {
+ protected:
+ void SetUp() override { Init(); }
+ void TearDown() override { Deinit(); }
+};
+
+TEST_F(AITTManualTest, WillSet_P)
+{
+ try {
+ AITT aitt("", LOCAL_IP, true);
+ aitt.Connect();
+ aitt.Subscribe(
+ "test/AITT_will",
+ [](aitt::MSG *handle, const void *msg, const size_t szmsg, void *cbdata) -> void {
+ AITTManualTest *test = static_cast<AITTManualTest *>(cbdata);
+ test->ToggleReady();
+ DBG("Subscribe invoked: %s %zu", static_cast<const char *>(msg), szmsg);
+ },
+ static_cast<void *>(this));
+
+ int pid = fork();
+ if (pid == 0) {
+ AITT aitt_will("test_will_AITT", LOCAL_IP, true);
+ aitt_will.SetWillInfo("test/AITT_will", TEST_MSG, sizeof(TEST_MSG),
+ AITT_QOS_AT_LEAST_ONCE, false);
+ aitt_will.Connect();
+ sleep(2);
+ // Do not call aitt_will.Disconnect()
+ } else {
+ sleep(1);
+ kill(pid, SIGKILL);
+
+ g_timeout_add(10, AittTests::ReadyCheck, static_cast<AittTests *>(this));
+ IterateEventLoop();
+
+ ASSERT_TRUE(ready);
+ }
+ } catch (std::exception &e) {
+ FAIL() << "Unexpected exception: " << e.what();
+ }
+}
+
+TEST(AITT_MANUAL, Connect_with_ID_P)
+{
+ try {
+ AITT aitt("", LOCAL_IP);
+ aitt.Connect(LOCAL_IP, 1883, "testID", "testPasswd");
+ } catch (std::exception &e) {
+ FAIL() << "Unexpected exception: " << e.what();
+ }
+}
diff --git a/tests/AITT_test.cc b/tests/AITT_test.cc
new file mode 100644
index 0000000..1dcd8c6
--- /dev/null
+++ b/tests/AITT_test.cc
@@ -0,0 +1,556 @@
+/*
+ * Copyright (c) 2021-2022 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "AITT.h"
+
+#include <glib.h>
+#include <gtest/gtest.h>
+#include <sys/random.h>
+
+#include <thread>
+
+#include "aitt_internal.h"
+#include "aitt_tests.h"
+
+using AITT = aitt::AITT;
+
+class AITTTest : public testing::Test, public AittTests {
+ protected:
+ void SetUp() override { Init(); }
+ void TearDown() override { Deinit(); }
+
+ void pubsub_template(const char *test_msg, AittProtocol protocol)
+ {
+ try {
+ AITT aitt(clientId, LOCAL_IP, true);
+ aitt.Connect();
+ aitt.Subscribe(
+ testTopic,
+ [](aitt::MSG *handle, const void *msg, const size_t szmsg, void *cbdata) -> void {
+ AITTTest *test = static_cast<AITTTest *>(cbdata);
+ test->ToggleReady();
+ DBG("Subscribe invoked: %s %zu", static_cast<const char *>(msg), szmsg);
+ },
+ static_cast<void *>(this), protocol);
+
+ // Wait a few seconds until the AITT client gets a server list (discover devices)
+ DBG("Sleep %d secs", SLEEP_MS);
+ sleep(SLEEP_MS);
+
+ DBG("Publish(%s) : %s(%zu)", testTopic.c_str(), test_msg, strlen(test_msg));
+ aitt.Publish(testTopic, test_msg, strlen(test_msg), protocol);
+
+ g_timeout_add(10, AittTests::ReadyCheck, static_cast<AittTests *>(this));
+
+ IterateEventLoop();
+
+ ASSERT_TRUE(ready);
+ } catch (std::exception &e) {
+ FAIL() << "Unexpected exception: " << e.what();
+ }
+ }
+};
+
+TEST_F(AITTTest, Positive_Create_Anytime)
+{
+ try {
+ AITT aitt(clientId, LOCAL_IP, true);
+ } catch (std::exception &e) {
+ FAIL() << "Unexpected exception: " << e.what();
+ }
+}
+
+TEST_F(AITTTest, SetConnectionCallback_P_Anytime)
+{
+ try {
+ AITT aitt(clientId, LOCAL_IP, true);
+ aitt.SetConnectionCallback(
+ [&](AITT &handle, int status, void *user_data) {
+ AITTTest *test = static_cast<AITTTest *>(user_data);
+
+ if (test->ready2) {
+ EXPECT_EQ(status, AITT_DISCONNECTED);
+ test->ToggleReady();
+ } else {
+ EXPECT_EQ(status, AITT_CONNECTED);
+ test->ToggleReady2();
+ handle.Disconnect();
+ }
+ },
+ this);
+ aitt.Connect();
+
+ g_timeout_add(10, AittTests::ReadyCheck, static_cast<AittTests *>(this));
+
+ IterateEventLoop();
+ ASSERT_TRUE(ready);
+ ASSERT_TRUE(ready2);
+ } catch (std::exception &e) {
+ FAIL() << "Unexpected exception: " << e.what();
+ }
+}
+
+TEST_F(AITTTest, UnsetConnectionCallback_P_Anytime)
+{
+ try {
+ AITT aitt(clientId, LOCAL_IP, true);
+ aitt.SetConnectionCallback(
+ [&](AITT &handle, int status, void *user_data) {
+ AITTTest *test = static_cast<AITTTest *>(user_data);
+
+ if (test->ready) {
+ FAIL() << "Should not be called";
+ } else {
+ EXPECT_EQ(status, AITT_CONNECTED);
+ test->ToggleReady();
+ handle.SetConnectionCallback(nullptr, nullptr);
+ handle.Disconnect();
+ }
+ },
+ this);
+ aitt.Connect();
+
+ g_timeout_add(10, AittTests::ReadyCheck, static_cast<AittTests *>(this));
+
+ IterateEventLoop();
+ sleep(1);
+ ASSERT_FALSE(ready2);
+ ASSERT_TRUE(ready);
+ } catch (std::exception &e) {
+ FAIL() << "Unexpected exception: " << e.what();
+ }
+}
+
+TEST_F(AITTTest, Positive_Connect_Anytime)
+{
+ try {
+ AITT aitt(clientId, LOCAL_IP, true);
+ aitt.Connect();
+ } catch (std::exception &e) {
+ FAIL() << "Unexpected exception: " << e.what();
+ }
+}
+
+TEST_F(AITTTest, Positive_Disconnect_Anytime)
+{
+ try {
+ AITT aitt(clientId, LOCAL_IP, true);
+ aitt.Connect();
+ aitt.Disconnect();
+ } catch (std::exception &e) {
+ FAIL() << "Unexpected exception: " << e.what();
+ }
+}
+
+TEST_F(AITTTest, Positive_Connect_twice_Anytime)
+{
+ try {
+ AITT aitt(clientId, LOCAL_IP, true);
+ aitt.Connect();
+ aitt.Disconnect();
+ aitt.Connect();
+ } catch (std::exception &e) {
+ FAIL() << "Unexpected exception: " << e.what();
+ }
+}
+
+TEST_F(AITTTest, Positive_Publish_MQTT_Anytime)
+{
+ try {
+ AITT aitt(clientId, LOCAL_IP, true);
+ aitt.Connect();
+ aitt.Publish(testTopic, TEST_MSG, sizeof(TEST_MSG));
+ } catch (std::exception &e) {
+ FAIL() << "Unexpected exception: " << e.what();
+ }
+}
+
+TEST_F(AITTTest, Positive_Publish_TCP_Anytime)
+{
+ try {
+ AITT aitt(clientId, LOCAL_IP, true);
+ aitt.Connect();
+ aitt.Publish(testTopic, TEST_MSG, sizeof(TEST_MSG), AITT_TYPE_TCP);
+ } catch (std::exception &e) {
+ FAIL() << "Unexpected exception: " << e.what();
+ }
+}
+
+TEST_F(AITTTest, Positive_Publish_Multiple_Protocols_Anytime)
+{
+ try {
+ AITT aitt(clientId, LOCAL_IP, true);
+ aitt.Connect();
+ aitt.Publish(testTopic, TEST_MSG, sizeof(TEST_MSG),
+ (AittProtocol)(AITT_TYPE_MQTT | AITT_TYPE_TCP));
+ } catch (std::exception &e) {
+ FAIL() << "Unexpected exception: " << e.what();
+ }
+}
+
+TEST_F(AITTTest, Positive_Subscribe_Anytime)
+{
+ try {
+ AITT aitt(clientId, LOCAL_IP, true);
+ aitt.Connect();
+ aitt.Subscribe(
+ testTopic,
+ [](aitt::MSG *handle, const void *msg, const size_t szmsg, void *cbdata) -> void {},
+ nullptr, AITT_TYPE_TCP);
+ } catch (std::exception &e) {
+ FAIL() << "Unexpected exception: " << e.what();
+ }
+}
+
+TEST_F(AITTTest, Positive_Unsubscribe_MQTT_Anytime)
+{
+ try {
+ AITT aitt(clientId, LOCAL_IP, true);
+ aitt.Connect();
+ subscribeHandle = aitt.Subscribe(
+ testTopic,
+ [](aitt::MSG *handle, const void *msg, const size_t szmsg, void *cbdata) -> void {},
+ nullptr, AITT_TYPE_MQTT);
+ DBG(">>> Handle: %p", reinterpret_cast<void *>(subscribeHandle));
+ aitt.Unsubscribe(subscribeHandle);
+ } catch (std::exception &e) {
+ FAIL() << "Unexpected exception: " << e.what();
+ }
+}
+
+TEST_F(AITTTest, Positive_Unsubscribe_TCP_Anytime)
+{
+ try {
+ AITT aitt(clientId, LOCAL_IP, true);
+ aitt.Connect();
+ subscribeHandle = aitt.Subscribe(
+ testTopic,
+ [](aitt::MSG *handle, const void *msg, const size_t szmsg, void *cbdata) -> void {},
+ nullptr, AITT_TYPE_TCP);
+ DBG("Subscribe handle: %p", reinterpret_cast<void *>(subscribeHandle));
+ aitt.Unsubscribe(subscribeHandle);
+ } catch (std::exception &e) {
+ FAIL() << "Unexpected exception: " << e.what();
+ }
+}
+
+TEST_F(AITTTest, Positve_PublishSubscribe_MQTT_Anytime)
+{
+ pubsub_template(TEST_MSG, AITT_TYPE_MQTT);
+}
+
+TEST_F(AITTTest, Positve_Publish_0_MQTT_Anytime)
+{
+ pubsub_template("", AITT_TYPE_MQTT);
+}
+
+TEST_F(AITTTest, Positve_Unsubscribe_in_Subscribe_MQTT_Anytime)
+{
+ try {
+ AITT aitt(clientId, LOCAL_IP, true);
+ aitt.Connect();
+ subscribeHandle = aitt.Subscribe(
+ testTopic,
+ [&](aitt::MSG *handle, const void *msg, const size_t szmsg, void *cbdata) -> void {
+ AITTTest *test = static_cast<AITTTest *>(cbdata);
+ DBG("Subscribe invoked: %s %zu", static_cast<const char *>(msg), szmsg);
+
+ static int cnt = 0;
+ ++cnt;
+ if (cnt == 2)
+ FAIL() << "Should not be called";
+
+ aitt.Unsubscribe(test->subscribeHandle);
+ DBG("Ready flag is toggled");
+ test->ToggleReady();
+ },
+ static_cast<void *>(this));
+
+ DBG("Publish message to %s (%s)", testTopic.c_str(), TEST_MSG);
+ aitt.Publish(testTopic, TEST_MSG, sizeof(TEST_MSG));
+
+ g_timeout_add(10, AittTests::ReadyCheck, static_cast<AittTests *>(this));
+
+ IterateEventLoop();
+
+ ASSERT_TRUE(ready);
+ } catch (std::exception &e) {
+ FAIL() << "Unexpected exception: " << e.what();
+ }
+}
+
+TEST_F(AITTTest, Positve_Subscribe_in_Subscribe_MQTT_Anytime)
+{
+ try {
+ AITT aitt(clientId, LOCAL_IP, true);
+ aitt.Connect();
+ subscribeHandle = aitt.Subscribe(
+ testTopic,
+ [&](aitt::MSG *handle, const void *msg, const size_t szmsg, void *cbdata) -> void {
+ DBG("Subscribe invoked: %s %zu", static_cast<const char *>(msg), szmsg);
+
+ static int cnt = 0;
+ ++cnt;
+ if (cnt == 2)
+ FAIL() << "Should not be called";
+
+ aitt.Subscribe(
+ "topic1InCallback",
+ [](aitt::MSG *handle, const void *msg, const size_t szmsg, void *cbdata) {},
+ cbdata);
+
+ aitt.Subscribe(
+ "topic2InCallback",
+ [](aitt::MSG *handle, const void *msg, const size_t szmsg, void *cbdata) {},
+ cbdata);
+ DBG("Ready flag is toggled");
+ g_timeout_add(
+ 100,
+ [](gpointer data) -> gboolean {
+ AITTTest *test = static_cast<AITTTest *>(data);
+ test->ToggleReady();
+ return G_SOURCE_REMOVE;
+ },
+ cbdata);
+ },
+ static_cast<void *>(this));
+
+ DBG("Publish message to %s (%s)", testTopic.c_str(), TEST_MSG);
+ aitt.Publish(testTopic, TEST_MSG, sizeof(TEST_MSG));
+
+ g_timeout_add(10, AittTests::ReadyCheck, static_cast<AittTests *>(this));
+
+ IterateEventLoop();
+
+ ASSERT_TRUE(ready);
+ } catch (std::exception &e) {
+ FAIL() << "Unexpected exception: " << e.what();
+ }
+}
+
+TEST_F(AITTTest, Positve_PublishSubscribe_TCP_Anytime)
+{
+ pubsub_template(TEST_MSG, AITT_TYPE_TCP);
+}
+
+TEST_F(AITTTest, Positve_Publish_0_TCP_Anytime)
+{
+ pubsub_template("", AITT_TYPE_TCP);
+}
+
+TEST_F(AITTTest, Positve_PublishSubscribe_Multiple_Protocols_Anytime)
+{
+ try {
+ AITT aitt(clientId, LOCAL_IP, true);
+ aitt.Connect();
+ aitt.Subscribe(
+ testTopic,
+ [&](aitt::MSG *handle, const void *msg, const size_t szmsg, void *cbdata) -> void {
+ AITTTest *test = static_cast<AITTTest *>(cbdata);
+ DBG("Subscribe invoked: %s %zu", static_cast<const char *>(msg), szmsg);
+ test->ToggleReady();
+ },
+ static_cast<void *>(this), AITT_TYPE_TCP);
+
+ aitt.Subscribe(
+ testTopic,
+ [&](aitt::MSG *handle, const void *msg, const size_t szmsg, void *cbdata) -> void {
+ AITTTest *test = static_cast<AITTTest *>(cbdata);
+ DBG("Subscribe invoked: %s %zu", static_cast<const char *>(msg), szmsg);
+ test->ToggleReady2();
+ },
+ static_cast<void *>(this), AITT_TYPE_MQTT);
+
+ // Wait a few seconds to the AITT client gets server list (discover devices)
+ DBG("Sleep %d secs", SLEEP_MS);
+ sleep(SLEEP_MS);
+
+ DBG("Publish message to %s (%s) / %zu", testTopic.c_str(), TEST_MSG, sizeof(TEST_MSG));
+ aitt.Publish(testTopic, TEST_MSG, sizeof(TEST_MSG),
+ (AittProtocol)(AITT_TYPE_MQTT | AITT_TYPE_TCP));
+
+ g_timeout_add(10, AittTests::ReadyCheck, static_cast<AittTests *>(this));
+
+ IterateEventLoop();
+
+ ASSERT_TRUE(ready);
+ ASSERT_TRUE(ready2);
+ } catch (std::exception &e) {
+ FAIL() << "Unexpected exception: " << e.what();
+ }
+}
+
+TEST_F(AITTTest, Positve_PublishSubscribe_twice_Anytime)
+{
+ try {
+ AITT aitt(clientId, LOCAL_IP);
+ aitt.Connect();
+ aitt.Subscribe(
+ testTopic,
+ [&](aitt::MSG *handle, const void *msg, const size_t szmsg, void *cbdata) -> void {
+ AITTTest *test = static_cast<AITTTest *>(cbdata);
+ // NOTE:
+ // Subscribe callback will be invoked 2 times
+ static int cnt = 0;
+ ++cnt;
+ if (cnt == 2)
+ test->ToggleReady();
+ DBG("Subscribe callback called: %d", cnt);
+ },
+ static_cast<void *>(this), AITT_TYPE_TCP);
+
+ // Wait a few seconds to the AITT client gets server list (discover devices)
+ sleep(SLEEP_MS);
+
+ // NOTE:
+ // Select target peers and send the data through the specified protocol - TCP
+ aitt.Publish(testTopic, TEST_MSG, sizeof(TEST_MSG), AITT_TYPE_TCP);
+
+ // NOTE:
+ // Publish message through the specified protocol - TCP
+ aitt.Publish(testTopic, TEST_MSG2, sizeof(TEST_MSG2), AITT_TYPE_TCP);
+
+ g_timeout_add(10, AittTests::ReadyCheck, static_cast<AittTests *>(this));
+
+ IterateEventLoop();
+
+ ASSERT_TRUE(ready);
+ } catch (std::exception &e) {
+ FAIL() << "Unexpected exception: " << e.what();
+ }
+}
+
+TEST_F(AITTTest, Positive_Subscribe_Retained_Anytime)
+{
+ try {
+ AITT aitt(clientId, LOCAL_IP);
+ aitt.Connect();
+ aitt.Subscribe(
+ testTopic,
+ [&](aitt::MSG *handle, const void *msg, const size_t szmsg, void *cbdata) -> void {
+ AITTTest *test = static_cast<AITTTest *>(cbdata);
+ static int cnt = 0;
+ ++cnt;
+ if (cnt == 1)
+ test->ToggleReady();
+ DBG("Subscribe callback called: %d", cnt);
+ },
+ static_cast<void *>(this), AITT_TYPE_TCP);
+
+ // Wait a few seconds to the AITT client gets server list (discover devices)
+ sleep(SLEEP_MS);
+
+ // NOTE:
+ // Publish a message with the retained flag
+ // This message will not be delivered, subscriber subscribes TCP protocol
+ aitt.Publish(testTopic, TEST_MSG, sizeof(TEST_MSG), AITT_TYPE_MQTT, AITT_QOS_AT_MOST_ONCE,
+ true);
+
+ aitt.Publish(testTopic, TEST_MSG2, sizeof(TEST_MSG2), AITT_TYPE_TCP);
+
+ g_timeout_add(10, AittTests::ReadyCheck, static_cast<AittTests *>(this));
+
+ IterateEventLoop();
+
+ aitt.Publish(testTopic, nullptr, 0, AITT_TYPE_MQTT, AITT_QOS_AT_LEAST_ONCE, true);
+
+ ASSERT_TRUE(ready);
+ } catch (std::exception &e) {
+ FAIL() << "Unexpected exception: " << e.what();
+ }
+}
+
+TEST_F(AITTTest, TCP_Publish_Disconnect_Anytime)
+{
+ try {
+ char dump_msg[204800] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
+ int dump_msg_size = getrandom(dump_msg, sizeof(dump_msg), 0);
+ if (-1 == dump_msg_size) {
+ ERR("getrandom() Fail(%d)", errno);
+ dump_msg_size = 62;
+ }
+
+ AITT aitt(clientId, LOCAL_IP);
+ AITT aitt_retry("retry_test", LOCAL_IP);
+ aitt.Connect();
+ aitt_retry.Connect();
+
+ aitt.Subscribe(
+ "test/stress1",
+ [&](aitt::MSG *handle, const void *msg, const size_t szmsg, void *cbdata) -> void {
+ AITTTest *test = static_cast<AITTTest *>(cbdata);
+ static int cnt = 0;
+ ++cnt;
+ if (szmsg == 0 && cnt != 12) {
+ FAIL() << "Unexpected value" << cnt;
+ }
+ if (cnt == 10)
+ test->ToggleReady();
+ if (cnt == 11)
+ test->ToggleReady();
+ },
+ static_cast<void *>(this), AITT_TYPE_TCP);
+
+ {
+ AITT aitt1("stress_test1", LOCAL_IP);
+ aitt1.Connect();
+
+ // Wait a few seconds to the AITT client gets server list (discover devices)
+ sleep(SLEEP_MS);
+
+ for (int i = 0; i < 10; i++) {
+ INFO("size = %d", dump_msg_size);
+ aitt1.Publish("test/stress1", dump_msg, dump_msg_size, AITT_TYPE_TCP,
+ AITT_QOS_AT_MOST_ONCE, true);
+ }
+ g_timeout_add(10, AittTests::ReadyCheck, static_cast<AittTests *>(this));
+
+ IterateEventLoop();
+ }
+ DBG("aitt1 client Finish");
+
+ // Here, It's automatically checked Unexpected callback(szmsg = 0)
+ // when publisher is disconnected.
+
+ ASSERT_TRUE(ready);
+ ready = false;
+
+ aitt_retry.Publish("test/stress1", dump_msg, dump_msg_size, AITT_TYPE_TCP,
+ AITT_QOS_AT_MOST_ONCE, true);
+
+ g_timeout_add(10, AittTests::ReadyCheck, static_cast<AittTests *>(this));
+
+ IterateEventLoop();
+
+ ASSERT_TRUE(ready);
+
+ aitt_retry.Publish("test/stress1", nullptr, 0, AITT_TYPE_TCP, AITT_QOS_AT_LEAST_ONCE);
+ // Check auto release of aitt. It sould be no Segmentation fault
+ } catch (std::exception &e) {
+ FAIL() << "Unexpected exception: " << e.what();
+ }
+}
+
+TEST_F(AITTTest, WillSet_N_Anytime)
+{
+ EXPECT_THROW(
+ {
+ AITT aitt_will("", LOCAL_IP, true);
+ aitt_will.SetWillInfo("+", "will msg", 8, AITT_QOS_AT_MOST_ONCE, false);
+ aitt_will.Connect();
+ aitt_will.Disconnect();
+ },
+ std::exception);
+}
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
new file mode 100644
index 0000000..307765d
--- /dev/null
+++ b/tests/CMakeLists.txt
@@ -0,0 +1,62 @@
+SET(AITT_UT ${PROJECT_NAME}_ut)
+
+ADD_DEFINITIONS(-DLOG_STDOUT)
+
+PKG_CHECK_MODULES(UT_NEEDS REQUIRED gmock_main)
+INCLUDE_DIRECTORIES(${UT_NEEDS_INCLUDE_DIRS})
+LINK_DIRECTORIES(${UT_NEEDS_LIBRARY_DIRS})
+
+###########################################################################
+SET(AITT_UT_SRC AITT_test.cc RequestResponse_test.cc MainLoopHandler_test.cc aitt_c_test.cc AITT_TCP_test.cc MQ_test.cc)
+ADD_EXECUTABLE(${AITT_UT} ${AITT_UT_SRC})
+TARGET_LINK_LIBRARIES(${AITT_UT} Threads::Threads ${UT_NEEDS_LIBRARIES} ${PROJECT_NAME})
+
+INSTALL(TARGETS ${AITT_UT} DESTINATION ${AITT_TEST_BINDIR})
+
+ADD_TEST(
+ NAME
+ ${AITT_UT}
+ COMMAND
+ ${CMAKE_COMMAND} -E env
+ LD_LIBRARY_PATH=../modules/tcp/:../:../common/:$ENV{LD_LIBRARY_PATH}
+ ${CMAKE_CURRENT_BINARY_DIR}/${AITT_UT} --gtest_filter=*_Anytime
+)
+
+###########################################################################
+FILE(GLOB AITT_MANUAL_SRC *_manualtest.cc)
+ADD_EXECUTABLE(${AITT_UT}_manual ${AITT_MANUAL_SRC})
+TARGET_LINK_LIBRARIES(${AITT_UT}_manual Threads::Threads ${UT_NEEDS_LIBRARIES} ${PROJECT_NAME})
+
+INSTALL(TARGETS ${AITT_UT}_manual DESTINATION ${AITT_TEST_BINDIR})
+
+###########################################################################
+AUX_SOURCE_DIRECTORY(../mock MOCK_SRC)
+ADD_EXECUTABLE(${AITT_UT}_mq MQ_mocktest.cc ${MOCK_SRC})
+TARGET_LINK_LIBRARIES(${AITT_UT}_mq ${UT_NEEDS_LIBRARIES} Threads::Threads ${AITT_NEEDS_LIBRARIES} ${AITT_COMMON})
+TARGET_INCLUDE_DIRECTORIES(${AITT_UT}_mq PRIVATE ../src ../mock)
+INSTALL(TARGETS ${AITT_UT}_mq DESTINATION ${AITT_TEST_BINDIR})
+
+ADD_TEST(
+ NAME
+ ${AITT_UT}_mq
+ COMMAND
+ ${CMAKE_COMMAND} -E env
+ LD_LIBRARY_PATH=../common/:$ENV{LD_LIBRARY_PATH}
+ ${CMAKE_CURRENT_BINARY_DIR}/${AITT_UT}_mq --gtest_filter=*_Anytime
+)
+
+###########################################################################
+ADD_EXECUTABLE(${AITT_UT}_module TransportModuleLoader_test.cc $<TARGET_OBJECTS:M_LOADER_OBJ>)
+TARGET_LINK_LIBRARIES(${AITT_UT}_module ${UT_NEEDS_LIBRARIES} ${AITT_NEEDS_LIBRARIES} ${CMAKE_DL_LIBS} ${AITT_COMMON})
+TARGET_INCLUDE_DIRECTORIES(${AITT_UT}_module PRIVATE ../src)
+
+INSTALL(TARGETS ${AITT_UT}_module DESTINATION ${AITT_TEST_BINDIR})
+
+ADD_TEST(
+ NAME
+ ${AITT_UT}_module
+ COMMAND
+ ${CMAKE_COMMAND} -E env
+ LD_LIBRARY_PATH=../modules/tcp/:../:../common/:$ENV{LD_LIBRARY_PATH}
+ ${CMAKE_CURRENT_BINARY_DIR}/${AITT_UT}_module --gtest_filter=*_Anytime
+)
diff --git a/tests/MQ_mocktest.cc b/tests/MQ_mocktest.cc
new file mode 100644
index 0000000..06fcc7d
--- /dev/null
+++ b/tests/MQ_mocktest.cc
@@ -0,0 +1,247 @@
+/*
+ * Copyright (c) 2021-2022 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include <condition_variable>
+#include <mutex>
+
+#include "AittTypes.h"
+#include "MQ.h"
+#include "MQMockTest.h"
+#include "MQTTMock.h"
+
+using ::testing::Return;
+
+#define TEST_TOPIC "Test/Topic"
+#define TEST_PAYLOAD "The last will is ..."
+#define TEST_CLIENT_ID "testClient"
+#define TEST_PORT 8123
+#define TEST_HOST "localhost"
+#define TEST_HANDLE reinterpret_cast<mosquitto *>(0xbeefbeef)
+
+TEST_F(MQMockTest, Negative_Create_lib_init_Anytime)
+{
+ EXPECT_CALL(GetMock(), mosquitto_lib_init()).WillOnce(Return(MOSQ_ERR_NOT_SUPPORTED));
+ EXPECT_CALL(GetMock(), mosquitto_destroy(nullptr)).WillOnce(Return());
+ EXPECT_CALL(GetMock(), mosquitto_lib_cleanup()).WillOnce(Return(MOSQ_ERR_SUCCESS));
+
+ try {
+ aitt::MQ mq(TEST_CLIENT_ID, true);
+ FAIL() << "lib_init must be failed";
+ } catch (std::exception &e) {
+ ASSERT_STREQ(e.what(), "MQTT failure : MQ Constructor Error");
+ }
+}
+
+TEST_F(MQMockTest, Negative_Create_new_Anytime)
+{
+ EXPECT_CALL(GetMock(), mosquitto_lib_init()).WillOnce(Return(MOSQ_ERR_SUCCESS));
+ EXPECT_CALL(GetMock(), mosquitto_new(testing::StrEq(TEST_CLIENT_ID), true, testing::_))
+ .WillOnce(Return(nullptr));
+ EXPECT_CALL(GetMock(), mosquitto_destroy(nullptr)).Times(1);
+ EXPECT_CALL(GetMock(), mosquitto_lib_cleanup()).WillOnce(Return(MOSQ_ERR_SUCCESS));
+
+ try {
+ aitt::MQ mq(TEST_CLIENT_ID, true);
+ FAIL() << "lib_init must be failed";
+ } catch (std::exception &e) {
+ ASSERT_STREQ(e.what(), "MQTT failure : MQ Constructor Error");
+ }
+}
+
+TEST_F(MQMockTest, Positive_Publish_Anytime)
+{
+ EXPECT_CALL(GetMock(), mosquitto_lib_init()).WillOnce(Return(MOSQ_ERR_SUCCESS));
+ EXPECT_CALL(GetMock(), mosquitto_new(testing::StrEq(TEST_CLIENT_ID), true, testing::_))
+ .WillOnce(Return(TEST_HANDLE));
+ EXPECT_CALL(GetMock(), mosquitto_message_v5_callback_set(TEST_HANDLE, testing::_)).Times(1);
+ EXPECT_CALL(GetMock(), mosquitto_loop_start(TEST_HANDLE)).WillOnce(Return(MOSQ_ERR_SUCCESS));
+ EXPECT_CALL(GetMock(), mosquitto_connect(TEST_HANDLE, testing::StrEq(TEST_HOST), TEST_PORT, 60))
+ .WillOnce(Return(MOSQ_ERR_SUCCESS));
+ EXPECT_CALL(GetMock(), mosquitto_publish(TEST_HANDLE, testing::_, testing::StrEq(TEST_TOPIC),
+ sizeof(TEST_PAYLOAD), TEST_PAYLOAD, AITT_QOS_AT_MOST_ONCE, false))
+ .WillOnce(Return(MOSQ_ERR_SUCCESS));
+ EXPECT_CALL(GetMock(), mosquitto_destroy(TEST_HANDLE)).Times(1);
+ EXPECT_CALL(GetMock(), mosquitto_lib_cleanup()).WillOnce(Return(MOSQ_ERR_SUCCESS));
+
+ try {
+ aitt::MQ mq(TEST_CLIENT_ID, true);
+ mq.Connect(TEST_HOST, TEST_PORT, "", "");
+ mq.Publish(TEST_TOPIC, TEST_PAYLOAD, sizeof(TEST_PAYLOAD));
+ } catch (std::exception &e) {
+ FAIL() << "Unexpected exception: " << e.what();
+ }
+}
+
+TEST_F(MQMockTest, Positive_Subscribe_Anytime)
+{
+ EXPECT_CALL(GetMock(), mosquitto_lib_init()).WillOnce(Return(MOSQ_ERR_SUCCESS));
+ EXPECT_CALL(GetMock(), mosquitto_new(testing::StrEq(TEST_CLIENT_ID), true, testing::_))
+ .WillOnce(Return(TEST_HANDLE));
+ EXPECT_CALL(GetMock(), mosquitto_message_v5_callback_set(TEST_HANDLE, testing::_)).Times(1);
+ EXPECT_CALL(GetMock(), mosquitto_loop_start(TEST_HANDLE)).WillOnce(Return(MOSQ_ERR_SUCCESS));
+ EXPECT_CALL(GetMock(), mosquitto_connect(TEST_HANDLE, testing::StrEq(TEST_HOST), TEST_PORT, 60))
+ .WillOnce(Return(MOSQ_ERR_SUCCESS));
+ EXPECT_CALL(GetMock(), mosquitto_subscribe(TEST_HANDLE, testing::_, testing::StrEq(TEST_TOPIC),
+ AITT_QOS_AT_MOST_ONCE))
+ .WillOnce(Return(MOSQ_ERR_SUCCESS));
+ EXPECT_CALL(GetMock(), mosquitto_destroy(TEST_HANDLE)).Times(1);
+ EXPECT_CALL(GetMock(), mosquitto_lib_cleanup()).WillOnce(Return(MOSQ_ERR_SUCCESS));
+
+ try {
+ aitt::MQ mq(TEST_CLIENT_ID, true);
+ mq.Connect(TEST_HOST, TEST_PORT, "", "");
+ mq.Subscribe(
+ TEST_TOPIC,
+ [](aitt::MSG *info, const std::string &topic, const void *msg, const int szmsg,
+ const void *cbdata) -> void {},
+ nullptr, AITT_QOS_AT_MOST_ONCE);
+ } catch (std::exception &e) {
+ FAIL() << "Unexpected exception: " << e.what();
+ }
+}
+
+TEST_F(MQMockTest, Positive_Unsubscribe_Anytime)
+{
+ EXPECT_CALL(GetMock(), mosquitto_lib_init()).WillOnce(Return(MOSQ_ERR_SUCCESS));
+ EXPECT_CALL(GetMock(), mosquitto_new(testing::StrEq(TEST_CLIENT_ID), true, testing::_))
+ .WillOnce(Return(TEST_HANDLE));
+ EXPECT_CALL(GetMock(), mosquitto_message_v5_callback_set(TEST_HANDLE, testing::_)).Times(1);
+ EXPECT_CALL(GetMock(), mosquitto_loop_start(TEST_HANDLE)).WillOnce(Return(MOSQ_ERR_SUCCESS));
+ EXPECT_CALL(GetMock(), mosquitto_connect(TEST_HANDLE, testing::StrEq(TEST_HOST), TEST_PORT, 60))
+ .WillOnce(Return(MOSQ_ERR_SUCCESS));
+ EXPECT_CALL(GetMock(),
+ mosquitto_subscribe(TEST_HANDLE, testing::_, testing::StrEq(TEST_TOPIC), 0))
+ .WillOnce(Return(MOSQ_ERR_SUCCESS));
+ EXPECT_CALL(GetMock(),
+ mosquitto_unsubscribe(TEST_HANDLE, testing::_, testing::StrEq(TEST_TOPIC)))
+ .WillOnce(Return(MOSQ_ERR_SUCCESS));
+ EXPECT_CALL(GetMock(), mosquitto_destroy(TEST_HANDLE)).Times(1);
+ EXPECT_CALL(GetMock(), mosquitto_lib_cleanup()).WillOnce(Return(MOSQ_ERR_SUCCESS));
+
+ try {
+ aitt::MQ mq(TEST_CLIENT_ID, true);
+ mq.Connect(TEST_HOST, TEST_PORT, "", "");
+ void *handle = mq.Subscribe(
+ TEST_TOPIC,
+ [](aitt::MSG *info, const std::string &topic, const void *msg, const int szmsg,
+ const void *cbdata) -> void {},
+ nullptr, AITT_QOS_AT_MOST_ONCE);
+ mq.Unsubscribe(handle);
+ } catch (std::exception &e) {
+ FAIL() << "Unexpected exception: " << e.what();
+ }
+}
+
+TEST_F(MQMockTest, Positive_Create_Anytime)
+{
+ EXPECT_CALL(GetMock(), mosquitto_lib_init()).WillOnce(Return(MOSQ_ERR_SUCCESS));
+ EXPECT_CALL(GetMock(), mosquitto_new(testing::StrEq(TEST_CLIENT_ID), true, testing::_))
+ .WillOnce(Return(TEST_HANDLE));
+ EXPECT_CALL(GetMock(),
+ mosquitto_int_option(TEST_HANDLE, MOSQ_OPT_PROTOCOL_VERSION, MQTT_PROTOCOL_V5))
+ .Times(1);
+ EXPECT_CALL(GetMock(), mosquitto_message_v5_callback_set(TEST_HANDLE, testing::_)).Times(1);
+ EXPECT_CALL(GetMock(), mosquitto_destroy(TEST_HANDLE)).Times(1);
+ EXPECT_CALL(GetMock(), mosquitto_lib_cleanup()).WillOnce(Return(MOSQ_ERR_SUCCESS));
+
+ try {
+ aitt::MQ mq(TEST_CLIENT_ID, true);
+ } catch (std::exception &e) {
+ FAIL() << "Unexpected exception occurred";
+ }
+}
+
+TEST_F(MQMockTest, Negative_Connect_will_set_Anytime)
+{
+ EXPECT_CALL(GetMock(), mosquitto_lib_init()).WillOnce(Return(MOSQ_ERR_SUCCESS));
+ EXPECT_CALL(GetMock(), mosquitto_new(testing::StrEq(TEST_CLIENT_ID), true, testing::_))
+ .WillOnce(Return(TEST_HANDLE));
+ EXPECT_CALL(GetMock(), mosquitto_message_v5_callback_set(TEST_HANDLE, testing::_)).Times(1);
+ EXPECT_CALL(GetMock(), mosquitto_will_set(TEST_HANDLE, testing::StrEq("lastWill"),
+ sizeof(TEST_PAYLOAD), TEST_PAYLOAD, AITT_QOS_AT_MOST_ONCE, true))
+ .WillOnce(Return(MOSQ_ERR_NOMEM));
+ EXPECT_CALL(GetMock(), mosquitto_destroy(TEST_HANDLE)).Times(1);
+ EXPECT_CALL(GetMock(), mosquitto_lib_cleanup()).WillOnce(Return(MOSQ_ERR_SUCCESS));
+ try {
+ aitt::MQ mq(TEST_CLIENT_ID, true);
+ mq.SetWillInfo("lastWill", TEST_PAYLOAD, sizeof(TEST_PAYLOAD), AITT_QOS_AT_MOST_ONCE, true);
+ mq.Connect(TEST_HOST, TEST_PORT, "", "");
+ FAIL() << "Connect() must be failed";
+ } catch (std::exception &e) {
+ ASSERT_STREQ(e.what(), "MQTT failure");
+ }
+}
+
+TEST_F(MQMockTest, Positive_Connect_Anytime)
+{
+ EXPECT_CALL(GetMock(), mosquitto_lib_init()).WillOnce(Return(MOSQ_ERR_SUCCESS));
+ EXPECT_CALL(GetMock(), mosquitto_new(testing::StrEq(TEST_CLIENT_ID), true, testing::_))
+ .WillOnce(Return(TEST_HANDLE));
+ EXPECT_CALL(GetMock(), mosquitto_message_v5_callback_set(TEST_HANDLE, testing::_)).Times(1);
+ EXPECT_CALL(GetMock(), mosquitto_connect(TEST_HANDLE, testing::StrEq(TEST_HOST), TEST_PORT, 60))
+ .WillOnce(Return(MOSQ_ERR_SUCCESS));
+ EXPECT_CALL(GetMock(), mosquitto_destroy(TEST_HANDLE)).Times(1);
+ EXPECT_CALL(GetMock(), mosquitto_lib_cleanup()).WillOnce(Return(MOSQ_ERR_SUCCESS));
+ try {
+ aitt::MQ mq(TEST_CLIENT_ID, true);
+ mq.Connect(TEST_HOST, TEST_PORT, "", "");
+ } catch (std::exception &e) {
+ FAIL() << "Unepxected exception: " << e.what();
+ }
+}
+
+TEST_F(MQMockTest, Positive_Connect_User_Anytime)
+{
+ std::string username = "test";
+ std::string password = "test";
+ EXPECT_CALL(GetMock(), mosquitto_lib_init()).WillOnce(Return(MOSQ_ERR_SUCCESS));
+ EXPECT_CALL(GetMock(), mosquitto_new(testing::StrEq(TEST_CLIENT_ID), true, testing::_))
+ .WillOnce(Return(TEST_HANDLE));
+ EXPECT_CALL(GetMock(), mosquitto_message_v5_callback_set(TEST_HANDLE, testing::_)).Times(1);
+ EXPECT_CALL(GetMock(),
+ mosquitto_username_pw_set(TEST_HANDLE, username.c_str(), password.c_str()))
+ .Times(1);
+ EXPECT_CALL(GetMock(), mosquitto_connect(TEST_HANDLE, testing::StrEq(TEST_HOST), TEST_PORT, 60))
+ .WillOnce(Return(MOSQ_ERR_SUCCESS));
+ EXPECT_CALL(GetMock(), mosquitto_destroy(TEST_HANDLE)).Times(1);
+ EXPECT_CALL(GetMock(), mosquitto_lib_cleanup()).WillOnce(Return(MOSQ_ERR_SUCCESS));
+ try {
+ aitt::MQ mq(TEST_CLIENT_ID, true);
+ mq.Connect(TEST_HOST, TEST_PORT, username, password);
+ } catch (std::exception &e) {
+ FAIL() << "Unepxected exception: " << e.what();
+ }
+}
+
+TEST_F(MQMockTest, Positive_Disconnect_Anytime)
+{
+ EXPECT_CALL(GetMock(), mosquitto_lib_init()).WillOnce(Return(MOSQ_ERR_SUCCESS));
+ EXPECT_CALL(GetMock(), mosquitto_new(testing::StrEq(TEST_CLIENT_ID), true, testing::_))
+ .WillOnce(Return(TEST_HANDLE));
+ EXPECT_CALL(GetMock(), mosquitto_message_v5_callback_set(TEST_HANDLE, testing::_)).Times(1);
+ EXPECT_CALL(GetMock(), mosquitto_disconnect(testing::_)).WillOnce(Return(MOSQ_ERR_SUCCESS));
+ EXPECT_CALL(GetMock(), mosquitto_will_clear(TEST_HANDLE)).WillOnce(Return(MOSQ_ERR_SUCCESS));
+ EXPECT_CALL(GetMock(), mosquitto_destroy(TEST_HANDLE)).Times(1);
+ EXPECT_CALL(GetMock(), mosquitto_lib_cleanup()).WillOnce(Return(MOSQ_ERR_SUCCESS));
+ try {
+ aitt::MQ mq(TEST_CLIENT_ID, true);
+ mq.Disconnect();
+ } catch (std::exception &e) {
+ FAIL() << "Unexpected exception: " << e.what();
+ }
+}
diff --git a/tests/MQ_test.cc b/tests/MQ_test.cc
new file mode 100644
index 0000000..4ff554b
--- /dev/null
+++ b/tests/MQ_test.cc
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2022 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "MQ.h"
+
+#include <gtest/gtest.h>
+
+#include "aitt_internal.h"
+#include "aitt_tests.h"
+
+using MQ = aitt::MQ;
+
+class MQTest : public testing::Test, public AittTests {
+ protected:
+ void SetUp() override { Init(); }
+ void TearDown() override { Deinit(); }
+};
+
+TEST_F(MQTest, Positve_Subscribe_in_Subscribe_Anytime)
+{
+ try {
+ MQ mq("MQ_TEST_ID");
+ mq.Connect(LOCAL_IP, 1883, "", "");
+ mq.Subscribe(
+ "MQ_TEST_TOPIC1",
+ [&](aitt::MSG *handle, const std::string &topic, const void *data,
+ const size_t datalen, void *user_data) {
+ DBG("Subscribe invoked: %s %zu", static_cast<const char *>(data), datalen);
+
+ mq.Subscribe(
+ "topic1InCallback",
+ [](aitt::MSG *handle, const std::string &topic, const void *msg,
+ const size_t szmsg, void *cbdata) {},
+ user_data);
+
+ mq.Subscribe(
+ "topic2InCallback",
+ [](aitt::MSG *handle, const std::string &topic, const void *msg,
+ const size_t szmsg, void *cbdata) {},
+ user_data);
+ g_timeout_add(
+ 100,
+ [](gpointer cbdata) -> gboolean {
+ MQTest *test = static_cast<MQTest *>(cbdata);
+ test->ToggleReady();
+ return G_SOURCE_REMOVE;
+ },
+ user_data);
+ },
+ static_cast<void *>(this));
+
+ DBG("Publish message to %s (%s)", "MQ_TEST_TOPIC1", TEST_MSG);
+ mq.Publish("MQ_TEST_TOPIC1", TEST_MSG, sizeof(TEST_MSG));
+
+ g_timeout_add(10, AittTests::ReadyCheck, static_cast<AittTests *>(this));
+
+ IterateEventLoop();
+
+ ASSERT_TRUE(ready);
+ } catch (std::exception &e) {
+ FAIL() << "Unexpected exception: " << e.what();
+ }
+}
diff --git a/tests/MainLoopHandler_test.cc b/tests/MainLoopHandler_test.cc
new file mode 100644
index 0000000..6aab8a4
--- /dev/null
+++ b/tests/MainLoopHandler_test.cc
@@ -0,0 +1,225 @@
+/*
+ * Copyright (c) 2022 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "MainLoopHandler.h"
+
+#include <fcntl.h>
+#include <gtest/gtest.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include <cstdlib>
+#include <thread>
+
+#include "aitt_internal.h"
+
+class MainLoopTest : public testing::Test {
+ protected:
+ void SetUp() override
+ {
+ bzero(&addr, sizeof(addr));
+
+ server_fd = socket(AF_UNIX, SOCK_STREAM, 0);
+ ASSERT_NE(server_fd, -1);
+
+ addr.sun_family = AF_UNIX;
+ strncpy(addr.sun_path, std::to_string(std::rand() / 1e10).c_str(),
+ sizeof(addr.sun_path) - 1);
+
+ int ret = bind(server_fd, (struct sockaddr *)&addr, SUN_LEN(&addr));
+ ASSERT_NE(ret, -1);
+
+ listen(server_fd, 1);
+ my_thread = std::thread(&MainLoopTest::eventWriter, this);
+ }
+
+ void TearDown() override
+ {
+ my_thread.join();
+ close(server_fd);
+ }
+
+ int server_fd;
+ struct sockaddr_un addr;
+ std::thread my_thread;
+
+ private:
+ void eventWriter()
+ {
+ int ret;
+ int fd = socket(AF_UNIX, SOCK_STREAM, 0);
+ ASSERT_NE(fd, -1);
+
+ ret = connect(fd, (struct sockaddr *)&addr, sizeof(struct sockaddr_un));
+ ASSERT_NE(ret, -1);
+
+ std::this_thread::sleep_for(std::chrono::milliseconds(500));
+ ret = write(fd, "1", 1);
+ ASSERT_NE(ret, -1);
+
+ std::this_thread::sleep_for(std::chrono::milliseconds(500));
+ close(fd);
+ }
+};
+
+using aitt::MainLoopHandler;
+
+TEST_F(MainLoopTest, Normal_Anytime)
+{
+ MainLoopHandler handler;
+ bool ret = false;
+
+ handler.AddWatch(
+ server_fd,
+ [&](MainLoopHandler::MainLoopResult result, int fd, MainLoopHandler::MainLoopData *data) {
+ int client_fd = accept(server_fd, 0, 0);
+ EXPECT_NE(client_fd, -1);
+ handler.AddWatch(
+ client_fd,
+ [&](MainLoopHandler::MainLoopResult result, int fd,
+ MainLoopHandler::MainLoopData *data) {
+ EXPECT_EQ(result, MainLoopHandler::OK);
+ char buf[2] = {0};
+ EXPECT_EQ(read(fd, buf, 1), 1);
+ EXPECT_STREQ(buf, "1");
+ handler.Quit();
+ ret = true;
+ },
+ nullptr);
+ },
+ nullptr);
+ handler.Run();
+
+ EXPECT_TRUE(ret);
+}
+
+TEST_F(MainLoopTest, HANGUP_Anytime)
+{
+ MainLoopHandler handler;
+ bool ret = false;
+
+ handler.AddWatch(
+ server_fd,
+ [&](MainLoopHandler::MainLoopResult result, int fd, MainLoopHandler::MainLoopData *data) {
+ int client_fd = accept(server_fd, 0, 0);
+ EXPECT_NE(client_fd, -1);
+ handler.AddWatch(
+ client_fd,
+ [&](MainLoopHandler::MainLoopResult result, int fd,
+ MainLoopHandler::MainLoopData *data) {
+ if (result == MainLoopHandler::OK) {
+ char buf[2] = {0};
+ EXPECT_EQ(read(fd, buf, 1), 1);
+ EXPECT_STREQ(buf, "1");
+ return;
+ }
+
+ EXPECT_EQ(result, MainLoopHandler::HANGUP);
+ handler.Quit();
+ ret = true;
+ },
+ nullptr);
+ },
+ nullptr);
+
+ handler.Run();
+
+ EXPECT_TRUE(ret);
+}
+
+TEST_F(MainLoopTest, removeWatch_Anytime)
+{
+ MainLoopHandler handler;
+ MainLoopHandler::MainLoopData test_data;
+
+ handler.AddWatch(
+ server_fd,
+ [&](MainLoopHandler::MainLoopResult result, int fd, MainLoopHandler::MainLoopData *data) {
+ FAIL() << "It's removed";
+ },
+ &test_data);
+ MainLoopHandler::MainLoopData *check_data = handler.RemoveWatch(server_fd);
+
+ EXPECT_TRUE(&test_data == check_data);
+}
+
+TEST_F(MainLoopTest, UserData_Anytime)
+{
+ MainLoopHandler handler;
+ bool ret = false;
+
+ MainLoopHandler::MainLoopData test_data;
+
+ handler.AddWatch(
+ server_fd,
+ [&](MainLoopHandler::MainLoopResult result, int fd, MainLoopHandler::MainLoopData *data) {
+ EXPECT_EQ(data, &test_data);
+ handler.Quit();
+ ret = true;
+ },
+ &test_data);
+
+ handler.Run();
+
+ EXPECT_TRUE(ret);
+}
+
+TEST_F(MainLoopTest, AddIdle_Anytime)
+{
+ bool ret = false;
+ MainLoopHandler handler;
+ MainLoopHandler::MainLoopData test_data;
+
+ handler.AddIdle(
+ &handler,
+ [&](MainLoopHandler::MainLoopResult result, int fd, MainLoopHandler::MainLoopData *data) {
+ EXPECT_EQ(data, &test_data);
+ handler.Quit();
+ ret = true;
+ },
+ &test_data);
+
+ handler.Run();
+
+ EXPECT_TRUE(ret);
+}
+
+TEST_F(MainLoopTest, AddTimeout_Anytime)
+{
+ bool ret = false;
+ int interval = 1000;
+ MainLoopHandler handler;
+ struct timespec ts_start, ts_end;
+ MainLoopHandler::MainLoopData test_data;
+
+ clock_gettime(CLOCK_MONOTONIC, &ts_start);
+
+ handler.AddTimeout(
+ interval,
+ [&](MainLoopHandler::MainLoopResult result, int fd, MainLoopHandler::MainLoopData *data) {
+ EXPECT_EQ(data, &test_data);
+ clock_gettime(CLOCK_MONOTONIC, &ts_end);
+ double diff = 1000.0 * ts_end.tv_sec + 1e-6 * ts_end.tv_nsec
+ - (1000.0 * ts_start.tv_sec + 1e-6 * ts_start.tv_nsec);
+ EXPECT_GE(diff, interval);
+ handler.Quit();
+ ret = true;
+ },
+ &test_data);
+
+ handler.Run();
+
+ EXPECT_TRUE(ret);
+}
diff --git a/tests/RequestResponse_test.cc b/tests/RequestResponse_test.cc
new file mode 100644
index 0000000..d723e3b
--- /dev/null
+++ b/tests/RequestResponse_test.cc
@@ -0,0 +1,393 @@
+/*
+ * Copyright (c) 2022 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <glib.h>
+#include <gtest/gtest.h>
+
+#include <iostream>
+
+#include "AITT.h"
+#include "aitt_internal.h"
+#include "aitt_tests.h"
+
+using AITT = aitt::AITT;
+
+class AITTRRTest : public testing::Test, public AittTests {
+ public:
+ void PublishSyncInCallback(aitt::AITT *aitt, bool *reply1_ok, bool *reply2_ok, aitt::MSG *msg,
+ const void *data, const size_t datalen, void *cbdata)
+ {
+ aitt->PublishWithReplySync(
+ rr_topic.c_str(), message.c_str(), message.size(), AITT_TYPE_MQTT,
+ AITT_QOS_AT_MOST_ONCE, false,
+ [&](aitt::MSG *msg, const void *data, const size_t datalen, void *cbdata) {
+ CheckReply(msg, data, datalen);
+ *reply1_ok = true;
+ },
+ nullptr, correlation);
+
+ CheckReply(msg, data, datalen);
+ *reply2_ok = true;
+
+ ToggleReady();
+ }
+
+ void CheckReplyCallback(bool toggle, bool *reply_ok, aitt::MSG *msg, const void *data,
+ const size_t datalen, void *cbdata)
+ {
+ CheckReply(msg, data, datalen);
+ *reply_ok = true;
+ if (toggle)
+ ToggleReady();
+ }
+
+ protected:
+ void SetUp() override { Init(); }
+ void TearDown() override { Deinit(); }
+
+ void CheckReply(aitt::MSG *msg, const void *data, const size_t datalen)
+ {
+ std::string received_data((const char *)data, datalen);
+ EXPECT_EQ(msg->GetCorrelation(), correlation);
+ EXPECT_EQ(received_data, reply);
+ EXPECT_EQ(msg->IsEndSequence(), true);
+ }
+
+ void CheckSubscribe(aitt::MSG *msg, const void *data, const size_t datalen)
+ {
+ std::string received_data((const char *)data, datalen);
+ EXPECT_TRUE(msg->GetTopic() == rr_topic);
+ EXPECT_TRUE(msg->GetCorrelation() == correlation);
+ EXPECT_FALSE(msg->GetResponseTopic().empty());
+ EXPECT_EQ(received_data, message);
+ }
+
+ void Call2Times(bool first_sync, bool second_sync)
+ {
+ bool sub_ok;
+ bool reply_ok[2];
+ sub_ok = reply_ok[0] = reply_ok[1] = false;
+
+ AITT aitt(clientId, LOCAL_IP, true);
+ aitt.Connect();
+
+ aitt.Subscribe(rr_topic.c_str(),
+ [&](aitt::MSG *msg, const void *data, const size_t datalen, void *cbdata) {
+ CheckSubscribe(msg, data, datalen);
+ aitt.SendReply(msg, reply.c_str(), reply.size());
+ sub_ok = true;
+ });
+
+ using namespace std::placeholders;
+ for (int i = 0; i < 2; i++) {
+ if ((i == 0 && first_sync) || (i == 1 && second_sync)) {
+ INFO("PublishWithReplySync() Call");
+ aitt.PublishWithReplySync(rr_topic.c_str(), message.c_str(), message.size(),
+ AITT_TYPE_MQTT, AITT_QOS_AT_MOST_ONCE, false,
+ std::bind(&AITTRRTest::CheckReplyCallback, this, (i == 1), &reply_ok[i], _1,
+ _2, _3, _4),
+ nullptr, correlation);
+ } else {
+ INFO("PublishWithReply() Call");
+ aitt.PublishWithReply(rr_topic.c_str(), message.c_str(), message.size(),
+ AITT_TYPE_MQTT, AITT_QOS_AT_MOST_ONCE, false,
+ std::bind(&AITTRRTest::CheckReplyCallback, this, (i == 1), &reply_ok[i], _1,
+ _2, _3, _4),
+ nullptr, correlation);
+ }
+ }
+
+ g_timeout_add(10, AittTests::ReadyCheck, static_cast<AittTests *>(this));
+ IterateEventLoop();
+
+ EXPECT_TRUE(sub_ok);
+ EXPECT_TRUE(reply_ok[0]);
+ EXPECT_TRUE(reply_ok[1]);
+ }
+
+ void SyncCallInCallback(bool sync)
+ {
+ bool sub_ok, reply1_ok, reply2_ok;
+ sub_ok = reply1_ok = reply2_ok = false;
+
+ AITT sub_aitt(clientId + "sub", LOCAL_IP, true);
+ INFO("Constructor Success");
+
+ sub_aitt.Connect();
+ INFO("Connected");
+
+ sub_aitt.Subscribe(rr_topic.c_str(),
+ [&](aitt::MSG *msg, const void *data, const size_t datalen, void *cbdata) {
+ CheckSubscribe(msg, data, datalen);
+ sub_aitt.SendReply(msg, reply.c_str(), reply.size());
+ sub_ok = true;
+ });
+
+ AITT aitt(clientId, LOCAL_IP, true);
+ aitt.Connect();
+
+ using namespace std::placeholders;
+ auto replyCB = std::bind(&AITTRRTest::PublishSyncInCallback, GetHandle(), &aitt, &reply1_ok,
+ &reply2_ok, _1, _2, _3, _4);
+
+ if (sync) {
+ aitt.PublishWithReplySync(rr_topic.c_str(), message.c_str(), message.size(),
+ AITT_TYPE_MQTT, AITT_QOS_AT_MOST_ONCE, false, replyCB, nullptr, correlation);
+ } else {
+ aitt.PublishWithReply(rr_topic.c_str(), message.c_str(), message.size(), AITT_TYPE_MQTT,
+ AITT_QOS_AT_MOST_ONCE, false, replyCB, nullptr, correlation);
+ }
+
+ g_timeout_add(10, AittTests::ReadyCheck, static_cast<AittTests *>(this));
+ IterateEventLoop();
+
+ EXPECT_TRUE(sub_ok);
+ EXPECT_TRUE(reply1_ok);
+ EXPECT_TRUE(reply2_ok);
+ }
+
+ AITTRRTest *GetHandle() { return this; }
+
+ const std::string rr_topic = "test/rr_topic";
+ const std::string message = "Hello world";
+ const std::string correlation = "0001";
+ const std::string reply = "Nice to meet you, RequestResponse";
+};
+
+TEST_F(AITTRRTest, RequestResponse_P_Anytime)
+{
+ bool sub_ok, reply_ok;
+ sub_ok = reply_ok = false;
+
+ try {
+ AITT aitt(clientId, LOCAL_IP, true);
+ aitt.Connect();
+
+ aitt.Subscribe(rr_topic.c_str(),
+ [&](aitt::MSG *msg, const void *data, const size_t datalen, void *cbdata) {
+ CheckSubscribe(msg, data, datalen);
+ aitt.SendReply(msg, reply.c_str(), reply.size());
+ sub_ok = true;
+ });
+
+ aitt.PublishWithReply(rr_topic.c_str(), message.c_str(), message.size(), AITT_TYPE_MQTT,
+ AITT_QOS_AT_MOST_ONCE, false,
+ std::bind(&AITTRRTest::CheckReplyCallback, GetHandle(), true, &reply_ok,
+ std::placeholders::_1, std::placeholders::_2, std::placeholders::_3,
+ std::placeholders::_4),
+ nullptr, correlation);
+
+ g_timeout_add(10, AittTests::ReadyCheck, static_cast<AittTests *>(this));
+ IterateEventLoop();
+
+ EXPECT_TRUE(sub_ok);
+ EXPECT_TRUE(reply_ok);
+ } catch (std::exception &e) {
+ FAIL() << e.what();
+ }
+}
+
+TEST_F(AITTRRTest, RequestResponse_asymmetry_Anytime)
+{
+ std::string reply1 = "1st data";
+ std::string reply2 = "2nd data";
+ std::string reply3 = "final data";
+
+ bool sub_ok, reply_ok;
+ sub_ok = reply_ok = false;
+
+ try {
+ AITT aitt(clientId, LOCAL_IP, true);
+ aitt.Connect();
+
+ aitt.Subscribe(rr_topic.c_str(),
+ [&](aitt::MSG *msg, const void *data, const size_t datalen, void *cbdata) {
+ CheckSubscribe(msg, data, datalen);
+
+ aitt.SendReply(msg, reply1.c_str(), reply1.size(), false);
+ aitt.SendReply(msg, reply2.c_str(), reply2.size(), false);
+ aitt.SendReply(msg, reply3.c_str(), reply3.size(), true);
+
+ sub_ok = true;
+ });
+
+ aitt.PublishWithReply(
+ rr_topic.c_str(), message.c_str(), message.size(), AITT_TYPE_MQTT,
+ AITT_QOS_AT_MOST_ONCE, false,
+ [&](aitt::MSG *msg, const void *data, const size_t datalen, void *cbdata) {
+ std::string reply((const char *)data, datalen);
+
+ EXPECT_EQ(msg->GetCorrelation(), correlation);
+ switch (msg->GetSequence()) {
+ case 1:
+ EXPECT_EQ(reply, reply1);
+ break;
+ case 2:
+ EXPECT_EQ(reply, reply2);
+ break;
+ case 3:
+ EXPECT_EQ(reply, reply3);
+ EXPECT_TRUE(msg->IsEndSequence());
+ reply_ok = true;
+ ToggleReady();
+ break;
+ default:
+ FAIL() << "Unknown sequence" << msg->GetSequence();
+ }
+ },
+ nullptr, correlation);
+
+ g_timeout_add(10, AittTests::ReadyCheck, static_cast<AittTests *>(this));
+ IterateEventLoop();
+
+ EXPECT_TRUE(sub_ok);
+ EXPECT_TRUE(reply_ok);
+
+ } catch (std::exception &e) {
+ FAIL() << e.what();
+ }
+}
+
+TEST_F(AITTRRTest, RequestResponse_2times_Anytime)
+{
+ try {
+ bool first_sync = false;
+ bool second_sync = false;
+ Call2Times(first_sync, second_sync);
+ } catch (std::exception &e) {
+ FAIL() << e.what();
+ }
+}
+
+TEST_F(AITTRRTest, RequestResponse_sync_P_Anytime)
+{
+ bool sub_ok, reply1_ok;
+ sub_ok = reply1_ok = false;
+
+ try {
+ AITT aitt(clientId, LOCAL_IP, true);
+ aitt.Connect();
+
+ aitt.Subscribe(rr_topic.c_str(),
+ [&](aitt::MSG *msg, const void *data, const size_t datalen, void *cbdata) {
+ CheckSubscribe(msg, data, datalen);
+ aitt.SendReply(msg, reply.c_str(), reply.size());
+ sub_ok = true;
+ });
+
+ aitt.PublishWithReplySync(rr_topic.c_str(), message.c_str(), message.size(), AITT_TYPE_MQTT,
+ AITT_QOS_AT_MOST_ONCE, false,
+ std::bind(&AITTRRTest::CheckReplyCallback, GetHandle(), false, &reply1_ok,
+ std::placeholders::_1, std::placeholders::_2, std::placeholders::_3,
+ std::placeholders::_4),
+ nullptr, correlation);
+
+ EXPECT_TRUE(sub_ok);
+ EXPECT_TRUE(reply1_ok);
+ } catch (std::exception &e) {
+ FAIL() << e.what();
+ }
+}
+
+TEST_F(AITTRRTest, RequestResponse_sync_async_P_Anytime)
+{
+ try {
+ bool first_sync = true;
+ bool second_sync = false;
+ Call2Times(first_sync, second_sync);
+ } catch (std::exception &e) {
+ FAIL() << e.what();
+ }
+}
+
+TEST_F(AITTRRTest, RequestResponse_sync_in_async_P_Anytime)
+{
+ try {
+ bool sync_callback = false;
+ SyncCallInCallback(sync_callback);
+ } catch (std::exception &e) {
+ FAIL() << e.what();
+ }
+}
+
+TEST_F(AITTRRTest, RequestResponse_sync_in_sync_P_Anytime)
+{
+ try {
+ bool sync_callback = true;
+ SyncCallInCallback(sync_callback);
+ } catch (std::exception &e) {
+ FAIL() << e.what();
+ }
+}
+
+TEST_F(AITTRRTest, RequestResponse_timeout_P_Anytime)
+{
+ try {
+ AITT aitt(clientId, LOCAL_IP, true);
+ aitt.Connect();
+
+ int ret = aitt.PublishWithReplySync(
+ rr_topic.c_str(), message.c_str(), message.size(), AITT_TYPE_MQTT,
+ AITT_QOS_AT_MOST_ONCE, false,
+ [&](aitt::MSG *msg, const void *data, const size_t datalen, void *cbdata) {
+ FAIL() << "Should not be called";
+ },
+ nullptr, correlation, 1);
+
+ EXPECT_EQ(ret, AITT_ERROR_TIMED_OUT);
+ } catch (std::exception &e) {
+ FAIL() << e.what();
+ }
+}
+
+TEST_F(AITTRRTest, RequestResponse_timeout_restart_P_Anytime)
+{
+ bool sub_ok, reply_ok;
+ sub_ok = reply_ok = false;
+
+ try {
+ AITT sub_aitt(clientId + "sub", LOCAL_IP, true);
+ sub_aitt.Connect();
+ sub_aitt.Subscribe(rr_topic.c_str(),
+ [&](aitt::MSG *msg, const void *data, const size_t datalen, void *cbdata) {
+ INFO("Subscribe Callback is called");
+ CheckSubscribe(msg, data, datalen);
+ sub_aitt.SendReply(msg, reply.c_str(), reply.size(), false);
+ sub_ok = true;
+ });
+
+ AITT aitt(clientId, LOCAL_IP, true);
+ aitt.Connect();
+
+ int ret = aitt.PublishWithReplySync(
+ rr_topic.c_str(), message.c_str(), message.size(), AITT_TYPE_MQTT,
+ AITT_QOS_AT_MOST_ONCE, false,
+ [&](aitt::MSG *msg, const void *data, const size_t datalen, void *cbdata) {
+ INFO("Reply Callback is called");
+ static int invalid = 0;
+ if (invalid)
+ FAIL() << "Should not be called";
+ else
+ reply_ok = true;
+ invalid++;
+ },
+ nullptr, correlation, 500);
+
+ EXPECT_TRUE(sub_ok == reply_ok);
+ EXPECT_EQ(ret, AITT_ERROR_TIMED_OUT);
+ } catch (std::exception &e) {
+ FAIL() << e.what();
+ }
+}
diff --git a/tests/TransportModuleLoader_test.cc b/tests/TransportModuleLoader_test.cc
new file mode 100644
index 0000000..57e69b1
--- /dev/null
+++ b/tests/TransportModuleLoader_test.cc
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2021 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "TransportModuleLoader.h"
+
+#include <AITT.h>
+#include <gtest/gtest.h>
+
+#include "AittTransport.h"
+#include "aitt_internal.h"
+
+class TransportModuleLoaderTest : public testing::Test {
+ public:
+ TransportModuleLoaderTest(void) : discovery("test"), loader("127.0.0.1")
+ {
+ loader.Init(discovery);
+ }
+
+ protected:
+ void SetUp() override {}
+ void TearDown() override {}
+
+ aitt::AittDiscovery discovery;
+ aitt::TransportModuleLoader loader;
+};
+
+TEST_F(TransportModuleLoaderTest, Positive_GetInstance_Anytime)
+{
+ std::shared_ptr<aitt::AittTransport> module = loader.GetInstance(AITT_TYPE_TCP);
+ ASSERT_NE(module, nullptr);
+}
+
+TEST_F(TransportModuleLoaderTest, Negative_GetInstance_Anytime)
+{
+ std::shared_ptr<aitt::AittTransport> module = loader.GetInstance(AITT_TYPE_MQTT);
+ ASSERT_EQ(module, nullptr);
+}
diff --git a/tests/aitt_c_manualtest.cc b/tests/aitt_c_manualtest.cc
new file mode 100644
index 0000000..6085ea5
--- /dev/null
+++ b/tests/aitt_c_manualtest.cc
@@ -0,0 +1,101 @@
+/*
+ * Copyright (c) 2022 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "aitt_c.h"
+
+#include <glib.h>
+#include <gtest/gtest.h>
+
+#include "aitt_tests.h"
+
+TEST(AITT_C_MANUAL, will_set_P)
+{
+ int ret;
+ aitt_h handle = aitt_new("test14", LOCAL_IP);
+ ASSERT_NE(handle, nullptr);
+
+ ret = aitt_connect(handle, LOCAL_IP, 1883);
+ ASSERT_EQ(ret, AITT_ERROR_NONE);
+
+ static bool sub_called = false;
+ GMainLoop *loop = g_main_loop_new(nullptr, FALSE);
+ aitt_sub_h sub_handle = nullptr;
+ ret = aitt_subscribe(
+ handle, "test/topic_will",
+ [](aitt_msg_h msg_handle, const void *msg, size_t msg_len, void *user_data) {
+ std::string received_data((const char *)msg, msg_len);
+ EXPECT_STREQ(received_data.c_str(), TEST_C_MSG);
+ EXPECT_STREQ(aitt_msg_get_topic(msg_handle), TEST_C_TOPIC);
+ sub_called = true;
+ },
+ loop, &sub_handle);
+ ASSERT_EQ(ret, AITT_ERROR_NONE);
+ EXPECT_TRUE(sub_handle != nullptr);
+
+ int pid = fork();
+ if (pid == 0) {
+ aitt_h handle_will = aitt_new("test_will", LOCAL_IP);
+ ASSERT_NE(handle_will, nullptr);
+
+ ret = aitt_will_set(handle_will, "test/topic_will", TEST_C_MSG, strlen(TEST_C_MSG),
+ AITT_QOS_AT_LEAST_ONCE, false);
+ ASSERT_EQ(ret, AITT_ERROR_NONE);
+
+ ret = aitt_connect(handle_will, LOCAL_IP, 1883);
+ ASSERT_EQ(ret, AITT_ERROR_NONE);
+
+ // Do not call below
+ // aitt_disconnect(handle_will)
+ // aitt _destroy(handle_will);
+ } else {
+ sleep(1);
+ kill(pid, SIGKILL);
+
+ g_timeout_add(
+ 10,
+ [](gpointer data) -> gboolean {
+ if (sub_called) {
+ GMainLoop *loop = static_cast<GMainLoop *>(data);
+ g_main_loop_quit(loop);
+ return FALSE;
+ }
+ return TRUE;
+ },
+ loop);
+
+ g_main_loop_run(loop);
+ g_main_loop_unref(loop);
+
+ ret = aitt_disconnect(handle);
+ EXPECT_EQ(ret, AITT_ERROR_NONE);
+
+ aitt_destroy(handle);
+ }
+}
+
+// Set user/passwd in mosquitto.conf before testing
+TEST(AITT_C_MANUAL, connect_id_passwd_P)
+{
+ aitt_h handle = aitt_new("test15", LOCAL_IP);
+ ASSERT_NE(handle, nullptr);
+
+ int ret = aitt_connect_full(handle, LOCAL_IP, 1883, "testID", "testPasswd");
+ ASSERT_EQ(ret, AITT_ERROR_NONE);
+
+ ret = aitt_disconnect(handle);
+ EXPECT_EQ(ret, AITT_ERROR_NONE);
+
+ aitt_destroy(handle);
+}
diff --git a/tests/aitt_c_test.cc b/tests/aitt_c_test.cc
new file mode 100644
index 0000000..a83f88d
--- /dev/null
+++ b/tests/aitt_c_test.cc
@@ -0,0 +1,370 @@
+/*
+ * Copyright (c) 2022 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "aitt_c.h"
+
+#include <glib.h>
+#include <gtest/gtest.h>
+
+#include "aitt_internal.h"
+#include "aitt_tests.h"
+
+TEST(AITT_C_INTERFACE, new_P_Anytime)
+{
+ aitt_h handle = aitt_new("test1", LOCAL_IP);
+ EXPECT_TRUE(handle != nullptr);
+ aitt_destroy(handle);
+
+ handle = aitt_new(nullptr, nullptr);
+ EXPECT_TRUE(handle != nullptr);
+ aitt_destroy(handle);
+
+ handle = aitt_new("", "");
+ EXPECT_TRUE(handle != nullptr);
+ aitt_destroy(handle);
+}
+
+TEST(AITT_C_INTERFACE, destroy_P_Anytime)
+{
+ aitt_h handle = aitt_new("test2", LOCAL_IP);
+ ASSERT_NE(handle, nullptr);
+
+ aitt_destroy(handle);
+ aitt_destroy(nullptr);
+}
+
+// TODO:: Not yet Support
+/*
+TEST(AITT_C_INTERFACE, option_P_Anytime)
+{
+ aitt_h handle = aitt_new("test3");
+ ASSERT_NE(handle, nullptr);
+
+ int ret = aitt_set_option(handle, AITT_OPT_MY_IP, LOCAL_IP);
+ EXPECT_EQ(ret, AITT_ERROR_NONE);
+ EXPECT_STREQ(LOCAL_IP, aitt_get_option(handle, AITT_OPT_MY_IP));
+
+ ret = aitt_set_option(handle, AITT_OPT_MY_IP, NULL);
+ EXPECT_EQ(ret, AITT_ERROR_NONE);
+ EXPECT_EQ(NULL, aitt_get_option(handle, AITT_OPT_MY_IP));
+
+ aitt_destroy(handle);
+}
+
+TEST(AITT_C_INTERFACE, option_N_Anytime)
+{
+ aitt_h handle = aitt_new("test4");
+ ASSERT_NE(handle, nullptr);
+
+ int ret = aitt_set_option(handle, AITT_OPT_UNKNOWN, LOCAL_IP);
+ EXPECT_EQ(ret, AITT_ERROR_INVALID_PARAMETER);
+
+ ret = aitt_set_option(nullptr, AITT_OPT_MY_IP, LOCAL_IP);
+ EXPECT_EQ(ret, AITT_ERROR_INVALID_PARAMETER);
+
+ aitt_destroy(handle);
+}
+*/
+
+TEST(AITT_C_INTERFACE, connect_disconnect_P_Anytime)
+{
+ aitt_h handle = aitt_new("test5", LOCAL_IP);
+ ASSERT_NE(handle, nullptr);
+
+ int ret = aitt_connect(handle, LOCAL_IP, 1883);
+ ASSERT_EQ(ret, AITT_ERROR_NONE);
+
+ ret = aitt_disconnect(handle);
+ EXPECT_EQ(ret, AITT_ERROR_NONE);
+
+ aitt_destroy(handle);
+}
+
+TEST(AITT_C_INTERFACE, connect_N_Anytime)
+{
+ aitt_h handle = aitt_new("test6", LOCAL_IP);
+ ASSERT_NE(handle, nullptr);
+
+ aitt_h invalid_handle = nullptr;
+ int ret = aitt_connect(invalid_handle, LOCAL_IP, 1883);
+ EXPECT_EQ(ret, AITT_ERROR_INVALID_PARAMETER);
+
+ const char *invalid_ip = "1.2.3";
+ ret = aitt_connect(handle, invalid_ip, 1883);
+ EXPECT_EQ(ret, AITT_ERROR_INVALID_PARAMETER);
+
+ invalid_ip = "";
+ ret = aitt_connect(handle, invalid_ip, 1883);
+ EXPECT_EQ(ret, AITT_ERROR_INVALID_PARAMETER);
+
+ int invalid_port = -1;
+ ret = aitt_connect(handle, LOCAL_IP, invalid_port);
+ EXPECT_EQ(ret, AITT_ERROR_SYSTEM);
+
+ aitt_destroy(handle);
+}
+
+TEST(AITT_C_INTERFACE, disconnect_N_Anytime)
+{
+ int ret = aitt_disconnect(nullptr);
+ EXPECT_EQ(ret, AITT_ERROR_INVALID_PARAMETER);
+
+ aitt_h handle = aitt_new("test7", LOCAL_IP);
+ ASSERT_NE(handle, nullptr);
+
+ ret = aitt_disconnect(handle);
+ EXPECT_EQ(ret, AITT_ERROR_NOT_READY);
+
+ aitt_destroy(handle);
+}
+
+TEST(AITT_C_INTERFACE, pub_sub_P_Anytime)
+{
+ aitt_h handle = aitt_new("test8", LOCAL_IP);
+ ASSERT_NE(handle, nullptr);
+
+ int ret = aitt_connect(handle, LOCAL_IP, 1883);
+ ASSERT_EQ(ret, AITT_ERROR_NONE);
+
+ GMainLoop *loop = g_main_loop_new(nullptr, FALSE);
+ aitt_sub_h sub_handle = nullptr;
+ ret = aitt_subscribe(
+ handle, TEST_C_TOPIC,
+ [](aitt_msg_h msg_handle, const void *msg, size_t msg_len, void *user_data) {
+ GMainLoop *loop = static_cast<GMainLoop *>(user_data);
+ std::string received_data((const char *)msg, msg_len);
+ EXPECT_STREQ(received_data.c_str(), TEST_C_MSG);
+ EXPECT_STREQ(aitt_msg_get_topic(msg_handle), TEST_C_TOPIC);
+ g_main_loop_quit(loop);
+ },
+ loop, &sub_handle);
+ ASSERT_EQ(ret, AITT_ERROR_NONE);
+ EXPECT_TRUE(sub_handle != nullptr);
+
+ ret = aitt_publish(handle, TEST_C_TOPIC, TEST_C_MSG, strlen(TEST_C_MSG));
+ ASSERT_EQ(ret, AITT_ERROR_NONE);
+
+ g_main_loop_run(loop);
+ g_main_loop_unref(loop);
+
+ ret = aitt_disconnect(handle);
+ EXPECT_EQ(ret, AITT_ERROR_NONE);
+
+ aitt_destroy(handle);
+}
+
+TEST(AITT_C_INTERFACE, pub_N_Anytime)
+{
+ aitt_h handle = aitt_new("test9", LOCAL_IP);
+ ASSERT_NE(handle, nullptr);
+
+ int ret = aitt_connect(handle, nullptr, 1883);
+ EXPECT_NE(ret, AITT_ERROR_NONE);
+
+ ret = aitt_publish(handle, TEST_C_TOPIC, TEST_C_MSG, strlen(TEST_C_MSG));
+ EXPECT_EQ(ret, AITT_ERROR_NOT_READY);
+
+ ret = aitt_publish(nullptr, TEST_C_TOPIC, TEST_C_MSG, strlen(TEST_C_MSG));
+ EXPECT_EQ(ret, AITT_ERROR_INVALID_PARAMETER);
+
+ ret = aitt_connect(handle, LOCAL_IP, 1883);
+ ASSERT_EQ(ret, AITT_ERROR_NONE);
+
+ ret = aitt_publish(handle, nullptr, TEST_C_MSG, strlen(TEST_C_MSG));
+ EXPECT_EQ(ret, AITT_ERROR_INVALID_PARAMETER);
+
+ ret = aitt_publish(handle, TEST_C_TOPIC, nullptr, strlen(TEST_C_MSG));
+ EXPECT_EQ(ret, AITT_ERROR_INVALID_PARAMETER);
+
+ aitt_disconnect(handle);
+ aitt_destroy(handle);
+}
+
+TEST(AITT_C_INTERFACE, sub_N_Anytime)
+{
+ aitt_h handle = aitt_new("test10", LOCAL_IP);
+ aitt_sub_h sub_handle = nullptr;
+ ASSERT_NE(handle, nullptr);
+
+ int ret = aitt_connect(handle, nullptr, 1883);
+ EXPECT_NE(ret, AITT_ERROR_NONE);
+
+ ret = aitt_subscribe(
+ handle, TEST_C_TOPIC, [](aitt_msg_h, const void *, size_t, void *) {}, nullptr,
+ &sub_handle);
+ EXPECT_EQ(ret, AITT_ERROR_NOT_READY);
+
+ ret = aitt_subscribe(
+ nullptr, TEST_C_TOPIC, [](aitt_msg_h, const void *, size_t, void *) {}, nullptr,
+ &sub_handle);
+ EXPECT_EQ(ret, AITT_ERROR_INVALID_PARAMETER);
+
+ ret = aitt_connect(handle, LOCAL_IP, 1883);
+ ASSERT_EQ(ret, AITT_ERROR_NONE);
+
+ ret = aitt_subscribe(
+ handle, nullptr, [](aitt_msg_h, const void *, size_t, void *) {}, nullptr, &sub_handle);
+ EXPECT_EQ(ret, AITT_ERROR_INVALID_PARAMETER);
+
+ ret = aitt_subscribe(handle, TEST_C_TOPIC, nullptr, nullptr, &sub_handle);
+ EXPECT_EQ(ret, AITT_ERROR_INVALID_PARAMETER);
+
+ aitt_disconnect(handle);
+ aitt_destroy(handle);
+}
+
+#define reply_msg "hello reply message"
+#define test_correlation "0001"
+
+TEST(AITT_C_INTERFACE, pub_with_reply_send_reply_P_Anytime)
+{
+ aitt_h handle = aitt_new("test11", LOCAL_IP);
+ ASSERT_NE(handle, nullptr);
+
+ int ret = aitt_connect(handle, LOCAL_IP, 1883);
+ ASSERT_EQ(ret, AITT_ERROR_NONE);
+
+ GMainLoop *loop = g_main_loop_new(nullptr, FALSE);
+ aitt_sub_h sub_handle = nullptr;
+ ret = aitt_subscribe(
+ handle, TEST_C_TOPIC,
+ [](aitt_msg_h msg_handle, const void *msg, size_t msg_len, void *user_data) {
+ aitt_h handle = static_cast<aitt_h>(user_data);
+ std::string received_data((const char *)msg, msg_len);
+ EXPECT_STREQ(received_data.c_str(), TEST_C_MSG);
+ EXPECT_STREQ(aitt_msg_get_topic(msg_handle), TEST_C_TOPIC);
+ aitt_send_reply(handle, msg_handle, reply_msg, sizeof(reply_msg), true);
+ },
+ handle, &sub_handle);
+ ASSERT_EQ(ret, AITT_ERROR_NONE);
+
+ ret = aitt_publish_with_reply(
+ handle, TEST_C_TOPIC, TEST_C_MSG, strlen(TEST_C_MSG), AITT_TYPE_MQTT,
+ AITT_QOS_AT_MOST_ONCE, test_correlation,
+ [](aitt_msg_h msg_handle, const void *msg, size_t msg_len, void *user_data) {
+ GMainLoop *loop = static_cast<GMainLoop *>(user_data);
+ std::string received_data((const char *)msg, msg_len);
+ EXPECT_STREQ(received_data.c_str(), reply_msg);
+ g_main_loop_quit(loop);
+ },
+ loop);
+
+ g_main_loop_run(loop);
+ g_main_loop_unref(loop);
+
+ ret = aitt_disconnect(handle);
+ EXPECT_EQ(ret, AITT_ERROR_NONE);
+
+ aitt_destroy(handle);
+}
+
+TEST(AITT_C_INTERFACE, pub_with_reply_N_Anytime)
+{
+ aitt_h handle = aitt_new("test12", LOCAL_IP);
+ ASSERT_NE(handle, nullptr);
+
+ int ret = aitt_connect(handle, LOCAL_IP, 1883);
+ ASSERT_EQ(ret, AITT_ERROR_NONE);
+
+ ret = aitt_publish_with_reply(
+ nullptr, TEST_C_TOPIC, TEST_C_MSG, strlen(TEST_C_MSG), AITT_TYPE_MQTT,
+ AITT_QOS_AT_MOST_ONCE, test_correlation,
+ [](aitt_msg_h msg_handle, const void *msg, size_t msg_len, void *user_data) {}, nullptr);
+ EXPECT_EQ(ret, AITT_ERROR_INVALID_PARAMETER);
+
+ ret = aitt_publish_with_reply(
+ handle, nullptr, TEST_C_MSG, strlen(TEST_C_MSG), AITT_TYPE_MQTT, AITT_QOS_AT_MOST_ONCE,
+ test_correlation,
+ [](aitt_msg_h msg_handle, const void *msg, size_t msg_len, void *user_data) {}, nullptr);
+ EXPECT_EQ(ret, AITT_ERROR_INVALID_PARAMETER);
+
+ ret = aitt_publish_with_reply(
+ handle, TEST_C_TOPIC, nullptr, 0, AITT_TYPE_MQTT, AITT_QOS_AT_MOST_ONCE, test_correlation,
+ [](aitt_msg_h msg_handle, const void *msg, size_t msg_len, void *user_data) {}, nullptr);
+ EXPECT_EQ(ret, AITT_ERROR_INVALID_PARAMETER);
+
+ ret = aitt_publish_with_reply(handle, TEST_C_TOPIC, TEST_C_MSG, strlen(TEST_C_MSG),
+ AITT_TYPE_MQTT, AITT_QOS_AT_MOST_ONCE, test_correlation, nullptr, nullptr);
+ EXPECT_EQ(ret, AITT_ERROR_INVALID_PARAMETER);
+
+ aitt_destroy(handle);
+}
+
+TEST(AITT_C_INTERFACE, sub_unsub_P_Anytime)
+{
+ aitt_h handle = aitt_new("test13", LOCAL_IP);
+ ASSERT_NE(handle, nullptr);
+
+ int ret = aitt_connect(handle, LOCAL_IP, 1883);
+ ASSERT_EQ(ret, AITT_ERROR_NONE);
+
+ static unsigned int sub_call_count = 0;
+ GMainLoop *loop = g_main_loop_new(nullptr, FALSE);
+ static aitt_sub_h sub_handle = nullptr;
+ ret = aitt_subscribe(
+ handle, TEST_C_TOPIC,
+ [](aitt_msg_h msg_handle, const void *msg, size_t msg_len, void *user_data) {
+ sub_call_count++;
+ },
+ nullptr, &sub_handle);
+ ASSERT_EQ(ret, AITT_ERROR_NONE);
+ EXPECT_TRUE(sub_handle != nullptr);
+
+ ret = aitt_publish(handle, TEST_C_TOPIC, TEST_C_MSG, strlen(TEST_C_MSG));
+ ASSERT_EQ(ret, AITT_ERROR_NONE);
+
+ g_timeout_add(
+ 1000,
+ [](gpointer data) -> gboolean {
+ aitt_h handle = static_cast<aitt_h>(data);
+ int ret = aitt_unsubscribe(handle, sub_handle);
+ EXPECT_EQ(ret, AITT_ERROR_NONE);
+ sub_handle = nullptr;
+
+ ret = aitt_publish(handle, TEST_C_TOPIC, TEST_C_MSG, strlen(TEST_C_MSG));
+ EXPECT_EQ(ret, AITT_ERROR_NONE);
+ return FALSE;
+ },
+ handle);
+
+ g_timeout_add(
+ 2000,
+ [](gpointer data) -> gboolean {
+ GMainLoop *loop = static_cast<GMainLoop *>(data);
+ EXPECT_EQ(sub_call_count, 1);
+
+ if (sub_call_count == 1) {
+ g_main_loop_quit(loop);
+ return FALSE;
+ }
+
+ return TRUE;
+ },
+ loop);
+
+ g_main_loop_run(loop);
+ g_main_loop_unref(loop);
+
+ ret = aitt_disconnect(handle);
+ EXPECT_EQ(ret, AITT_ERROR_NONE);
+
+ aitt_destroy(handle);
+}
+
+TEST(AITT_C_INTERFACE, will_set_N_Anytime)
+{
+ int ret = aitt_will_set(nullptr, "test/will_topic", "test", 4, AITT_QOS_AT_MOST_ONCE, false);
+ EXPECT_EQ(ret, AITT_ERROR_INVALID_PARAMETER);
+}
diff --git a/tests/aitt_tests.h b/tests/aitt_tests.h
new file mode 100644
index 0000000..f740007
--- /dev/null
+++ b/tests/aitt_tests.h
@@ -0,0 +1,77 @@
+/*
+ * Copyright (c) 2022 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+#include <glib.h>
+#include <sys/time.h>
+
+#include "aitt_internal.h"
+
+#define LOCAL_IP "127.0.0.1"
+#define TEST_C_TOPIC "test/topic_c"
+#define TEST_C_MSG "test123456789"
+
+#define TEST_MSG "This is aitt test message"
+#define TEST_MSG2 "This message is going to be delivered through a specified AittProtocol"
+#define SLEEP_MS 1
+
+class AittTests {
+ public:
+ void Init()
+ {
+ ready = false;
+ ready2 = false;
+
+ timeval tv;
+ char buffer[256];
+ gettimeofday(&tv, nullptr);
+ snprintf(buffer, sizeof(buffer), "UniqueID.%lX%lX", tv.tv_sec, tv.tv_usec);
+ clientId = buffer;
+ snprintf(buffer, sizeof(buffer), "TestTopic.%lX%lX", tv.tv_sec, tv.tv_usec);
+ testTopic = buffer;
+ mainLoop = g_main_loop_new(nullptr, FALSE);
+ }
+
+ void Deinit() { g_main_loop_unref(mainLoop); }
+
+ void ToggleReady() { ready = true; }
+ void ToggleReady2() { ready2 = true; }
+ static gboolean ReadyCheck(gpointer data)
+ {
+ AittTests *test = static_cast<AittTests *>(data);
+
+ if (test->ready) {
+ g_main_loop_quit(test->mainLoop);
+ return G_SOURCE_REMOVE;
+ }
+
+ return G_SOURCE_CONTINUE;
+ }
+
+ void IterateEventLoop(void)
+ {
+ g_main_loop_run(mainLoop);
+ DBG("Go forward");
+ }
+
+ void *subscribeHandle;
+ bool ready;
+ bool ready2;
+
+ GMainLoop *mainLoop;
+ std::string clientId;
+ std::string testTopic;
+};
diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt
new file mode 100644
index 0000000..d8fe56d
--- /dev/null
+++ b/tools/CMakeLists.txt
@@ -0,0 +1,7 @@
+SET(AITT_DISCOVERY_TOOL ${PROJECT_NAME}_discovery_viewer)
+
+###########################################################################
+ADD_EXECUTABLE(${AITT_DISCOVERY_TOOL} discovery_viewer.cc FlexbufPrinter.cc)
+TARGET_LINK_LIBRARIES(${AITT_DISCOVERY_TOOL} ${AITT_NEEDS_LIBRARIES})
+
+INSTALL(TARGETS ${AITT_DISCOVERY_TOOL} DESTINATION ${AITT_TEST_BINDIR})
diff --git a/tools/FlexbufPrinter.cc b/tools/FlexbufPrinter.cc
new file mode 100644
index 0000000..339a9cd
--- /dev/null
+++ b/tools/FlexbufPrinter.cc
@@ -0,0 +1,133 @@
+/*
+ * Copyright (c) 2022 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "FlexbufPrinter.h"
+
+#include <flatbuffers/flexbuffers.h>
+
+#include <iostream>
+
+#include "aitt_internal.h"
+
+FlexbufPrinter::FlexbufPrinter() : tab(0)
+{
+}
+
+void FlexbufPrinter::PrettyPrint(const std::string &name, const uint8_t *data, int datalen)
+{
+ std::cout << name << std::endl;
+
+ auto root = flexbuffers::GetRoot(data, datalen);
+ PrettyParsing(root, false);
+}
+
+std::string FlexbufPrinter::PrettyTab(bool ignore)
+{
+ if (ignore)
+ return std::string();
+
+ std::stringstream ss;
+ for (int i = 0; i < tab; i++)
+ ss << '\t';
+
+ return ss.str();
+}
+
+void FlexbufPrinter::PrettyMap(const flexbuffers::Reference &data, bool inline_value)
+{
+ std::cout << PrettyTab(inline_value) << "{" << std::endl;
+ tab++;
+
+ auto map = data.AsMap();
+ auto keys = map.Keys();
+ for (size_t i = 0; i < keys.size(); i++) {
+ std::cout << PrettyTab(false) << keys[i].AsKey() << " : ";
+ PrettyParsing(map[keys[i].AsKey()], true);
+ }
+
+ tab--;
+ std::cout << PrettyTab(false) << "}" << std::endl;
+}
+
+void FlexbufPrinter::PrettyVector(const flexbuffers::Reference &data, bool inline_value)
+{
+ auto vec = data.AsVector();
+ std::cout << PrettyTab(inline_value) << "[" << std::endl;
+ tab++;
+
+ for (size_t i = 0; i < vec.size(); i++)
+ PrettyParsing(vec[i], false);
+
+ tab--;
+ std::cout << PrettyTab(false) << "]" << std::endl;
+}
+
+void FlexbufPrinter::PrettyBlob(const flexbuffers::Reference &data, bool inline_value)
+{
+ auto blob = data.AsBlob();
+ auto root = flexbuffers::GetRoot(static_cast<const uint8_t *>(blob.data()), blob.size());
+
+ PrettyParsing(root, true);
+}
+
+void FlexbufPrinter::PrettyParsing(const flexbuffers::Reference &data, bool inline_value)
+{
+ using namespace flexbuffers;
+ switch (data.GetType()) {
+ case FBT_NULL:
+ ERR("Unknown Type : case FBT_NULL");
+ break;
+ case FBT_KEY:
+ ERR("Unknown Type : case FBT_KEY");
+ break;
+ case FBT_MAP:
+ PrettyMap(data, inline_value);
+ break;
+ case FBT_BLOB:
+ PrettyBlob(data, inline_value);
+ break;
+ case FBT_VECTOR:
+ PrettyVector(data, inline_value);
+ break;
+ case FBT_INT:
+ case FBT_INDIRECT_INT:
+ case FBT_UINT:
+ case FBT_INDIRECT_UINT:
+ case FBT_FLOAT:
+ case FBT_INDIRECT_FLOAT:
+ case FBT_STRING:
+ case FBT_VECTOR_INT:
+ case FBT_VECTOR_UINT:
+ case FBT_VECTOR_FLOAT:
+ case FBT_VECTOR_KEY:
+ case FBT_VECTOR_STRING_DEPRECATED:
+ case FBT_VECTOR_INT2:
+ case FBT_VECTOR_UINT2:
+ case FBT_VECTOR_FLOAT2:
+ case FBT_VECTOR_INT3:
+ case FBT_VECTOR_UINT3:
+ case FBT_VECTOR_FLOAT3:
+ case FBT_VECTOR_INT4:
+ case FBT_VECTOR_UINT4:
+ case FBT_VECTOR_FLOAT4:
+ case FBT_VECTOR_BOOL:
+ case FBT_BOOL:
+ std::cout << PrettyTab(inline_value) << data.ToString() << std::endl;
+ break;
+ default:
+ ERR("Unknown Type(%d)", data.GetType());
+ break;
+ }
+}
diff --git a/tools/FlexbufPrinter.h b/tools/FlexbufPrinter.h
new file mode 100644
index 0000000..46bfe51
--- /dev/null
+++ b/tools/FlexbufPrinter.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2022 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+#include <flatbuffers/flexbuffers.h>
+
+class FlexbufPrinter {
+ public:
+ FlexbufPrinter();
+
+ void PrettyPrint(const std::string &name, const uint8_t *data, int datalen);
+
+ private:
+ std::string PrettyTab(bool ignore);
+ void PrettyMap(const flexbuffers::Reference &data, bool inline_value);
+ void PrettyVector(const flexbuffers::Reference &data, bool inline_value);
+ void PrettyBlob(const flexbuffers::Reference &data, bool inline_value);
+ void PrettyParsing(const flexbuffers::Reference &data, bool inline_value);
+
+ int tab;
+};
diff --git a/tools/discovery_viewer.cc b/tools/discovery_viewer.cc
new file mode 100644
index 0000000..a3c2c76
--- /dev/null
+++ b/tools/discovery_viewer.cc
@@ -0,0 +1,129 @@
+/*
+ * Copyright (c) 2022 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <argp.h>
+#include <flatbuffers/flexbuffers.h>
+#include <mosquitto.h>
+
+#include "FlexbufPrinter.h"
+#include "aitt_internal.h"
+
+struct ParsingData {
+ ParsingData() : clean(false), broker_ip("127.0.0.1") {}
+ bool clean;
+ std::string broker_ip;
+};
+const char *argp_program_version = "aitt-discovery-viewer 1.0";
+
+static char argp_doc[] =
+ "AITT Discovery topic Viewer"
+ "\v"
+ "Tizen: <http://www.tizen.org>";
+
+/* The options we understand. */
+static struct argp_option options[] = {{"broker", 'b', "IP", 0, "broker ip address"},
+ {"clean", 'c', 0, OPTION_ARG_OPTIONAL, "clean discovery topic"}, {0}};
+
+static error_t parse_opt(int key, char *arg, struct argp_state *state)
+{
+ struct ParsingData *args = (struct ParsingData *)state->input;
+
+ switch (key) {
+ case 'b':
+ args->broker_ip = arg;
+ break;
+ case 'c':
+ args->clean = true;
+ break;
+ case ARGP_KEY_NO_ARGS:
+ // argp_usage(state);
+ break;
+ default:
+ return ARGP_ERR_UNKNOWN;
+ }
+ return 0;
+}
+
+class MQTTHandler {
+ public:
+ MQTTHandler()
+ {
+ int ret = mosquitto_lib_init();
+ if (ret != MOSQ_ERR_SUCCESS)
+ ERR("mosquitto_lib_init() Fail(%s)", mosquitto_strerror(ret));
+
+ std::string id = std::to_string(rand() % 100);
+ handle = mosquitto_new(id.c_str(), true, nullptr);
+ if (handle == nullptr) {
+ ERR("mosquitto_new() Fail");
+ mosquitto_lib_cleanup();
+ return;
+ }
+ mosquitto_int_option(handle, MOSQ_OPT_PROTOCOL_VERSION, MQTT_PROTOCOL_V5);
+
+ mosquitto_message_v5_callback_set(handle,
+ [](mosquitto *handle, void *user_data, const mosquitto_message *msg,
+ const mosquitto_property *props) {
+ std::string topic = msg->topic;
+ size_t end = topic.find("/", DISCOVERY_TOPIC_BASE.length());
+ std::string clientId = topic.substr(DISCOVERY_TOPIC_BASE.length(), end);
+ FlexbufPrinter printer;
+ if (msg->payloadlen)
+ printer.PrettyPrint(clientId, static_cast<const uint8_t *>(msg->payload),
+ msg->payloadlen);
+ });
+
+ ret = mosquitto_loop_start(handle);
+ if (ret != MOSQ_ERR_SUCCESS)
+ ERR("mosquitto_loop_start() Fail(%s)", mosquitto_strerror(ret));
+ }
+
+ void Connect(const std::string &broker_ip)
+ {
+ int ret = mosquitto_connect(handle, broker_ip.c_str(), 1883, 60);
+ if (ret != MOSQ_ERR_SUCCESS)
+ ERR("mosquitto_connect() Fail(%s)", mosquitto_strerror(ret));
+
+ int mid = 0;
+ ret = mosquitto_subscribe(handle, &mid, (DISCOVERY_TOPIC_BASE + "+").c_str(), 0);
+ if (ret != MOSQ_ERR_SUCCESS)
+ ERR("mosquitto_subscribe() Fail(%s)", mosquitto_strerror(ret));
+ }
+
+ private:
+ mosquitto *handle;
+};
+
+int main(int argc, char **argv)
+{
+ struct ParsingData arg;
+ struct argp argp_conf = {options, parse_opt, 0, argp_doc};
+
+ argp_parse(&argp_conf, argc, argv, 0, 0, &arg);
+
+ if (arg.clean) {
+ ERR("Not Supported");
+ return -1;
+ }
+
+ MQTTHandler mqtt;
+ mqtt.Connect(arg.broker_ip);
+
+ while (1) {
+ sleep(10);
+ }
+
+ return 0;
+}