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