pkgmgr-info
aul
uuid
+ libsmack
+ libtzplatform-config
+ security-manager
)
FOREACH(flag ${notification-ex_CFLAGS})
#include <memory>
#include <algorithm>
#include <vector>
+#include <map>
#include "notification-ex/exception.h"
#include "notification-ex/abstract_item.h"
return {};
}
+void AbstractItem::SetSharedPath() {
+}
+
+std::list<std::map<std::string, std::string>> AbstractItem::GetPathMapList() const {
+ return {};
+}
+
shared_ptr<AbstractAction> AbstractItem::GetAction() const {
return impl_->action_;
}
#include <string>
#include <list>
#include <vector>
+#include <map>
#include "notification-ex/abstract_action.h"
#include "notification-ex/multi_language.h"
virtual std::list<std::string> GetSharedPath() const;
/**
+ * @brief Sets the shared file path to original file path.
+ */
+ virtual void SetSharedPath();
+
+ /**
+ * @brief
+ * @since_tizen 5.5
+ * @return
+ */
+ virtual std::list<std::map<std::string, std::string>> GetPathMapList() const;
+
+ /**
* @brief Gets the notification item id.
* @since_tizen 5.5
* @return The notification item id.
return AbstractItem::ChatMessage;
}
-std::list<std::string> ChatMessageItem::GetSharedPath() const {
- std::list<std::string> ret;
-
- auto name = impl_->name_->GetSharedPath();
- auto text = impl_->text_->GetSharedPath();
- auto image = impl_->image_->GetSharedPath();
- auto time = impl_->time_->GetSharedPath();
-
- for (auto& i : name) {
- ret.push_back(std::move(i));
- }
-
- for (auto& i : text) {
- ret.push_back(std::move(i));
- }
-
- for (auto& i : image) {
- ret.push_back(std::move(i));
- }
-
- for (auto& i : time) {
- ret.push_back(std::move(i));
- }
-
- return ret;
-}
-
Bundle ChatMessageItem::Serialize() const {
Bundle b;
b = AbstractItem::Serialize();
return false;
}
+std::list<std::string> ChatMessageItem::GetSharedPath() const {
+ std::list<std::string> ret;
+
+ auto image = impl_->image_->GetSharedPath();
+ for (auto& i : image) {
+ ret.push_back(std::move(i));
+ }
+
+ return ret;
+}
+
TextItem& ChatMessageItem::GetNameItem() const {
return *(impl_->name_);
}
#include <tizen.h>
+#define REGULAR_UID_MIN 5000
+
namespace notification {
enum NotificationError {
DBusEventListener::Impl::Impl(DBusEventListener* parent, string path)
: subscribe_id_(0), registration_id_(0), path_(path), parent_(parent) {
- LOGI("ButtonItem impl created");
+ LOGI("Dbus_event_listener impl created");
}
void DBusEventListener::Impl::SignalCb(GDBusConnection* connection,
DBusSender::Impl::Impl(DBusSender* parent, string path)
: path_(path), parent_(parent) {
- LOGI("ButtonItem impl created");
+ LOGI("Dbus_sender impl created");
}
DBusSender::~DBusSender() {
return "Count";
case DeleteAll:
return "DeleteAll";
+ case Register:
+ return "Register";
+ case Unregister:
+ return "Unregister";
default:
return "Custom" + std::to_string(Custom);
}
return ret;
}
+void GroupItem::SetSharedPath() {
+ for (auto& i : impl_->children_list_)
+ i->SetSharedPath();
+}
+
+list<map<string, string>> GroupItem::GetPathMapList() const {
+ list<map<string, string>> path_map_list;
+
+ for (auto& i : impl_->children_list_) {
+ auto path_map_list_ = i->GetPathMapList();
+ for (auto& path_map : path_map_list_) {
+ path_map_list.push_back(move(path_map));
+ }
+ }
+
+ return path_map_list;
+}
+
void GroupItem::AddChild(shared_ptr<AbstractItem> child) {
impl_->children_list_.emplace_back(child);
}
#include <string>
#include <memory>
#include <list>
+#include <map>
#include "notification-ex/abstract_item.h"
std::list<std::string> GetSharedPath() const override;
/**
+ * @brief Sets the shared file path to original file path.
+ * @since_tizen 5.5
+ */
+ void SetSharedPath() override;
+
+ /**
+ * @brief Gets the paths of shared file location.
+ * @since_tizen 5.5
+ * @return The list of private path and original path.
+ */
+ std::list<std::map<std::string, std::string>> GetPathMapList() const override;
+
+ /**
* @brief Sets the vertical state.
* @details The vertical state is true, the children of GroupItem are associated vertically.
* And if it is false, the children are associated horizontally.
Error,
Count,
DeleteAll,
+ Register,
+ Unregister,
Custom = 100
};
virtual ~IEventInfo() = default;
* limitations under the License.
*/
+#include <unistd.h>
+#include <sys/types.h>
#include <dlog.h>
+#include <map>
+#include <string>
+#include <list>
+#include <utility>
+
#include "notification-ex/image_item.h"
#include "notification-ex/image_item_implementation.h"
#include "notification-ex/factory_manager.h"
#include "notification-ex/exception.h"
+#include "notification-ex/shared_file.h"
+#include "notification-ex/item_info_internal.h"
+#include "notification-ex/ex_util.h"
#ifdef LOG_TAG
#undef LOG_TAG
#define LOG_TAG "NOTIFICATION_EX"
#define IMAGE_PATH_KEY "__IMAGE_PATH_KEY__"
+#define PRIV_IMAGE_PATH_KEY "__PRIV_IMAGE_PATH_KEY__"
using namespace tizen_base;
namespace notification {
namespace item {
-
ImageItem::ImageItem(std::string image_path,
std::shared_ptr<AbstractAction> action)
: AbstractItem(action), impl_(new Impl(this, image_path)) {
+ UpdatePrivatePath();
}
ImageItem::ImageItem(std::string id, std::string image_path,
std::shared_ptr<AbstractAction> action)
: AbstractItem(id, action), impl_(new Impl(this, image_path)) {
+ UpdatePrivatePath();
}
ImageItem::Impl::Impl(ImageItem* parent, std::string image_path)
b = AbstractItem::Serialize();
b.Add(IMAGE_PATH_KEY, impl_->image_path_);
+ if (!impl_->priv_image_path_.empty())
+ b.Add(PRIV_IMAGE_PATH_KEY, impl_->priv_image_path_);
+
return b;
}
void ImageItem::Deserialize(Bundle b) {
AbstractItem::Deserialize(b);
impl_->image_path_ = b.GetString(IMAGE_PATH_KEY);
+ impl_->priv_image_path_ = b.GetString(PRIV_IMAGE_PATH_KEY);
}
bool ImageItem::IsItemTypeExist(int type) {
return impl_->image_path_;
}
+std::list<std::string> ImageItem::GetSharedPath() const {
+ std::list<std::string> ret;
+
+ if (!impl_->priv_image_path_.empty())
+ ret.push_back(impl_->priv_image_path_);
+
+ return ret;
+}
+
+void ImageItem::SetSharedPath() {
+ if (!impl_->priv_image_path_.empty())
+ impl_->image_path_ = impl_->priv_image_path_;
+}
+
+std::list<std::map<std::string, std::string>> ImageItem::GetPathMapList() const {
+ std::list<std::map<std::string, std::string>> path_map_list;
+ std::map<std::string, std::string> path_map;
+
+ if (!impl_->priv_image_path_.empty()) {
+ path_map.insert(std::pair<std::string, std::string>(impl_->image_path_,
+ impl_->priv_image_path_));
+ path_map_list.push_back(path_map);
+ }
+
+ return path_map_list;
+}
+
+void ImageItem::UpdatePrivatePath() {
+ std::string path;
+
+ SharedFile* shared_file = new SharedFile();
+ if (!shared_file->IsPrivatePath(impl_->image_path_)) {
+ delete(shared_file);
+ return;
+ }
+
+ path = shared_file->GetDataPath(AbstractItem::GetSenderAppId(),
+ impl_->image_path_);
+ if (!path.empty())
+ impl_->priv_image_path_ = path;
+
+ delete(shared_file);
+}
+
ImageItem::~ImageItem() = default;
ImageItem::Impl::~Impl() = default;
#include <string>
#include <memory>
#include <list>
+#include <map>
#include "notification-ex/abstract_item.h"
*/
std::string GetImagePath() const;
+ /**
+ * @brief Gets the path of shared file location.
+ * @since_tizen 5.5
+ * @return The list of shared path.
+ */
+ std::list<std::string> GetSharedPath() const override;
+
+ /**
+ * @brief Sets the shared file path to original file path.
+ * @since_tizen 5.5
+ */
+ void SetSharedPath() override;
+
+ std::list<std::map<std::string, std::string>> GetPathMapList(void) const override;
+
private:
class Impl;
std::unique_ptr<Impl> impl_;
-}; // class ImageItem
+ void UpdatePrivatePath();
+}; //class ImageItem
} // namespace item
} // namespace notification
private:
ImageItem* parent_;
std::string image_path_;
+ std::string priv_image_path_;
};
} // namespace item
#define MAX_PACKAGE_STR_SIZE 512
#define NOTIFICATION_EX_MANAGER_OBJECT_PATH "/org/tizen/notification_ex_manager"
+#define DATA_PROVIDER_MASTER_ID "data-provider-master"
using namespace std;
using namespace tizen_base;
Manager::Impl::~Impl() {
listener_->UnRegisterObserver(parent_);
+
+ list<Bundle> dummy_list {};
+ EventInfo info(EventInfo::Unregister, util::GetAppId(), "", "");
+ sender_->Notify(info, dummy_list, DATA_PROVIDER_MASTER_ID);
}
Manager::Impl::Impl(Manager* parent,
unique_ptr<IEventSender> sender,
parent_(parent) {
LOGI("impl created");
listener_->RegisterObserver(parent_);
+
+ list<Bundle> serialized_list {};
+ EventInfo info(EventInfo::Register, util::GetAppId(), receiver_group, "");
+ sender_->Notify(info, serialized_list, DATA_PROVIDER_MASTER_ID);
}
int Manager::Impl::SendNotify(shared_ptr<item::AbstractItem> noti,
#include "notification-ex/dbus_connection_manager.h"
#include "notification-ex/ex_util.h"
#include "notification-ex/item_info_internal.h"
+#include "notification-ex/shared_file.h"
#ifdef LOG_TAG
#undef LOG_TAG
int Reporter::Post(std::shared_ptr<item::AbstractItem> noti) {
LOGI("Post noti");
static_pointer_cast<IItemInfoInternal>(noti->GetInfo())->SetTime(time(NULL));
+ SharedFile* shared_file = new SharedFile();
+ shared_file->CopyPrivateFile(noti);
+ delete shared_file;
+
return impl_->SendNotify(noti, EventInfo::Post);
}
static_pointer_cast<IItemInfoInternal>(i->GetInfo())->SetTime(time(NULL));
Bundle b = i->Serialize();
serialized_list.push_back(b);
+
+ SharedFile* shared_file = new SharedFile();
+ shared_file->CopyPrivateFile(i);
+ delete shared_file;
}
impl_->sender_->Notify(info, serialized_list);
return info.GetRequestId();
int Reporter::Update(std::shared_ptr<AbstractItem> noti) {
static_pointer_cast<IItemInfoInternal>(noti->GetInfo())->SetTime(time(NULL));
+ SharedFile* shared_file = new SharedFile();
+ shared_file->CopyPrivateFile(noti);
+ delete shared_file;
+
return impl_->SendNotify(noti, EventInfo::Update);
}
return NOTIFICATION_EX_REPORTER_OBJECT_PATH;
}
+void Reporter::OnRegister(const IEventInfo& info) {
+}
+
} // namespace notification
std::list<tizen_base::Bundle> OnRequest(const IEventInfo& info) override;
int OnRequestNumber(const IEventInfo& info) override;
static std::string GetPath();
+ virtual void OnRegister(const IEventInfo& info);
private:
class Impl;
--- /dev/null
+/*
+ * Copyright (c) 2019 Samsung Electronics Co., Ltd.
+ *
+ * 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 <unistd.h>
+#include <dlog.h>
+#include <linux/xattr.h>
+#include <sys/smack.h>
+#include <gio/gio.h>
+#include <glib/gstdio.h>
+#include <utime.h>
+#include <tzplatform_config.h>
+#include <security-manager.h>
+
+#include <string>
+#include <list>
+#include <map>
+#include <algorithm>
+#include <vector>
+
+#include "notification-ex/common.h"
+#include "notification-ex/shared_file.h"
+#include "notification-ex/group_item.h"
+
+#ifdef LOG_TAG
+#undef LOG_TAG
+#endif
+
+#define LOG_TAG "NOTIFICATION_EX"
+#define CHECK_LABEL "::RO"
+#define NOTI_PRIV_DATA_DIR "data/.notification_ex"
+
+using namespace std;
+
+namespace notification {
+namespace item {
+SharedFile::SharedFile() = default;
+SharedFile::~SharedFile() = default;
+SharedFile::SharingData::SharingData() = default;
+SharedFile::SharingData::~SharingData() = default;
+SharedFile::SharingTarget::SharingTarget() = default;
+SharedFile::SharingTarget::~SharingTarget() = default;
+
+const char* SharedFile::GetLastIndex(const char* path, const char* search) const {
+ int i;
+ int search_len;
+ const char* index;
+
+ if (path == nullptr || search == nullptr)
+ return nullptr;
+
+ search_len = strlen(search);
+ index = path + strlen(path) - search_len;
+
+ while (index >= path) {
+ for (i = 0; i < search_len; i++) {
+ if (index[i] != search[i])
+ break;
+ }
+
+ if (i == search_len)
+ return index;
+
+ index--;
+ }
+
+ return nullptr;
+}
+
+bool SharedFile::MakeDir(const char* path) {
+ if (access(path, R_OK) == 0)
+ return true;
+
+ auto noti_dir = unique_ptr<GFile, decltype(&g_object_unref)>(
+ g_file_new_for_path(path), g_object_unref);
+ if (noti_dir == nullptr)
+ return false;
+
+ GError* g_err = nullptr;
+ if (!g_file_make_directory(noti_dir.get(), nullptr, &g_err)) {
+ if (g_err) {
+ LOGE("Failed to make sharing dir[%s]", g_err->message);
+ g_error_free(g_err);
+ }
+ return false;
+ }
+
+ return true;
+}
+
+string SharedFile::GetDir(string path) {
+ int dir_len;
+ const char* index;
+ string dir;
+
+ if (path.empty())
+ return "";
+
+ index = GetLastIndex(path.c_str(), "/");
+ if (index == nullptr) {
+ LOGE("Failed to find directory separator");
+ return "";
+ }
+
+ dir_len = index - path.c_str() + 1;
+ if (dir_len <= 0 || dir_len > PATH_MAX)
+ return "";
+
+ dir.reserve(PATH_MAX);
+ snprintf(&dir[0], dir_len, "%s", path.c_str());
+
+ return dir;
+}
+
+int SharedFile::CopyFile(const char* source, const char* dest) {
+ GError* g_err = nullptr;
+ struct utimbuf ut = {0, };
+
+ if (source == nullptr || dest == nullptr) {
+ LOGE("Invalid parameter");
+ return ERROR_INVALID_PARAMETER;
+ }
+
+ auto dst = unique_ptr<GFile, decltype(&g_object_unref)>(
+ g_file_new_for_path(dest), g_object_unref);
+ if (dst == nullptr) {
+ LOGE("dest path is wrong [%s]", dest);
+ return ERROR_IO_ERROR;
+ }
+
+ if (g_file_query_exists(dst.get(), nullptr)) {
+ LOGD("dst path already existed [%s]", dest);
+ return ERROR_ALREADY_EXIST_ID;
+ }
+
+ auto src = unique_ptr<GFile, decltype(&g_object_unref)>(
+ g_file_new_for_path(source), g_object_unref);
+ if (src == nullptr) {
+ LOGE("src path is wrong [%s]", source);
+ return ERROR_IO_ERROR;
+ }
+
+ if (!g_file_copy(src.get(), dst.get(), G_FILE_COPY_NOFOLLOW_SYMLINKS, nullptr,
+ nullptr, nullptr, &g_err)) {
+ if (g_err) {
+ LOGE("Copying file from [%s] to [%s] is failed [%s]", source, dest,
+ g_err->message);
+ g_error_free(g_err);
+ }
+ return ERROR_IO_ERROR;
+ }
+
+ ut.modtime = time(nullptr);
+ if (g_utime(dest, &ut) != 0)
+ LOGD("Failed to set g_utime %d ", errno);
+
+ return ERROR_NONE;
+}
+
+vector<char*> SharedFile::ConvertListToArray(const list<string>& data) {
+ vector<char*> v;
+
+ if (data.empty())
+ return {};
+
+ for (const auto& i : data)
+ v.push_back(const_cast<char*>(i.c_str()));
+
+ return v;
+}
+
+bool SharedFile::IsPrivatePath(string path) const {
+ char* smack_label = nullptr;
+ bool ret = false;
+
+ if (path.empty()) {
+ LOGE("Invalid parameter");
+ return false;
+ }
+
+ if (smack_new_label_from_path(path.c_str(), XATTR_NAME_SMACK, 1, &smack_label)
+ <= 0) {
+ LOGE("smack_new_label_from_path failed");
+ return false;
+ }
+
+ if (GetLastIndex(smack_label, CHECK_LABEL) != nullptr)
+ ret = true;
+
+ free(smack_label);
+ return ret;
+}
+
+string SharedFile::GetDataPath(string app_id, string path) const {
+ string user_app;
+ string dir;
+
+ if (app_id.empty() || path.empty()) {
+ LOGE("Invalid parameter");
+ return nullptr;
+ }
+
+ tzplatform_set_user(getuid());
+ user_app = tzplatform_getenv(TZ_USER_APP);
+ tzplatform_reset_user();
+
+ dir = user_app + "/" + app_id + "/" + NOTI_PRIV_DATA_DIR + "/";
+ dir += string(GetLastIndex(path.c_str(), "/") + 1);
+
+ return dir;
+}
+
+int SharedFile::SetSharingData(SharingData sharing_data,
+ list<string> new_shared_file_list, list<string> new_receiver_group_list,
+ multimap<string, string> receiver_group_map) {
+ private_sharing_req* handle = nullptr;
+ vector<char*> path_array;
+ vector<char*> target_array;
+ const char* appid = nullptr;
+ int ret;
+
+ if (new_shared_file_list.empty() || new_receiver_group_list.empty()) {
+ LOGE("Invalid parameter");
+ return ERROR_INVALID_PARAMETER;
+ }
+
+ ret = security_manager_private_sharing_req_new(&handle);
+ if (ret != SECURITY_MANAGER_SUCCESS) {
+ LOGE("Failed to create private sharing request handle[%d]", ret);
+ return ERROR_IO_ERROR;
+ }
+
+ auto req = unique_ptr<private_sharing_req,
+ decltype(&security_manager_private_sharing_req_free)>(
+ handle, security_manager_private_sharing_req_free);
+ if (req == nullptr)
+ return ERROR_IO_ERROR;
+
+ appid = strdup(sharing_data.app_id.c_str());
+ if (appid == nullptr) {
+ LOGE("Failed to get Sender appid");
+ return ERROR_OUT_OF_MEMORY;
+ }
+
+ ret = security_manager_private_sharing_req_set_owner_appid(req.get(), appid);
+ if (ret != SECURITY_MANAGER_SUCCESS) {
+ LOGE("Failed to set owner appid[%s][%d]", appid, ret);
+ return ERROR_IO_ERROR;
+ }
+
+ path_array = ConvertListToArray(new_shared_file_list);
+ if (path_array.empty()) {
+ LOGE("path_array is null");
+ return ERROR_IO_ERROR;
+ }
+
+ ret = security_manager_private_sharing_req_add_paths(req.get(),
+ const_cast<const char**>(path_array.data()), path_array.size());
+ if (ret != SECURITY_MANAGER_SUCCESS) {
+ LOGE("Failed to add paths [%d]", ret);
+ return ERROR_IO_ERROR;
+ }
+
+ target_array = ConvertListToArray(new_receiver_group_list);
+ if (target_array.empty()) {
+ LOGE("target_array is null");
+ return ERROR_IO_ERROR;
+ }
+
+ for (int i = 0; i < static_cast<int>(target_array.size()); i++) {
+ multimap<string, string>::iterator it;
+ for (it = receiver_group_map.begin(); it != receiver_group_map.end(); it++) {
+ if (it->first.compare(target_array[i]) == 0) {
+ ret = security_manager_private_sharing_req_set_target_appid(req.get(),
+ it->second.c_str());
+ if (ret != SECURITY_MANAGER_SUCCESS) {
+ LOGE("Failed to set target appid [%s]", appid);
+ return ERROR_IO_ERROR;
+ }
+
+ ret = security_manager_private_sharing_apply(req.get());
+ if (ret != SECURITY_MANAGER_SUCCESS) {
+ LOGE("Failed to apply private sharing [%d]", ret);
+ return ERROR_IO_ERROR;
+ }
+ }
+ }
+ }
+
+ return ERROR_NONE;
+}
+
+int SharedFile::UnsetSharingData(SharingData sharing_data,
+ multimap<string, string> receiver_group_map) {
+ private_sharing_req* handle = nullptr;
+ vector<char*> path_array;
+ vector<char*> target_array;
+ const char* appid = nullptr;
+ int ret;
+
+ if (receiver_group_map.empty()) {
+ LOGE("Invalid parameter");
+ return ERROR_INVALID_PARAMETER;
+ }
+
+ ret = security_manager_private_sharing_req_new(&handle);
+ if (ret != SECURITY_MANAGER_SUCCESS) {
+ LOGE("Failed to create private sharing request handle[%d]", ret);
+ return ERROR_IO_ERROR;
+ }
+
+ auto req = unique_ptr<private_sharing_req,
+ decltype(&security_manager_private_sharing_req_free)>(
+ handle, security_manager_private_sharing_req_free);
+ if (req == nullptr)
+ return ERROR_IO_ERROR;
+
+ appid = strdup(sharing_data.app_id.c_str());
+ if (appid == nullptr) {
+ LOGE("Failed to get Sender appid");
+ return ERROR_OUT_OF_MEMORY;
+ }
+
+ ret = security_manager_private_sharing_req_set_owner_appid(req.get(), appid);
+ if (ret != SECURITY_MANAGER_SUCCESS) {
+ LOGE("Failed to set owner appid[%s][%d]", appid, ret);
+ return ERROR_IO_ERROR;
+ }
+
+ path_array = ConvertListToArray(sharing_data.shared_file_list);
+ if (path_array.empty()) {
+ LOGE("path_array is null");
+ return ERROR_IO_ERROR;
+ }
+
+ ret = security_manager_private_sharing_req_add_paths(req.get(),
+ const_cast<const char**>(path_array.data()), path_array.size());
+ if (ret != SECURITY_MANAGER_SUCCESS) {
+ LOGE("Failed to add paths [%d]", ret);
+ return ERROR_IO_ERROR;
+ }
+
+ target_array = ConvertListToArray(sharing_data.receiver_group_list);
+ if (target_array.empty()) {
+ LOGE("target_array is null");
+ return ERROR_IO_ERROR;
+ }
+
+ for (int i = 0; i < static_cast<int>(target_array.size()); i++) {
+ multimap<string, string>::iterator it;
+ for (it = receiver_group_map.begin(); it != receiver_group_map.end(); it++) {
+ if (it->first.compare(target_array[i]) == 0) {
+ ret = security_manager_private_sharing_req_set_target_appid(req.get(),
+ it->second.c_str());
+ if (ret != SECURITY_MANAGER_SUCCESS) {
+ LOGE("Failed to set target appid [%s]", appid);
+ return ERROR_IO_ERROR;
+ }
+
+ ret = security_manager_private_sharing_drop(req.get());
+ if (ret != SECURITY_MANAGER_SUCCESS) {
+ LOGE("Failed to drop private sharing [%d]", ret);
+ return ERROR_IO_ERROR;
+ }
+ }
+ }
+ }
+
+ return ERROR_NONE;
+}
+
+SharedFile::SharingData SharedFile::FindSharingData(string appid) {
+ if (sharing_data_list_.size() == 0 || appid.empty())
+ return {};
+
+ for (auto sharing_data : sharing_data_list_) {
+ if (sharing_data.app_id.compare(appid) == 0)
+ return sharing_data;
+ }
+
+ return {};
+}
+
+int SharedFile::SetPrivateSharing(list<shared_ptr<AbstractItem>> item,
+ multimap<string, string> receiver_group_map) {
+ int ret;
+
+ if (item.empty() || receiver_group_map.empty()) {
+ LOGE("Invalid parameter");
+ return ERROR_INVALID_PARAMETER;
+ }
+
+ for (auto& i : item) {
+ SharingData sharing_data;
+ list<string> new_shared_file_list;
+ list<string> new_receiver_group_list;
+
+ list<string> shared_path_list = i->GetSharedPath();
+ if (shared_path_list.empty())
+ continue;
+
+ string appid = i->GetSenderAppId();
+ if (appid.empty())
+ continue;
+
+ sharing_data = FindSharingData(appid);
+ if (!sharing_data.app_id.empty()) {
+ string noti_id = i->GetId();
+ list<string>::iterator it;
+ it = find_if(sharing_data.noti_id_list.begin(),
+ sharing_data.noti_id_list.end(),
+ [¬i_id](std::string id){
+ return id.compare(noti_id) == 0;
+ });
+ if (it == sharing_data.noti_id_list.end())
+ sharing_data.noti_id_list.push_back(noti_id);
+
+ for (auto& shared_path : shared_path_list) {
+ list<string>::iterator it;
+ it = find_if(sharing_data.shared_file_list.begin(),
+ sharing_data.shared_file_list.end(),
+ [&shared_path](std::string shared_file){
+ return shared_file.compare(shared_path) == 0;
+ });
+ if (it == sharing_data.shared_file_list.end()) {
+ sharing_data.shared_file_list.push_back(shared_path);
+ new_shared_file_list.push_back(shared_path);
+ }
+ }
+
+ list<string> receiver_list = i->GetReceiverList();
+ for (auto& receiver : receiver_list) {
+ list<string>::iterator it;
+ it = find_if(sharing_data.receiver_group_list.begin(),
+ sharing_data.receiver_group_list.end(),
+ [&receiver](std::string receiver_group){
+ return receiver_group.compare(receiver) == 0;
+ });
+ if (it == sharing_data.receiver_group_list.end()) {
+ sharing_data.receiver_group_list.push_back(receiver);
+ new_receiver_group_list.push_back(receiver);
+ }
+ }
+ } else {
+ sharing_data.app_id = appid;
+ sharing_data.dir = GetDir(*(shared_path_list.begin()));
+ sharing_data.noti_id_list.emplace_back(i->GetId());
+ sharing_data.shared_file_list = shared_path_list;
+ sharing_data.receiver_group_list = i->GetReceiverList();
+ sharing_data_list_.emplace_back(sharing_data);
+
+ new_shared_file_list = shared_path_list;
+ new_receiver_group_list = i->GetReceiverList();
+ }
+
+ ret = SetSharingData(sharing_data, new_shared_file_list,
+ new_receiver_group_list, receiver_group_map);
+ if (ret == ERROR_NONE)
+ i->SetSharedPath();
+ }
+
+ LOGD("SetPrivateSharing");
+ return ERROR_NONE;
+}
+
+int SharedFile::UpdatePrivateSharing(shared_ptr<AbstractItem> item,
+ multimap<string, string> receiver_group_map) {
+ list<string> new_shared_file_list;
+ list<string> new_receiver_group_list;
+ int ret;
+
+ if (item == nullptr || receiver_group_map.empty()) {
+ LOGE("Invalid parameter");
+ return ERROR_INVALID_PARAMETER;
+ }
+
+ list<string> shared_path_list = item->GetSharedPath();
+ if (shared_path_list.empty())
+ return ERROR_NONE;
+
+ string appid = item->GetSenderAppId();
+ SharingData sharing_data = FindSharingData(appid);
+ if (appid.empty() || sharing_data.app_id.empty())
+ return ERROR_IO_ERROR;
+
+ for (auto& shared_path : shared_path_list) {
+ std::list<std::string>::iterator it;
+ it = std::find_if(sharing_data.shared_file_list.begin(),
+ sharing_data.shared_file_list.end(),
+ [&shared_path](std::string shared_file){
+ return shared_file.compare(shared_path) == 0;
+ });
+ if (it == sharing_data.shared_file_list.end()) {
+ sharing_data.shared_file_list.push_back(shared_path);
+ new_shared_file_list.push_back(shared_path);
+ }
+ }
+
+ new_receiver_group_list = item->GetReceiverList();
+ ret = SetSharingData(sharing_data, new_shared_file_list,
+ new_receiver_group_list, receiver_group_map);
+ if (ret == ERROR_NONE)
+ item->SetSharedPath();
+
+ LOGD("UpdatePrivateSharing");
+ return ERROR_NONE;
+}
+
+int SharedFile::RemovePrivateSharing(shared_ptr<AbstractItem> item,
+ multimap<string, string> receiver_group_map) {
+ if (item == nullptr || receiver_group_map.empty()) {
+ LOGE("Invalid parameter");
+ return ERROR_INVALID_PARAMETER;
+ }
+
+ string appid = item->GetSenderAppId();
+ if (appid.empty())
+ return ERROR_IO_ERROR;
+
+ SharingData sharing_data = FindSharingData(appid);
+ if (sharing_data.app_id.empty())
+ return ERROR_NONE;
+
+ sharing_data.noti_id_list.remove(item->GetId());
+ if (sharing_data.noti_id_list.size() == 0) {
+ UnsetSharingData(sharing_data, receiver_group_map);
+
+ vector<char*> path_array = ConvertListToArray(sharing_data.shared_file_list);
+ if (!path_array.empty()) {
+ for (int i = 0; i < static_cast<int>(path_array.size()); i++) {
+ if (g_remove(path_array[i]) == -1)
+ LOGE("Failed to remove shared_file(%s)", path_array[i]);
+ }
+ }
+ g_rmdir(sharing_data.dir.c_str());
+ }
+
+ LOGD("RemovePrivateSharing");
+ return ERROR_NONE;
+}
+
+int SharedFile::CopyPrivateFile(shared_ptr<item::AbstractItem> item) {
+ if (item == nullptr) {
+ LOGE("Invalid paramenter");
+ return ERROR_INVALID_PARAMETER;
+ }
+
+ list<map<string, string>> path_map_list = item->GetPathMapList();
+ if (path_map_list.empty())
+ return ERROR_NONE;
+
+ if (!MakeDir(GetDir((*(path_map_list.begin())).begin()->second).c_str()))
+ return ERROR_IO_ERROR;
+
+ list<map<string, string>>::iterator list_it;
+ for (list_it = path_map_list.begin(); list_it != path_map_list.end(); ++list_it) {
+ map<string, string>::iterator map_it;
+ for (map_it = (*list_it).begin(); map_it != (*list_it).end(); map_it++) {
+ CopyFile((map_it->first).c_str(), (map_it->second).c_str());
+ }
+ }
+
+ return ERROR_NONE;
+}
+
+} // namespace item
+} // namespace notification
--- /dev/null
+/*
+ * Copyright (c) 2019 Samsung Electronics Co., Ltd.
+ *
+ * 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.
+ */
+
+#ifndef NOTIFICATION_EX_SHARED_FILE_H_
+#define NOTIFICATION_EX_SHARED_FILE_H_
+
+#include <security-manager.h>
+
+#include <string>
+#include <memory>
+#include <list>
+#include <map>
+#include <vector>
+
+#include "notification-ex/abstract_item.h"
+
+#ifndef EXPORT_API
+#define EXPORT_API __attribute__((visibility("default")))
+#endif
+
+namespace notification {
+namespace item {
+
+class EXPORT_API SharedFile {
+ public:
+ SharedFile();
+ virtual ~SharedFile();
+
+ bool IsPrivatePath(std::string path) const;
+ std::string GetDataPath(std::string app_id, std::string path) const;
+ int SetPrivateSharing(std::list<std::shared_ptr<AbstractItem>> item,
+ std::multimap<std::string, std::string> receiver_group_map);
+ int UpdatePrivateSharing(std::shared_ptr<AbstractItem>item,
+ std::multimap<std::string, std::string> receiver_group_map);
+ int RemovePrivateSharing(std::shared_ptr<AbstractItem>item,
+ std::multimap<std::string, std::string> receiver_group_map);
+ int CopyPrivateFile(std::shared_ptr<item::AbstractItem>added_item);
+
+ private:
+ class SharingData {
+ public:
+ SharingData();
+ ~SharingData();
+ std::string app_id;
+ std::string dir;
+ std::list<std::string> noti_id_list;
+ std::list<std::string> shared_file_list;
+ std::list<std::string> receiver_group_list;
+ };
+ class SharingTarget {
+ public:
+ SharingTarget();
+ ~SharingTarget();
+ std::string target_id;
+ };
+ const char* GetLastIndex(const char* path, const char* search) const;
+ bool MakeDir(const char* path);
+ std::string GetDir(std::string path);
+ int CopyFile(const char* source, const char* dest);
+ std::vector<char*> ConvertListToArray(const std::list<std::string>& data);
+ SharingData FindSharingData(std::string appid);
+ int SetSharingData(SharingData sharing_data,
+ std::list<std::string> new_shared_file_list,
+ std::list<std::string> new_receiver_group_list,
+ std::multimap<std::string, std::string> receiver_group_map);
+ int UnsetSharingData(SharingData sharing_data,
+ std::multimap<std::string, std::string> receiver_group_map);
+
+ std::list<SharingData> sharing_data_list_;
+ std::list<SharingTarget> sharing_target_list_;
+}; // class SharedFile
+
+} // namespace item
+} // namespace notification
+
+#endif // NOTIFICATION_EX_SHARED_FILE_H_
capi-appfw-app-control
glib-2.0
aul
+ security-manager
+ libtzplatform-config
)
FOREACH(flag ${notification-ex_unittests_CFLAGS})
--- /dev/null
+/*
+ * Copyright (c) 2019 Samsung Electronics Co., Ltd.
+ *
+ * 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.
+ */
+
+#ifndef MOCK_GIO_H_
+#define MOCK_GIO_H_
+
+#include "mock.h"
+#include <glib.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct _GFile GFile;
+typedef struct _GError GError;
+typedef void GDBusConnection;
+typedef void GCancellable;
+typedef void* gpointer;
+typedef int gboolean;
+
+typedef enum {
+ G_FILE_COPY_NONE = 0,
+ G_FILE_COPY_OVERWRITE = (1 << 0),
+ G_FILE_COPY_BACKUP = (1 << 1),
+ G_FILE_COPY_NOFOLLOW_SYMLINKS = (1 << 2),
+ G_FILE_COPY_ALL_METADATA = (1 << 3),
+ G_FILE_COPY_NO_FALLBACK_FOR_MOVE = (1 << 4),
+ G_FILE_COPY_TARGET_DEFAULT_PERMS = (1 << 5)
+} GFileCopyFlags;
+
+
+typedef void (*GFileProgressCallback) (goffset current_num_bytes,
+ goffset total_num_bytes, gpointer user_data);
+
+DECLARE_FAKE_VALUE_FUNC(GFile*, g_file_new_for_path, const char*);
+DECLARE_FAKE_VALUE_FUNC(gboolean, g_file_query_exists, GFile*, GCancellable*);
+DECLARE_FAKE_VALUE_FUNC(gboolean, g_file_copy, GFile*, GFile*, GFileCopyFlags,
+ GCancellable*, GFileProgressCallback, gpointer, GError**);
+DECLARE_FAKE_VALUE_FUNC(gboolean, g_file_make_directory, GFile*, GCancellable*,
+ GError**);
+DECLARE_FAKE_VALUE_FUNC(int, access, const char*, int);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* MOCK_GIO_H_ */
* limitations under the License.
*/
-#include "app_common.h"
+#include <sys/types.h>
+
#include "mock.h"
+#include "app_common.h"
+#include "gio_mock.h"
+#include "smack_mock.h"
+#include "tzplatform_config_mock.h"
+#include "security_manager_mock.h"
DEFINE_FFF_GLOBALS;
-DEFINE_FAKE_VALUE_FUNC(int, app_get_name, char**);
\ No newline at end of file
+DEFINE_FAKE_VALUE_FUNC(int, app_get_name, char**);
+
+/* gio */
+DEFINE_FAKE_VALUE_FUNC(GFile*, g_file_new_for_path, const char*);
+DEFINE_FAKE_VALUE_FUNC(gboolean, g_file_query_exists, GFile*, GCancellable*);
+DEFINE_FAKE_VALUE_FUNC(gboolean, g_file_copy, GFile*, GFile*, GFileCopyFlags,
+ GCancellable*, GFileProgressCallback, gpointer, GError**);
+DEFINE_FAKE_VALUE_FUNC(gboolean, g_file_make_directory, GFile*, GCancellable*,
+ GError**);
+DEFINE_FAKE_VALUE_FUNC(int, access, const char*, int);
+
+/* smack */
+DEFINE_FAKE_VALUE_FUNC(ssize_t, smack_new_label_from_path, const char*,
+ const char*, int, char**);
+
+/* tzplatform */
+DEFINE_FAKE_VALUE_FUNC(const char*, tzplatform_getenv, enum tzplatform_variable);
+
+/* security-manager */
+DEFINE_FAKE_VALUE_FUNC(int, security_manager_private_sharing_req_new,
+ private_sharing_req**);
+DEFINE_FAKE_VALUE_FUNC(int, security_manager_private_sharing_req_add_paths,
+ private_sharing_req*, const char**, size_t);
+DEFINE_FAKE_VALUE_FUNC(int, security_manager_private_sharing_req_set_target_appid,
+ private_sharing_req*, const char*);
+DEFINE_FAKE_VALUE_FUNC(int, security_manager_private_sharing_apply,
+ const private_sharing_req*);
+DEFINE_FAKE_VALUE_FUNC(int, security_manager_private_sharing_drop,
+ const private_sharing_req*);
--- /dev/null
+/*
+ * Copyright (c) 2019 Samsung Electronics Co., Ltd.
+ *
+ * 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.
+ */
+
+#ifndef MOCK_SECURITY_MANAGER_H
+#define MOCK_SECURITY_MANAGER_H
+
+#include "mock.h"
+
+#include <string>
+#include <vector>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct private_sharing_req {
+ std::string ownerAppName;
+ std::string targetAppName;
+ std::vector<std::string> paths;
+};
+
+DECLARE_FAKE_VALUE_FUNC(int, security_manager_private_sharing_req_new,
+ private_sharing_req**);
+DECLARE_FAKE_VALUE_FUNC(int, security_manager_private_sharing_req_add_paths,
+ private_sharing_req*, const char**, size_t);
+DECLARE_FAKE_VALUE_FUNC(int, security_manager_private_sharing_req_set_target_appid,
+ private_sharing_req*, const char*);
+DECLARE_FAKE_VALUE_FUNC(int, security_manager_private_sharing_apply,
+ const private_sharing_req*);
+DECLARE_FAKE_VALUE_FUNC(int, security_manager_private_sharing_drop,
+ const private_sharing_req*);
+
+#ifdef __cplusplus
+}
+#endif
+#endif /* MOCK_SECURITY_MANAGER_H */
--- /dev/null
+/*
+ * Copyright (c) 2019 Samsung Electronics Co., Ltd.
+ *
+ * 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.
+ */
+
+#ifndef MOCK_SMACK_H
+#define MOCK_SMACK_H
+
+#include "mock.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+DECLARE_FAKE_VALUE_FUNC(ssize_t, smack_new_label_from_path, const char*,
+ const char*, int, char**);
+
+#ifdef __cplusplus
+}
+#endif
+#endif /* MOCK_SMACK_H */
+
--- /dev/null
+/*
+ * Copyright (c) 2019 Samsung Electronics Co., Ltd.
+ *
+ * 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.
+ */
+
+#ifndef MOCK_TZPLATFORM_CONFIG_H_
+#define MOCK_TZPLATFORM_CONFIG_H_
+
+#include "mock.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+enum tzplatform_variable {
+ USER = 0,
+ SYSTEM = 1,
+};
+
+DECLARE_FAKE_VALUE_FUNC(const char*, tzplatform_getenv, enum tzplatform_variable);
+
+#ifdef __cplusplus
+}
+#endif
+#endif /* MOCK_TZPLATFORM_CONFIG_H_ */
--- /dev/null
+/*
+ * Copyright (c) 2019 Samsung Electronics Co., Ltd.
+ *
+ * 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 <gmock/gmock.h>
+#include <app_common.h>
+
+#include "notification-ex/shared_file.h"
+#include "notification-ex/image_item.h"
+#include "notification-ex/common.h"
+
+#include "unittest/mock/gio_mock.h"
+#include "unittest/mock/smack_mock.h"
+#include "unittest/mock/tzplatform_config_mock.h"
+#include "unittest/mock/security_manager_mock.h"
+
+
+using namespace notification;
+using namespace notification::item;
+using namespace std;
+
+namespace {
+
+GFile* __fake_g_file_new_for_path(const char* path) {
+ GFile* noti_dir = {};
+ return noti_dir;
+}
+
+gboolean __fake_g_file_query_exists(GFile* file, GCancellable* cancellable) {
+ return false;
+}
+
+gboolean __fake_g_file_make_directory(GFile* file, GCancellable* cancellable,
+ GError** error) {
+ return true;
+}
+
+gboolean __fake_g_file_copy(GFile* source, GFile* dest, GFileCopyFlags flags,
+ GCancellable* cancellable, GFileProgressCallback callback,
+ gpointer data, GError** error) {
+ return false;
+}
+
+int __fake_access(const char* path, int mode) {
+ return 0;
+}
+
+// smack
+ssize_t __fake_smack_new_label_from_path(const char* path, const char* xattr,
+ int follow, char** label) {
+ *label = strdup("User::Pkg::unittest::RO");
+ return 1;
+}
+
+// tzplatform
+const char* __fake_tzplatform_getenv(enum tzplatform_variable id) {
+ return "/opt/usr/home/owner/apps_rw";
+}
+
+// security-manager
+int __fake_security_manager_private_sharing_req_new(private_sharing_req** pp_req) {
+ return 0;
+}
+
+int __fake_security_manager_private_sharing_req_add_paths(
+ private_sharing_req* p_req, const char** pp_paths, size_t count) {
+ return 0;
+}
+
+int __fake_security_manager_private_sharing_req_set_target_appid(
+ private_sharing_req* p_req, const char* appid) {
+ return 0;
+}
+
+int __fake_security_manager_private_sharing_apply(const private_sharing_req* p_req) {
+ return 0;
+}
+
+int __fake_security_manager_private_sharing_drop(const private_sharing_req* p_req) {
+ return 0;
+}
+
+class SharedFileTest : public ::testing::Test {
+ public:
+ std::shared_ptr<ImageItem> item;
+ std::string id = "image_id";
+ std::string image_path = "res/image.png";
+
+ SharedFile* shared_file;
+
+ virtual void SetUp() {
+ smack_new_label_from_path_fake.custom_fake = __fake_smack_new_label_from_path;
+ tzplatform_getenv_fake.custom_fake = __fake_tzplatform_getenv;
+ g_file_new_for_path_fake.custom_fake = __fake_g_file_new_for_path;
+ g_file_query_exists_fake.custom_fake = __fake_g_file_query_exists;
+ g_file_make_directory_fake.custom_fake = __fake_g_file_make_directory;
+ g_file_copy_fake.custom_fake = __fake_g_file_copy;
+ access_fake.custom_fake = __fake_access;
+ security_manager_private_sharing_req_new_fake.custom_fake =
+ __fake_security_manager_private_sharing_req_new;
+ security_manager_private_sharing_req_add_paths_fake.custom_fake =
+ __fake_security_manager_private_sharing_req_add_paths;
+ security_manager_private_sharing_req_set_target_appid_fake.custom_fake =
+ __fake_security_manager_private_sharing_req_set_target_appid;
+ security_manager_private_sharing_apply_fake.custom_fake =
+ __fake_security_manager_private_sharing_apply;
+ security_manager_private_sharing_drop_fake.custom_fake =
+ __fake_security_manager_private_sharing_drop;
+
+ item = make_shared<ImageItem>(id, image_path);
+ shared_file = new SharedFile();
+ }
+
+ virtual void TearDown() {
+ }
+};
+
+TEST_F(SharedFileTest, IsPrivatePath) {
+ ASSERT_TRUE(shared_file->IsPrivatePath(SharedFileTest::image_path));
+}
+
+TEST_F(SharedFileTest, GetDataPath) {
+ ASSERT_EQ(shared_file->GetDataPath(item->GetSenderAppId(), SharedFileTest::image_path),
+ "/opt/usr/home/owner/apps_rw/notification-ex_unittests/data/.notification_ex/image.png");
+}
+
+TEST_F(SharedFileTest, SetPrivateSharing) {
+ list<shared_ptr<item::AbstractItem>> notiList;
+ notiList.push_back(SharedFileTest::item);
+
+ std::multimap<std::string, std::string> map_;
+ map_.insert(make_pair("tizen.org/receiver/popup", "test_appid"));
+
+ ASSERT_EQ(shared_file->SetPrivateSharing(notiList, map_), ERROR_NONE);
+}
+
+TEST_F(SharedFileTest, RemovePrivateSharing) {
+ std::multimap<std::string, std::string> map_;
+ map_.insert(make_pair("tizen.org/receiver/popup", "test_appid"));
+
+ ASSERT_EQ(shared_file->RemovePrivateSharing(SharedFileTest::item, map_), ERROR_NONE);
+}
+
+} // namespace