19d5b500a223ef73560f0dce38f50b809bde5dd0dBrian Duddie/*
29d5b500a223ef73560f0dce38f50b809bde5dd0dBrian Duddie * Copyright (C) 2017 The Android Open Source Project
39d5b500a223ef73560f0dce38f50b809bde5dd0dBrian Duddie *
49d5b500a223ef73560f0dce38f50b809bde5dd0dBrian Duddie * Licensed under the Apache License, Version 2.0 (the "License");
59d5b500a223ef73560f0dce38f50b809bde5dd0dBrian Duddie * you may not use this file except in compliance with the License.
69d5b500a223ef73560f0dce38f50b809bde5dd0dBrian Duddie * You may obtain a copy of the License at
79d5b500a223ef73560f0dce38f50b809bde5dd0dBrian Duddie *
89d5b500a223ef73560f0dce38f50b809bde5dd0dBrian Duddie *      http://www.apache.org/licenses/LICENSE-2.0
99d5b500a223ef73560f0dce38f50b809bde5dd0dBrian Duddie *
109d5b500a223ef73560f0dce38f50b809bde5dd0dBrian Duddie * Unless required by applicable law or agreed to in writing, software
119d5b500a223ef73560f0dce38f50b809bde5dd0dBrian Duddie * distributed under the License is distributed on an "AS IS" BASIS,
129d5b500a223ef73560f0dce38f50b809bde5dd0dBrian Duddie * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
139d5b500a223ef73560f0dce38f50b809bde5dd0dBrian Duddie * See the License for the specific language governing permissions and
149d5b500a223ef73560f0dce38f50b809bde5dd0dBrian Duddie * limitations under the License.
159d5b500a223ef73560f0dce38f50b809bde5dd0dBrian Duddie */
169d5b500a223ef73560f0dce38f50b809bde5dd0dBrian Duddie
179d5b500a223ef73560f0dce38f50b809bde5dd0dBrian Duddie#include "chre/platform/platform_nanoapp.h"
189d5b500a223ef73560f0dce38f50b809bde5dd0dBrian Duddie
1947a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie#include "chre/platform/assert.h"
2047a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie#include "chre/platform/log.h"
2147a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie#include "chre/platform/memory.h"
2247a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie#include "chre/platform/shared/nanoapp_support_lib_dso.h"
2347a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie#include "chre_api/chre/version.h"
2447a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie
2547a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie#include "dlfcn.h"
2647a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie
2747a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie#include <inttypes.h>
2847a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie#include <string.h>
2947a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie
309d5b500a223ef73560f0dce38f50b809bde5dd0dBrian Duddienamespace chre {
319d5b500a223ef73560f0dce38f50b809bde5dd0dBrian Duddie
3247a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddienamespace {
3347a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie
3447a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie/**
3547a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie * Performs sanity checks on the app info structure included in a dynamically
3647a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie * loaded nanoapp.
3747a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie *
3847a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie * @param expectedAppId
3947a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie * @param expectedAppVersion
4047a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie * @param appInfo
4147a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie *
4247a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie * @return true if validation was successful
4347a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie */
4447a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddiebool validateAppInfo(uint64_t expectedAppId, uint32_t expectedAppVersion,
4547a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie                     const struct chreNslNanoappInfo *appInfo) {
4647a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie  uint32_t ourApiMajorVersion = CHRE_EXTRACT_MAJOR_VERSION(chreGetApiVersion());
4747a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie  uint32_t targetApiMajorVersion = CHRE_EXTRACT_MAJOR_VERSION(
4847a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie      appInfo->targetApiVersion);
4947a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie
5047a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie  bool success = false;
5147a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie  if (appInfo->magic != CHRE_NSL_NANOAPP_INFO_MAGIC) {
5247a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie    LOGE("Invalid app info magic: got 0x%08" PRIx32 " expected 0x%08" PRIx32,
5347a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie         appInfo->magic, CHRE_NSL_NANOAPP_INFO_MAGIC);
5447a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie  } else if (appInfo->appId == 0) {
5547a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie    LOGE("Rejecting invalid app ID 0");
5647a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie  } else if (expectedAppId != appInfo->appId) {
5747a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie    LOGE("Expected app ID (0x%016" PRIx64 ") doesn't match internal one (0x%016"
5847a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie         PRIx64 ")", expectedAppId, appInfo->appId);
5947a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie  } else if (expectedAppVersion != appInfo->appVersion) {
6047a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie    LOGE("Expected app version (0x%" PRIx32 ") doesn't match internal one (0x%"
6147a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie         PRIx32 ")", expectedAppVersion, appInfo->appVersion);
6247a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie  } else if (targetApiMajorVersion != ourApiMajorVersion) {
6347a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie    LOGE("App targets a different major API version (%" PRIu32 ") than what we "
6447a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie         "provide (%" PRIu32 ")", targetApiMajorVersion, ourApiMajorVersion);
6547a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie  } else if (strlen(appInfo->name) > CHRE_NSL_DSO_NANOAPP_STRING_MAX_LEN) {
6647a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie    LOGE("App name is too long");
6747a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie  } else if (strlen(appInfo->name) > CHRE_NSL_DSO_NANOAPP_STRING_MAX_LEN) {
6847a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie    LOGE("App vendor is too long");
6947a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie  } else {
7047a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie    success = true;
7147a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie  }
7247a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie
7347a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie  return success;
7447a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie}
7547a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie
7647a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie}  // anonymous namespace
7747a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie
7847a99dc3cacacb0418548609c0e2d3b2b70d821eBrian DuddiePlatformNanoapp::~PlatformNanoapp() {
7947a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie  closeNanoapp();
8047a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie  if (mAppBinary != nullptr) {
8147a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie    memoryFree(mAppBinary);
8247a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie  }
8347a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie}
849d5b500a223ef73560f0dce38f50b809bde5dd0dBrian Duddie
859d5b500a223ef73560f0dce38f50b809bde5dd0dBrian Duddiebool PlatformNanoapp::start() {
8647a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie  // Invoke the start entry point after successfully opening the app
8747a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie  return (mIsStatic || openNanoapp()) ? mAppInfo->entryPoints.start() : false;
889d5b500a223ef73560f0dce38f50b809bde5dd0dBrian Duddie}
899d5b500a223ef73560f0dce38f50b809bde5dd0dBrian Duddie
909d5b500a223ef73560f0dce38f50b809bde5dd0dBrian Duddievoid PlatformNanoapp::handleEvent(uint32_t senderInstanceId,
919d5b500a223ef73560f0dce38f50b809bde5dd0dBrian Duddie                                  uint16_t eventType,
929d5b500a223ef73560f0dce38f50b809bde5dd0dBrian Duddie                                  const void *eventData) {
939d5b500a223ef73560f0dce38f50b809bde5dd0dBrian Duddie  mAppInfo->entryPoints.handleEvent(senderInstanceId, eventType, eventData);
949d5b500a223ef73560f0dce38f50b809bde5dd0dBrian Duddie}
959d5b500a223ef73560f0dce38f50b809bde5dd0dBrian Duddie
962b9d71a9f6a9e8cc0e787957d022154231f29962Brian Duddievoid PlatformNanoapp::end() {
972b9d71a9f6a9e8cc0e787957d022154231f29962Brian Duddie  mAppInfo->entryPoints.end();
9847a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie  closeNanoapp();
9947a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie}
10047a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie
10147a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddiebool PlatformNanoappBase::loadFromBuffer(uint64_t appId, uint32_t appVersion,
10247a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie                                         const void *appBinary,
10347a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie                                         size_t appBinaryLen) {
10447a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie  CHRE_ASSERT(!isLoaded());
10547a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie  bool success = false;
10647a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie  constexpr size_t kMaxAppSize = 2 * 1024 * 1024;  // 2 MiB
10747a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie
10847a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie  if (appBinaryLen > kMaxAppSize) {
10947a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie    LOGE("Rejecting app size %zu above limit %zu", appBinaryLen, kMaxAppSize);
11047a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie  } else {
11147a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie    mAppBinary = memoryAlloc(appBinaryLen);
11247a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie    if (mAppBinary == nullptr) {
11347a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie      LOGE("Couldn't allocate %zu byte buffer for nanoapp 0x%016" PRIx64,
11447a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie           appBinaryLen, appId);
11547a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie    } else {
11647a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie      mExpectedAppId = appId;
11747a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie      mExpectedAppVersion = appVersion;
11847a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie      mAppBinaryLen = appBinaryLen;
11947a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie      memcpy(mAppBinary, appBinary, appBinaryLen);
12047a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie      success = true;
12147a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie    }
12247a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie  }
12347a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie
12447a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie  return success;
1259d5b500a223ef73560f0dce38f50b809bde5dd0dBrian Duddie}
1269d5b500a223ef73560f0dce38f50b809bde5dd0dBrian Duddie
1279d5b500a223ef73560f0dce38f50b809bde5dd0dBrian Duddievoid PlatformNanoappBase::loadStatic(const struct chreNslNanoappInfo *appInfo) {
12847a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie  CHRE_ASSERT(!isLoaded());
12947a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie  mIsStatic = true;
1309d5b500a223ef73560f0dce38f50b809bde5dd0dBrian Duddie  mAppInfo = appInfo;
1319d5b500a223ef73560f0dce38f50b809bde5dd0dBrian Duddie}
1329d5b500a223ef73560f0dce38f50b809bde5dd0dBrian Duddie
13347a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddiebool PlatformNanoappBase::isLoaded() const {
13447a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie  return (mIsStatic || mAppBinary != nullptr);
13547a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie}
13647a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie
13747a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddievoid PlatformNanoappBase::closeNanoapp() {
13847a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie  if (mDsoHandle != nullptr) {
13947a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie    if (dlclose(mDsoHandle) != 0) {
14047a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie      const char *name = (mAppInfo != nullptr) ? mAppInfo->name : "unknown";
14147a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie      LOGE("dlclose of %s failed: %s", name, dlerror());
14247a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie    }
14347a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie    mDsoHandle = nullptr;
14447a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie  }
14547a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie}
14647a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie
14747a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddiebool PlatformNanoappBase::openNanoapp() {
14847a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie  bool success = false;
14947a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie
15047a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie  // Populate a filename string (just a requirement of the dlopenbuf API)
15147a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie  constexpr size_t kMaxFilenameLen = 17;
15247a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie  char filename[kMaxFilenameLen];
15347a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie  snprintf(filename, sizeof(filename), "%016" PRIx64, mExpectedAppId);
15447a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie
15547a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie  CHRE_ASSERT(mAppBinary != nullptr);
15647a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie  CHRE_ASSERT_LOG(mDsoHandle == nullptr, "Re-opening nanoapp");
15747a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie  mDsoHandle = dlopenbuf(
15847a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie      filename, static_cast<const char *>(mAppBinary),
15947a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie      static_cast<int>(mAppBinaryLen), RTLD_NOW);
16047a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie  if (mDsoHandle == nullptr) {
16147a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie    LOGE("Failed to load nanoapp: %s", dlerror());
16247a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie  } else {
16347a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie    mAppInfo = static_cast<const struct chreNslNanoappInfo *>(
16447a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie        dlsym(mDsoHandle, CHRE_NSL_DSO_NANOAPP_INFO_SYMBOL_NAME));
16547a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie    if (mAppInfo == nullptr) {
16647a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie      LOGE("Failed to find app info symbol: %s", dlerror());
16747a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie    } else {
16847a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie      success = validateAppInfo(mExpectedAppId, mExpectedAppVersion, mAppInfo);
16947a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie      if (!success) {
17047a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie        mAppInfo = nullptr;
17147a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie      } else {
17247a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie        LOGI("Successfully loaded nanoapp: %s (0x%016" PRIx64 ") version 0x%"
17347a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie             PRIx32, mAppInfo->name, mAppInfo->appId, mAppInfo->appVersion);
17447a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie      }
17547a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie    }
17647a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie  }
17747a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie
17847a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie  return success;
17947a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie}
18047a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie
1819d5b500a223ef73560f0dce38f50b809bde5dd0dBrian Duddieuint64_t PlatformNanoapp::getAppId() const {
18247a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie  return (mAppInfo != nullptr) ? mAppInfo->appId : mExpectedAppId;
1839d5b500a223ef73560f0dce38f50b809bde5dd0dBrian Duddie}
1849d5b500a223ef73560f0dce38f50b809bde5dd0dBrian Duddie
1859d5b500a223ef73560f0dce38f50b809bde5dd0dBrian Duddieuint32_t PlatformNanoapp::getAppVersion() const {
18647a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie  return (mAppInfo != nullptr) ? mAppInfo->appVersion : mExpectedAppVersion;
1879d5b500a223ef73560f0dce38f50b809bde5dd0dBrian Duddie}
1889d5b500a223ef73560f0dce38f50b809bde5dd0dBrian Duddie
1899d5b500a223ef73560f0dce38f50b809bde5dd0dBrian Duddieuint32_t PlatformNanoapp::getTargetApiVersion() const {
19047a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie  return (mAppInfo != nullptr) ? mAppInfo->targetApiVersion : 0;
1919d5b500a223ef73560f0dce38f50b809bde5dd0dBrian Duddie}
1929d5b500a223ef73560f0dce38f50b809bde5dd0dBrian Duddie
1939d5b500a223ef73560f0dce38f50b809bde5dd0dBrian Duddiebool PlatformNanoapp::isSystemNanoapp() const {
19447a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie  // Right now, we assume that system nanoapps are always static nanoapps. Since
19547a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie  // mAppInfo can only be null either prior to loading the app (in which case
19647a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie  // this function is not expected to return a valid value anyway), or when a
19747a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie  // dynamic nanoapp is not running, "false" is the correct return value in that
19847a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie  // case.
19947a99dc3cacacb0418548609c0e2d3b2b70d821eBrian Duddie  return (mAppInfo != nullptr) ? mAppInfo->isSystemNanoapp : false;
2009d5b500a223ef73560f0dce38f50b809bde5dd0dBrian Duddie}
2019d5b500a223ef73560f0dce38f50b809bde5dd0dBrian Duddie
2029d5b500a223ef73560f0dce38f50b809bde5dd0dBrian Duddie}  // namespace chre
203