1# Design for Compatibility 2 3[TOC] 4 5Compatibility is an important attribute of CHRE, which is accomplished through a 6combination of thoughtful API and framework design. When we refer to 7compatibility within the scope of CHRE, there are two main categories: 8 9* **Code compatibility**, which means that a nanoapp can be recompiled to run on 10 a new platform without needing any code changes. CHRE provides this 11 cross-device compatibility for all nanoapps which are written in a supported 12 programming language (C99 or C++11), and reference only the standard CHRE APIs 13 and mandatory standard library elements (or have these standard library 14 functions statically linked into their binary). 15 16* **Binary compatibility**, which means that a nanoapp binary which has been 17 compiled against a particular version of the CHRE API can run on a CHRE 18 framework implementation which was compiled against a different version of the 19 API. This is also called *cross-version compatibility*. Note that this does 20 *not* mean that a nanoapp compiled against one version of the CHRE API can be 21 compiled against a different version of the CHRE API without compiler errors - 22 although rare, compile-time breakages are permitted with sufficient 23 justification, since nanoapp developers can update their code at the time they 24 migrate to the new API version. 25 26This section provides an overview of the mechanisms used to ensure 27compatibility. 28 29## CHRE API 30 31The CHRE API is a native C API that defines the interface between a nanoapp and 32any underlying CHRE implementation to provide cross-platform and cross-version 33compatibility. It is designed to be supportable even in very memory-constrained 34environments (total system memory in the hundreds of kilobytes range), and is 35thoroughly documented to clearly indicate the intended behavior. 36 37The CHRE API follows [semantic versioning](https://semver.org) principles to 38maintain binary compatibility. In short, this means that the minor version is 39incremented when new features and changes are introduced in a backwards 40compatible way, and the major version is only incremented on a 41compatibility-breaking change. One key design goal of the CHRE API is to avoid 42major version changes if at all possible, through use of 43compatibility-preserving code in the framework and Nanoapp Support Library 44(NSL). 45 46Minor version updates to the CHRE API typically occur alongside each Android 47release, but the CHRE version and Android version are not intrinsically related. 48Nanoapps should be compiled against the latest version to be able to use any 49newly added features, though nanoapp binaries are compatible across minor 50version changes. 51 52### API Compatibility Design Principles 53 54API design principles applied within CHRE to ensure compatibility include the 55following (not an exhaustive list). These are recommended to be followed for any 56vendor-specific API extensions as well. 57 58* Functionality must not be removed unless it was optional at the time of 59 introduction, for example as indicated by a capabilities flag (an exception 60 exists if it has no impact on the regular functionality of a nanoapp, for 61 example a feature that only aids in debugging) 62* Reserved fields must be set to 0 by the sender and ignored by the recipient 63* Fields within a structure must not be reordered - new fields may only be 64 introduced by reclaiming reserved fields (preferred), or adding to the end of 65 a structure 66* When reclaiming a reserved field, the default value of 0 must indicate a 67 property that is guaranteed to hold for previous API versions, or “unknown” 68* Arguments to a function must not be added or removed - introduce a new 69 function instead 70* The meaning of constants (e.g. event types) must never be changed, but may be 71 deprecated and eventually replaced 72 73## Binary Backward Compatibility and the NSL 74 75This is where we want a nanoapp compiled against e.g. v1.2 to run on a CHRE v1.1 76or older implementation. This is done through a combination of runtime feature 77discovery, and compatibility behaviors included in the Nanoapp Support Library 78(NSL). 79 80Runtime feature discovery involves a nanoapp querying for the support of a 81feature (e.g. RTT support indicated in `chreWifiGetCapabilities()`, or querying 82for a specific sensor in `chreSensorFindDefault()`), which allows it determine 83whether the associated functionality is expected to work. The nanoapp may also 84query `chreGetApiVersion()` to find out the version of the CHRE API supported by 85the platform it is running on. If a nanoapp has a hard requirement on some 86missing functionality, it may choose to return false from `nanoappStart()` to 87abort initialization. 88 89However, a CHRE implementation cannot anticipate all future API changes and 90automatically provide compatibility. So the NSL serves as a transparent shim 91which is compiled into the nanoapp binary to ensure this compatibility. For 92example, a nanoapp compiled against v1.2 must be able to reference and call 93`chreConfigureHostSleepStateEvents()` when running on a CHRE v1.1 or earlier, 94although such a function call would have no effect in that case. Typical dynamic 95linking approaches would find an unsatisfied dependency and fail to load the 96nanoapp, even if it does not actually call the function, for example by wrapping 97it in a condition that first checks the CHRE version. In 98`platform/shared/nanoapp/nanoapp_support_lib_dso.cc`, this is supported by 99intercepting CHRE API function calls and either calling through to the 100underlying platform if it’s supported, or replacing it with stub functionality. 101 102Along similar lines, if new fields are added to the end of a structure without 103repurposing a reserved field in an update to the CHRE API, as was the case with 104`bearing_accuracy` in `chreGnssLocationEvent`, the nanoapp must be able to 105reference the new field without reading uninitialized memory. This is enabled by 106the NSL, which can intercept the event, and copy it into the new, larger 107structure, and set the new fields to their default values. 108 109Since these NSL compatibility behaviors carry some amount of overhead (even if 110very slight), they can be disabled if it is known that a nanoapp will never run 111on an older CHRE version. This may be the case for a nanoapp developed for a 112specific device, for example. The NSL may also limit its compatibility range 113based on knowledge of the API version at which support for given hardware was 114introduced. For example, if a new hardware family first added support for the 115CHRE framework at API v1.1, then NSL support for v1.0 is unnecessary. 116 117Outside of these cases, the NSL must provide backwards compatibility for at 118least 3 previous versions, and is strongly recommended to provide support for 119all available versions. This means that if the first API supported by a target 120device is v1.0, then a nanoapp compiled against API v1.4 must have NSL support 121for v1.1 through v1.4, and should ideally also support v1.0. 122 123## Binary Forward Compatibility and Framework Requirements 124 125Conversely, this is where we want a nanoapp compiled against e.g. v1.1 to run 126against CHRE v1.2 or later implementations. The NSL cannot directly provide this 127kind of compatibility, so it must be ensured through a combination of careful 128CHRE API design, and compatibility behaviors in the CHRE framework. 129 130Similar to how Android apps have a “target SDK” attribute, nanoapps have a 131“target API version” which indicates the version of the CHRE API they were 132compiled against. The framework can inspect this value and provide compatibility 133behavior as needed. For example, `chreGetSensorInfo()` populates memory provided 134by the nanoapp with information about a given sensor. In CHRE API v1.1, this 135structure was extended with a new field, `minInterval`. Therefore, the framework 136must check if the nanoapp’s target API is v1.1 or later before writing this 137field. 138 139To avoid carrying forward compatibility code indefinitely, it is permitted for a 140CHRE implementation to reject compatibility with nanoapps compiled against an 141API minor version that is 2 or more generations older. For example, a CHRE v1.4 142implementation may reject attempts to load a nanoapp compiled against CHRE API 143v1.2, but it must ensure compatibility with v1.3. However, providing the full 144range of compatibility generally does not require significant effort on behalf 145of the CHRE implementation, so this is recommended for maximum flexibility. 146 147## ABI Stability 148 149CHRE does not define a standard Application Binary Interface (ABI) - this is 150left as a platform responsibility in order to provide maximum flexibility. 151However, CHRE implementations must ensure that binary compatibility is 152maintained with nanoapps, by choosing a design that provides this property. For 153example, if a syscall-like approach is used (with the help of the NSL) to call 154from position-independent nanoapp code into fixed-position CHRE API functions 155(e.g. in a statically linked monolithic firmware image), syscall IDs and their 156calling conventions must remain stable. It is not acceptable to require all 157nanoapps to be recompiled to be able to work with an updated CHRE 158implementation. 159 160## CHRE PALs 161 162Since the PAL APIs are largely based on the CHRE APIs, they benefit from many of 163the compatibility efforts by default. Overall, binary compatibility in the CHRE 164PAL APIs are less involved than the CHRE APIs, because we expect CHRE and CHRE 165PAL implementations to be built into the vendor image together, and usually run 166at the same version except for limited periods during development. However, a 167PAL implementation can simultaneously support multiple PAL API versions from a 168single codebase by adapting its behavior based on the `requestedApiVersion` 169parameter in the \*GetApi method, e.g. `chrePalWifiGetApi()`. 170 171## Deprecation Strategy 172 173In general, nanoapp compilation may be broken in a minor update (given 174sufficient justification - this is not a light decision to make, considering the 175downstream impact to nanoapp developers), but deprecation of functionality at a 176binary level occurs over a minimum of 2 years (minor versions). The general 177process for deprecating a function in the CHRE API is as follows: 178 179* In a new minor version `N` of the CHRE API, the function is marked with 180 `@deprecated`, with a description of the recommended alternative, and ideally 181 the justification for the deprecation, so nanoapp developers know why it's 182 important to update. 183 184 * Depending on the severity of impact, the function may also be tagged with a 185 compiler attribute to generate a warning (e.g. `CHRE_DEPRECATED`) that may 186 be ignored. Or, version `N` or later, an attribute or other method may be 187 used to break compilation of nanoapps using the deprecated function, forcing 188 them to update. If not considered a high severity issue and compatibility is 189 easy to maintain, it is recommended to break compilation only in version 190 `N+2` or later. 191 192 * Binary compatibility at this stage must be maintained. For example the NSL 193 should map the new functionality to the deprecated function when running on 194 CHRE `N-1` or older, or a suitable alternative must be devised. Likewise, 195 CHRE must continue to provide the deprecated function to support nanoapps 196 built against `N-1`. 197 198* Impacts to binary compatibility on the CHRE side may occur 2 versions after 199 the function is made compilation-breaking for nanoapps, since forward 200 compatibility is guaranteed for 2 minor versions. If done, the nanoapp must be 201 rejected at load time. 202 203* Impacts to binary compatibility on the nanoapp side may occur 4 versions after 204 the function is marked deprecated (at `N+4`), since backward compatibility is 205 guaranteed for 4 minor versions. If done, the NSL must cause `nanoappStart()` 206 to return false on version `N` or older. 207 208For example, if a function is marked deprecated in `N`, and becomes a 209compilation-breaking error in `N+2`, then a CHRE implementation at `N+4` may 210remove the deprecated functionality only if it rejects a nanoapp built against 211`N+1` or older at load time. Likewise, the NSL can remove compatibility code for 212the deprecated function at `N+4`. CHRE and NSL implementations must not break 213compatibility in a fragmented, unpredictable, or hidden way, for example by 214replacing the deprecated function with a stub that does nothing. If it is 215possible for CHRE and/or the NSL to detect only nanoapps that use the deprecated 216functionality, then it is permissible to block loading of only those nanoapps, 217but otherwise this must be a blanket ban of all nanoapps compiled against the 218old API version. 219