Ref count IPCAOpen() with same app ID.
authorSoemin Tjong <stjong@microsoft.com>
Thu, 6 Apr 2017 01:52:31 +0000 (18:52 -0700)
committerDan Mihai <Daniel.Mihai@microsoft.com>
Mon, 12 Jun 2017 18:26:25 +0000 (18:26 +0000)
In codegen implementation, it is difficult to pass around IPCAAppHandle
returned by IPCAOpen() to compartmentalized code generated for
each device or resource type.

IoTivity currently supports 1 server (an app is also a server) per
process (tracked by https://jira.iotivity.org/browse/IOT-1379).
Therefore multiple calls to IPCAOpen() in the same process is not
supported.

This change makes an exception so that IPCAOpen() will now return same
IPCAAppHandle when called with same app ID.

Change-Id: I9a3a2334e6fc9230595a68e3451c2a863c532824
Signed-off-by: Soemin Tjong <stjong@microsoft.com>
Reviewed-on: https://gerrit.iotivity.org/gerrit/18851
Tested-by: jenkins-iotivity <jenkins@iotivity.org>
Reviewed-by: Dan Mihai <Daniel.Mihai@microsoft.com>
resource/IPCA/src/app.cpp
resource/IPCA/src/callback.cpp
resource/IPCA/src/device.cpp
resource/IPCA/src/inc/app.h
resource/IPCA/src/inc/callback.h
resource/IPCA/src/inc/device.h
resource/IPCA/src/ipca.cpp
resource/IPCA/unittests/ipcaunittests.cpp

index bfc6adf..bf83cad 100644 (file)
@@ -19,6 +19,7 @@
 
 #include "oic_time.h"
 #include "ipcainternal.h"
+#include "ocrandom.h"
 
 // Object that implements interface to IoTivity.
 // @future: Consider having an instance of this per app when there's mechanism to unregister
@@ -45,9 +46,18 @@ App::~App()
 {
 }
 
-IPCAStatus App::Start(bool unitTestMode)
+IPCAStatus App::Start(bool unitTestMode, App::Ptr thisSharedPtr)
 {
-    m_callback = std::shared_ptr<Callback>(new Callback(this));
+    char appId[UUID_STRING_SIZE];
+    if (!OCConvertUuidToString(m_ipcaAppInfo.appId.uuid, appId))
+    {
+        return IPCA_FAIL;
+    }
+
+    m_appId = appId;
+    m_thisSharedPtr = thisSharedPtr;
+
+    m_callback = std::make_shared<Callback>(m_thisSharedPtr);
     if (m_callback == nullptr)
     {
         return IPCA_OUT_OF_MEMORY;
@@ -70,7 +80,7 @@ IPCAStatus App::Start(bool unitTestMode)
     }
 
     // Start periodic discovery thread.
-    m_appWorkerThread = std::thread(&App::AppWorkerThread, this);
+    m_appWorkerThread = std::thread(&App::AppWorkerThread, m_thisSharedPtr);
     return IPCA_OK;
 }
 
@@ -121,9 +131,16 @@ void App::Stop()
         DeleteAndUnregisterCallbackInfo(m_passwordDisplayCallbackInfo->mapKey);
         m_passwordDisplayCallbackInfo = nullptr;
     }
+
+    m_thisSharedPtr = nullptr;  // Release reference to self.
+}
+
+std::string App::GetAppId()
+{
+    return m_appId;
 }
 
-void App::AppWorkerThread(App* app)
+void App::AppWorkerThread(App::Ptr app)
 {
     const uint64_t FastDiscoveryCount = 4;  // First 4 periodic discovery requests use fast period.
     const uint64_t SlowDiscoveryPeriodMs = 30000;
@@ -211,7 +228,7 @@ void App::AppWorkerThread(App* app)
     OIC_LOG_V(INFO, TAG, "-AppWorkerThread exit.");
 }
 
-IPCAStatus App::OpenDevice(const char* deviceId, IPCADeviceHandle* deviceHandle)
+IPCAStatus App::OpenDevice(App::Ptr thisApp, const char* deviceId, IPCADeviceHandle* deviceHandle)
 {
     *deviceHandle = nullptr;
 
@@ -221,7 +238,7 @@ IPCAStatus App::OpenDevice(const char* deviceId, IPCADeviceHandle* deviceHandle)
         return IPCA_OUT_OF_MEMORY;
     }
 
-    Device::Ptr device = std::shared_ptr<Device>(new Device(deviceId, &ocfFramework, this));
+    Device::Ptr device = std::shared_ptr<Device>(new Device(deviceId, &ocfFramework, thisApp));
     if (device == nullptr)
     {
         return IPCA_OUT_OF_MEMORY;
@@ -233,10 +250,10 @@ IPCAStatus App::OpenDevice(const char* deviceId, IPCADeviceHandle* deviceHandle)
         return status;
     }
 
-    deviceWrapper->app = this;
-    deviceWrapper->device = device;
+    deviceWrapper->app = thisApp;
+    deviceWrapper->device = device;  // Take a device reference.
     *deviceHandle =  reinterpret_cast<IPCADeviceHandle>(deviceWrapper.get());
-    m_openedDevices[deviceWrapper.get()] = deviceWrapper.get();  // Take a device reference.
+    m_openedDevices[deviceWrapper.get()] = deviceWrapper.get();
     deviceWrapper.release();
     return IPCA_OK;
 }
index dffce6b..a7c9b2d 100644 (file)
 
 #define TAG "IPCA_Callback"
 
+// Next key for the m_callbackInfoList map. Key is unique across all IPCA apps.
+static std::atomic<size_t> g_nextKey(1);
+
 extern OCFFramework ocfFramework;
 
-Callback::Callback(App* app) :
-    m_nextKey(0),
+Callback::Callback(AppPtr app) :
     m_app(app),
     m_stopCalled(false),
     m_expiredCallbacksInProgress(0)
@@ -282,7 +284,13 @@ IPCAStatus Callback::AddCallbackInfo(CallbackInfo::Ptr cbInfo)
     uint32_t i = 0;
     while (i++ < UINT_MAX)
     {
-        size_t newKey = m_nextKey++;
+        size_t newKey = g_nextKey++;
+        if (newKey == 0)
+        {
+            // Avoid misinterpreted as NULL handle.
+            continue;
+        }
+
         if (m_callbackInfoList.find(newKey) == m_callbackInfoList.end())
         {
             OIC_LOG_V(INFO, TAG, "AddCallbackInfo() with key: %" PRIuPTR, newKey);
index 65dc6a6..a36c1e5 100644 (file)
@@ -21,7 +21,7 @@
 #include "ipcainternal.h"
 #include "oic_malloc.h"
 
-Device::Device(const char* deviceId, OCFFramework* ocf, App* app) :
+Device::Device(const char* deviceId, OCFFramework* ocf, App::Ptr app) :
     m_deviceId(deviceId),
     m_app(app),
     m_ocfFramework(ocf),
index 0307f3d..d167a1f 100644 (file)
@@ -26,6 +26,7 @@ typedef std::shared_ptr<CallbackInfo> CallbackInfoPtr;
 struct IPCAAppInfoInternal
 {
     IPCAUuid     appId;
+    std::string  appIdString;
     std::string  appName;
     std::string  appSoftwareVersion;
     std::string  appCompanyName;
@@ -46,13 +47,18 @@ typedef struct _DiscoveryDetails
 class App
 {
     public:
+        typedef std::shared_ptr<App> Ptr;
+
         App(const IPCAAppInfo* ipcaAppInfo, IPCAVersion ipcaVersion);
         ~App();
 
         // Application calls IPCAOpen()/IPCAClose().
-        IPCAStatus Start(bool unitTestMode);
+        IPCAStatus Start(bool unitTestMode, App::Ptr thisSharedPtr);
         void Stop();
 
+        // Returns application's ID.
+        std::string GetAppId();
+
         // Application calls IPCADiscoverDevices().
         IPCAStatus DiscoverDevices(
                         IPCADiscoverDeviceCallback callback,
@@ -62,7 +68,11 @@ class App
                         IPCAHandle* handle);
 
         // Application calls IPCAOpenDevice().
-        IPCAStatus OpenDevice(const char* deviceId, IPCADeviceHandle* deviceHandle);
+        IPCAStatus OpenDevice(
+                        App::Ptr thisApp,
+                        const char* deviceId,
+                        IPCADeviceHandle* deviceHandle);
+
         void CloseDevice(IPCADeviceHandle deviceHandle);
 
         // Application calls IPCAGetProperties().
@@ -128,17 +138,19 @@ class App
                             IPCAHandle* handle);
 
         // Close handle returned in DiscoverDevices(), GetProperties(), SetProperties(),
-        // ObserveResource(), CreateResource() and RequestAccess().
+        // ObserveResource(), CreateResource() or RequestAccess().
+        // Return IPCA_OK if successful.  Or failure, for example, if handle is already closed.
         IPCAStatus CloseIPCAHandle(
                         IPCAHandle handle,
                         IPCACloseHandleComplete closeHandleComplete,
                         const void* context);
-
     private:
         std::mutex m_appMutex;
+        App::Ptr m_thisSharedPtr;   // shared_ptr of this object.
         volatile bool m_isStopped;  // set to true when Stop() is called.
 
         IPCAAppInfoInternal m_ipcaAppInfo;
+        std::string m_appId;        // String of m_ipcaAppInfo.appId.
         IPCAVersion m_ipcaVersion;  // IPCA version requested in the call to IPCAOpen().
 
         // Object that implements callbacks to the app.
@@ -174,7 +186,7 @@ class App
                                     const void* context = nullptr);
 
         // Thread performing periodic discovery.
-        static void AppWorkerThread(App* app);
+        static void AppWorkerThread(App::Ptr app);
 
         // List of resource types to discover periodically.
         // Key is cbInfo->mapKey of each IPCADiscoverDevices() request.
index dcc8c64..ea5d1a4 100644 (file)
@@ -40,6 +40,7 @@ namespace OC {
 class App;
 class Device;
 typedef std::shared_ptr<Device> DevicePtr;
+typedef std::shared_ptr<App> AppPtr;
 
 // Structure contains information to make callbacks to app.
 struct CallbackInfo
@@ -49,8 +50,8 @@ struct CallbackInfo
                          std::cout << mapKey << std::endl; */ }
     typedef std::shared_ptr<CallbackInfo> Ptr;
     size_t mapKey;          // key to m_callbackInfoList map.  Initialized in AddCallbackInfo().
-    App* app;               // the app that creates this callback.
-    DevicePtr device;     // the device expected to respond to the callback.
+    AppPtr app;             // the app that creates this callback.
+    DevicePtr device;       // the device expected to respond to the callback.
     CallbackType type;
     union
     {
@@ -100,7 +101,7 @@ class Callback
 {
     public:
         typedef std::shared_ptr<Callback> Ptr;
-        Callback(App* app);
+        Callback(AppPtr app);
         ~Callback();
 
         // Preparation to shut down.
@@ -218,9 +219,8 @@ class Callback
         std::mutex m_discoverDeviceCallbackMutex;
 
         // Table of CallbackInfo.  Key is autogenerated.
-        std::atomic<size_t> m_nextKey;  // next key for the m_callbackInfoList map.
         std::map<size_t, CallbackInfo::Ptr> m_callbackInfoList;  // List of expected callbacks.
-        App* m_app; // Callback object is per app.
+        AppPtr m_app; // Callback object is per app.
         volatile bool m_stopCalled;    // Set to true when Stop() is called.
 
         // Number of expired callbacks in progress.
index 29d1763..9d2ab5a 100644 (file)
@@ -23,6 +23,8 @@
 class App;
 class OCFFramework;
 
+typedef std::shared_ptr<App> AppPtr;
+
 /* Type of resource info requested in GetResourceInfo() */
 enum class ResourceInfoType
 {
@@ -35,10 +37,10 @@ class Device
     public:
         typedef std::shared_ptr<Device> Ptr;
 
-        Device(const char* deviceId, OCFFramework* ocf, App* app);
+        Device(const char* deviceId, OCFFramework* ocf, AppPtr app);
         ~Device();
 
-        App* GetApp() { return m_app; }
+        AppPtr GetApp() { return m_app; }
         std::string GetDeviceId() { return m_deviceId; }
 
         // Make sure OCFFramework has found this device and let it know that it's opened.
@@ -94,7 +96,7 @@ class Device
 
     private:
         std::string m_deviceId;   // from IPCAOpenDevice()
-        App* m_app;
+        AppPtr m_app;
         OCFFramework* m_ocfFramework;
         bool m_isClosed;  // set to true after Close().
 };
@@ -102,7 +104,7 @@ class Device
 // returned as IPCAHandle to the app.
 typedef struct
 {
-    App* app;
+    AppPtr app;
     Device::Ptr device;
 } DeviceWrapper;
 
index c5f2a7f..00493cd 100644 (file)
 #include "iotivity_config.h"
 #include "oic_malloc.h"
 #include "ipca.h"
+#include "ocrandom.h"
 #include "ipcainternal.h"
 
 #define OCFFactoryReset     "fr"
 #define OCFReboot           "rb"
 
-// As in proc there's no reason for multiple IPCAOpen().
-// There's no ref count for g_app.  Therefore must hold g_ipcaAppMutex when using g_app.
-App* g_app = nullptr;
-std::mutex g_ipcaAppMutex;
-
+// List of applications. Since IPCA runs inproc and also IoTivity does not yet support multiple
+// apps (i.e. servers) in same process, the max count of g_AppList is currently 1.
+static std::map<size_t, App::Ptr> g_AppList;    // Map's key is generated from g_nextAppKey.
+static std::map<size_t, uint32_t> g_AppListRefCount;     // Ref count of App objects.
+size_t g_nextAppKey = 1;
+std::recursive_mutex g_ipcaAppMutex;
 bool g_unitTestMode = false;
 
+// Return App with matching appId.
+App::Ptr FindApp(size_t appId)
+{
+    std::lock_guard<std::recursive_mutex> lock(g_ipcaAppMutex);
+    if (g_AppList.find(appId) == g_AppList.end())
+    {
+        return nullptr;
+    }
+
+    return g_AppList[appId];
+}
+
 IPCAStatus IPCA_CALL IPCAOpen(
                         const IPCAAppInfo* ipcaAppInfo,
                         IPCAVersion ipcaVersion,
                         IPCAAppHandle* ipcaAppHandle)
 {
-    std::lock_guard<std::mutex> lock(g_ipcaAppMutex);
+    App::Ptr app = nullptr;
+
+    std::lock_guard<std::recursive_mutex> lock(g_ipcaAppMutex);
 
     if (ipcaVersion != IPCA_VERSION_1)
     {
         return IPCA_INVALID_ARGUMENT;
     }
 
-    if (g_app != nullptr)
-    {
-        return IPCA_ALREADY_OPENED;
-    }
-
     if (ipcaAppInfo == nullptr ||
         ipcaAppInfo->appName == nullptr ||
         ipcaAppInfo->appSoftwareVersion == nullptr ||
@@ -57,37 +68,87 @@ IPCAStatus IPCA_CALL IPCAOpen(
         return IPCA_INVALID_ARGUMENT;
     }
 
-    g_app= new App(ipcaAppInfo, ipcaVersion);
+    char appId[UUID_STRING_SIZE];
+    if (!OCConvertUuidToString(ipcaAppInfo->appId.uuid, appId))
+    {
+        return IPCA_FAIL;
+    }
+
+    // Since IPCA runs in proc, support one IPCAOpen() per process.
+    // An exception is made for app calling IPCAOpen() with same app ID to conveniently retrieve the
+    // IPCAAppHandle.
+    for (auto registeredApp : g_AppList)
+    {
+        if (registeredApp.second->GetAppId().compare(appId) == 0)
+        {
+            *ipcaAppHandle = reinterpret_cast<IPCAAppHandle>(registeredApp.first);
+            g_AppListRefCount[registeredApp.first]++;
+            return IPCA_OK;
+        }
+    }
 
-    if (g_app == nullptr)
+    // One App object per process. In future, may support multiple App objects per process when
+    // https://jira.iotivity.org/browse/IOT-1379 is resolved.
+    if (g_AppList.size() != 0)
+    {
+        return IPCA_ALREADY_OPENED;
+    }
+
+    app = std::make_shared<App>(ipcaAppInfo, ipcaVersion);
+    if (app == nullptr)
     {
         return IPCA_OUT_OF_MEMORY;
     }
 
-    IPCAStatus status = g_app->Start(g_unitTestMode);
+    IPCAStatus status = app->Start(g_unitTestMode, app);
     if (status != IPCA_OK)
     {
-        delete g_app;
-        g_app = nullptr;
         return status;
     }
 
-    *ipcaAppHandle = reinterpret_cast<IPCAAppHandle>(g_app);
-    return IPCA_OK;
+    uint32_t i = 0;
+    while (i++ < UINT_MAX)
+    {
+        size_t newAppKey = g_nextAppKey++;
+
+        if (newAppKey == 0)
+        {
+            // Skip returning handle with value 0.
+            continue;
+        }
+
+        if (g_AppList.find(newAppKey) == g_AppList.end())
+        {
+            g_AppList[newAppKey] = app;
+            g_AppListRefCount[newAppKey] = 1;
+            *ipcaAppHandle = reinterpret_cast<IPCAAppHandle>(newAppKey);
+            return IPCA_OK;
+        }
+    }
+
+    // Too many apps.
+    app->Stop();
+    return IPCA_FAIL;
 }
 
 void IPCA_CALL IPCAClose(IPCAAppHandle ipcaAppHandle)
 {
-    App* app = reinterpret_cast<App*>(ipcaAppHandle);
-    std::lock_guard<std::mutex> lock(g_ipcaAppMutex);
-    if (g_app == nullptr)
+    std::lock_guard<std::recursive_mutex> lock(g_ipcaAppMutex);
+    App::Ptr app = FindApp(reinterpret_cast<size_t>(ipcaAppHandle));
+    if (app == nullptr)
     {
         return;
     }
 
-    app->Stop();
-    delete app;
-    g_app = nullptr;
+    if ((--g_AppListRefCount[reinterpret_cast<size_t>(ipcaAppHandle)]) == 0)
+    {
+        // stop the app.
+        app->Stop();
+
+        // remove app from list.
+        g_AppList.erase(reinterpret_cast<size_t>(ipcaAppHandle));
+        g_AppListRefCount.erase(reinterpret_cast<size_t>(ipcaAppHandle));
+    }
 }
 
 IPCAStatus IPCA_CALL IPCADiscoverDevices(
@@ -98,7 +159,12 @@ IPCAStatus IPCA_CALL IPCADiscoverDevices(
                                     int resourceTypeCount,
                                     IPCAHandle* handle)
 {
-    App* app = reinterpret_cast<App*>(ipcaAppHandle);
+    App::Ptr app = FindApp(reinterpret_cast<size_t>(ipcaAppHandle));
+    if (app == nullptr)
+    {
+        return IPCA_INVALID_ARGUMENT;
+    }
+
     return app->DiscoverDevices(callback, context, resourceTypeList, resourceTypeCount, handle);
 }
 
@@ -107,14 +173,19 @@ IPCAStatus IPCA_CALL IPCAOpenDevice(
                                     const char* deviceId,
                                     IPCADeviceHandle* deviceHandle)
 {
-    App* app = reinterpret_cast<App*>(ipcaAppHandle);
-    return app->OpenDevice(deviceId, deviceHandle);
+    App::Ptr app = FindApp(reinterpret_cast<size_t>(ipcaAppHandle));
+    if (app == nullptr)
+    {
+        return IPCA_INVALID_ARGUMENT;
+    }
+
+    return app->OpenDevice(app, deviceId, deviceHandle);
 }
 
 void IPCA_CALL IPCACloseDevice(IPCADeviceHandle deviceHandle)
 {
     DeviceWrapper* deviceWrapper = reinterpret_cast<DeviceWrapper*>(deviceHandle);
-    App* app = deviceWrapper->device->GetApp();
+    App::Ptr app = deviceWrapper->device->GetApp();
     app->CloseDevice(deviceHandle);
 }
 
@@ -294,10 +365,17 @@ IPCAStatus IPCA_CALL IPCACloseHandle(IPCAHandle handle,
                         IPCACloseHandleComplete closeHandleComplete,
                         void* context)
 {
-    std::lock_guard<std::mutex> lock(g_ipcaAppMutex);
-    if (g_app != nullptr)
+    std::lock_guard<std::recursive_mutex> lock(g_ipcaAppMutex);
+
+    // There's no direct mapping from handle to App. When number of App objects is large, may need
+    // to create a mapping table for efficiency.
+    for (auto app : g_AppList)
     {
-        return (g_app->CloseIPCAHandle(handle, closeHandleComplete, context));
+        if (app.second->CloseIPCAHandle(handle, closeHandleComplete, context) == IPCA_OK)
+        {
+            // handle is unique across all apps.
+            return IPCA_OK;
+        }
     }
 
     return IPCA_FAIL;
@@ -424,7 +502,12 @@ IPCAStatus IPCA_CALL IPCASetPasswordCallbacks(
                                 IPCADisplayPasswordCallback displayCallback,
                                 void* context)
 {
-    App* app = reinterpret_cast<App*>(ipcaAppHandle);
+    App::Ptr app = FindApp(reinterpret_cast<size_t>(ipcaAppHandle));
+    if (app == nullptr)
+    {
+        return IPCA_INVALID_ARGUMENT;
+    }
+
     return app->SetPasswordCallbacks(inputCallback, displayCallback, context);
 }
 
index f15879a..927a8f4 100644 (file)
@@ -424,11 +424,24 @@ class IPCAMiscTest : public testing::Test
         IPCAAppHandle m_anotherIPCAAppHandle;
         IPCAAppInfo m_ipcaAppInfo;
 
-        IPCAStatus DoAnotherIPCAOpen()
+        IPCAStatus DoAnotherIPCAOpenWithSameAppId()
         {
             return IPCAOpen(&m_ipcaAppInfo, IPCA_VERSION_1, &m_anotherIPCAAppHandle);
         }
 
+        IPCAStatus DoAnotherIPCAOpenWithDifferentAppId()
+        {
+            IPCAAppInfo ipcaAppInfoExtra;
+            IPCAAppHandle ipcaAppHandleExtra;
+            IPCAUuid extraTestAppUuid = {
+                                            {0x63, 0x72, 0xc0, 0xea, 0x1f, 0x12, 0x11, 0xe7,
+                                             0x93, 0xae, 0x92, 0x36, 0x1f, 0x00, 0x26, 0x71}
+                                        };
+
+            ipcaAppInfoExtra = { extraTestAppUuid, IPCATestAppName, "1.0.0", "Microsoft" };
+            return IPCAOpen(&ipcaAppInfoExtra, IPCA_VERSION_1, &ipcaAppHandleExtra);
+        }
+
     protected:
         virtual void SetUp()
         {
@@ -452,9 +465,10 @@ class IPCAMiscTest : public testing::Test
         }
 };
 
-TEST_F(IPCAMiscTest, ShouldNotAllowMultipleCallsToIPCOpen)
+TEST_F(IPCAMiscTest, MultipleIPCAOpen)
 {
-    EXPECT_EQ(IPCA_ALREADY_OPENED, DoAnotherIPCAOpen());
+    EXPECT_EQ(IPCA_OK, DoAnotherIPCAOpenWithSameAppId());
+    EXPECT_EQ(IPCA_ALREADY_OPENED, DoAnotherIPCAOpenWithDifferentAppId());
 }
 
 TEST_F(IPCAMiscTest, IPCAOpenShouldBeAllowedAfterIPCAClose)
@@ -462,7 +476,7 @@ TEST_F(IPCAMiscTest, IPCAOpenShouldBeAllowedAfterIPCAClose)
     IPCAClose(m_ipcaAppHandle);
     m_ipcaAppHandle = NULL;
 
-    ASSERT_EQ(IPCA_OK, DoAnotherIPCAOpen());
+    ASSERT_EQ(IPCA_OK, DoAnotherIPCAOpenWithSameAppId());
 }
 
 /*