Initial commit 00/279400/1 master accepted/tizen/unified/20220809.022038 accepted/tizen/unified/20220809.093639 submit/tizen/20220809.012345
authorJihoon Jung <jh8801.jung@samsung.com>
Tue, 9 Aug 2022 00:07:09 +0000 (09:07 +0900)
committerJihoon Jung <jh8801.jung@samsung.com>
Tue, 9 Aug 2022 00:08:16 +0000 (09:08 +0900)
Change-Id: I7e19c06adf39f36e326f32e11ef00932a3dbfaa5

136 files changed:
.clang-format [new file with mode: 0644]
.gitignore [new file with mode: 0644]
CMakeLists.txt [new file with mode: 0644]
LICENSE.APLv2 [new file with mode: 0644]
NOTICE [new file with mode: 0644]
aitt.pc.in [new file with mode: 0644]
android/aitt/.gitignore [new file with mode: 0644]
android/aitt/CMakeLists.txt [new file with mode: 0644]
android/aitt/build.gradle [new file with mode: 0644]
android/aitt/proguard-rules.pro [new file with mode: 0644]
android/aitt/src/main/AndroidManifest.xml [new file with mode: 0644]
android/aitt/src/main/java/com/samsung/android/aitt/Aitt.java [new file with mode: 0644]
android/aitt/src/main/java/com/samsung/android/aitt/AittMessage.java [new file with mode: 0644]
android/aitt/src/main/jni/aitt_jni.cc [new file with mode: 0644]
android/aitt/src/main/jni/aitt_jni.h [new file with mode: 0644]
android/aitt/src/test/java/com/samsung/android/aitt/AittMessageUnitTest.java [new file with mode: 0644]
android/aitt/src/test/java/com/samsung/android/aitt/AittUnitTest.java [new file with mode: 0644]
android/aitt/src/test/resources/robolectric.properties [new file with mode: 0644]
android/flatbuffers/build.gradle [new file with mode: 0644]
android/flatbuffers/proguard-rules.pro [new file with mode: 0644]
android/flatbuffers/src/main/AndroidManifest.xml [new file with mode: 0644]
android/modules/webrtc/.gitignore [new file with mode: 0644]
android/modules/webrtc/build.gradle [new file with mode: 0644]
android/modules/webrtc/proguard-rules.pro [new file with mode: 0644]
android/modules/webrtc/src/main/AndroidManifest.xml [new file with mode: 0644]
android/modules/webrtc/src/main/java/com/samsung/android/modules/webrtc/WebRTC.java [new file with mode: 0644]
android/modules/webrtc/src/main/java/com/samsung/android/modules/webrtc/WebRTCServer.java [new file with mode: 0644]
android/modules/webrtc/src/test/java/com/samsung/android/modules/webrtc/ExampleUnitTest.java [new file with mode: 0644]
android/mosquitto/build.gradle [new file with mode: 0644]
android/mosquitto/src/main/AndroidManifest.xml [new file with mode: 0644]
android/settings.gradle [new file with mode: 0644]
build.gradle [new file with mode: 0644]
cmake/aitt_android_flatbuffers.cmake [new file with mode: 0644]
cmake/aitt_android_glib.cmake [new file with mode: 0644]
cmake/aitt_android_mosquitto.cmake [new file with mode: 0644]
common/AITTEx.cc [new file with mode: 0755]
common/AITTEx.h [new file with mode: 0755]
common/AittDiscovery.cc [new file with mode: 0644]
common/CMakeLists.txt [new file with mode: 0644]
common/MQ.cc [new file with mode: 0644]
common/MQ.h [new file with mode: 0644]
common/MSG.cc [new file with mode: 0644]
common/MainLoopHandler.cc [new file with mode: 0644]
common/MainLoopHandler.h [new file with mode: 0644]
common/aitt_internal.h [new file with mode: 0644]
common/aitt_internal_definitions.h [new file with mode: 0644]
common/aitt_internal_profiling.h [new file with mode: 0644]
common/aitt_platform.h [new file with mode: 0644]
common/tizen/aitt_platform.h [new file with mode: 0644]
debian/aitt-dev.install [new file with mode: 0644]
debian/aitt-plugins.install [new file with mode: 0644]
debian/aitt.install [new file with mode: 0644]
debian/changelog [new file with mode: 0644]
debian/compat [new file with mode: 0644]
debian/control [new file with mode: 0644]
debian/copyright [new file with mode: 0644]
debian/rules [new file with mode: 0755]
gradle.properties [new file with mode: 0644]
gradle/wrapper/gradle-wrapper.properties [new file with mode: 0644]
gradlew [new file with mode: 0644]
gradlew.bat [new file with mode: 0644]
include/AITT.h [new file with mode: 0644]
include/AittDiscovery.h [new file with mode: 0644]
include/AittTransport.h [new file with mode: 0644]
include/AittTypes.h [new file with mode: 0644]
include/MSG.h [new file with mode: 0644]
include/aitt_c.h [new file with mode: 0644]
mock/MQMockTest.h [new file with mode: 0644]
mock/MQTTMock.h [new file with mode: 0644]
mock/mosquitto.cc [new file with mode: 0644]
modules/main.cc [new file with mode: 0644]
modules/tcp/CMakeLists.txt [new file with mode: 0644]
modules/tcp/Module.cc [new file with mode: 0644]
modules/tcp/Module.h [new file with mode: 0644]
modules/tcp/TCP.cc [new file with mode: 0644]
modules/tcp/TCP.h [new file with mode: 0644]
modules/tcp/TCPServer.cc [new file with mode: 0644]
modules/tcp/TCPServer.h [new file with mode: 0644]
modules/tcp/samples/CMakeLists.txt [new file with mode: 0644]
modules/tcp/samples/tcp_test.cc [new file with mode: 0644]
modules/tcp/tests/CMakeLists.txt [new file with mode: 0644]
modules/tcp/tests/TCPServer_test.cc [new file with mode: 0644]
modules/tcp/tests/TCP_test.cc [new file with mode: 0644]
modules/webrtc/CMakeLists.txt [new file with mode: 0644]
modules/webrtc/CameraHandler.cc [new file with mode: 0644]
modules/webrtc/CameraHandler.h [new file with mode: 0644]
modules/webrtc/Config.h [new file with mode: 0644]
modules/webrtc/IfaceServer.h [new file with mode: 0644]
modules/webrtc/Module.cc [new file with mode: 0644]
modules/webrtc/Module.h [new file with mode: 0644]
modules/webrtc/MqttServer.cc [new file with mode: 0644]
modules/webrtc/MqttServer.h [new file with mode: 0644]
modules/webrtc/PublishStream.cc [new file with mode: 0644]
modules/webrtc/PublishStream.h [new file with mode: 0644]
modules/webrtc/SubscribeStream.cc [new file with mode: 0644]
modules/webrtc/SubscribeStream.h [new file with mode: 0644]
modules/webrtc/WebRtcEventHandler.h [new file with mode: 0644]
modules/webrtc/WebRtcMessage.cc [new file with mode: 0644]
modules/webrtc/WebRtcMessage.h [new file with mode: 0644]
modules/webrtc/WebRtcPeer.cc [new file with mode: 0644]
modules/webrtc/WebRtcPeer.h [new file with mode: 0644]
modules/webrtc/WebRtcRoom.cc [new file with mode: 0644]
modules/webrtc/WebRtcRoom.h [new file with mode: 0644]
modules/webrtc/WebRtcState.cc [new file with mode: 0644]
modules/webrtc/WebRtcState.h [new file with mode: 0644]
modules/webrtc/WebRtcStream.cc [new file with mode: 0644]
modules/webrtc/WebRtcStream.h [new file with mode: 0644]
modules/webrtc/tests/CMakeLists.txt [new file with mode: 0644]
modules/webrtc/tests/MockPublishStream.h [new file with mode: 0644]
modules/webrtc/tests/MockSubscribeStream.h [new file with mode: 0644]
modules/webrtc/tests/WEBRTC_test.cc [new file with mode: 0644]
packaging/aitt.manifest [new file with mode: 0644]
packaging/aitt.spec [new file with mode: 0644]
settings.gradle [new file with mode: 0644]
src/AITT.cc [new file with mode: 0644]
src/AITTImpl.cc [new file with mode: 0644]
src/AITTImpl.h [new file with mode: 0644]
src/TransportModuleLoader.cc [new file with mode: 0644]
src/TransportModuleLoader.h [new file with mode: 0644]
src/aitt_c.cc [new file with mode: 0644]
tests/AITT_TCP_test.cc [new file with mode: 0644]
tests/AITT_manualtest.cc [new file with mode: 0644]
tests/AITT_test.cc [new file with mode: 0644]
tests/CMakeLists.txt [new file with mode: 0644]
tests/MQ_mocktest.cc [new file with mode: 0644]
tests/MQ_test.cc [new file with mode: 0644]
tests/MainLoopHandler_test.cc [new file with mode: 0644]
tests/RequestResponse_test.cc [new file with mode: 0644]
tests/TransportModuleLoader_test.cc [new file with mode: 0644]
tests/aitt_c_manualtest.cc [new file with mode: 0644]
tests/aitt_c_test.cc [new file with mode: 0644]
tests/aitt_tests.h [new file with mode: 0644]
tools/CMakeLists.txt [new file with mode: 0644]
tools/FlexbufPrinter.cc [new file with mode: 0644]
tools/FlexbufPrinter.h [new file with mode: 0644]
tools/discovery_viewer.cc [new file with mode: 0644]

diff --git a/.clang-format b/.clang-format
new file mode 100644 (file)
index 0000000..f9a6b2c
--- /dev/null
@@ -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 (file)
index 0000000..0c9507e
--- /dev/null
@@ -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 (file)
index 0000000..c6fde39
--- /dev/null
@@ -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 (file)
index 0000000..a4da8fd
--- /dev/null
@@ -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 (file)
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 (file)
index 0000000..1cd7e88
--- /dev/null
@@ -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 (file)
index 0000000..796b96d
--- /dev/null
@@ -0,0 +1 @@
+/build
diff --git a/android/aitt/CMakeLists.txt b/android/aitt/CMakeLists.txt
new file mode 100644 (file)
index 0000000..4d1b82f
--- /dev/null
@@ -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 (file)
index 0000000..2fb82be
--- /dev/null
@@ -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 (file)
index 0000000..f1b4245
--- /dev/null
@@ -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 (file)
index 0000000..e51cebf
--- /dev/null
@@ -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 (file)
index 0000000..b335314
--- /dev/null
@@ -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 (file)
index 0000000..7ba570f
--- /dev/null
@@ -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 (file)
index 0000000..801b906
--- /dev/null
@@ -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 (file)
index 0000000..69e3262
--- /dev/null
@@ -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 (file)
index 0000000..abbb0a8
--- /dev/null
@@ -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 (file)
index 0000000..a7d1789
--- /dev/null
@@ -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 (file)
index 0000000..932b01b
--- /dev/null
@@ -0,0 +1 @@
+sdk=28
diff --git a/android/flatbuffers/build.gradle b/android/flatbuffers/build.gradle
new file mode 100644 (file)
index 0000000..ccd0ce7
--- /dev/null
@@ -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 (file)
index 0000000..f1b4245
--- /dev/null
@@ -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 (file)
index 0000000..f4119ba
--- /dev/null
@@ -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 (file)
index 0000000..796b96d
--- /dev/null
@@ -0,0 +1 @@
+/build
diff --git a/android/modules/webrtc/build.gradle b/android/modules/webrtc/build.gradle
new file mode 100644 (file)
index 0000000..9764253
--- /dev/null
@@ -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 (file)
index 0000000..f1b4245
--- /dev/null
@@ -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 (file)
index 0000000..7b7ae3f
--- /dev/null
@@ -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 (file)
index 0000000..ebf6c6e
--- /dev/null
@@ -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 (file)
index 0000000..6e93ed7
--- /dev/null
@@ -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 (file)
index 0000000..1536b3d
--- /dev/null
@@ -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 (file)
index 0000000..0b6001a
--- /dev/null
@@ -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 (file)
index 0000000..458e804
--- /dev/null
@@ -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 (file)
index 0000000..aa2ce81
--- /dev/null
@@ -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 (file)
index 0000000..a8ae137
--- /dev/null
@@ -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 (file)
index 0000000..048d7ec
--- /dev/null
@@ -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 (file)
index 0000000..44c6618
--- /dev/null
@@ -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 (file)
index 0000000..4428534
--- /dev/null
@@ -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 (executable)
index 0000000..b99d305
--- /dev/null
@@ -0,0 +1,59 @@
+/*\r
+ * Copyright (c) 2021-2022 Samsung Electronics Co., Ltd All Rights Reserved\r
+ *\r
+ * Licensed under the Apache License, Version 2.0 (the "License");\r
+ * you may not use this file except in compliance with the License.\r
+ * You may obtain a copy of the License at\r
+ *\r
+ *     http://www.apache.org/licenses/LICENSE-2.0\r
+ *\r
+ * Unless required by applicable law or agreed to in writing, software\r
+ * distributed under the License is distributed on an "AS IS" BASIS,\r
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ * See the License for the specific language governing permissions and\r
+ * limitations under the License.\r
+ */\r
+#include "AITTEx.h"\r
+\r
+using namespace aitt;\r
+\r
+AITTEx::AITTEx(ErrCode code) : err_code(code)\r
+{\r
+    err_msg = getErrString();\r
+}\r
+\r
+AITTEx::AITTEx(ErrCode code, const std::string& msg) : err_code(code)\r
+{\r
+    err_msg = getErrString() + " : " + msg;\r
+}\r
+\r
+AITTEx::ErrCode AITTEx::getErrCode()\r
+{\r
+    return err_code;\r
+}\r
+\r
+std::string AITTEx::getErrString() const\r
+{\r
+    switch (err_code) {\r
+    case INVALID_ARG:\r
+        return "Invalid Argument";\r
+    case NO_MEMORY:\r
+        return "Memory allocation failure";\r
+    case OPERATION_FAILED:\r
+        return "Operation failure";\r
+    case SYSTEM_ERR:\r
+        return "System failure";\r
+    case MQTT_ERR:\r
+        return "MQTT failure";\r
+    case NO_DATA:\r
+        return "No data found";\r
+    default:\r
+        return "Unknown Error";\r
+    }\r
+}\r
+\r
+const char* AITTEx::what() const throw()\r
+{\r
+    return err_msg.c_str();\r
+}\r
+\r
diff --git a/common/AITTEx.h b/common/AITTEx.h
new file mode 100755 (executable)
index 0000000..8018c7f
--- /dev/null
@@ -0,0 +1,49 @@
+/*\r
+ * Copyright (c) 2021-2022 Samsung Electronics Co., Ltd All Rights Reserved\r
+ *\r
+ * Licensed under the Apache License, Version 2.0 (the "License");\r
+ * you may not use this file except in compliance with the License.\r
+ * You may obtain a copy of the License at\r
+ *\r
+ *     http://www.apache.org/licenses/LICENSE-2.0\r
+ *\r
+ * Unless required by applicable law or agreed to in writing, software\r
+ * distributed under the License is distributed on an "AS IS" BASIS,\r
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ * See the License for the specific language governing permissions and\r
+ * limitations under the License.\r
+ */\r
+#pragma once\r
+\r
+#include <exception>\r
+#include <string>\r
+#include <vector>\r
+\r
+namespace aitt {\r
+\r
+class AITTEx : public std::exception {\r
+  public:\r
+    typedef enum {\r
+        INVALID_ARG,\r
+        NO_MEMORY,\r
+        OPERATION_FAILED,\r
+        SYSTEM_ERR,\r
+        MQTT_ERR,\r
+        NO_DATA\r
+    } ErrCode;\r
+\r
+    AITTEx(ErrCode err_code);\r
+    AITTEx(ErrCode err_code, const std::string& custom_err_msg);\r
+\r
+    ErrCode getErrCode();\r
+    virtual const char* what() const throw() override;\r
+\r
+  private:\r
+    ErrCode err_code;\r
+    std::string err_msg;\r
+\r
+    std::string getErrString() const;\r
+};\r
+\r
+}  // namespace aitt\r
+\r
diff --git a/common/AittDiscovery.cc b/common/AittDiscovery.cc
new file mode 100644 (file)
index 0000000..8f383c0
--- /dev/null
@@ -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 (file)
index 0000000..53eadd9
--- /dev/null
@@ -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 (file)
index 0000000..2be4518
--- /dev/null
@@ -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 (file)
index 0000000..3da6097
--- /dev/null
@@ -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 (file)
index 0000000..46eb8e3
--- /dev/null
@@ -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 (file)
index 0000000..747a6bc
--- /dev/null
@@ -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 (file)
index 0000000..42737b1
--- /dev/null
@@ -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 (file)
index 0000000..feef42b
--- /dev/null
@@ -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 (file)
index 0000000..10cde41
--- /dev/null
@@ -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 (file)
index 0000000..11fcd47
--- /dev/null
@@ -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 (file)
index 0000000..dd13d4d
--- /dev/null
@@ -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 (file)
index 0000000..2a898a5
--- /dev/null
@@ -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 (file)
index 0000000..7354e42
--- /dev/null
@@ -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 (file)
index 0000000..38722db
--- /dev/null
@@ -0,0 +1 @@
+/usr/lib/*/libaitt-transport*.so*
diff --git a/debian/aitt.install b/debian/aitt.install
new file mode 100644 (file)
index 0000000..ccdd217
--- /dev/null
@@ -0,0 +1,2 @@
+/usr/lib/*/libaitt*.so*
+/usr/bin/*
diff --git a/debian/changelog b/debian/changelog
new file mode 100644 (file)
index 0000000..5dc0313
--- /dev/null
@@ -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 (file)
index 0000000..ec63514
--- /dev/null
@@ -0,0 +1 @@
+9
diff --git a/debian/control b/debian/control
new file mode 100644 (file)
index 0000000..c4c06ab
--- /dev/null
@@ -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 (file)
index 0000000..deb2e6f
--- /dev/null
@@ -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 (executable)
index 0000000..cbfdc90
--- /dev/null
@@ -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 (file)
index 0000000..d5e8af3
--- /dev/null
@@ -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 (file)
index 0000000..c6f3907
--- /dev/null
@@ -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 (file)
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 (file)
index 0000000..107acd3
--- /dev/null
@@ -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 (file)
index 0000000..aeeacd8
--- /dev/null
@@ -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 (file)
index 0000000..f7e3f46
--- /dev/null
@@ -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 (file)
index 0000000..6523b2e
--- /dev/null
@@ -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 (file)
index 0000000..1770922
--- /dev/null
@@ -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 (file)
index 0000000..85ecc76
--- /dev/null
@@ -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 (file)
index 0000000..24ffce3
--- /dev/null
@@ -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 (file)
index 0000000..307640b
--- /dev/null
@@ -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 (file)
index 0000000..10d484e
--- /dev/null
@@ -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 (file)
index 0000000..5ea4f1b
--- /dev/null
@@ -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 (file)
index 0000000..86e9c36
--- /dev/null
@@ -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 (file)
index 0000000..edac2fd
--- /dev/null
@@ -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 (file)
index 0000000..bc50d7d
--- /dev/null
@@ -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 (file)
index 0000000..4011980
--- /dev/null
@@ -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 (file)
index 0000000..3b6751e
--- /dev/null
@@ -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 (file)
index 0000000..535819c
--- /dev/null
@@ -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 (file)
index 0000000..55f8511
--- /dev/null
@@ -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 (file)
index 0000000..3c82bc6
--- /dev/null
@@ -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 (file)
index 0000000..8fd1b4b
--- /dev/null
@@ -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 (file)
index 0000000..d319e27
--- /dev/null
@@ -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 (file)
index 0000000..bf1adf1
--- /dev/null
@@ -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 (file)
index 0000000..e8b48b1
--- /dev/null
@@ -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 (file)
index 0000000..604bd23
--- /dev/null
@@ -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 (file)
index 0000000..9452b2b
--- /dev/null
@@ -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 (file)
index 0000000..c3fc8ec
--- /dev/null
@@ -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 (file)
index 0000000..5c44828
--- /dev/null
@@ -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 (file)
index 0000000..63dbd4b
--- /dev/null
@@ -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 (file)
index 0000000..ad6d36d
--- /dev/null
@@ -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 (file)
index 0000000..3c9e4f8
--- /dev/null
@@ -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 (file)
index 0000000..ca31eb8
--- /dev/null
@@ -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 (file)
index 0000000..70a07ed
--- /dev/null
@@ -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 (file)
index 0000000..7f93192
--- /dev/null
@@ -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 (file)
index 0000000..f93ecea
--- /dev/null
@@ -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 (file)
index 0000000..1805528
--- /dev/null
@@ -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 (file)
index 0000000..841cfa6
--- /dev/null
@@ -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 (file)
index 0000000..c8853f6
--- /dev/null
@@ -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 (file)
index 0000000..c922672
--- /dev/null
@@ -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 (file)
index 0000000..3d9af79
--- /dev/null
@@ -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 (file)
index 0000000..6057a22
--- /dev/null
@@ -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 (file)
index 0000000..119f6e4
--- /dev/null
@@ -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 (file)
index 0000000..1ccb4e9
--- /dev/null
@@ -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 (file)
index 0000000..781b72b
--- /dev/null
@@ -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 (file)
index 0000000..fabeb1e
--- /dev/null
@@ -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 (file)
index 0000000..437460d
--- /dev/null
@@ -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 (file)
index 0000000..c6ad8d0
--- /dev/null
@@ -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 (file)
index 0000000..ef717b0
--- /dev/null
@@ -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 (file)
index 0000000..755c1ae
--- /dev/null
@@ -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 (file)
index 0000000..a1dd90f
--- /dev/null
@@ -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 (file)
index 0000000..ced285e
--- /dev/null
@@ -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 (file)
index 0000000..ea2736d
--- /dev/null
@@ -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 (file)
index 0000000..5ee42c1
--- /dev/null
@@ -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 (file)
index 0000000..a76fdba
--- /dev/null
@@ -0,0 +1,5 @@
+<manifest>
+       <request>
+               <domain name="_" />
+       </request>
+</manifest>
diff --git a/packaging/aitt.spec b/packaging/aitt.spec
new file mode 100644 (file)
index 0000000..50aaa78
--- /dev/null
@@ -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 (file)
index 0000000..a8e5154
--- /dev/null
@@ -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 (file)
index 0000000..c50332d
--- /dev/null
@@ -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 (file)
index 0000000..b851625
--- /dev/null
@@ -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 (file)
index 0000000..08bec4a
--- /dev/null
@@ -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 (file)
index 0000000..a953ac9
--- /dev/null
@@ -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 (file)
index 0000000..576c97e
--- /dev/null
@@ -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 (file)
index 0000000..1135353
--- /dev/null
@@ -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 (file)
index 0000000..bd28993
--- /dev/null
@@ -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 (file)
index 0000000..cea0fcf
--- /dev/null
@@ -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 (file)
index 0000000..1dcd8c6
--- /dev/null
@@ -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 (file)
index 0000000..307765d
--- /dev/null
@@ -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 (file)
index 0000000..06fcc7d
--- /dev/null
@@ -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 (file)
index 0000000..4ff554b
--- /dev/null
@@ -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 (file)
index 0000000..6aab8a4
--- /dev/null
@@ -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 (file)
index 0000000..d723e3b
--- /dev/null
@@ -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 (file)
index 0000000..57e69b1
--- /dev/null
@@ -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 (file)
index 0000000..6085ea5
--- /dev/null
@@ -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 (file)
index 0000000..a83f88d
--- /dev/null
@@ -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 (file)
index 0000000..f740007
--- /dev/null
@@ -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 (file)
index 0000000..d8fe56d
--- /dev/null
@@ -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 (file)
index 0000000..339a9cd
--- /dev/null
@@ -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 (file)
index 0000000..46bfe51
--- /dev/null
@@ -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 (file)
index 0000000..a3c2c76
--- /dev/null
@@ -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;
+}