diff options
author | Scott Lobdell <slobdell@google.com> | 2021-01-26 13:54:20 -0800 |
---|---|---|
committer | Scott Lobdell <slobdell@google.com> | 2021-01-26 13:54:20 -0800 |
commit | f072d1ca00fe4e68a9944d8922e09d700f326c85 (patch) | |
tree | dd8a7d623ca33b5c0040ac4e338c2287a169fb4f | |
parent | c1c3917a4fa8b5a2182affe9cb7085e39db656a3 (diff) | |
parent | 36b9cdeceab74933a1dd9b0174edc37edab862dc (diff) |
Merge SP1A.210122.003
Change-Id: I48e52b88645c81351c04f3783085751522b6e99c
393 files changed, 21110 insertions, 2975 deletions
diff --git a/audio/7.0/IDevice.hal b/audio/7.0/IDevice.hal index e30e5456bc..e423f2967a 100644 --- a/audio/7.0/IDevice.hal +++ b/audio/7.0/IDevice.hal @@ -103,6 +103,11 @@ interface IDevice { * If the stream can not be opened with the proposed audio config, * HAL must provide suggested values for the audio config. * + * Note: INVALID_ARGUMENTS is returned both in the case when the + * HAL can not use the provided config and in the case when + * the value of any argument is invalid. In the latter case the + * HAL must provide a default initialized suggested config. + * * @param ioHandle handle assigned by AudioFlinger. * @param device device type and (if needed) address. * @param config stream configuration. @@ -111,7 +116,8 @@ interface IDevice { May be used by implementations to configure hardware effects. * @return retval operation completion status. * @return outStream created output stream. - * @return suggestedConfig in case of invalid parameters, suggested config. + * @return suggestedConfig in the case of rejection of the proposed config, + * a config suggested by the HAL. */ openOutputStream( AudioIoHandle ioHandle, @@ -128,6 +134,11 @@ interface IDevice { * If the stream can not be opened with the proposed audio config, * HAL must provide suggested values for the audio config. * + * Note: INVALID_ARGUMENTS is returned both in the case when the + * HAL can not use the provided config and in the case when + * the value of any argument is invalid. In the latter case the + * HAL must provide a default initialized suggested config. + * * @param ioHandle handle assigned by AudioFlinger. * @param device device type and (if needed) address. * @param config stream configuration. @@ -136,7 +147,8 @@ interface IDevice { * May be used by implementations to configure processing effects. * @return retval operation completion status. * @return inStream in case of success, created input stream. - * @return suggestedConfig in case of invalid parameters, suggested config. + * @return suggestedConfig in the case of rejection of the proposed config, + * a config suggested by the HAL. */ openInputStream( AudioIoHandle ioHandle, @@ -162,6 +174,9 @@ interface IDevice { * Creates an audio patch between several source and sink ports. The handle * is allocated by the HAL and must be unique for this audio HAL module. * + * Optional method. HAL must support it if 'supportsAudioPatches' returns + * 'true'. + * * @param sources patch sources. * @param sinks patch sinks. * @return retval operation completion status. @@ -177,6 +192,9 @@ interface IDevice { * as the HAL module can figure out a way of switching the route without * causing audio disruption. * + * Optional method. HAL must support it if 'supportsAudioPatches' returns + * 'true'. + * * @param previousPatch handle of the previous patch to update. * @param sources new patch sources. * @param sinks new patch sinks. @@ -192,6 +210,9 @@ interface IDevice { /** * Release an audio patch. * + * Optional method. HAL must support it if 'supportsAudioPatches' returns + * 'true'. + * * @param patch patch handle. * @return retval operation completion status. */ diff --git a/audio/7.0/IStreamIn.hal b/audio/7.0/IStreamIn.hal index 0a3f24b840..bf9ae52923 100644 --- a/audio/7.0/IStreamIn.hal +++ b/audio/7.0/IStreamIn.hal @@ -41,6 +41,18 @@ interface IStreamIn extends IStream { setGain(float gain) generates (Result retval); /** + * Called when the metadata of the stream's sink has been changed. + * Optional method + * + * @param sinkMetadata Description of the audio that is suggested by the clients. + * @return retval operation completion status. + * If any of the metadata fields contains an invalid value, + * returns INVALID_ARGUMENTS. + * If method isn't supported by the HAL returns NOT_SUPPORTED. + */ + updateSinkMetadata(SinkMetadata sinkMetadata) generates (Result retval); + + /** * Commands that can be executed on the driver reader thread. */ enum ReadCommand : int32_t { @@ -82,12 +94,6 @@ interface IStreamIn extends IStream { }; /** - * Called when the metadata of the stream's sink has been changed. - * @param sinkMetadata Description of the audio that is suggested by the clients. - */ - updateSinkMetadata(SinkMetadata sinkMetadata); - - /** * Set up required transports for receiving audio buffers from the driver. * * The transport consists of three message queues: diff --git a/audio/7.0/IStreamOut.hal b/audio/7.0/IStreamOut.hal index 0951a9e42a..78cb51b662 100644 --- a/audio/7.0/IStreamOut.hal +++ b/audio/7.0/IStreamOut.hal @@ -45,6 +45,18 @@ interface IStreamOut extends IStream { setVolume(float left, float right) generates (Result retval); /** + * Called when the metadata of the stream's source has been changed. + * Optional method + * + * @param sourceMetadata Description of the audio that is played by the clients. + * @return retval operation completion status. + * If any of the metadata fields contains an invalid value, + * returns INVALID_ARGUMENTS. + * If method isn't supported by the HAL returns NOT_SUPPORTED. + */ + updateSourceMetadata(SourceMetadata sourceMetadata) generates (Result retval); + + /** * Commands that can be executed on the driver writer thread. */ enum WriteCommand : int32_t { @@ -77,12 +89,6 @@ interface IStreamOut extends IStream { }; /** - * Called when the metadata of the stream's source has been changed. - * @param sourceMetadata Description of the audio that is played by the clients. - */ - updateSourceMetadata(SourceMetadata sourceMetadata); - - /** * Set up required transports for passing audio buffers to the driver. * * The transport consists of three message queues: diff --git a/audio/common/7.0/enums/include/android_audio_policy_configuration_V7_0-enums.h b/audio/common/7.0/enums/include/android_audio_policy_configuration_V7_0-enums.h index b7c1cc97bc..c0042db0ba 100644 --- a/audio/common/7.0/enums/include/android_audio_policy_configuration_V7_0-enums.h +++ b/audio/common/7.0/enums/include/android_audio_policy_configuration_V7_0-enums.h @@ -212,12 +212,11 @@ static inline bool isOutputDevice(const std::string& device) { return isOutputDevice(stringToAudioDevice(device)); } -static inline bool isVendorExtension(const std::string& device) { +static inline bool isVendorExtension(const std::string& s) { // Must match the "vendorExtension" rule from the XSD file. static const std::string vendorPrefix = "VX_"; - return device.size() > vendorPrefix.size() && - device.substr(0, vendorPrefix.size()) == vendorPrefix && - std::all_of(device.begin() + vendorPrefix.size(), device.end(), + return s.size() > vendorPrefix.size() && s.substr(0, vendorPrefix.size()) == vendorPrefix && + std::all_of(s.begin() + vendorPrefix.size(), s.end(), [](unsigned char c) { return c == '_' || std::isalnum(c); }); } diff --git a/audio/common/7.0/types.hal b/audio/common/7.0/types.hal index b14ebd48d9..ed6d94f988 100644 --- a/audio/common/7.0/types.hal +++ b/audio/common/7.0/types.hal @@ -270,7 +270,10 @@ struct AudioOffloadInfo { */ struct AudioConfig { AudioConfigBase base; - AudioOffloadInfo offloadInfo; + safe_union OffloadInfo { + Monostate unspecified; + AudioOffloadInfo info; + } offloadInfo; uint64_t frameCount; }; @@ -278,7 +281,8 @@ struct AudioConfig { * AudioTag is an additional use case qualifier complementing * AudioUsage and AudioContentType. Tags are set by vendor specific applications * and must be prefixed by "VX_". Vendor must namespace their tag - * names to avoid conflicts. + * names to avoid conflicts. See 'vendorExtension' in audio_policy_configuration.xsd + * for a formal definition. */ typedef string AudioTag; diff --git a/audio/common/all-versions/default/7.0/HidlUtils.cpp b/audio/common/all-versions/default/7.0/HidlUtils.cpp index c985a7027a..de19faf153 100644 --- a/audio/common/all-versions/default/7.0/HidlUtils.cpp +++ b/audio/common/all-versions/default/7.0/HidlUtils.cpp @@ -21,6 +21,7 @@ #include <log/log.h> #include <android_audio_policy_configuration_V7_0-enums.h> +#include <common/all-versions/HidlSupport.h> #include <common/all-versions/VersionUtils.h> #include "HidlUtils.h" @@ -306,7 +307,12 @@ status_t HidlUtils::audioConfigFromHal(const audio_config_t& halConfig, bool isI audio_config_base_t halConfigBase = {halConfig.sample_rate, halConfig.channel_mask, halConfig.format}; CONVERT_CHECKED(audioConfigBaseFromHal(halConfigBase, isInput, &config->base), result); - CONVERT_CHECKED(audioOffloadInfoFromHal(halConfig.offload_info, &config->offloadInfo), result); + if (halConfig.offload_info.sample_rate != 0) { + config->offloadInfo.info({}); + CONVERT_CHECKED( + audioOffloadInfoFromHal(halConfig.offload_info, &config->offloadInfo.info()), + result); + } config->frameCount = halConfig.frame_count; return result; } @@ -319,7 +325,11 @@ status_t HidlUtils::audioConfigToHal(const AudioConfig& config, audio_config_t* halConfig->sample_rate = halConfigBase.sample_rate; halConfig->channel_mask = halConfigBase.channel_mask; halConfig->format = halConfigBase.format; - CONVERT_CHECKED(audioOffloadInfoToHal(config.offloadInfo, &halConfig->offload_info), result); + if (config.offloadInfo.getDiscriminator() == + AudioConfig::OffloadInfo::hidl_discriminator::info) { + CONVERT_CHECKED(audioOffloadInfoToHal(config.offloadInfo.info(), &halConfig->offload_info), + result); + } halConfig->frame_count = config.frameCount; return result; } @@ -800,6 +810,47 @@ status_t HidlUtils::audioProfileToHal(const AudioProfile& profile, return result; } +status_t HidlUtils::audioTagsFromHal(const char* halTags, hidl_vec<AudioTag>* tags) { + std::vector<std::string> strTags = utils::splitString(halTags, sAudioTagSeparator); + status_t result = NO_ERROR; + tags->resize(strTags.size()); + size_t to = 0; + for (size_t from = 0; from < strTags.size(); ++from) { + if (xsd::isVendorExtension(strTags[from])) { + (*tags)[to++] = strTags[from]; + } else { + result = BAD_VALUE; + } + } + if (to != strTags.size()) { + tags->resize(to); + } + return result; +} + +status_t HidlUtils::audioTagsToHal(const hidl_vec<AudioTag>& tags, char* halTags) { + memset(halTags, 0, AUDIO_ATTRIBUTES_TAGS_MAX_SIZE); + status_t result = NO_ERROR; + std::ostringstream halTagsBuffer; + bool hasValue = false; + for (const auto& tag : tags) { + if (hasValue) { + halTagsBuffer << sAudioTagSeparator; + } + if (xsd::isVendorExtension(tag) && strchr(tag.c_str(), sAudioTagSeparator) == nullptr) { + halTagsBuffer << tag; + hasValue = true; + } else { + result = BAD_VALUE; + } + } + std::string fullHalTags{std::move(halTagsBuffer.str())}; + strncpy(halTags, fullHalTags.c_str(), AUDIO_ATTRIBUTES_TAGS_MAX_SIZE); + CONVERT_CHECKED(fullHalTags.length() <= AUDIO_ATTRIBUTES_TAGS_MAX_SIZE ? NO_ERROR : BAD_VALUE, + result); + return result; +} + status_t HidlUtils::deviceAddressFromHal(audio_devices_t halDeviceType, const char* halDeviceAddress, DeviceAddress* device) { status_t result = NO_ERROR; diff --git a/audio/common/all-versions/default/HidlUtils.h b/audio/common/all-versions/default/HidlUtils.h index d8b7ba419e..8e9275c441 100644 --- a/audio/common/all-versions/default/HidlUtils.h +++ b/audio/common/all-versions/default/HidlUtils.h @@ -109,6 +109,8 @@ struct HidlUtils { AudioStreamType* streamType); static status_t audioStreamTypeToHal(const AudioStreamType& streamType, audio_stream_type_t* halStreamType); + static status_t audioTagsFromHal(const char* halTags, hidl_vec<AudioTag>* tags); + static status_t audioTagsToHal(const hidl_vec<AudioTag>& tags, char* halTags); private: static status_t audioIndexChannelMaskFromHal(audio_channel_mask_t halChannelMask, diff --git a/audio/common/all-versions/default/tests/hidlutils_tests.cpp b/audio/common/all-versions/default/tests/hidlutils_tests.cpp index 22571c0411..fef88b450b 100644 --- a/audio/common/all-versions/default/tests/hidlutils_tests.cpp +++ b/audio/common/all-versions/default/tests/hidlutils_tests.cpp @@ -589,16 +589,29 @@ TEST(HidlUtils, ConvertConfig) { config.base.sampleRateHz = 44100; config.base.channelMask = toString(xsd::AudioChannelMask::AUDIO_CHANNEL_OUT_STEREO); config.base.format = toString(xsd::AudioFormat::AUDIO_FORMAT_PCM_16_BIT); - config.offloadInfo.base = config.base; - config.offloadInfo.streamType = toString(xsd::AudioStreamType::AUDIO_STREAM_MUSIC); - config.offloadInfo.bitRatePerSecond = 320; - config.offloadInfo.durationMicroseconds = -1; - config.offloadInfo.bitWidth = 16; - config.offloadInfo.bufferSize = 1024; - config.offloadInfo.usage = toString(xsd::AudioUsage::AUDIO_USAGE_MEDIA); - config.offloadInfo.encapsulationMode = AudioEncapsulationMode::ELEMENTARY_STREAM; - config.offloadInfo.contentId = 42; - config.offloadInfo.syncId = 13; + audio_config_t halConfig; + EXPECT_EQ(NO_ERROR, HidlUtils::audioConfigToHal(config, &halConfig)); + AudioConfig configBack; + EXPECT_EQ(NO_ERROR, HidlUtils::audioConfigFromHal(halConfig, false /*isInput*/, &configBack)); + EXPECT_EQ(config, configBack); +} + +TEST(HidlUtils, ConvertConfigWithOffloadInfo) { + AudioConfig config = {}; + config.base.sampleRateHz = 44100; + config.base.channelMask = toString(xsd::AudioChannelMask::AUDIO_CHANNEL_OUT_STEREO); + config.base.format = toString(xsd::AudioFormat::AUDIO_FORMAT_PCM_16_BIT); + config.offloadInfo.info( + AudioOffloadInfo{.base = config.base, + .streamType = toString(xsd::AudioStreamType::AUDIO_STREAM_MUSIC), + .bitRatePerSecond = 320, + .durationMicroseconds = -1, + .bitWidth = 16, + .bufferSize = 1024, + .usage = toString(xsd::AudioUsage::AUDIO_USAGE_MEDIA), + .encapsulationMode = AudioEncapsulationMode::ELEMENTARY_STREAM, + .contentId = 42, + .syncId = 13}); audio_config_t halConfig; EXPECT_EQ(NO_ERROR, HidlUtils::audioConfigToHal(config, &halConfig)); AudioConfig configBack; @@ -707,3 +720,43 @@ TEST(HidlUtils, ConvertAudioPort) { EXPECT_EQ(NO_ERROR, HidlUtils::audioPortToHal(portBack, &halPortBack)); EXPECT_TRUE(audio_ports_v7_are_equal(&halPort, &halPortBack)); } + +TEST(HidlUtils, ConvertInvalidAudioTags) { + char halTag[AUDIO_ATTRIBUTES_TAGS_MAX_SIZE] = {}; + + hidl_vec<AudioTag> emptyTag = {{""}}; + EXPECT_EQ(BAD_VALUE, HidlUtils::audioTagsToHal(emptyTag, halTag)); + + hidl_vec<AudioTag> longTag = {{std::string(AUDIO_ATTRIBUTES_TAGS_MAX_SIZE + 1, 'A')}}; + EXPECT_EQ(BAD_VALUE, HidlUtils::audioTagsToHal(longTag, halTag)); + + hidl_vec<AudioTag> tagSeparator = { + {std::string(AUDIO_ATTRIBUTES_TAGS_MAX_SIZE - 1, HidlUtils::sAudioTagSeparator)}}; + EXPECT_EQ(BAD_VALUE, HidlUtils::audioTagsToHal(tagSeparator, halTag)); + + hidl_vec<AudioTag> notExtensions = {{"random string", "VX_", "VX_GOOGLE_$$"}}; + EXPECT_EQ(BAD_VALUE, HidlUtils::audioTagsToHal(notExtensions, halTag)); +} + +TEST(HidlUtils, ConvertAudioTags) { + hidl_vec<AudioTag> emptyTags; + char halEmptyTags[AUDIO_ATTRIBUTES_TAGS_MAX_SIZE] = {}; + EXPECT_EQ(NO_ERROR, HidlUtils::audioTagsToHal(emptyTags, halEmptyTags)); + hidl_vec<AudioTag> emptyTagsBack; + EXPECT_EQ(NO_ERROR, HidlUtils::audioTagsFromHal(halEmptyTags, &emptyTagsBack)); + EXPECT_EQ(emptyTags, emptyTagsBack); + + hidl_vec<AudioTag> oneTag = {{"VX_GOOGLE_VR"}}; + char halOneTag[AUDIO_ATTRIBUTES_TAGS_MAX_SIZE] = {}; + EXPECT_EQ(NO_ERROR, HidlUtils::audioTagsToHal(oneTag, halOneTag)); + hidl_vec<AudioTag> oneTagBack; + EXPECT_EQ(NO_ERROR, HidlUtils::audioTagsFromHal(halOneTag, &oneTagBack)); + EXPECT_EQ(oneTag, oneTagBack); + + hidl_vec<AudioTag> twoTags = {{"VX_GOOGLE_VR_42", "VX_GOOGLE_1E100"}}; + char halTwoTags[AUDIO_ATTRIBUTES_TAGS_MAX_SIZE] = {}; + EXPECT_EQ(NO_ERROR, HidlUtils::audioTagsToHal(twoTags, halTwoTags)); + hidl_vec<AudioTag> twoTagsBack; + EXPECT_EQ(NO_ERROR, HidlUtils::audioTagsFromHal(halTwoTags, &twoTagsBack)); + EXPECT_EQ(twoTags, twoTagsBack); +} diff --git a/audio/common/all-versions/util/include/common/all-versions/HidlSupport.h b/audio/common/all-versions/util/include/common/all-versions/HidlSupport.h index b514a43a85..d7802cf683 100644 --- a/audio/common/all-versions/util/include/common/all-versions/HidlSupport.h +++ b/audio/common/all-versions/util/include/common/all-versions/HidlSupport.h @@ -20,6 +20,9 @@ #include <hidl/HidlSupport.h> #include <algorithm> +#include <sstream> +#include <string> +#include <vector> namespace android::hardware::audio::common::utils { @@ -29,6 +32,16 @@ bool isValidHidlEnum(Enum e) { return std::find(values.begin(), values.end(), e) != values.end(); } +static inline std::vector<std::string> splitString(const std::string& s, char separator) { + std::istringstream iss(s); + std::string t; + std::vector<std::string> result; + while (std::getline(iss, t, separator)) { + result.push_back(std::move(t)); + } + return result; +} + } // namespace android::hardware::audio::common::utils #endif // android_hardware_audio_common_HidlSupport_H_ diff --git a/audio/core/all-versions/default/Android.bp b/audio/core/all-versions/default/Android.bp index e0f086094c..c75c779a91 100644 --- a/audio/core/all-versions/default/Android.bp +++ b/audio/core/all-versions/default/Android.bp @@ -51,6 +51,7 @@ cc_defaults { "libaudio_system_headers", "libhardware_headers", "libmedia_headers", + "libmediautils_headers", ], export_header_lib_headers: [ diff --git a/audio/core/all-versions/default/Conversions.cpp b/audio/core/all-versions/default/Conversions.cpp index 8e0a140f0b..f1752ccba3 100644 --- a/audio/core/all-versions/default/Conversions.cpp +++ b/audio/core/all-versions/default/Conversions.cpp @@ -32,14 +32,10 @@ namespace implementation { using ::android::hardware::audio::common::CPP_VERSION::implementation::HidlUtils; -#if MAJOR_VERSION <= 6 -std::string deviceAddressToHal(const DeviceAddress& address) { - audio_devices_t halDevice; - char halAddress[AUDIO_DEVICE_MAX_ADDRESS_LEN]; - (void)deviceAddressToHal(address, &halDevice, halAddress); - return halAddress; -} -#endif +#define CONVERT_CHECKED(expr, result) \ + if (status_t status = (expr); status != NO_ERROR) { \ + result = status; \ + } status_t deviceAddressToHal(const DeviceAddress& device, audio_devices_t* halDeviceType, char* halDeviceAddress) { @@ -97,6 +93,52 @@ bool halToMicrophoneCharacteristics(MicrophoneInfo* pDst, } return status; } + +status_t sinkMetadataToHal(const SinkMetadata& sinkMetadata, + std::vector<record_track_metadata>* halTracks) { + status_t result = NO_ERROR; + if (halTracks != nullptr) { + halTracks->reserve(sinkMetadata.tracks.size()); + } + for (auto& metadata : sinkMetadata.tracks) { + record_track_metadata halTrackMetadata{.gain = metadata.gain}; + CONVERT_CHECKED(HidlUtils::audioSourceToHal(metadata.source, &halTrackMetadata.source), + result); +#if MAJOR_VERSION >= 5 + if (metadata.destination.getDiscriminator() == + RecordTrackMetadata::Destination::hidl_discriminator::device) { + CONVERT_CHECKED( + deviceAddressToHal(metadata.destination.device(), &halTrackMetadata.dest_device, + halTrackMetadata.dest_device_address), + result); + } +#endif + if (halTracks != nullptr) { + halTracks->push_back(std::move(halTrackMetadata)); + } + } + return result; +} + +status_t sourceMetadataToHal(const SourceMetadata& sourceMetadata, + std::vector<playback_track_metadata_t>* halTracks) { + status_t result = NO_ERROR; + if (halTracks != nullptr) { + halTracks->reserve(sourceMetadata.tracks.size()); + } + for (auto& metadata : sourceMetadata.tracks) { + playback_track_metadata_t halTrackMetadata{.gain = metadata.gain}; + CONVERT_CHECKED(HidlUtils::audioUsageToHal(metadata.usage, &halTrackMetadata.usage), + result); + CONVERT_CHECKED(HidlUtils::audioContentTypeToHal(metadata.contentType, + &halTrackMetadata.content_type), + result); + if (halTracks != nullptr) { + halTracks->push_back(std::move(halTrackMetadata)); + } + } + return result; +} #endif // MAJOR_VERSION >= 4 #if MAJOR_VERSION >= 7 @@ -135,6 +177,50 @@ bool audioOutputFlagsToHal(const hidl_vec<AudioInOutFlag>& flags, audio_output_f } return success; } + +status_t sinkMetadataToHalV7(const SinkMetadata& sinkMetadata, + std::vector<record_track_metadata_v7_t>* halTracks) { + std::vector<record_track_metadata> bases; + status_t result = sinkMetadataToHal(sinkMetadata, halTracks != nullptr ? &bases : nullptr); + if (halTracks != nullptr) { + halTracks->reserve(bases.size()); + } + auto baseIter = std::make_move_iterator(bases.begin()); + for (auto& metadata : sinkMetadata.tracks) { + record_track_metadata_v7_t halTrackMetadata; + CONVERT_CHECKED(HidlUtils::audioChannelMaskToHal(metadata.channelMask, + &halTrackMetadata.channel_mask), + result); + CONVERT_CHECKED(HidlUtils::audioTagsToHal(metadata.tags, halTrackMetadata.tags), result); + if (halTracks != nullptr) { + halTrackMetadata.base = std::move(*baseIter++); + halTracks->push_back(std::move(halTrackMetadata)); + } + } + return result; +} + +status_t sourceMetadataToHalV7(const SourceMetadata& sourceMetadata, + std::vector<playback_track_metadata_v7_t>* halTracks) { + std::vector<playback_track_metadata_t> bases; + status_t result = sourceMetadataToHal(sourceMetadata, halTracks != nullptr ? &bases : nullptr); + if (halTracks != nullptr) { + halTracks->reserve(bases.size()); + } + auto baseIter = std::make_move_iterator(bases.begin()); + for (auto& metadata : sourceMetadata.tracks) { + playback_track_metadata_v7_t halTrackMetadata; + CONVERT_CHECKED(HidlUtils::audioChannelMaskToHal(metadata.channelMask, + &halTrackMetadata.channel_mask), + result); + CONVERT_CHECKED(HidlUtils::audioTagsToHal(metadata.tags, halTrackMetadata.tags), result); + if (halTracks != nullptr) { + halTrackMetadata.base = std::move(*baseIter++); + halTracks->push_back(std::move(halTrackMetadata)); + } + } + return result; +} #endif } // namespace implementation diff --git a/audio/core/all-versions/default/Device.cpp b/audio/core/all-versions/default/Device.cpp index bb69f0b88d..7caed44bc9 100644 --- a/audio/core/all-versions/default/Device.cpp +++ b/audio/core/all-versions/default/Device.cpp @@ -135,13 +135,14 @@ Return<void> Device::getMasterMute(getMasterMute_cb _hidl_cb) { Return<void> Device::getInputBufferSize(const AudioConfig& config, getInputBufferSize_cb _hidl_cb) { audio_config_t halConfig; - HidlUtils::audioConfigToHal(config, &halConfig); - size_t halBufferSize = mDevice->get_input_buffer_size(mDevice, &halConfig); Result retval(Result::INVALID_ARGUMENTS); uint64_t bufferSize = 0; - if (halBufferSize != 0) { - retval = Result::OK; - bufferSize = halBufferSize; + if (HidlUtils::audioConfigToHal(config, &halConfig) == NO_ERROR) { + size_t halBufferSize = mDevice->get_input_buffer_size(mDevice, &halConfig); + if (halBufferSize != 0) { + retval = Result::OK; + bufferSize = halBufferSize; + } } _hidl_cb(retval, bufferSize); return Void(); @@ -153,7 +154,9 @@ std::tuple<Result, sp<IStreamOut>> Device::openOutputStreamImpl(int32_t ioHandle const AudioOutputFlags& flags, AudioConfig* suggestedConfig) { audio_config_t halConfig; - HidlUtils::audioConfigToHal(config, &halConfig); + if (HidlUtils::audioConfigToHal(config, &halConfig) != NO_ERROR) { + return {Result::INVALID_ARGUMENTS, nullptr}; + } audio_stream_out_t* halStream; audio_devices_t halDevice; char halDeviceAddress[AUDIO_DEVICE_MAX_ADDRESS_LEN]; @@ -186,7 +189,9 @@ std::tuple<Result, sp<IStreamIn>> Device::openInputStreamImpl( int32_t ioHandle, const DeviceAddress& device, const AudioConfig& config, const AudioInputFlags& flags, AudioSource source, AudioConfig* suggestedConfig) { audio_config_t halConfig; - HidlUtils::audioConfigToHal(config, &halConfig); + if (HidlUtils::audioConfigToHal(config, &halConfig) != NO_ERROR) { + return {Result::INVALID_ARGUMENTS, nullptr}; + } audio_stream_in_t* halStream; audio_devices_t halDevice; char halDeviceAddress[AUDIO_DEVICE_MAX_ADDRESS_LEN]; @@ -248,6 +253,14 @@ Return<void> Device::openOutputStream(int32_t ioHandle, const DeviceAddress& dev #endif const SourceMetadata& sourceMetadata, openOutputStream_cb _hidl_cb) { +#if MAJOR_VERSION <= 6 + if (status_t status = sourceMetadataToHal(sourceMetadata, nullptr); status != NO_ERROR) { +#else + if (status_t status = sourceMetadataToHalV7(sourceMetadata, nullptr); status != NO_ERROR) { +#endif + _hidl_cb(analyzeStatus("sourceMetadataToHal", status), nullptr, AudioConfig{}); + return Void(); + } AudioConfig suggestedConfig; auto [result, streamOut] = openOutputStreamImpl(ioHandle, device, config, flags, &suggestedConfig); @@ -271,7 +284,15 @@ Return<void> Device::openInputStream(int32_t ioHandle, const DeviceAddress& devi // This should never happen, the framework must not create as stream // if there is no client ALOGE("openInputStream called without tracks connected"); - _hidl_cb(Result::INVALID_ARGUMENTS, nullptr, AudioConfig()); + _hidl_cb(Result::INVALID_ARGUMENTS, nullptr, AudioConfig{}); + return Void(); + } +#if MAJOR_VERSION <= 6 + if (status_t status = sinkMetadataToHal(sinkMetadata, nullptr); status != NO_ERROR) { +#else + if (status_t status = sinkMetadataToHalV7(sinkMetadata, nullptr); status != NO_ERROR) { +#endif + _hidl_cb(analyzeStatus("sinkMetadataToHal", status), nullptr, AudioConfig{}); return Void(); } // Pick the first one as the main. @@ -304,11 +325,17 @@ std::tuple<Result, AudioPatchHandle> Device::createOrUpdateAudioPatch( const hidl_vec<AudioPortConfig>& sinks) { Result retval(Result::NOT_SUPPORTED); if (version() >= AUDIO_DEVICE_API_VERSION_3_0) { + audio_patch_handle_t halPatch = static_cast<audio_patch_handle_t>(patch); std::unique_ptr<audio_port_config[]> halSources; - HidlUtils::audioPortConfigsToHal(sources, &halSources); + if (status_t status = HidlUtils::audioPortConfigsToHal(sources, &halSources); + status != NO_ERROR) { + return {analyzeStatus("audioPortConfigsToHal;sources", status), patch}; + } std::unique_ptr<audio_port_config[]> halSinks; - HidlUtils::audioPortConfigsToHal(sinks, &halSinks); - audio_patch_handle_t halPatch = static_cast<audio_patch_handle_t>(patch); + if (status_t status = HidlUtils::audioPortConfigsToHal(sinks, &halSinks); + status != NO_ERROR) { + return {analyzeStatus("audioPortConfigsToHal;sinks", status), patch}; + } retval = analyzeStatus("create_audio_patch", mDevice->create_audio_patch(mDevice, sources.size(), &halSources[0], sinks.size(), &halSinks[0], &halPatch)); @@ -343,7 +370,10 @@ Return<void> Device::getAudioPort(const AudioPort& port, getAudioPort_cb _hidl_c Return<Result> Device::setAudioPortConfig(const AudioPortConfig& config) { if (version() >= AUDIO_DEVICE_API_VERSION_3_0) { struct audio_port_config halPortConfig; - HidlUtils::audioPortConfigToHal(config, &halPortConfig); + if (status_t status = HidlUtils::audioPortConfigToHal(config, &halPortConfig); + status != NO_ERROR) { + return analyzeStatus("audioPortConfigToHal", status); + } return analyzeStatus("set_audio_port_config", mDevice->set_audio_port_config(mDevice, &halPortConfig)); } diff --git a/audio/core/all-versions/default/Stream.cpp b/audio/core/all-versions/default/Stream.cpp index c74079d9ce..f964cbb804 100644 --- a/audio/core/all-versions/default/Stream.cpp +++ b/audio/core/all-versions/default/Stream.cpp @@ -17,6 +17,7 @@ #define LOG_TAG "StreamHAL" #include "core/default/Stream.h" +#include "common/all-versions/HidlSupport.h" #include "common/all-versions/default/EffectMap.h" #include "core/default/Conversions.h" #include "core/default/Util.h" @@ -37,6 +38,7 @@ namespace CPP_VERSION { namespace implementation { using ::android::hardware::audio::common::CPP_VERSION::implementation::HidlUtils; +using ::android::hardware::audio::common::utils::splitString; Stream::Stream(bool isInput, audio_stream_t* stream) : mIsInput(isInput), mStream(stream) { (void)mIsInput; // prevent 'unused field' warnings in pre-V7 versions. @@ -220,7 +222,7 @@ Return<void> Stream::getSupportedProfiles(getSupportedProfiles_cb _hidl_cb) { // Ensure that the separator is one character, despite that it's defined as a C string. static_assert(sizeof(AUDIO_PARAMETER_VALUE_LIST_SEPARATOR) == 2); std::vector<std::string> halFormats = - util::splitString(halListValue.string(), AUDIO_PARAMETER_VALUE_LIST_SEPARATOR[0]); + splitString(halListValue.string(), AUDIO_PARAMETER_VALUE_LIST_SEPARATOR[0]); hidl_vec<AudioFormat> formats; (void)HidlUtils::audioFormatsFromHal(halFormats, &formats); std::vector<AudioProfile> tempProfiles; @@ -235,7 +237,7 @@ Return<void> Stream::getSupportedProfiles(getSupportedProfiles_cb _hidl_cb) { result = getParam(AudioParameter::keyStreamSupportedSamplingRates, &halListValue, context); if (result != Result::OK) break; std::vector<std::string> halSampleRates = - util::splitString(halListValue.string(), AUDIO_PARAMETER_VALUE_LIST_SEPARATOR[0]); + splitString(halListValue.string(), AUDIO_PARAMETER_VALUE_LIST_SEPARATOR[0]); hidl_vec<uint32_t> sampleRates; sampleRates.resize(halSampleRates.size()); for (size_t i = 0; i < sampleRates.size(); ++i) { @@ -245,7 +247,7 @@ Return<void> Stream::getSupportedProfiles(getSupportedProfiles_cb _hidl_cb) { result = getParam(AudioParameter::keyStreamSupportedChannels, &halListValue, context); if (result != Result::OK) break; std::vector<std::string> halChannelMasks = - util::splitString(halListValue.string(), AUDIO_PARAMETER_VALUE_LIST_SEPARATOR[0]); + splitString(halListValue.string(), AUDIO_PARAMETER_VALUE_LIST_SEPARATOR[0]); hidl_vec<AudioChannelMask> channelMasks; (void)HidlUtils::audioChannelMasksFromHal(halChannelMasks, &channelMasks); // Create a profile. diff --git a/audio/core/all-versions/default/StreamIn.cpp b/audio/core/all-versions/default/StreamIn.cpp index a6735546f3..2c5e9f1521 100644 --- a/audio/core/all-versions/default/StreamIn.cpp +++ b/audio/core/all-versions/default/StreamIn.cpp @@ -478,87 +478,70 @@ Return<void> StreamIn::debug(const hidl_handle& fd, const hidl_vec<hidl_string>& } #if MAJOR_VERSION >= 4 - -record_track_metadata StreamIn::convertRecordTrackMetadata( - const RecordTrackMetadata& trackMetadata) { - record_track_metadata halTrackMetadata = {.gain = trackMetadata.gain}; - (void)HidlUtils::audioSourceToHal(trackMetadata.source, &halTrackMetadata.source); -#if MAJOR_VERSION >= 5 - if (trackMetadata.destination.getDiscriminator() == - RecordTrackMetadata::Destination::hidl_discriminator::device) { - (void)deviceAddressToHal(trackMetadata.destination.device(), &halTrackMetadata.dest_device, - halTrackMetadata.dest_device_address); - } -#endif - return halTrackMetadata; -} - -void StreamIn::doUpdateSinkMetadata(const SinkMetadata& sinkMetadata) { +Result StreamIn::doUpdateSinkMetadata(const SinkMetadata& sinkMetadata) { std::vector<record_track_metadata> halTracks; - halTracks.reserve(sinkMetadata.tracks.size()); - for (auto& metadata : sinkMetadata.tracks) { - halTracks.push_back(convertRecordTrackMetadata(metadata)); +#if MAJOR_VERSION <= 6 + (void)sinkMetadataToHal(sinkMetadata, &halTracks); +#else + // Validate whether a conversion to V7 is possible. This is needed + // to have a consistent behavior of the HAL regardless of the API + // version of the legacy HAL (and also to be consistent with openInputStream). + std::vector<record_track_metadata_v7> halTracksV7; + if (status_t status = sinkMetadataToHalV7(sinkMetadata, &halTracksV7); status == NO_ERROR) { + halTracks.reserve(halTracksV7.size()); + for (auto metadata_v7 : halTracksV7) { + halTracks.push_back(std::move(metadata_v7.base)); + } + } else { + return Stream::analyzeStatus("sinkMetadataToHal", status); } +#endif // MAJOR_VERSION <= 6 const sink_metadata_t halMetadata = { .track_count = halTracks.size(), .tracks = halTracks.data(), }; mStream->update_sink_metadata(mStream, &halMetadata); + return Result::OK; } #if MAJOR_VERSION >= 7 -record_track_metadata_v7 StreamIn::convertRecordTrackMetadataV7( - const RecordTrackMetadata& trackMetadata) { - record_track_metadata_v7 halTrackMetadata; - halTrackMetadata.base = convertRecordTrackMetadata(trackMetadata); - (void)HidlUtils::audioChannelMaskToHal(trackMetadata.channelMask, - &halTrackMetadata.channel_mask); - std::string halTags; - for (const auto& tag : trackMetadata.tags) { - if (&tag != &trackMetadata.tags[0]) { - halTags += HidlUtils::sAudioTagSeparator; - } - halTags += tag.c_str(); - } - strncpy(halTrackMetadata.tags, halTags.c_str(), AUDIO_ATTRIBUTES_TAGS_MAX_SIZE); - return halTrackMetadata; -} - -void StreamIn::doUpdateSinkMetadataV7(const SinkMetadata& sinkMetadata) { +Result StreamIn::doUpdateSinkMetadataV7(const SinkMetadata& sinkMetadata) { std::vector<record_track_metadata_v7> halTracks; - halTracks.reserve(sinkMetadata.tracks.size()); - for (auto& metadata : sinkMetadata.tracks) { - halTracks.push_back(convertRecordTrackMetadataV7(metadata)); + if (status_t status = sinkMetadataToHalV7(sinkMetadata, &halTracks); status != NO_ERROR) { + return Stream::analyzeStatus("sinkMetadataToHal", status); } const sink_metadata_v7_t halMetadata = { .track_count = halTracks.size(), .tracks = halTracks.data(), }; mStream->update_sink_metadata_v7(mStream, &halMetadata); + return Result::OK; } #endif // MAJOR_VERSION >= 7 +#if MAJOR_VERSION <= 6 Return<void> StreamIn::updateSinkMetadata(const SinkMetadata& sinkMetadata) { -#if MAJOR_VERSION < 7 if (mStream->update_sink_metadata == nullptr) { return Void(); // not supported by the HAL } - doUpdateSinkMetadata(sinkMetadata); -#else + (void)doUpdateSinkMetadata(sinkMetadata); + return Void(); +} +#elif MAJOR_VERSION >= 7 +Return<Result> StreamIn::updateSinkMetadata(const SinkMetadata& sinkMetadata) { if (mDevice->version() < AUDIO_DEVICE_API_VERSION_3_2) { if (mStream->update_sink_metadata == nullptr) { - return Void(); // not supported by the HAL + return Result::NOT_SUPPORTED; } - doUpdateSinkMetadata(sinkMetadata); + return doUpdateSinkMetadata(sinkMetadata); } else { if (mStream->update_sink_metadata_v7 == nullptr) { - return Void(); // not supported by the HAL + return Result::NOT_SUPPORTED; } - doUpdateSinkMetadataV7(sinkMetadata); + return doUpdateSinkMetadataV7(sinkMetadata); } -#endif // MAJOR_VERSION < 7 - return Void(); } +#endif Return<void> StreamIn::getActiveMicrophones(getActiveMicrophones_cb _hidl_cb) { Result retval = Result::NOT_SUPPORTED; diff --git a/audio/core/all-versions/default/StreamOut.cpp b/audio/core/all-versions/default/StreamOut.cpp index 2451b9eb3a..357fd941bd 100644 --- a/audio/core/all-versions/default/StreamOut.cpp +++ b/audio/core/all-versions/default/StreamOut.cpp @@ -17,6 +17,7 @@ #define LOG_TAG "StreamOutHAL" #include "core/default/StreamOut.h" +#include "core/default/Conversions.h" #include "core/default/Util.h" //#define LOG_NDEBUG 0 @@ -162,7 +163,7 @@ StreamOut::~StreamOut() { status_t status = EventFlag::deleteEventFlag(&mEfGroup); ALOGE_IF(status, "write MQ event flag deletion error: %s", strerror(-status)); } - mCallback.clear(); + mCallback = nullptr; #if MAJOR_VERSION <= 5 mDevice->closeOutputStream(mStream); // Closing the output stream in the HAL waits for the callback to finish, @@ -461,7 +462,7 @@ Return<Result> StreamOut::setCallback(const sp<IStreamOutCallback>& callback) { Return<Result> StreamOut::clearCallback() { if (mStream->set_callback == NULL) return Result::NOT_SUPPORTED; - mCallback.clear(); + mCallback = nullptr; return Result::OK; } @@ -476,7 +477,7 @@ int StreamOut::asyncCallback(stream_callback_event_t event, void*, void* cookie) // It's correct to hold an sp<> to callback because the reference // in the StreamOut instance can be cleared in the meantime. There is // no difference on which thread to run IStreamOutCallback's destructor. - sp<IStreamOutCallback> callback = self->mCallback; + sp<IStreamOutCallback> callback = self->mCallback.load(); if (callback.get() == nullptr) return 0; ALOGV("asyncCallback() event %d", event); Return<void> result; @@ -585,81 +586,70 @@ Return<void> StreamOut::debug(const hidl_handle& fd, const hidl_vec<hidl_string> } #if MAJOR_VERSION >= 4 -playback_track_metadata StreamOut::convertPlaybackTrackMetadata( - const PlaybackTrackMetadata& trackMetadata) { - playback_track_metadata_t halTrackMetadata = {.gain = trackMetadata.gain}; - (void)HidlUtils::audioUsageToHal(trackMetadata.usage, &halTrackMetadata.usage); - (void)HidlUtils::audioContentTypeToHal(trackMetadata.contentType, - &halTrackMetadata.content_type); - return halTrackMetadata; -} - -void StreamOut::doUpdateSourceMetadata(const SourceMetadata& sourceMetadata) { +Result StreamOut::doUpdateSourceMetadata(const SourceMetadata& sourceMetadata) { std::vector<playback_track_metadata_t> halTracks; - halTracks.reserve(sourceMetadata.tracks.size()); - for (auto& metadata : sourceMetadata.tracks) { - halTracks.push_back(convertPlaybackTrackMetadata(metadata)); +#if MAJOR_VERSION <= 6 + (void)sourceMetadataToHal(sourceMetadata, &halTracks); +#else + // Validate whether a conversion to V7 is possible. This is needed + // to have a consistent behavior of the HAL regardless of the API + // version of the legacy HAL (and also to be consistent with openOutputStream). + std::vector<playback_track_metadata_v7> halTracksV7; + if (status_t status = sourceMetadataToHalV7(sourceMetadata, &halTracksV7); status == NO_ERROR) { + halTracks.reserve(halTracksV7.size()); + for (auto metadata_v7 : halTracksV7) { + halTracks.push_back(std::move(metadata_v7.base)); + } + } else { + return Stream::analyzeStatus("sourceMetadataToHal", status); } +#endif // MAJOR_VERSION <= 6 const source_metadata_t halMetadata = { .track_count = halTracks.size(), .tracks = halTracks.data(), }; mStream->update_source_metadata(mStream, &halMetadata); + return Result::OK; } #if MAJOR_VERSION >= 7 -playback_track_metadata_v7 StreamOut::convertPlaybackTrackMetadataV7( - const PlaybackTrackMetadata& trackMetadata) { - playback_track_metadata_v7 halTrackMetadata; - halTrackMetadata.base = convertPlaybackTrackMetadata(trackMetadata); - (void)HidlUtils::audioChannelMaskToHal(trackMetadata.channelMask, - &halTrackMetadata.channel_mask); - std::string halTags; - for (const auto& tag : trackMetadata.tags) { - if (&tag != &trackMetadata.tags[0]) { - halTags += HidlUtils::sAudioTagSeparator; - } - halTags += tag.c_str(); - } - strncpy(halTrackMetadata.tags, halTags.c_str(), AUDIO_ATTRIBUTES_TAGS_MAX_SIZE); - return halTrackMetadata; -} - -void StreamOut::doUpdateSourceMetadataV7(const SourceMetadata& sourceMetadata) { +Result StreamOut::doUpdateSourceMetadataV7(const SourceMetadata& sourceMetadata) { std::vector<playback_track_metadata_v7> halTracks; - halTracks.reserve(sourceMetadata.tracks.size()); - for (auto& metadata : sourceMetadata.tracks) { - halTracks.push_back(convertPlaybackTrackMetadataV7(metadata)); + if (status_t status = sourceMetadataToHalV7(sourceMetadata, &halTracks); status != NO_ERROR) { + return Stream::analyzeStatus("sourceMetadataToHal", status); } const source_metadata_v7_t halMetadata = { .track_count = halTracks.size(), .tracks = halTracks.data(), }; mStream->update_source_metadata_v7(mStream, &halMetadata); + return Result::OK; } #endif // MAJOR_VERSION >= 7 +#if MAJOR_VERSION <= 6 Return<void> StreamOut::updateSourceMetadata(const SourceMetadata& sourceMetadata) { -#if MAJOR_VERSION < 7 if (mStream->update_source_metadata == nullptr) { return Void(); // not supported by the HAL } - doUpdateSourceMetadata(sourceMetadata); -#else + (void)doUpdateSourceMetadata(sourceMetadata); + return Void(); +} +#elif MAJOR_VERSION >= 7 +Return<Result> StreamOut::updateSourceMetadata(const SourceMetadata& sourceMetadata) { if (mDevice->version() < AUDIO_DEVICE_API_VERSION_3_2) { if (mStream->update_source_metadata == nullptr) { - return Void(); // not supported by the HAL + return Result::NOT_SUPPORTED; } - doUpdateSourceMetadata(sourceMetadata); + return doUpdateSourceMetadata(sourceMetadata); } else { if (mStream->update_source_metadata_v7 == nullptr) { - return Void(); // not supported by the HAL + return Result::NOT_SUPPORTED; } - doUpdateSourceMetadataV7(sourceMetadata); + return doUpdateSourceMetadataV7(sourceMetadata); } -#endif // MAJOR_VERSION < 7 - return Void(); } +#endif Return<Result> StreamOut::selectPresentation(int32_t /*presentationId*/, int32_t /*programId*/) { return Result::NOT_SUPPORTED; // TODO: propagate to legacy @@ -708,7 +698,7 @@ Return<Result> StreamOut::setEventCallback(const sp<IStreamOutEventCallback>& ca // static int StreamOut::asyncEventCallback(stream_event_callback_type_t event, void* param, void* cookie) { StreamOut* self = reinterpret_cast<StreamOut*>(cookie); - sp<IStreamOutEventCallback> eventCallback = self->mEventCallback; + sp<IStreamOutEventCallback> eventCallback = self->mEventCallback.load(); if (eventCallback.get() == nullptr) return 0; ALOGV("%s event %d", __func__, event); Return<void> result; diff --git a/audio/core/all-versions/default/include/core/default/Conversions.h b/audio/core/all-versions/default/include/core/default/Conversions.h index 2372771b85..61720d5303 100644 --- a/audio/core/all-versions/default/include/core/default/Conversions.h +++ b/audio/core/all-versions/default/include/core/default/Conversions.h @@ -35,12 +35,6 @@ using ::android::hardware::hidl_vec; using namespace ::android::hardware::audio::common::CPP_VERSION; using namespace ::android::hardware::audio::CPP_VERSION; -#if MAJOR_VERSION <= 6 -// Temporary version for compatibility with forks of the default implementation. -// Will be removed, do not use! -std::string deviceAddressToHal(const DeviceAddress& address); -#endif - status_t deviceAddressToHal(const DeviceAddress& device, audio_devices_t* halDeviceType, char* halDeviceAddress); status_t deviceAddressFromHal(audio_devices_t halDeviceType, const char* halDeviceAddress, @@ -49,6 +43,10 @@ status_t deviceAddressFromHal(audio_devices_t halDeviceType, const char* halDevi #if MAJOR_VERSION >= 4 bool halToMicrophoneCharacteristics(MicrophoneInfo* pDst, const struct audio_microphone_characteristic_t& src); +status_t sinkMetadataToHal(const SinkMetadata& sinkMetadata, + std::vector<record_track_metadata>* halTracks); +status_t sourceMetadataToHal(const SourceMetadata& sourceMetadata, + std::vector<playback_track_metadata_t>* halTracks); #endif #if MAJOR_VERSION <= 6 @@ -69,6 +67,11 @@ inline bool audioOutputFlagsToHal(AudioOutputFlags flags, audio_output_flags_t* #else bool audioInputFlagsToHal(const hidl_vec<AudioInOutFlag>& flags, audio_input_flags_t* halFlags); bool audioOutputFlagsToHal(const hidl_vec<AudioInOutFlag>& flags, audio_output_flags_t* halFlags); +// Overloading isn't convenient when passing a nullptr. +status_t sinkMetadataToHalV7(const SinkMetadata& sinkMetadata, + std::vector<record_track_metadata_v7_t>* halTracks); +status_t sourceMetadataToHalV7(const SourceMetadata& sourceMetadata, + std::vector<playback_track_metadata_v7_t>* halTracks); #endif } // namespace implementation diff --git a/audio/core/all-versions/default/include/core/default/StreamIn.h b/audio/core/all-versions/default/include/core/default/StreamIn.h index 512da559bf..651b3a6ae1 100644 --- a/audio/core/all-versions/default/include/core/default/StreamIn.h +++ b/audio/core/all-versions/default/include/core/default/StreamIn.h @@ -114,9 +114,13 @@ struct StreamIn : public IStreamIn { Return<void> createMmapBuffer(int32_t minSizeFrames, createMmapBuffer_cb _hidl_cb) override; Return<void> getMmapPosition(getMmapPosition_cb _hidl_cb) override; #if MAJOR_VERSION >= 4 +#if MAJOR_VERSION <= 6 Return<void> updateSinkMetadata(const SinkMetadata& sinkMetadata) override; - Return<void> getActiveMicrophones(getActiveMicrophones_cb _hidl_cb) override; +#else + Return<Result> updateSinkMetadata(const SinkMetadata& sinkMetadata) override; #endif + Return<void> getActiveMicrophones(getActiveMicrophones_cb _hidl_cb) override; +#endif // MAJOR_VERSION >= 4 #if MAJOR_VERSION >= 5 Return<Result> setMicrophoneDirection(MicrophoneDirection direction) override; Return<Result> setMicrophoneFieldDimension(float zoom) override; @@ -126,13 +130,11 @@ struct StreamIn : public IStreamIn { private: #if MAJOR_VERSION >= 4 - record_track_metadata convertRecordTrackMetadata(const RecordTrackMetadata& trackMetadata); - void doUpdateSinkMetadata(const SinkMetadata& sinkMetadata); + Result doUpdateSinkMetadata(const SinkMetadata& sinkMetadata); #if MAJOR_VERSION >= 7 - record_track_metadata_v7 convertRecordTrackMetadataV7(const RecordTrackMetadata& trackMetadata); - void doUpdateSinkMetadataV7(const SinkMetadata& sinkMetadata); -#endif + Result doUpdateSinkMetadataV7(const SinkMetadata& sinkMetadata); #endif +#endif // MAJOR_VERSION >= 4 const sp<Device> mDevice; audio_stream_in_t* mStream; diff --git a/audio/core/all-versions/default/include/core/default/StreamOut.h b/audio/core/all-versions/default/include/core/default/StreamOut.h index 8da940d846..02d8e8962d 100644 --- a/audio/core/all-versions/default/include/core/default/StreamOut.h +++ b/audio/core/all-versions/default/include/core/default/StreamOut.h @@ -29,6 +29,7 @@ #include <fmq/MessageQueue.h> #include <hidl/MQDescriptor.h> #include <hidl/Status.h> +#include <mediautils/Synchronization.h> #include <utils/Thread.h> namespace android { @@ -123,9 +124,13 @@ struct StreamOut : public IStreamOut { Return<void> createMmapBuffer(int32_t minSizeFrames, createMmapBuffer_cb _hidl_cb) override; Return<void> getMmapPosition(getMmapPosition_cb _hidl_cb) override; #if MAJOR_VERSION >= 4 - Return<void> updateSourceMetadata(const SourceMetadata& sourceMetadata) override; Return<Result> selectPresentation(int32_t presentationId, int32_t programId) override; +#if MAJOR_VERSION <= 6 + Return<void> updateSourceMetadata(const SourceMetadata& sourceMetadata) override; +#else + Return<Result> updateSourceMetadata(const SourceMetadata& sourceMetadata) override; #endif +#endif // MAJOR_VERSION >= 4 #if MAJOR_VERSION >= 6 Return<void> getDualMonoMode(getDualMonoMode_cb _hidl_cb) override; Return<Result> setDualMonoMode(DualMonoMode mode) override; @@ -144,23 +149,19 @@ struct StreamOut : public IStreamOut { private: #if MAJOR_VERSION >= 4 - playback_track_metadata convertPlaybackTrackMetadata( - const PlaybackTrackMetadata& trackMetadata); - void doUpdateSourceMetadata(const SourceMetadata& sourceMetadata); + Result doUpdateSourceMetadata(const SourceMetadata& sourceMetadata); #if MAJOR_VERSION >= 7 - playback_track_metadata_v7 convertPlaybackTrackMetadataV7( - const PlaybackTrackMetadata& trackMetadata); - void doUpdateSourceMetadataV7(const SourceMetadata& sourceMetadata); -#endif + Result doUpdateSourceMetadataV7(const SourceMetadata& sourceMetadata); #endif +#endif // MAJOR_VERSION >= 4 const sp<Device> mDevice; audio_stream_out_t* mStream; const sp<Stream> mStreamCommon; const sp<StreamMmap<audio_stream_out_t>> mStreamMmap; - sp<IStreamOutCallback> mCallback; // Callback for non-blocking write and drain + mediautils::atomic_sp<IStreamOutCallback> mCallback; // for non-blocking write and drain #if MAJOR_VERSION >= 6 - sp<IStreamOutEventCallback> mEventCallback; + mediautils::atomic_sp<IStreamOutEventCallback> mEventCallback; #endif std::unique_ptr<CommandMQ> mCommandMQ; std::unique_ptr<DataMQ> mDataMQ; diff --git a/audio/core/all-versions/default/include/core/default/Util.h b/audio/core/all-versions/default/include/core/default/Util.h index 3d629cdefd..78ae03eab2 100644 --- a/audio/core/all-versions/default/include/core/default/Util.h +++ b/audio/core/all-versions/default/include/core/default/Util.h @@ -20,8 +20,6 @@ #include PATH(android/hardware/audio/FILE_VERSION/types.h) #include <algorithm> -#include <sstream> -#include <string> #include <vector> #include <system/audio.h> @@ -72,16 +70,6 @@ static inline Result analyzeStatus(const char* className, const char* funcName, return analyzeStatus(status); } -static inline std::vector<std::string> splitString(const std::string& s, char separator) { - std::istringstream iss(s); - std::string t; - std::vector<std::string> result; - while (std::getline(iss, t, separator)) { - result.push_back(std::move(t)); - } - return result; -} - } // namespace util } // namespace implementation } // namespace CPP_VERSION diff --git a/audio/core/all-versions/vts/functional/4.0/AudioPrimaryHidlHalTest.cpp b/audio/core/all-versions/vts/functional/4.0/AudioPrimaryHidlHalTest.cpp index c53ae8dfb4..5076dcfdb0 100644 --- a/audio/core/all-versions/vts/functional/4.0/AudioPrimaryHidlHalTest.cpp +++ b/audio/core/all-versions/vts/functional/4.0/AudioPrimaryHidlHalTest.cpp @@ -237,36 +237,45 @@ TEST_IO_STREAM(GetHwAvSync, "Get hardware sync can not fail", checkGetHwAVSync(g TEST_P(InputStreamTest, updateSinkMetadata) { doc::test("The HAL should not crash on metadata change"); - #if MAJOR_VERSION <= 6 hidl_enum_range<AudioSource> range; -#elif MAJOR_VERSION >= 7 - xsdc_enum_range<android::audio::policy::configuration::V7_0::AudioSource> range; -#endif // Test all possible track configuration for (auto source : range) { for (float volume : {0.0, 0.5, 1.0}) { -#if MAJOR_VERSION <= 6 const SinkMetadata metadata = {{{.source = source, .gain = volume}}}; + ASSERT_OK(stream->updateSinkMetadata(metadata)) + << "source=" << toString(source) << ", volume=" << volume; + } + } + + // Do not test concurrent capture as this is not officially supported + + // Set no metadata as if all stream track had stopped + ASSERT_OK(stream->updateSinkMetadata({})); + // Restore initial + ASSERT_OK(stream->updateSinkMetadata(initMetadata)); + #elif MAJOR_VERSION >= 7 + xsdc_enum_range<android::audio::policy::configuration::V7_0::AudioSource> range; + // Test all possible track configuration + for (auto source : range) { + for (float volume : {0.0, 0.5, 1.0}) { const SinkMetadata metadata = { {{.source = toString(source), .gain = volume, .tags = {}, .channelMask = toString(xsd::AudioChannelMask::AUDIO_CHANNEL_IN_MONO)}}}; -#endif - ASSERT_OK(stream->updateSinkMetadata(metadata)) - << "source=" << toString(source) << ", volume=" << volume; + ASSERT_RESULT(okOrNotSupported, stream->updateSinkMetadata(metadata)) + << "source=" << toString(source) << ", volume=" << volume; } } - // Do not test concurrent capture as this is not officially supported // Set no metadata as if all stream track had stopped - ASSERT_OK(stream->updateSinkMetadata({})); - + ASSERT_RESULT(okOrNotSupported, stream->updateSinkMetadata({})); // Restore initial - ASSERT_OK(stream->updateSinkMetadata(initMetadata)); + ASSERT_RESULT(okOrNotSupported, stream->updateSinkMetadata(initMetadata)); +#endif } TEST_P(OutputStreamTest, SelectPresentation) { @@ -276,44 +285,55 @@ TEST_P(OutputStreamTest, SelectPresentation) { TEST_P(OutputStreamTest, updateSourceMetadata) { doc::test("The HAL should not crash on metadata change"); - #if MAJOR_VERSION <= 6 hidl_enum_range<AudioUsage> usageRange; hidl_enum_range<AudioContentType> contentRange; -#elif MAJOR_VERSION >= 7 - xsdc_enum_range<android::audio::policy::configuration::V7_0::AudioUsage> usageRange; - xsdc_enum_range<android::audio::policy::configuration::V7_0::AudioContentType> contentRange; -#endif // Test all possible track configuration for (auto usage : usageRange) { for (auto content : contentRange) { for (float volume : {0.0, 0.5, 1.0}) { -#if MAJOR_VERSION <= 6 const SourceMetadata metadata = {{{usage, content, volume}}}; -#elif MAJOR_VERSION >= 7 - const SourceMetadata metadata = { - {{toString(usage), - toString(content), - {} /* tags */, - toString(xsd::AudioChannelMask::AUDIO_CHANNEL_OUT_STEREO), - volume}}}; -#endif ASSERT_OK(stream->updateSourceMetadata(metadata)) << "usage=" << toString(usage) << ", content=" << toString(content) << ", volume=" << volume; } } } - - // clang-format off // Set many track of different configuration + // clang-format off ASSERT_OK(stream->updateSourceMetadata( -#if MAJOR_VERSION <= 6 {{{AudioUsage::MEDIA, AudioContentType::MUSIC, 0.1}, {AudioUsage::VOICE_COMMUNICATION, AudioContentType::SPEECH, 1.0}, {AudioUsage::ALARM, AudioContentType::SONIFICATION, 0.0}, {AudioUsage::ASSISTANT, AudioContentType::UNKNOWN, 0.3}}} + )); + // clang-format on + // Set no metadata as if all stream track had stopped + ASSERT_OK(stream->updateSourceMetadata({})); + // Restore initial + ASSERT_OK(stream->updateSourceMetadata(initMetadata)); #elif MAJOR_VERSION >= 7 + xsdc_enum_range<android::audio::policy::configuration::V7_0::AudioUsage> usageRange; + xsdc_enum_range<android::audio::policy::configuration::V7_0::AudioContentType> contentRange; + // Test all possible track configuration + for (auto usage : usageRange) { + for (auto content : contentRange) { + for (float volume : {0.0, 0.5, 1.0}) { + const SourceMetadata metadata = { + {{toString(usage), + toString(content), + {} /* tags */, + toString(xsd::AudioChannelMask::AUDIO_CHANNEL_OUT_STEREO), + volume}}}; + ASSERT_RESULT(okOrNotSupported, stream->updateSourceMetadata(metadata)) + << "usage=" << toString(usage) << ", content=" << toString(content) + << ", volume=" << volume; + } + } + } + // Set many track of different configuration + // clang-format off + ASSERT_RESULT(okOrNotSupported, stream->updateSourceMetadata( {{{toString(xsd::AudioUsage::AUDIO_USAGE_MEDIA), toString(xsd::AudioContentType::AUDIO_CONTENT_TYPE_MUSIC), {}, @@ -334,15 +354,13 @@ TEST_P(OutputStreamTest, updateSourceMetadata) { {}, toString(xsd::AudioChannelMask::AUDIO_CHANNEL_OUT_MONO), 0.3}}} -#endif )); // clang-format on - // Set no metadata as if all stream track had stopped - ASSERT_OK(stream->updateSourceMetadata({})); - + ASSERT_RESULT(okOrNotSupported, stream->updateSourceMetadata({})); // Restore initial - ASSERT_OK(stream->updateSourceMetadata(initMetadata)); + ASSERT_RESULT(okOrNotSupported, stream->updateSourceMetadata(initMetadata)); +#endif } TEST_P(AudioPrimaryHidlTest, setMode) { diff --git a/audio/core/all-versions/vts/functional/6.0/AudioPrimaryHidlHalTest.cpp b/audio/core/all-versions/vts/functional/6.0/AudioPrimaryHidlHalTest.cpp index 5ad38de412..0ebe4c24ff 100644 --- a/audio/core/all-versions/vts/functional/6.0/AudioPrimaryHidlHalTest.cpp +++ b/audio/core/all-versions/vts/functional/6.0/AudioPrimaryHidlHalTest.cpp @@ -18,142 +18,131 @@ #include "5.0/AudioPrimaryHidlHalTest.cpp" #if MAJOR_VERSION <= 6 -const std::vector<DeviceConfigParameter>& getOutputDeviceConfigParameters() { - static std::vector<DeviceConfigParameter> parameters = [] { - std::vector<DeviceConfigParameter> result; - for (const auto& device : getDeviceParameters()) { - auto module = - getCachedPolicyConfig().getModuleFromName(std::get<PARAM_DEVICE_NAME>(device)); - for (const auto& ioProfile : module->getOutputProfiles()) { - for (const auto& profile : ioProfile->getAudioProfiles()) { - const auto& channels = profile->getChannels(); - const auto& sampleRates = profile->getSampleRates(); - auto configs = ConfigHelper::combineAudioConfig( - std::vector<audio_channel_mask_t>(channels.begin(), channels.end()), - std::vector<uint32_t>(sampleRates.begin(), sampleRates.end()), - profile->getFormat()); - auto flags = ioProfile->getFlags(); - for (auto& config : configs) { - // Some combinations of flags declared in the config file require special - // treatment. - if (flags & AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD) { - config.offloadInfo.sampleRateHz = config.sampleRateHz; - config.offloadInfo.channelMask = config.channelMask; - config.offloadInfo.format = config.format; - config.offloadInfo.streamType = AudioStreamType::MUSIC; - config.offloadInfo.bitRatePerSecond = 320; - config.offloadInfo.durationMicroseconds = -1; - config.offloadInfo.bitWidth = 16; - config.offloadInfo.bufferSize = 256; // arbitrary value - config.offloadInfo.usage = AudioUsage::MEDIA; - result.emplace_back(device, config, - AudioOutputFlag(AudioOutputFlag::COMPRESS_OFFLOAD | - AudioOutputFlag::DIRECT)); - } else { - if (flags & AUDIO_OUTPUT_FLAG_PRIMARY) { // ignore the flag - flags &= ~AUDIO_OUTPUT_FLAG_PRIMARY; - } - result.emplace_back(device, config, AudioOutputFlag(flags)); +static std::vector<DeviceConfigParameter> generateOutputDeviceConfigParameters( + bool oneProfilePerDevice) { + std::vector<DeviceConfigParameter> result; + for (const auto& device : getDeviceParameters()) { + auto module = + getCachedPolicyConfig().getModuleFromName(std::get<PARAM_DEVICE_NAME>(device)); + for (const auto& ioProfile : module->getOutputProfiles()) { + for (const auto& profile : ioProfile->getAudioProfiles()) { + const auto& channels = profile->getChannels(); + const auto& sampleRates = profile->getSampleRates(); + auto configs = ConfigHelper::combineAudioConfig( + std::vector<audio_channel_mask_t>(channels.begin(), channels.end()), + std::vector<uint32_t>(sampleRates.begin(), sampleRates.end()), + profile->getFormat()); + auto flags = ioProfile->getFlags(); + for (auto& config : configs) { + // Some combinations of flags declared in the config file require special + // treatment. + if (flags & AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD) { + config.offloadInfo.sampleRateHz = config.sampleRateHz; + config.offloadInfo.channelMask = config.channelMask; + config.offloadInfo.format = config.format; + config.offloadInfo.streamType = AudioStreamType::MUSIC; + config.offloadInfo.bitRatePerSecond = 320; + config.offloadInfo.durationMicroseconds = -1; + config.offloadInfo.bitWidth = 16; + config.offloadInfo.bufferSize = 256; // arbitrary value + config.offloadInfo.usage = AudioUsage::MEDIA; + result.emplace_back(device, config, + AudioOutputFlag(AudioOutputFlag::COMPRESS_OFFLOAD | + AudioOutputFlag::DIRECT)); + } else { + if (flags & AUDIO_OUTPUT_FLAG_PRIMARY) { // ignore the flag + flags &= ~AUDIO_OUTPUT_FLAG_PRIMARY; } + result.emplace_back(device, config, AudioOutputFlag(flags)); } + if (oneProfilePerDevice) break; } + if (oneProfilePerDevice) break; } + if (oneProfilePerDevice) break; } - return result; - }(); + } + return result; +} + +const std::vector<DeviceConfigParameter>& getOutputDeviceConfigParameters() { + static std::vector<DeviceConfigParameter> parameters = + generateOutputDeviceConfigParameters(false); return parameters; } -const std::vector<DeviceConfigParameter>& getInputDeviceConfigParameters() { - static std::vector<DeviceConfigParameter> parameters = [] { - std::vector<DeviceConfigParameter> result; - for (const auto& device : getDeviceParameters()) { - auto module = - getCachedPolicyConfig().getModuleFromName(std::get<PARAM_DEVICE_NAME>(device)); - for (const auto& ioProfile : module->getInputProfiles()) { - for (const auto& profile : ioProfile->getAudioProfiles()) { - const auto& channels = profile->getChannels(); - const auto& sampleRates = profile->getSampleRates(); - auto configs = ConfigHelper::combineAudioConfig( - std::vector<audio_channel_mask_t>(channels.begin(), channels.end()), - std::vector<uint32_t>(sampleRates.begin(), sampleRates.end()), - profile->getFormat()); - for (const auto& config : configs) { - result.emplace_back(device, config, AudioInputFlag(ioProfile->getFlags())); - } +const std::vector<DeviceConfigParameter>& getOutputDeviceSingleConfigParameters() { + static std::vector<DeviceConfigParameter> parameters = + generateOutputDeviceConfigParameters(true); + return parameters; +} + +static std::vector<DeviceConfigParameter> generateInputDeviceConfigParameters( + bool oneProfilePerDevice) { + std::vector<DeviceConfigParameter> result; + for (const auto& device : getDeviceParameters()) { + auto module = + getCachedPolicyConfig().getModuleFromName(std::get<PARAM_DEVICE_NAME>(device)); + for (const auto& ioProfile : module->getInputProfiles()) { + for (const auto& profile : ioProfile->getAudioProfiles()) { + const auto& channels = profile->getChannels(); + const auto& sampleRates = profile->getSampleRates(); + auto configs = ConfigHelper::combineAudioConfig( + std::vector<audio_channel_mask_t>(channels.begin(), channels.end()), + std::vector<uint32_t>(sampleRates.begin(), sampleRates.end()), + profile->getFormat()); + for (const auto& config : configs) { + result.emplace_back(device, config, AudioInputFlag(ioProfile->getFlags())); + if (oneProfilePerDevice) break; } + if (oneProfilePerDevice) break; } + if (oneProfilePerDevice) break; } - return result; - }(); + } + return result; +} + +const std::vector<DeviceConfigParameter>& getInputDeviceConfigParameters() { + static std::vector<DeviceConfigParameter> parameters = + generateInputDeviceConfigParameters(false); + return parameters; +} + +const std::vector<DeviceConfigParameter>& getInputDeviceSingleConfigParameters() { + static std::vector<DeviceConfigParameter> parameters = + generateInputDeviceConfigParameters(true); return parameters; } #endif // MAJOR_VERSION <= 6 -TEST_P(AudioHidlDeviceTest, CloseDeviceWithOpenedOutputStreams) { - doc::test("Verify that a device can't be closed if there are streams opened"); -#if MAJOR_VERSION <= 6 - DeviceAddress address{.device = AudioDevice::OUT_DEFAULT}; - SourceMetadata initMetadata = {{{AudioUsage::MEDIA, AudioContentType::MUSIC, 1 /* gain */}}}; - auto flags = hidl_bitfield<AudioOutputFlag>(AudioOutputFlag::NONE); -#elif MAJOR_VERSION >= 7 - DeviceAddress address{.deviceType = toString(xsd::AudioDevice::AUDIO_DEVICE_OUT_DEFAULT)}; - SourceMetadata initMetadata = {{{toString(xsd::AudioUsage::AUDIO_USAGE_MEDIA), - toString(xsd::AudioContentType::AUDIO_CONTENT_TYPE_MUSIC), - {} /* tags */, - toString(xsd::AudioChannelMask::AUDIO_CHANNEL_OUT_STEREO), - 1 /* gain */}}}; - hidl_vec<AudioInOutFlag> flags; -#endif - AudioConfig config{}; - sp<IStreamOut> stream; - StreamHelper<IStreamOut> helper(stream); - AudioConfig suggestedConfig{}; - ASSERT_NO_FATAL_FAILURE(helper.open( - [&](AudioIoHandle handle, AudioConfig config, auto cb) { - return getDevice()->openOutputStream(handle, address, config, flags, initMetadata, - cb); - }, - config, &res, &suggestedConfig)); +class SingleConfigOutputStreamTest : public OutputStreamTest {}; +TEST_P(SingleConfigOutputStreamTest, CloseDeviceWithOpenedOutputStreams) { + doc::test("Verify that a device can't be closed if there are output streams opened"); + // Opening of the stream is done in SetUp. ASSERT_RESULT(Result::INVALID_STATE, getDevice()->close()); - ASSERT_NO_FATAL_FAILURE(helper.close(true /*clear*/, &res)); + ASSERT_OK(closeStream(true /*clear*/)); ASSERT_OK(getDevice()->close()); ASSERT_TRUE(resetDevice()); } +INSTANTIATE_TEST_CASE_P(SingleConfigOutputStream, SingleConfigOutputStreamTest, + ::testing::ValuesIn(getOutputDeviceSingleConfigParameters()), + &DeviceConfigParameterToString); +GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(SingleConfigOutputStreamTest); -TEST_P(AudioHidlDeviceTest, CloseDeviceWithOpenedInputStreams) { - doc::test("Verify that a device can't be closed if there are streams opened"); - if (!getCachedPolicyConfig().haveInputProfilesInModule(getDeviceName())) { - GTEST_SKIP() << "Device doesn't have input profiles"; - } -#if MAJOR_VERSION <= 6 - DeviceAddress address{.device = AudioDevice::IN_DEFAULT}; - SinkMetadata initMetadata = {{{.source = AudioSource::MIC, .gain = 1}}}; - auto flags = hidl_bitfield<AudioInputFlag>(AudioInputFlag::NONE); -#elif MAJOR_VERSION >= 7 - DeviceAddress address{.deviceType = toString(xsd::AudioDevice::AUDIO_DEVICE_IN_DEFAULT)}; - SinkMetadata initMetadata = { - {{.source = toString(xsd::AudioSource::AUDIO_SOURCE_MIC), - .gain = 1, - .tags = {}, - .channelMask = toString(xsd::AudioChannelMask::AUDIO_CHANNEL_IN_MONO)}}}; - hidl_vec<AudioInOutFlag> flags; -#endif - AudioConfig config{}; - sp<IStreamIn> stream; - StreamHelper<IStreamIn> helper(stream); - AudioConfig suggestedConfig{}; - ASSERT_NO_FATAL_FAILURE(helper.open( - [&](AudioIoHandle handle, AudioConfig config, auto cb) { - return getDevice()->openInputStream(handle, address, config, flags, initMetadata, - cb); - }, - config, &res, &suggestedConfig)); +class SingleConfigInputStreamTest : public InputStreamTest {}; +TEST_P(SingleConfigInputStreamTest, CloseDeviceWithOpenedInputStreams) { + doc::test("Verify that a device can't be closed if there are input streams opened"); + // Opening of the stream is done in SetUp. ASSERT_RESULT(Result::INVALID_STATE, getDevice()->close()); - ASSERT_NO_FATAL_FAILURE(helper.close(true /*clear*/, &res)); + ASSERT_OK(closeStream(true /*clear*/)); ASSERT_OK(getDevice()->close()); ASSERT_TRUE(resetDevice()); } +INSTANTIATE_TEST_CASE_P(SingleConfigInputStream, SingleConfigInputStreamTest, + ::testing::ValuesIn(getInputDeviceSingleConfigParameters()), + &DeviceConfigParameterToString); +GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(SingleConfigInputStreamTest); TEST_P(AudioPatchHidlTest, UpdatePatchInvalidHandle) { doc::test("Verify that passing an invalid handle to updateAudioPatch is checked"); diff --git a/audio/core/all-versions/vts/functional/7.0/AudioPrimaryHidlHalTest.cpp b/audio/core/all-versions/vts/functional/7.0/AudioPrimaryHidlHalTest.cpp index 6bb7995353..7fca61036d 100644 --- a/audio/core/all-versions/vts/functional/7.0/AudioPrimaryHidlHalTest.cpp +++ b/audio/core/all-versions/vts/functional/7.0/AudioPrimaryHidlHalTest.cpp @@ -25,7 +25,6 @@ static std::vector<AudioConfig> combineAudioConfig(std::vector<xsd::AudioChannel for (auto channelMask : channelMasks) { for (auto sampleRate : sampleRates) { AudioConfig config{}; - // leave offloadInfo to 0 config.base.channelMask = toString(channelMask); config.base.sampleRateHz = sampleRate; config.base.format = format; @@ -35,56 +34,158 @@ static std::vector<AudioConfig> combineAudioConfig(std::vector<xsd::AudioChannel return configs; } +static std::tuple<std::vector<AudioInOutFlag>, bool> generateOutFlags( + const xsd::MixPorts::MixPort& mixPort) { + static const std::vector<AudioInOutFlag> offloadFlags = { + toString(xsd::AudioInOutFlag::AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD), + toString(xsd::AudioInOutFlag::AUDIO_OUTPUT_FLAG_DIRECT)}; + std::vector<AudioInOutFlag> flags; + bool isOffload = false; + if (mixPort.hasFlags()) { + auto xsdFlags = mixPort.getFlags(); + isOffload = std::find(xsdFlags.begin(), xsdFlags.end(), + xsd::AudioInOutFlag::AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD) != + xsdFlags.end(); + if (!isOffload) { + for (auto flag : xsdFlags) { + if (flag != xsd::AudioInOutFlag::AUDIO_OUTPUT_FLAG_PRIMARY) { + flags.push_back(toString(flag)); + } + } + } else { + flags = offloadFlags; + } + } + return {flags, isOffload}; +} + +static AudioOffloadInfo generateOffloadInfo(const AudioConfigBase& base) { + return AudioOffloadInfo{ + .base = base, + .streamType = toString(xsd::AudioStreamType::AUDIO_STREAM_MUSIC), + .usage = toString(xsd::AudioUsage::AUDIO_USAGE_MEDIA), + .bitRatePerSecond = 320, + .durationMicroseconds = -1, + .bitWidth = 16, + .bufferSize = 256 // arbitrary value + }; +} + +static std::vector<DeviceConfigParameter> generateOutputDeviceConfigParameters( + bool oneProfilePerDevice) { + std::vector<DeviceConfigParameter> result; + for (const auto& device : getDeviceParameters()) { + auto module = + getCachedPolicyConfig().getModuleFromName(std::get<PARAM_DEVICE_NAME>(device)); + if (!module || !module->getFirstMixPorts()) break; + for (const auto& mixPort : module->getFirstMixPorts()->getMixPort()) { + if (mixPort.getRole() != xsd::Role::source) continue; // not an output profile + auto [flags, isOffload] = generateOutFlags(mixPort); + for (const auto& profile : mixPort.getProfile()) { + auto configs = combineAudioConfig(profile.getChannelMasks(), + profile.getSamplingRates(), profile.getFormat()); + for (auto& config : configs) { + // Some combinations of flags declared in the config file require special + // treatment. + if (isOffload) { + config.offloadInfo.info(generateOffloadInfo(config.base)); + } + result.emplace_back(device, config, flags); + if (oneProfilePerDevice) break; + } + if (oneProfilePerDevice) break; + } + if (oneProfilePerDevice) break; + } + } + return result; +} + const std::vector<DeviceConfigParameter>& getOutputDeviceConfigParameters() { - static std::vector<DeviceConfigParameter> parameters = [] { + static std::vector<DeviceConfigParameter> parameters = + generateOutputDeviceConfigParameters(false); + return parameters; +} + +const std::vector<DeviceConfigParameter>& getOutputDeviceSingleConfigParameters() { + static std::vector<DeviceConfigParameter> parameters = + generateOutputDeviceConfigParameters(true); + return parameters; +} + +const std::vector<DeviceConfigParameter>& getOutputDeviceInvalidConfigParameters( + bool generateInvalidFlags = true) { + static std::vector<DeviceConfigParameter> parameters = [&] { std::vector<DeviceConfigParameter> result; - const std::vector<AudioInOutFlag> offloadFlags = { - toString(xsd::AudioInOutFlag::AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD), - toString(xsd::AudioInOutFlag::AUDIO_OUTPUT_FLAG_DIRECT)}; for (const auto& device : getDeviceParameters()) { auto module = getCachedPolicyConfig().getModuleFromName(std::get<PARAM_DEVICE_NAME>(device)); if (!module || !module->getFirstMixPorts()) break; + bool hasRegularConfig = false, hasOffloadConfig = false; for (const auto& mixPort : module->getFirstMixPorts()->getMixPort()) { if (mixPort.getRole() != xsd::Role::source) continue; // not an output profile - std::vector<AudioInOutFlag> flags; - bool isOffload = false; - if (mixPort.hasFlags()) { - auto xsdFlags = mixPort.getFlags(); - isOffload = - std::find(xsdFlags.begin(), xsdFlags.end(), - xsd::AudioInOutFlag::AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD) != - xsdFlags.end(); - if (!isOffload) { - for (auto flag : xsdFlags) { - if (flag != xsd::AudioInOutFlag::AUDIO_OUTPUT_FLAG_PRIMARY) { - flags.push_back(toString(flag)); - } + auto [validFlags, isOffload] = generateOutFlags(mixPort); + if ((!isOffload && hasRegularConfig) || (isOffload && hasOffloadConfig)) continue; + for (const auto& profile : mixPort.getProfile()) { + if (!profile.hasFormat() || !profile.hasSamplingRates() || + !profile.hasChannelMasks()) + continue; + AudioConfigBase validBase = { + profile.getFormat(), + static_cast<uint32_t>(profile.getSamplingRates()[0]), + toString(profile.getChannelMasks()[0])}; + { + AudioConfig config{.base = validBase}; + config.base.channelMask = "random_string"; + if (isOffload) { + config.offloadInfo.info(generateOffloadInfo(validBase)); } - } else { - flags = offloadFlags; + result.emplace_back(device, config, validFlags); } - } - for (const auto& profile : mixPort.getProfile()) { - auto configs = - combineAudioConfig(profile.getChannelMasks(), - profile.getSamplingRates(), profile.getFormat()); - for (auto& config : configs) { - // Some combinations of flags declared in the config file require special - // treatment. + { + AudioConfig config{.base = validBase}; + config.base.format = "random_string"; if (isOffload) { - config.offloadInfo.base = config.base; - config.offloadInfo.streamType = - toString(xsd::AudioStreamType::AUDIO_STREAM_MUSIC); - config.offloadInfo.usage = toString(xsd::AudioUsage::AUDIO_USAGE_MEDIA); - config.offloadInfo.bitRatePerSecond = 320; - config.offloadInfo.durationMicroseconds = -1; - config.offloadInfo.bitWidth = 16; - config.offloadInfo.bufferSize = 256; // arbitrary value + config.offloadInfo.info(generateOffloadInfo(validBase)); } + result.emplace_back(device, config, validFlags); + } + if (generateInvalidFlags) { + AudioConfig config{.base = validBase}; + if (isOffload) { + config.offloadInfo.info(generateOffloadInfo(validBase)); + } + std::vector<AudioInOutFlag> flags = {"random_string", ""}; result.emplace_back(device, config, flags); } + if (isOffload) { + { + AudioConfig config{.base = validBase}; + config.offloadInfo.info(generateOffloadInfo(validBase)); + config.offloadInfo.info().base.channelMask = "random_string"; + } + { + AudioConfig config{.base = validBase}; + config.offloadInfo.info(generateOffloadInfo(validBase)); + config.offloadInfo.info().base.format = "random_string"; + } + { + AudioConfig config{.base = validBase}; + config.offloadInfo.info(generateOffloadInfo(validBase)); + config.offloadInfo.info().streamType = "random_string"; + } + { + AudioConfig config{.base = validBase}; + config.offloadInfo.info(generateOffloadInfo(validBase)); + config.offloadInfo.info().usage = "random_string"; + } + hasOffloadConfig = true; + } else { + hasRegularConfig = true; + } + break; } + if (hasOffloadConfig && hasRegularConfig) break; } } return result; @@ -92,32 +193,563 @@ const std::vector<DeviceConfigParameter>& getOutputDeviceConfigParameters() { return parameters; } +static std::vector<DeviceConfigParameter> generateInputDeviceConfigParameters( + bool oneProfilePerDevice) { + std::vector<DeviceConfigParameter> result; + for (const auto& device : getDeviceParameters()) { + auto module = + getCachedPolicyConfig().getModuleFromName(std::get<PARAM_DEVICE_NAME>(device)); + if (!module || !module->getFirstMixPorts()) break; + for (const auto& mixPort : module->getFirstMixPorts()->getMixPort()) { + if (mixPort.getRole() != xsd::Role::sink) continue; // not an input profile + std::vector<AudioInOutFlag> flags; + if (mixPort.hasFlags()) { + std::transform(mixPort.getFlags().begin(), mixPort.getFlags().end(), + std::back_inserter(flags), [](auto flag) { return toString(flag); }); + } + for (const auto& profile : mixPort.getProfile()) { + auto configs = combineAudioConfig(profile.getChannelMasks(), + profile.getSamplingRates(), profile.getFormat()); + for (const auto& config : configs) { + result.emplace_back(device, config, flags); + if (oneProfilePerDevice) break; + } + if (oneProfilePerDevice) break; + } + if (oneProfilePerDevice) break; + } + } + return result; +} + const std::vector<DeviceConfigParameter>& getInputDeviceConfigParameters() { - static std::vector<DeviceConfigParameter> parameters = [] { + static std::vector<DeviceConfigParameter> parameters = + generateInputDeviceConfigParameters(false); + return parameters; +} + +const std::vector<DeviceConfigParameter>& getInputDeviceSingleConfigParameters() { + static std::vector<DeviceConfigParameter> parameters = + generateInputDeviceConfigParameters(true); + return parameters; +} + +const std::vector<DeviceConfigParameter>& getInputDeviceInvalidConfigParameters( + bool generateInvalidFlags = true) { + static std::vector<DeviceConfigParameter> parameters = [&] { std::vector<DeviceConfigParameter> result; for (const auto& device : getDeviceParameters()) { auto module = getCachedPolicyConfig().getModuleFromName(std::get<PARAM_DEVICE_NAME>(device)); if (!module || !module->getFirstMixPorts()) break; + bool hasConfig = false; for (const auto& mixPort : module->getFirstMixPorts()->getMixPort()) { if (mixPort.getRole() != xsd::Role::sink) continue; // not an input profile - std::vector<AudioInOutFlag> flags; + std::vector<AudioInOutFlag> validFlags; if (mixPort.hasFlags()) { std::transform(mixPort.getFlags().begin(), mixPort.getFlags().end(), - std::back_inserter(flags), + std::back_inserter(validFlags), [](auto flag) { return toString(flag); }); } for (const auto& profile : mixPort.getProfile()) { - auto configs = - combineAudioConfig(profile.getChannelMasks(), - profile.getSamplingRates(), profile.getFormat()); - for (const auto& config : configs) { + if (!profile.hasFormat() || !profile.hasSamplingRates() || + !profile.hasChannelMasks()) + continue; + AudioConfigBase validBase = { + profile.getFormat(), + static_cast<uint32_t>(profile.getSamplingRates()[0]), + toString(profile.getChannelMasks()[0])}; + { + AudioConfig config{.base = validBase}; + config.base.channelMask = "random_string"; + result.emplace_back(device, config, validFlags); + } + { + AudioConfig config{.base = validBase}; + config.base.format = "random_string"; + result.emplace_back(device, config, validFlags); + } + if (generateInvalidFlags) { + AudioConfig config{.base = validBase}; + std::vector<AudioInOutFlag> flags = {"random_string", ""}; result.emplace_back(device, config, flags); } + hasConfig = true; + break; } + if (hasConfig) break; } } return result; }(); return parameters; } + +class InvalidInputConfigNoFlagsTest : public AudioHidlTestWithDeviceConfigParameter {}; +TEST_P(InvalidInputConfigNoFlagsTest, InputBufferSizeTest) { + doc::test("Verify that invalid config is rejected by IDevice::getInputBufferSize method."); + uint64_t bufferSize; + ASSERT_OK(getDevice()->getInputBufferSize(getConfig(), returnIn(res, bufferSize))); + EXPECT_EQ(Result::INVALID_ARGUMENTS, res); +} +INSTANTIATE_TEST_CASE_P( + InputBufferSizeInvalidConfig, InvalidInputConfigNoFlagsTest, + ::testing::ValuesIn(getInputDeviceInvalidConfigParameters(false /*generateInvalidFlags*/)), + &DeviceConfigParameterToString); +GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(InvalidInputConfigNoFlagsTest); + +static const DeviceAddress& getValidInputDeviceAddress() { + static const DeviceAddress valid = { + .deviceType = toString(xsd::AudioDevice::AUDIO_DEVICE_IN_DEFAULT)}; + return valid; +} + +static const DeviceAddress& getValidOutputDeviceAddress() { + static const DeviceAddress valid = { + .deviceType = toString(xsd::AudioDevice::AUDIO_DEVICE_OUT_DEFAULT)}; + return valid; +} + +static const DeviceAddress& getInvalidDeviceAddress() { + static const DeviceAddress valid = {.deviceType = "random_string"}; + return valid; +} + +TEST_P(AudioHidlDeviceTest, SetConnectedStateInvalidDeviceAddress) { + doc::test("Check that invalid device address is rejected by IDevice::setConnectedState"); + EXPECT_RESULT(Result::INVALID_ARGUMENTS, + getDevice()->setConnectedState(getInvalidDeviceAddress(), true)); + EXPECT_RESULT(Result::INVALID_ARGUMENTS, + getDevice()->setConnectedState(getInvalidDeviceAddress(), false)); +} + +static std::vector<AudioPortConfig>& generatePortConfigs(bool valid) { + enum { // Note: This is for convenience when deriving "invalid" configs from "valid". + PORT_CONF_MINIMAL, + PORT_CONF_WITH_GAIN, + PORT_CONF_EXT_DEVICE, + PORT_CONF_EXT_MIX_SOURCE, + PORT_CONF_EXT_MIX_SINK, + PORT_CONF_EXT_SESSION + }; + static std::vector<AudioPortConfig> valids = [] { + std::vector<AudioPortConfig> result; + result.reserve(PORT_CONF_EXT_SESSION + 1); + result.push_back(AudioPortConfig{}); + AudioPortConfig configWithGain{}; + configWithGain.gain.config(AudioGainConfig{ + .index = 0, + .mode = {toString(xsd::AudioGainMode::AUDIO_GAIN_MODE_JOINT)}, + .channelMask = toString(xsd::AudioChannelMask::AUDIO_CHANNEL_OUT_MONO), + .rampDurationMs = 1}); + configWithGain.gain.config().values.resize(1); + configWithGain.gain.config().values[0] = 1000; + result.push_back(std::move(configWithGain)); + AudioPortConfig configWithPortExtDevice{}; + configWithPortExtDevice.ext.device(getValidOutputDeviceAddress()); + result.push_back(std::move(configWithPortExtDevice)); + AudioPortConfig configWithPortExtMixSource{}; + configWithPortExtMixSource.ext.mix({}); + configWithPortExtMixSource.ext.mix().useCase.stream( + toString(xsd::AudioStreamType::AUDIO_STREAM_VOICE_CALL)); + result.push_back(std::move(configWithPortExtMixSource)); + AudioPortConfig configWithPortExtMixSink{}; + configWithPortExtMixSink.ext.mix({}); + configWithPortExtMixSink.ext.mix().useCase.source( + toString(xsd::AudioSource::AUDIO_SOURCE_DEFAULT)); + result.push_back(std::move(configWithPortExtMixSink)); + AudioPortConfig configWithPortExtSession{}; + configWithPortExtSession.ext.session( + static_cast<AudioSession>(AudioSessionConsts::OUTPUT_MIX)); + result.push_back(std::move(configWithPortExtSession)); + return result; + }(); + static std::vector<AudioPortConfig> invalids = [&] { + std::vector<AudioPortConfig> result; + AudioPortConfig invalidBaseChannelMask = valids[PORT_CONF_MINIMAL]; + invalidBaseChannelMask.base.channelMask = "random_string"; + result.push_back(std::move(invalidBaseChannelMask)); + AudioPortConfig invalidBaseFormat = valids[PORT_CONF_MINIMAL]; + invalidBaseFormat.base.format = "random_string"; + result.push_back(std::move(invalidBaseFormat)); + AudioPortConfig invalidGainMode = valids[PORT_CONF_WITH_GAIN]; + invalidGainMode.gain.config().mode = {{"random_string"}}; + result.push_back(std::move(invalidGainMode)); + AudioPortConfig invalidGainChannelMask = valids[PORT_CONF_WITH_GAIN]; + invalidGainChannelMask.gain.config().channelMask = "random_string"; + result.push_back(std::move(invalidGainChannelMask)); + AudioPortConfig invalidDeviceType = valids[PORT_CONF_EXT_DEVICE]; + invalidDeviceType.ext.device().deviceType = "random_string"; + result.push_back(std::move(invalidDeviceType)); + AudioPortConfig invalidStreamType = valids[PORT_CONF_EXT_MIX_SOURCE]; + invalidStreamType.ext.mix().useCase.stream() = "random_string"; + result.push_back(std::move(invalidStreamType)); + AudioPortConfig invalidSource = valids[PORT_CONF_EXT_MIX_SINK]; + invalidSource.ext.mix().useCase.source() = "random_string"; + result.push_back(std::move(invalidSource)); + return result; + }(); + return valid ? valids : invalids; +} + +TEST_P(AudioHidlDeviceTest, SetAudioPortConfigInvalidArguments) { + doc::test("Check that invalid port configs are rejected by IDevice::setAudioPortConfig"); + for (const auto& invalidConfig : generatePortConfigs(false /*valid*/)) { + EXPECT_RESULT(invalidArgsOrNotSupported, getDevice()->setAudioPortConfig(invalidConfig)) + << ::testing::PrintToString(invalidConfig); + } +} + +TEST_P(AudioPatchHidlTest, CreatePatchInvalidArguments) { + doc::test("Check that invalid port configs are rejected by IDevice::createAudioPatch"); + // Note that HAL actually might reject the proposed source / sink combo + // due to other reasons than presence of invalid enum-strings. + // TODO: Come up with a way to guarantee validity of a source / sink combo. + for (const auto& validSource : generatePortConfigs(true /*valid*/)) { + for (const auto& invalidSink : generatePortConfigs(false /*valid*/)) { + AudioPatchHandle handle; + EXPECT_OK(getDevice()->createAudioPatch(hidl_vec<AudioPortConfig>{validSource}, + hidl_vec<AudioPortConfig>{invalidSink}, + returnIn(res, handle))); + EXPECT_EQ(Result::INVALID_ARGUMENTS, res) + << "Source: " << ::testing::PrintToString(validSource) + << "; Sink: " << ::testing::PrintToString(invalidSink); + } + } + for (const auto& validSink : generatePortConfigs(true /*valid*/)) { + for (const auto& invalidSource : generatePortConfigs(false /*valid*/)) { + AudioPatchHandle handle; + EXPECT_OK(getDevice()->createAudioPatch(hidl_vec<AudioPortConfig>{invalidSource}, + hidl_vec<AudioPortConfig>{validSink}, + returnIn(res, handle))); + EXPECT_EQ(Result::INVALID_ARGUMENTS, res) + << "Source: " << ::testing::PrintToString(invalidSource) + << "; Sink: " << ::testing::PrintToString(validSink); + } + } +} + +TEST_P(AudioPatchHidlTest, UpdatePatchInvalidArguments) { + doc::test("Check that invalid port configs are rejected by IDevice::updateAudioPatch"); + // Note that HAL actually might reject the proposed source / sink combo + // due to other reasons than presence of invalid enum-strings. + // TODO: Come up with a way to guarantee validity of a source / sink combo. + for (const auto& validSource : generatePortConfigs(true /*valid*/)) { + for (const auto& invalidSink : generatePortConfigs(false /*valid*/)) { + AudioPatchHandle handle{}; + EXPECT_OK(getDevice()->updateAudioPatch(handle, hidl_vec<AudioPortConfig>{validSource}, + hidl_vec<AudioPortConfig>{invalidSink}, + returnIn(res, handle))); + EXPECT_EQ(Result::INVALID_ARGUMENTS, res) + << "Source: " << ::testing::PrintToString(validSource) + << "; Sink: " << ::testing::PrintToString(invalidSink); + } + } + for (const auto& validSink : generatePortConfigs(true /*valid*/)) { + for (const auto& invalidSource : generatePortConfigs(false /*valid*/)) { + AudioPatchHandle handle{}; + EXPECT_OK(getDevice()->updateAudioPatch( + handle, hidl_vec<AudioPortConfig>{invalidSource}, + hidl_vec<AudioPortConfig>{validSink}, returnIn(res, handle))); + EXPECT_EQ(Result::INVALID_ARGUMENTS, res) + << "Source: " << ::testing::PrintToString(invalidSource) + << "; Sink: " << ::testing::PrintToString(validSink); + } + } +} + +enum { PARAM_DEVICE_CONFIG, PARAM_ADDRESS, PARAM_METADATA }; +enum { INDEX_SINK, INDEX_SOURCE }; +using SinkOrSourceMetadata = std::variant<SinkMetadata, SourceMetadata>; +using StreamOpenParameter = std::tuple<DeviceConfigParameter, DeviceAddress, SinkOrSourceMetadata>; + +static std::string StreamOpenParameterToString( + const ::testing::TestParamInfo<StreamOpenParameter>& info) { + return DeviceConfigParameterToString(::testing::TestParamInfo<DeviceConfigParameter>{ + std::get<PARAM_DEVICE_CONFIG>(info.param), info.index}) + + "__" + + SanitizeStringForGTestName( + ::testing::PrintToString(std::get<PARAM_ADDRESS>(info.param))) + + "__" + + SanitizeStringForGTestName(std::visit( + [](auto&& arg) -> std::string { return ::testing::PrintToString(arg); }, + std::get<PARAM_METADATA>(info.param))); +} + +class StreamOpenTest : public HidlTest, public ::testing::WithParamInterface<StreamOpenParameter> { + protected: + void SetUp() override { + ASSERT_NO_FATAL_FAILURE(HidlTest::SetUp()); // setup base + ASSERT_TRUE(getDevicesFactory() != nullptr); + ASSERT_TRUE(getDevice() != nullptr); + } + const std::string& getFactoryName() const override { + return std::get<PARAM_FACTORY_NAME>( + std::get<PARAM_DEVICE>(std::get<PARAM_DEVICE_CONFIG>(GetParam()))); + } + const std::string& getDeviceName() const override { + return std::get<PARAM_DEVICE_NAME>( + std::get<PARAM_DEVICE>(std::get<PARAM_DEVICE_CONFIG>(GetParam()))); + } + const AudioConfig& getConfig() const { + return std::get<PARAM_CONFIG>(std::get<PARAM_DEVICE_CONFIG>(GetParam())); + } + const hidl_vec<AudioInOutFlag> getFlags() const { + return std::get<PARAM_FLAGS>(std::get<PARAM_DEVICE_CONFIG>(GetParam())); + } + const DeviceAddress& getDeviceAddress() const { return std::get<PARAM_ADDRESS>(GetParam()); } + bool isParamForInputStream() const { + return std::get<PARAM_METADATA>(GetParam()).index() == INDEX_SINK; + } + const SinkMetadata& getSinkMetadata() const { + return std::get<INDEX_SINK>(std::get<PARAM_METADATA>(GetParam())); + } + const SourceMetadata& getSourceMetadata() const { + return std::get<INDEX_SOURCE>(std::get<PARAM_METADATA>(GetParam())); + } +}; + +static const RecordTrackMetadata& getValidRecordTrackMetadata() { + static const RecordTrackMetadata valid = { + .source = toString(xsd::AudioSource::AUDIO_SOURCE_DEFAULT), .gain = 1}; + return valid; +} + +static const RecordTrackMetadata& getValidRecordTrackMetadataWithDest() { + static const RecordTrackMetadata valid = { + .source = toString(xsd::AudioSource::AUDIO_SOURCE_DEFAULT), + .gain = 1, + .destination = [] { + RecordTrackMetadata::Destination dest; + dest.device(getValidOutputDeviceAddress()); + return dest; + }()}; + return valid; +} + +static const RecordTrackMetadata& getInvalidSourceRecordTrackMetadata() { + static const RecordTrackMetadata invalid = {.source = "random_string", .gain = 1}; + return invalid; +} + +static const RecordTrackMetadata& getRecordTrackMetadataWithInvalidDest() { + static const RecordTrackMetadata invalid = { + .source = toString(xsd::AudioSource::AUDIO_SOURCE_DEFAULT), + .gain = 1, + .destination = [] { + RecordTrackMetadata::Destination dest; + dest.device(getInvalidDeviceAddress()); + return dest; + }()}; + return invalid; +} + +static const RecordTrackMetadata& getInvalidChannelMaskRecordTrackMetadata() { + static const RecordTrackMetadata invalid = { + .source = toString(xsd::AudioSource::AUDIO_SOURCE_DEFAULT), + .gain = 1, + .channelMask = "random_string"}; + return invalid; +} + +static const RecordTrackMetadata& getInvalidTagsRecordTrackMetadata() { + static const RecordTrackMetadata invalid = { + .source = toString(xsd::AudioSource::AUDIO_SOURCE_DEFAULT), + .gain = 1, + .tags = {{"random_string"}}}; + return invalid; +} + +static const PlaybackTrackMetadata& getValidPlaybackTrackMetadata() { + static const PlaybackTrackMetadata valid = { + .usage = toString(xsd::AudioUsage::AUDIO_USAGE_MEDIA), + .contentType = toString(xsd::AudioContentType::AUDIO_CONTENT_TYPE_MUSIC), + .gain = 1}; + return valid; +} + +static const PlaybackTrackMetadata& getInvalidUsagePlaybackTrackMetadata() { + static const PlaybackTrackMetadata invalid = { + .usage = "random_string", + .contentType = toString(xsd::AudioContentType::AUDIO_CONTENT_TYPE_MUSIC), + .gain = 1}; + return invalid; +} + +static const PlaybackTrackMetadata& getInvalidContentTypePlaybackTrackMetadata() { + static const PlaybackTrackMetadata invalid = { + .usage = toString(xsd::AudioUsage::AUDIO_USAGE_MEDIA), + .contentType = "random_string", + .gain = 1}; + return invalid; +} + +static const PlaybackTrackMetadata& getInvalidChannelMaskPlaybackTrackMetadata() { + static const PlaybackTrackMetadata invalid = { + .usage = toString(xsd::AudioUsage::AUDIO_USAGE_MEDIA), + .contentType = toString(xsd::AudioContentType::AUDIO_CONTENT_TYPE_MUSIC), + .gain = 1, + .channelMask = "random_string"}; + return invalid; +} + +static const PlaybackTrackMetadata& getInvalidTagsPlaybackTrackMetadata() { + static const PlaybackTrackMetadata invalid = { + .usage = toString(xsd::AudioUsage::AUDIO_USAGE_MEDIA), + .contentType = toString(xsd::AudioContentType::AUDIO_CONTENT_TYPE_MUSIC), + .gain = 1, + .tags = {{"random_string"}}}; + return invalid; +} + +static const std::vector<SourceMetadata>& getInvalidSourceMetadatas() { + static const std::vector<SourceMetadata> invalids = { + SourceMetadata{.tracks = {{getInvalidUsagePlaybackTrackMetadata()}}}, + SourceMetadata{.tracks = {{getInvalidContentTypePlaybackTrackMetadata()}}}, + SourceMetadata{.tracks = {{getInvalidChannelMaskPlaybackTrackMetadata()}}}, + SourceMetadata{.tracks = {{getInvalidTagsPlaybackTrackMetadata()}}}, + SourceMetadata{.tracks = {{getValidPlaybackTrackMetadata(), + getInvalidUsagePlaybackTrackMetadata()}}}, + SourceMetadata{.tracks = {{getValidPlaybackTrackMetadata(), + getInvalidContentTypePlaybackTrackMetadata()}}}, + SourceMetadata{.tracks = {{getValidPlaybackTrackMetadata(), + getInvalidChannelMaskPlaybackTrackMetadata()}}}, + SourceMetadata{.tracks = {{getValidPlaybackTrackMetadata(), + getInvalidTagsPlaybackTrackMetadata()}}}}; + return invalids; +} +static const std::vector<SinkMetadata>& getInvalidSinkMetadatas() { + static const std::vector<SinkMetadata> invalids = { + SinkMetadata{.tracks = {{getInvalidSourceRecordTrackMetadata()}}}, + SinkMetadata{.tracks = {{getRecordTrackMetadataWithInvalidDest()}}}, + SinkMetadata{.tracks = {{getInvalidChannelMaskRecordTrackMetadata()}}}, + SinkMetadata{.tracks = {{getInvalidTagsRecordTrackMetadata()}}}, + SinkMetadata{.tracks = {{getValidRecordTrackMetadata(), + getInvalidSourceRecordTrackMetadata()}}}, + SinkMetadata{.tracks = {{getValidRecordTrackMetadata(), + getRecordTrackMetadataWithInvalidDest()}}}, + SinkMetadata{.tracks = {{getValidRecordTrackMetadata(), + getInvalidChannelMaskRecordTrackMetadata()}}}, + SinkMetadata{.tracks = {{getValidRecordTrackMetadata(), + getInvalidTagsRecordTrackMetadata()}}}}; + return invalids; +} +template <typename T> +static inline std::vector<SinkOrSourceMetadata> wrapMetadata(const std::vector<T>& metadata) { + return std::vector<SinkOrSourceMetadata>{metadata.begin(), metadata.end()}; +} + +TEST_P(StreamOpenTest, OpenInputOrOutputStreamTest) { + doc::test( + "Verify that invalid arguments are rejected by " + "IDevice::open{Input|Output}Stream method."); + AudioConfig suggestedConfig{}; + if (isParamForInputStream()) { + sp<IStreamIn> stream; + ASSERT_OK(getDevice()->openInputStream(AudioIoHandle{}, getDeviceAddress(), getConfig(), + getFlags(), getSinkMetadata(), + returnIn(res, stream, suggestedConfig))); + ASSERT_TRUE(stream == nullptr); + } else { + sp<IStreamOut> stream; + ASSERT_OK(getDevice()->openOutputStream(AudioIoHandle{}, getDeviceAddress(), getConfig(), + getFlags(), getSourceMetadata(), + returnIn(res, stream, suggestedConfig))); + ASSERT_TRUE(stream == nullptr); + } + EXPECT_EQ(Result::INVALID_ARGUMENTS, res); + EXPECT_EQ(AudioConfig{}, suggestedConfig); +} +INSTANTIATE_TEST_CASE_P( + InputStreamInvalidConfig, StreamOpenTest, + ::testing::Combine(::testing::ValuesIn(getInputDeviceInvalidConfigParameters()), + ::testing::Values(getValidInputDeviceAddress()), + ::testing::Values(SinkMetadata{ + .tracks = {{getValidRecordTrackMetadata(), + getValidRecordTrackMetadataWithDest()}}})), + &StreamOpenParameterToString); +INSTANTIATE_TEST_CASE_P( + InputStreamInvalidAddress, StreamOpenTest, + ::testing::Combine(::testing::ValuesIn(getInputDeviceSingleConfigParameters()), + ::testing::Values(getInvalidDeviceAddress()), + ::testing::Values(SinkMetadata{ + .tracks = {{getValidRecordTrackMetadata(), + getValidRecordTrackMetadataWithDest()}}})), + &StreamOpenParameterToString); +INSTANTIATE_TEST_CASE_P( + InputStreamInvalidMetadata, StreamOpenTest, + ::testing::Combine(::testing::ValuesIn(getInputDeviceSingleConfigParameters()), + ::testing::Values(getValidInputDeviceAddress()), + ::testing::ValuesIn(wrapMetadata(getInvalidSinkMetadatas()))), + &StreamOpenParameterToString); +GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(StreamOpenTest); + +INSTANTIATE_TEST_CASE_P( + OutputStreamInvalidConfig, StreamOpenTest, + ::testing::Combine(::testing::ValuesIn(getOutputDeviceInvalidConfigParameters()), + ::testing::Values(getValidOutputDeviceAddress()), + ::testing::Values(SourceMetadata{ + .tracks = {{getValidPlaybackTrackMetadata()}}})), + &StreamOpenParameterToString); +INSTANTIATE_TEST_CASE_P( + OutputStreamInvalidAddress, StreamOpenTest, + ::testing::Combine(::testing::ValuesIn(getOutputDeviceSingleConfigParameters()), + ::testing::Values(getInvalidDeviceAddress()), + ::testing::Values(SourceMetadata{ + .tracks = {{getValidPlaybackTrackMetadata()}}})), + &StreamOpenParameterToString); +INSTANTIATE_TEST_CASE_P( + OutputStreamInvalidMetadata, StreamOpenTest, + ::testing::Combine(::testing::ValuesIn(getOutputDeviceSingleConfigParameters()), + ::testing::Values(getValidOutputDeviceAddress()), + ::testing::ValuesIn(wrapMetadata(getInvalidSourceMetadatas()))), + &StreamOpenParameterToString); + +#define TEST_SINGLE_CONFIG_IO_STREAM(test_name, documentation, code) \ + TEST_P(SingleConfigInputStreamTest, test_name) { \ + doc::test(documentation); \ + code; \ + } \ + TEST_P(SingleConfigOutputStreamTest, test_name) { \ + doc::test(documentation); \ + code; \ + } + +static void testSetDevicesInvalidDeviceAddress(IStream* stream) { + ASSERT_RESULT(Result::INVALID_ARGUMENTS, stream->setDevices({getInvalidDeviceAddress()})); +} +TEST_SINGLE_CONFIG_IO_STREAM( + SetDevicesInvalidDeviceAddress, + "Verify that invalid device address is rejected by IStream::setDevices", + areAudioPatchesSupported() ? doc::partialTest("Audio patches are supported") + : testSetDevicesInvalidDeviceAddress(stream.get())); + +static void testSetAudioPropertiesInvalidArguments(IStream* stream, const AudioConfigBase& base) { + AudioConfigBase invalidFormat = base; + invalidFormat.format = "random_string"; + ASSERT_RESULT(invalidArgsOrNotSupported, stream->setAudioProperties(invalidFormat)); + + AudioConfigBase invalidChannelMask = base; + invalidChannelMask.channelMask = "random_string"; + ASSERT_RESULT(invalidArgsOrNotSupported, stream->setAudioProperties(invalidChannelMask)); +} +TEST_SINGLE_CONFIG_IO_STREAM( + SetAudioPropertiesInvalidArguments, + "Verify that invalid arguments are rejected by IStream::setAudioProperties", + testSetAudioPropertiesInvalidArguments(stream.get(), audioConfig.base)); + +TEST_P(SingleConfigOutputStreamTest, UpdateInvalidSourceMetadata) { + doc::test("Verify that invalid metadata is rejected by IStreamOut::updateSourceMetadata"); + for (const auto& metadata : getInvalidSourceMetadatas()) { + ASSERT_RESULT(invalidArgsOrNotSupported, stream->updateSourceMetadata(metadata)) + << ::testing::PrintToString(metadata); + } +} + +TEST_P(SingleConfigInputStreamTest, UpdateInvalidSinkMetadata) { + doc::test("Verify that invalid metadata is rejected by IStreamIn::updateSinkMetadata"); + for (const auto& metadata : getInvalidSinkMetadatas()) { + ASSERT_RESULT(invalidArgsOrNotSupported, stream->updateSinkMetadata(metadata)) + << ::testing::PrintToString(metadata); + } +} diff --git a/audio/core/all-versions/vts/functional/AudioPrimaryHidlHalTest.h b/audio/core/all-versions/vts/functional/AudioPrimaryHidlHalTest.h index a16a0fb4d3..f145b606de 100644 --- a/audio/core/all-versions/vts/functional/AudioPrimaryHidlHalTest.h +++ b/audio/core/all-versions/vts/functional/AudioPrimaryHidlHalTest.h @@ -522,7 +522,9 @@ using DeviceConfigParameter = std::tuple<DeviceParameter, AudioConfig, std::vect #if MAJOR_VERSION >= 6 const std::vector<DeviceConfigParameter>& getInputDeviceConfigParameters(); +const std::vector<DeviceConfigParameter>& getInputDeviceSingleConfigParameters(); const std::vector<DeviceConfigParameter>& getOutputDeviceConfigParameters(); +const std::vector<DeviceConfigParameter>& getOutputDeviceSingleConfigParameters(); #endif #if MAJOR_VERSION >= 4 @@ -883,7 +885,7 @@ class OpenStreamTest : public AudioHidlTestWithDeviceConfigParameter { AudioHidlTestWithDeviceConfigParameter::TearDown(); } - protected: + protected: AudioConfig audioConfig; DeviceAddress address = {}; sp<Stream> stream; @@ -973,7 +975,7 @@ class InputStreamTest : public OpenStreamTest<IStreamIn> { ASSERT_NO_FATAL_FAILURE(OpenStreamTest::SetUp()); // setup base #if MAJOR_VERSION <= 6 address.device = AudioDevice::IN_DEFAULT; -#elif MAJOR_VERSION <= 7 +#elif MAJOR_VERSION >= 7 address.deviceType = toString(xsd::AudioDevice::AUDIO_DEVICE_IN_DEFAULT); #endif const AudioConfig& config = getConfig(); @@ -988,7 +990,7 @@ class InputStreamTest : public OpenStreamTest<IStreamIn> { protected: #if MAJOR_VERSION == 2 - const AudioSource initMetadata = AudioSource::DEFAULT; + const AudioSource initMetadata = AudioSource::DEFAULT; #elif MAJOR_VERSION >= 4 && MAJOR_VERSION <= 6 const SinkMetadata initMetadata = {{ {.source = AudioSource::DEFAULT, .gain = 1 } }}; #elif MAJOR_VERSION >= 7 diff --git a/audio/effect/all-versions/vts/functional/VtsHalAudioEffectTargetTest.cpp b/audio/effect/all-versions/vts/functional/VtsHalAudioEffectTargetTest.cpp index d39fbcd5c3..35ff8693a4 100644 --- a/audio/effect/all-versions/vts/functional/VtsHalAudioEffectTargetTest.cpp +++ b/audio/effect/all-versions/vts/functional/VtsHalAudioEffectTargetTest.cpp @@ -14,6 +14,8 @@ * limitations under the License. */ +#include <vector> + #define LOG_TAG "AudioEffectHidlHalTest" #include <android-base/logging.h> #if MAJOR_VERSION <= 6 @@ -309,6 +311,47 @@ TEST_P(AudioEffectHidlTest, GetSetConfig) { EXPECT_EQ(Result::OK, ret2); } +#if MAJOR_VERSION >= 7 +std::vector<EffectBufferConfig> generateInvalidConfigs(const EffectBufferConfig& src) { + std::vector<EffectBufferConfig> result; + EffectBufferConfig invalidFormat = src; + invalidFormat.base.format = "random_string"; + result.push_back(std::move(invalidFormat)); + EffectBufferConfig invalidChannelMask = src; + invalidChannelMask.base.channelMask = "random_string"; + result.push_back(std::move(invalidChannelMask)); + return result; +} + +TEST_P(AudioEffectHidlTest, SetConfigInvalidArguments) { + description("Verify that invalid arguments are rejected by SetConfig"); + Result retval = Result::NOT_INITIALIZED; + EffectConfig currentConfig; + Return<void> ret = effect->getConfig([&](Result r, const EffectConfig& conf) { + retval = r; + if (r == Result::OK) { + currentConfig = conf; + } + }); + EXPECT_TRUE(ret.isOk()); + EXPECT_EQ(Result::OK, retval); + for (const auto& invalidInputCfg : generateInvalidConfigs(currentConfig.inputCfg)) { + EffectConfig invalidConfig = currentConfig; + invalidConfig.inputCfg = invalidInputCfg; + Return<Result> ret = effect->setConfig(invalidConfig, nullptr, nullptr); + EXPECT_TRUE(ret.isOk()); + EXPECT_EQ(Result::INVALID_ARGUMENTS, ret); + } + for (const auto& invalidOutputCfg : generateInvalidConfigs(currentConfig.outputCfg)) { + EffectConfig invalidConfig = currentConfig; + invalidConfig.outputCfg = invalidOutputCfg; + Return<Result> ret = effect->setConfig(invalidConfig, nullptr, nullptr); + EXPECT_TRUE(ret.isOk()); + EXPECT_EQ(Result::INVALID_ARGUMENTS, ret); + } +} +#endif + TEST_P(AudioEffectHidlTest, GetConfigReverse) { description("Verify that GetConfigReverse does not crash"); Return<void> ret = effect->getConfigReverse([&](Result, const EffectConfig&) {}); @@ -413,6 +456,16 @@ TEST_P(AudioEffectHidlTest, DisableEnableDisable) { EXPECT_EQ(Result::OK, ret); } +#if MAJOR_VERSION >= 7 +TEST_P(AudioEffectHidlTest, SetDeviceInvalidDeviceAddress) { + description("Verify that invalid device address is rejected by SetDevice"); + DeviceAddress device{.deviceType = "random_string"}; + Return<Result> ret = effect->setDevice(device); + EXPECT_TRUE(ret.isOk()); + EXPECT_EQ(Result::INVALID_ARGUMENTS, ret); +} +#endif + TEST_P(AudioEffectHidlTest, SetDevice) { description("Verify that SetDevice works for an output chain effect"); #if MAJOR_VERSION <= 6 @@ -468,6 +521,17 @@ TEST_P(AudioEffectHidlTest, SetConfigReverse) { EXPECT_TRUE(ret.isOk()); } +#if MAJOR_VERSION >= 7 +TEST_P(AudioEffectHidlTest, SetInputDeviceInvalidDeviceAddress) { + description("Verify that invalid device address is rejected by SetInputDevice"); + DeviceAddress device{.deviceType = "random_string"}; + Return<Result> ret = effect->setInputDevice(device); + EXPECT_TRUE(ret.isOk()); + EXPECT_TRUE(ret == Result::INVALID_ARGUMENTS || ret == Result::NOT_SUPPORTED) + << ::testing::PrintToString(ret); +} +#endif + TEST_P(AudioEffectHidlTest, SetInputDevice) { description("Verify that SetInputDevice does not crash"); #if MAJOR_VERSION <= 6 @@ -479,6 +543,16 @@ TEST_P(AudioEffectHidlTest, SetInputDevice) { EXPECT_TRUE(ret.isOk()); } +#if MAJOR_VERSION >= 7 +TEST_P(AudioEffectHidlTest, SetInvalidAudioSource) { + description("Verify that an invalid audio source is rejected by SetAudioSource"); + Return<Result> ret = effect->setAudioSource("random_string"); + ASSERT_TRUE(ret.isOk()); + EXPECT_TRUE(ret == Result::INVALID_ARGUMENTS || ret == Result::NOT_SUPPORTED) + << ::testing::PrintToString(ret); +} +#endif + TEST_P(AudioEffectHidlTest, SetAudioSource) { description("Verify that SetAudioSource does not crash"); #if MAJOR_VERSION <= 6 diff --git a/authsecret/aidl/Android.bp b/authsecret/aidl/Android.bp new file mode 100644 index 0000000000..0a05034762 --- /dev/null +++ b/authsecret/aidl/Android.bp @@ -0,0 +1,16 @@ +aidl_interface { + name: "android.hardware.authsecret", + vendor_available: true, + srcs: ["android/hardware/authsecret/*.aidl"], + stability: "vintf", + backend: { + java: { + platform_apis: true, + }, + ndk: { + vndk: { + enabled: true, + }, + }, + }, +} diff --git a/authsecret/aidl/aidl_api/android.hardware.authsecret/current/android/hardware/authsecret/IAuthSecret.aidl b/authsecret/aidl/aidl_api/android.hardware.authsecret/current/android/hardware/authsecret/IAuthSecret.aidl new file mode 100644 index 0000000000..14e83257a6 --- /dev/null +++ b/authsecret/aidl/aidl_api/android.hardware.authsecret/current/android/hardware/authsecret/IAuthSecret.aidl @@ -0,0 +1,23 @@ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. +// +// You must not make a backward incompatible change to any AIDL file built +// with the aidl_interface module type with versions property set. The module +// type is used to build AIDL files in a way that they can be used across +// independently updatable components of the system. If a device is shipped +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.hardware.authsecret; +@VintfStability +interface IAuthSecret { + oneway void setPrimaryUserCredential(in byte[] secret); +} diff --git a/authsecret/aidl/android/hardware/authsecret/IAuthSecret.aidl b/authsecret/aidl/android/hardware/authsecret/IAuthSecret.aidl new file mode 100644 index 0000000000..3849ec0d7c --- /dev/null +++ b/authsecret/aidl/android/hardware/authsecret/IAuthSecret.aidl @@ -0,0 +1,47 @@ +/* + * Copyright 2020 The Android Open Source Project + * + * 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. + */ + +package android.hardware.authsecret; + +/** + * This security HAL allows vendor components to be cryptographically tied to + * the primary user's credential. For example, security hardware can require + * proof that the credential is known before applying updates. + * + */ +@VintfStability +interface IAuthSecret { + /** + * When the primary user is unlocked, this method is passed a secret to + * prove that is has been successfully unlocked. The primary user can either + * be unlocked by a person entering their credential or by another party + * using an escrow token e.g. a device administrator. + * + * The first time this is called, the secret must be used to provision state + * that depends on the primary user's secret. The same secret must be passed + * on each call until the next factory reset. + * + * Upon factory reset, any dependence on the secret must be removed as that + * secret is now lost and must never be derived again. A new secret must be + * created for the new primary user which must be used to newly provision + * state the first time this method is called after factory reset. + * + * The secret must be at least 16 bytes, or the secret must be dropped. + * + * @param secret blob derived from the primary user's credential. + */ + oneway void setPrimaryUserCredential(in byte[] secret); +} diff --git a/authsecret/aidl/default/Android.bp b/authsecret/aidl/default/Android.bp new file mode 100644 index 0000000000..d5983442f5 --- /dev/null +++ b/authsecret/aidl/default/Android.bp @@ -0,0 +1,32 @@ +// +// Copyright (C) 2020 The Android Open Source Project +// +// 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. +// + +cc_binary { + name: "android.hardware.authsecret-service.example", + relative_install_path: "hw", + init_rc: ["android.hardware.authsecret-service.example.rc"], + vintf_fragments: ["android.hardware.authsecret-service.example.xml"], + vendor: true, + srcs: [ + "service.cpp", + "AuthSecret.cpp", + ], + shared_libs: [ + "android.hardware.authsecret-ndk_platform", + "libbase", + "libbinder_ndk", + ], +} diff --git a/authsecret/aidl/default/AuthSecret.cpp b/authsecret/aidl/default/AuthSecret.cpp new file mode 100644 index 0000000000..9645e4d924 --- /dev/null +++ b/authsecret/aidl/default/AuthSecret.cpp @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * 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 "AuthSecret.h" + +namespace aidl { +namespace android { +namespace hardware { +namespace authsecret { + +// Methods from ::android::hardware::authsecret::IAuthSecret follow. +::ndk::ScopedAStatus AuthSecret::setPrimaryUserCredential(const std::vector<uint8_t>& in_secret) { + (void)in_secret; + return ::ndk::ScopedAStatus::ok(); +} + +} // namespace authsecret +} // namespace hardware +} // namespace android +} // aidl diff --git a/authsecret/aidl/default/AuthSecret.h b/authsecret/aidl/default/AuthSecret.h new file mode 100644 index 0000000000..b40fc48e92 --- /dev/null +++ b/authsecret/aidl/default/AuthSecret.h @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * 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. + */ + +#pragma once + +#include <aidl/android/hardware/authsecret/BnAuthSecret.h> + +namespace aidl { +namespace android { +namespace hardware { +namespace authsecret { + +struct AuthSecret : public BnAuthSecret { + AuthSecret() = default; + + // Methods from ::android::hardware::authsecret::IAuthSecret follow. + ::ndk::ScopedAStatus setPrimaryUserCredential(const std::vector<uint8_t>& in_secret) override; + +}; + +} // namespace authsecret +} // namespace hardware +} // namespace android +} // aidl diff --git a/authsecret/aidl/default/android.hardware.authsecret-service.example.rc b/authsecret/aidl/default/android.hardware.authsecret-service.example.rc new file mode 100644 index 0000000000..fef6e2401e --- /dev/null +++ b/authsecret/aidl/default/android.hardware.authsecret-service.example.rc @@ -0,0 +1,4 @@ +service vendor.authsecret_default /vendor/bin/hw/android.hardware.authsecret-service.example + class hal + user hsm + group hsm diff --git a/authsecret/aidl/default/android.hardware.authsecret-service.example.xml b/authsecret/aidl/default/android.hardware.authsecret-service.example.xml new file mode 100644 index 0000000000..9d4e1622cf --- /dev/null +++ b/authsecret/aidl/default/android.hardware.authsecret-service.example.xml @@ -0,0 +1,10 @@ +<manifest version="1.0" type="device"> + <hal format="aidl"> + <name>android.hardware.authsecret</name> + <version>1</version> + <interface> + <name>IAuthSecret</name> + <instance>default</instance> + </interface> + </hal> +</manifest> diff --git a/authsecret/aidl/default/service.cpp b/authsecret/aidl/default/service.cpp new file mode 100644 index 0000000000..efecf10f55 --- /dev/null +++ b/authsecret/aidl/default/service.cpp @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * 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 <android-base/logging.h> +#include <android/binder_manager.h> +#include <android/binder_process.h> + +#include "AuthSecret.h" + +using ::aidl::android::hardware::authsecret::AuthSecret; + +int main() { + ABinderProcess_setThreadPoolMaxThreadCount(0); + std::shared_ptr<AuthSecret> authsecret = ndk::SharedRefBase::make<AuthSecret>(); + + const std::string instance = std::string() + AuthSecret::descriptor + "/default"; + binder_status_t status = AServiceManager_addService(authsecret->asBinder().get(), instance.c_str()); + CHECK(status == STATUS_OK); + + ABinderProcess_joinThreadPool(); + return -1; // Should never be reached +} diff --git a/health/1.0/vts/functional/Android.bp b/authsecret/aidl/vts/Android.bp index f4a04a7308..83a85b26ce 100644 --- a/health/1.0/vts/functional/Android.bp +++ b/authsecret/aidl/vts/Android.bp @@ -1,5 +1,5 @@ // -// Copyright (C) 2017 The Android Open Source Project +// Copyright (C) 2018 The Android Open Source Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -15,9 +15,17 @@ // cc_test { - name: "VtsHalHealthV1_0TargetTest", - defaults: ["VtsHalTargetTestDefaults"], - srcs: ["VtsHalHealthV1_0TargetTest.cpp"], - static_libs: ["android.hardware.health@1.0"], - test_suites: ["general-tests", "vts"], + name: "VtsHalAuthSecretTargetTest", + defaults: [ + "VtsHalTargetTestDefaults", + "use_libaidlvintf_gtest_helper_static", + ], + srcs: ["VtsHalAuthSecretTargetTest.cpp"], + static_libs: ["android.hardware.authsecret-ndk_platform"], + shared_libs: ["libbinder_ndk"], + test_suites: [ + "general-tests", + "vts", + ], + require_root: true, } diff --git a/authsecret/aidl/vts/OWNERS b/authsecret/aidl/vts/OWNERS new file mode 100644 index 0000000000..40d95e4bf0 --- /dev/null +++ b/authsecret/aidl/vts/OWNERS @@ -0,0 +1,2 @@ +chengyouho@google.com +frankwoo@google.com diff --git a/authsecret/aidl/vts/VtsHalAuthSecretTargetTest.cpp b/authsecret/aidl/vts/VtsHalAuthSecretTargetTest.cpp new file mode 100644 index 0000000000..31c28346b5 --- /dev/null +++ b/authsecret/aidl/vts/VtsHalAuthSecretTargetTest.cpp @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * 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 <aidl/Gtest.h> +#include <aidl/Vintf.h> + +#include <aidl/android/hardware/authsecret/IAuthSecret.h> +#include <android/binder_manager.h> +#include <android/binder_process.h> + +using ::aidl::android::hardware::authsecret::IAuthSecret; + +using ::ndk::SpAIBinder; + +/** + * There is no expected behaviour that can be tested so these tests check the + * HAL doesn't crash with different execution orders. + */ +class AuthSecretAidlTest : public testing::TestWithParam<std::string> { + public: + virtual void SetUp() override { + authsecret = IAuthSecret::fromBinder( + SpAIBinder(AServiceManager_waitForService(GetParam().c_str()))); + ASSERT_NE(authsecret, nullptr); + + // Notify LSS to generate PIN code '1234' and corresponding secret. + (void)system("cmd lock_settings set-pin 1234"); + + // All tests must enroll the correct secret first as this cannot be changed + // without a factory reset and the order of tests could change. + authsecret->setPrimaryUserCredential(CORRECT_SECRET); + } + + static void TearDownTestSuite() { + // clean up PIN code after testing + (void)system("cmd lock_settings clear --old 1234"); + } + + std::shared_ptr<IAuthSecret> authsecret; + std::vector<uint8_t> CORRECT_SECRET{61, 93, 124, 240, 5, 0, 7, 201, 9, 129, 11, 12, 0, 14, 0, 16}; + std::vector<uint8_t> WRONG_SECRET{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}; +}; + +/* Provision the primary user with a secret. */ +TEST_P(AuthSecretAidlTest, provisionPrimaryUserCredential) { + // Secret provisioned by SetUp() +} + +/* Provision the primary user with a secret and pass the secret again. */ +TEST_P(AuthSecretAidlTest, provisionPrimaryUserCredentialAndPassAgain) { + // Secret provisioned by SetUp() + authsecret->setPrimaryUserCredential(CORRECT_SECRET); +} + +/* Provision the primary user with a secret and pass the secret again repeatedly. */ +TEST_P(AuthSecretAidlTest, provisionPrimaryUserCredentialAndPassAgainMultipleTimes) { + // Secret provisioned by SetUp() + constexpr int N = 5; + for (int i = 0; i < N; ++i) { + authsecret->setPrimaryUserCredential(CORRECT_SECRET); + } +} + +/* Provision the primary user with a secret and then pass the wrong secret. This + * should never happen and is an framework bug if it does. As the secret is + * wrong, the HAL implementation may not be able to function correctly but it + * should fail gracefully. */ +TEST_P(AuthSecretAidlTest, provisionPrimaryUserCredentialAndWrongSecret) { + // Secret provisioned by SetUp() + authsecret->setPrimaryUserCredential(WRONG_SECRET); +} + +GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(AuthSecretAidlTest); +INSTANTIATE_TEST_SUITE_P( + PerInstance, AuthSecretAidlTest, + testing::ValuesIn(android::getAidlHalInstanceNames(IAuthSecret::descriptor)), + android::PrintInstanceNameToString); + +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + ABinderProcess_setThreadPoolMaxThreadCount(1); + ABinderProcess_startThreadPool(); + return RUN_ALL_TESTS(); +} diff --git a/automotive/audiocontrol/aidl/default/Android.bp b/automotive/audiocontrol/aidl/default/Android.bp index faf7ad2485..427709abb3 100644 --- a/automotive/audiocontrol/aidl/default/Android.bp +++ b/automotive/audiocontrol/aidl/default/Android.bp @@ -22,15 +22,18 @@ cc_binary { generated_sources: ["audio_policy_configuration_V7_0"], header_libs: ["libxsdc-utils"], shared_libs: [ + "android.frameworks.automotive.powerpolicy-ndk_platform", "android.hardware.automotive.audiocontrol-ndk_platform", "libbase", "libbinder_ndk", - "liblog", "libcutils", + "liblog", + "libpowerpolicyclient", "libxml2", ], srcs: [ "AudioControl.cpp", "main.cpp", + "PowerPolicyClient.cpp", ], } diff --git a/automotive/audiocontrol/aidl/default/PowerPolicyClient.cpp b/automotive/audiocontrol/aidl/default/PowerPolicyClient.cpp new file mode 100644 index 0000000000..765733716b --- /dev/null +++ b/automotive/audiocontrol/aidl/default/PowerPolicyClient.cpp @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * 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 "PowerPolicyClient.h" +#include "AudioControl.h" + +#include <android-base/logging.h> + +namespace aidl { +namespace android { +namespace hardware { +namespace automotive { +namespace audiocontrol { + +namespace aafap = aidl::android::frameworks::automotive::powerpolicy; + +using aafap::CarPowerPolicy; +using aafap::CarPowerPolicyFilter; +using aafap::PowerComponent; +using ::android::frameworks::automotive::powerpolicy::hasComponent; +using ::ndk::ScopedAStatus; + +namespace { + +constexpr PowerComponent kAudioComponent = PowerComponent::AUDIO; + +} // namespace + +PowerPolicyClient::PowerPolicyClient(std::shared_ptr<AudioControl> audioControl) + : mAudioControl(audioControl) {} + +void PowerPolicyClient::onInitFailed() { + LOG(ERROR) << "Initializing power policy client failed"; +} + +std::vector<PowerComponent> PowerPolicyClient::getComponentsOfInterest() { + std::vector<PowerComponent> components{kAudioComponent}; + return components; +} + +ScopedAStatus PowerPolicyClient::onPolicyChanged(const CarPowerPolicy& powerPolicy) { + if (hasComponent(powerPolicy.enabledComponents, kAudioComponent)) { + LOG(DEBUG) << "Power policy: Audio component is enabled"; + // TODO(b/173719953): Do something when AUDIO is enabled. + } else if (hasComponent(powerPolicy.disabledComponents, kAudioComponent)) { + LOG(DEBUG) << "Power policy: Audio component is disabled"; + // TODO(b/173719953): Do something when AUDIO is disabled. + } + return ScopedAStatus::ok(); +} + +} // namespace audiocontrol +} // namespace automotive +} // namespace hardware +} // namespace android +} // namespace aidl diff --git a/automotive/audiocontrol/aidl/default/PowerPolicyClient.h b/automotive/audiocontrol/aidl/default/PowerPolicyClient.h new file mode 100644 index 0000000000..0b4d5b6c0a --- /dev/null +++ b/automotive/audiocontrol/aidl/default/PowerPolicyClient.h @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * 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 AUTOMOTIVE_AUDIOCONTROL_AIDL_DEFAULT_POWERPOLICYCLIENT_H_ +#define AUTOMOTIVE_AUDIOCONTROL_AIDL_DEFAULT_POWERPOLICYCLIENT_H_ + +#include "PowerPolicyClientBase.h" + +#include <memory> + +namespace aidl::android::hardware::automotive::audiocontrol { + +class AudioControl; + +class PowerPolicyClient + : public ::android::frameworks::automotive::powerpolicy::PowerPolicyClientBase { + public: + explicit PowerPolicyClient(std::shared_ptr<AudioControl> audioControl); + + void onInitFailed(); + std::vector<::aidl::android::frameworks::automotive::powerpolicy::PowerComponent> + getComponentsOfInterest() override; + ::ndk::ScopedAStatus onPolicyChanged( + const ::aidl::android::frameworks::automotive::powerpolicy::CarPowerPolicy&) override; + + private: + std::shared_ptr<AudioControl> mAudioControl; +}; + +} // namespace aidl::android::hardware::automotive::audiocontrol + +#endif // AUTOMOTIVE_AUDIOCONTROL_AIDL_DEFAULT_POWERPOLICYCLIENT_H_ diff --git a/automotive/audiocontrol/aidl/default/main.cpp b/automotive/audiocontrol/aidl/default/main.cpp index 996665fe5c..9b259fca0a 100644 --- a/automotive/audiocontrol/aidl/default/main.cpp +++ b/automotive/audiocontrol/aidl/default/main.cpp @@ -15,22 +15,28 @@ */ #include "AudioControl.h" +#include "PowerPolicyClient.h" #include <android-base/logging.h> #include <android/binder_manager.h> #include <android/binder_process.h> using aidl::android::hardware::automotive::audiocontrol::AudioControl; +using aidl::android::hardware::automotive::audiocontrol::PowerPolicyClient; int main() { ABinderProcess_setThreadPoolMaxThreadCount(0); - std::shared_ptr<AudioControl> audioControl = ndk::SharedRefBase::make<AudioControl>(); + std::shared_ptr<AudioControl> audioControl = ::ndk::SharedRefBase::make<AudioControl>(); const std::string instance = std::string() + AudioControl::descriptor + "/default"; binder_status_t status = AServiceManager_addService(audioControl->asBinder().get(), instance.c_str()); CHECK(status == STATUS_OK); + std::shared_ptr<PowerPolicyClient> powerPolicyClient = + ::ndk::SharedRefBase::make<PowerPolicyClient>(audioControl); + powerPolicyClient->init(); + ABinderProcess_joinThreadPool(); return EXIT_FAILURE; // should not reach } diff --git a/automotive/vehicle/2.0/default/impl/vhal_v2_0/DefaultConfig.h b/automotive/vehicle/2.0/default/impl/vhal_v2_0/DefaultConfig.h index 8ef2b6018a..73513f4e4c 100644 --- a/automotive/vehicle/2.0/default/impl/vhal_v2_0/DefaultConfig.h +++ b/automotive/vehicle/2.0/default/impl/vhal_v2_0/DefaultConfig.h @@ -1153,6 +1153,47 @@ const ConfigDeclaration kVehicleProperties[]{ .changeMode = VehiclePropertyChangeMode::ON_CHANGE, }, }, + { + .config = + { + .prop = toInt(VehicleProperty::CLUSTER_SWITCH_UI), + .access = VehiclePropertyAccess::READ, + .changeMode = VehiclePropertyChangeMode::ON_CHANGE, + }, + }, + { + .config = + { + .prop = toInt(VehicleProperty::CLUSTER_DISPLAY_STATE), + .access = VehiclePropertyAccess::READ, + .changeMode = VehiclePropertyChangeMode::ON_CHANGE, + }, + }, + { + .config = + { + .prop = toInt(VehicleProperty::CLUSTER_REPORT_STATE), + .access = VehiclePropertyAccess::WRITE, + .changeMode = VehiclePropertyChangeMode::ON_CHANGE, + .configArray = {0, 0, 0, 9, 0, 0, 0, 0, 16}, + }, + }, + { + .config = + { + .prop = toInt(VehicleProperty::CLUSTER_REQUEST_DISPLAY), + .access = VehiclePropertyAccess::WRITE, + .changeMode = VehiclePropertyChangeMode::ON_CHANGE, + }, + }, + { + .config = + { + .prop = toInt(VehicleProperty::CLUSTER_NAVIGATION_STATE_LEGACY), + .access = VehiclePropertyAccess::WRITE, + .changeMode = VehiclePropertyChangeMode::ON_CHANGE, + }, + }, }; } // impl diff --git a/automotive/vehicle/2.0/types.hal b/automotive/vehicle/2.0/types.hal index 0488b57196..ed75e1dfb7 100644 --- a/automotive/vehicle/2.0/types.hal +++ b/automotive/vehicle/2.0/types.hal @@ -3027,6 +3027,115 @@ enum VehicleProperty : int32_t { | VehiclePropertyGroup:SYSTEM | VehiclePropertyType:INT64 | VehicleArea:GLOBAL), + + /** + * Starts the ClusterUI in cluster display. + * + * int32[0]: the type of ClusterUI to show + * 0 indicates ClusterHome, that is a home screen of cluster display, and provides + * the default UI and a kind of launcher functionality for cluster display. + * the other values are followed by OEM's definition. + * + * @change_mode VehiclePropertyChangeMode:ON_CHANGE + * @access VehiclePropertyAccess:READ + */ + CLUSTER_SWITCH_UI = ( + 0x0F34 + | VehiclePropertyGroup:SYSTEM + | VehiclePropertyType:INT32 + | VehicleArea:GLOBAL), + + /** + * Changes the state of the cluster display. + * + * int32[0]: on/off: 0 - off, 1 - on, -1 - don't care + * int32[1]: width: positive number - actual width in pixels + -1 - don't care (should set "don't care" both width and height) + * int32[2]: height: ditto with width + * int32[3]: Inset - left: positive number - actual left inset value in pixels + -1 - don't care (should set "don't care" all Inset fields) + * int32[4]: Inset - top + * int32[5]: Inset - right + * int32[6]: Inset - bottom + * + * @change_mode VehiclePropertyChangeMode:ON_CHANGE + * @access VehiclePropertyAccess:READ + */ + CLUSTER_DISPLAY_STATE = ( + 0x0F35 + | VehiclePropertyGroup:SYSTEM + | VehiclePropertyType:INT32_VEC + | VehicleArea:GLOBAL), + + /** + * Reports the current display state and ClusterUI state. + * + * ClusterHome will send this message when it handles CLUSTER_SWITCH_UI, CLUSTER_DISPLAY_STATE. + * + * In addition, ClusterHome should send this message when it starts for the first time. + * When ClusterOS receives this message and if the internal expectation is different with the + * received message, then it should send CLUSTER_SWITCH_UI, CLUSTER_DISPLAY_STATE again to + * match the state. + * + * int32[0]: on/off: 0 - off, 1 - on + * int32[1]: width + * int32[2]: height + * int32[3]: Inset - left + * int32[4]: Inset - top + * int32[5]: Inset - right + * int32[6]: Inset - bottom + * int32[7]: the type of ClusterUI in the fullscreen or main screen. + * 0 indicates ClusterHome. + * the other values are followed by OEM's definition. + * int32[8]: the type of ClusterUI in sub screen if the currently two UIs are shown. + * -1 indicates the area isn't used any more. + * bytes: the array to represent the availability of ClusterUI. + * 0 indicates non-available and 1 indicates available. + * For example, let's assume a car supports 3 UI like HOME, MAPS, CALL and it only supports + * CALL UI only when the cellular network is available. Then, if the nework is avaibale, + * it'll send [1 1 1], and if it's out of network, it'll send [1 1 0]. + * + * @change_mode VehiclePropertyChangeMode:ON_CHANGE + * @access VehiclePropertyAccess:WRITE + */ + CLUSTER_REPORT_STATE = ( + 0x0F36 + | VehiclePropertyGroup:SYSTEM + | VehiclePropertyType:MIXED + | VehicleArea:GLOBAL), + + /** + * Requests to change the cluster display state to show some ClusterUI. + * + * When the current display state is off and ClusterHome sends this message to ClusterOS to + * request to turn the display on to show some specific ClusterUI. + * ClusterOS should response this with CLUSTER_DISPLAY_STATE. + * + * int32[0]: the type of ClusterUI to show + * + * @change_mode VehiclePropertyChangeMode:ON_CHANGE + * @access VehiclePropertyAccess:WRITE + */ + CLUSTER_REQUEST_DISPLAY = ( + 0x0F37 + | VehiclePropertyGroup:SYSTEM + | VehiclePropertyType:INT32 + | VehicleArea:GLOBAL), + + /** + * Informs the current navigation state. + * + * bytes: the serialized message of NavigationStateProto. + * + * @change_mode VehiclePropertyChangeMode:ON_CHANGE + * @access VehiclePropertyAccess:WRITE + */ + CLUSTER_NAVIGATION_STATE_LEGACY = ( + 0x0F38 + | VehiclePropertyGroup:SYSTEM + | VehiclePropertyType:BYTES + | VehicleArea:GLOBAL), + }; /** @@ -3691,7 +3800,10 @@ struct VehiclePropConfig { * events. */ struct VehiclePropValue { - /** Time is elapsed nanoseconds since boot */ + /** + * Time is elapsed nanoseconds since boot. It's equivalent to + * {@code SystemClock.elapsedRealtimeNano()}. + */ int64_t timestamp; /** diff --git a/biometrics/face/aidl/aidl_api/android.hardware.biometrics.face/current/android/hardware/biometrics/face/AcquiredInfo.aidl b/biometrics/face/aidl/aidl_api/android.hardware.biometrics.face/current/android/hardware/biometrics/face/AcquiredInfo.aidl index a05fad9da9..56d097d5ff 100644 --- a/biometrics/face/aidl/aidl_api/android.hardware.biometrics.face/current/android/hardware/biometrics/face/AcquiredInfo.aidl +++ b/biometrics/face/aidl/aidl_api/android.hardware.biometrics.face/current/android/hardware/biometrics/face/AcquiredInfo.aidl @@ -2,13 +2,14 @@ // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // /////////////////////////////////////////////////////////////////////////////// -// This file is a snapshot of an AIDL interface (or parcelable). Do not try to -// edit this file. It looks like you are doing that because you have modified -// an AIDL interface in a backward-incompatible way, e.g., deleting a function -// from an interface or a field from a parcelable and it broke the build. That -// breakage is intended. +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. // -// You must not make a backward incompatible changes to the AIDL files built +// You must not make a backward incompatible change to any AIDL file built // with the aidl_interface module type with versions property set. The module // type is used to build AIDL files in a way that they can be used across // independently updatable components of the system. If a device is shipped diff --git a/biometrics/face/aidl/aidl_api/android.hardware.biometrics.face/current/android/hardware/biometrics/face/AuthenticationFrame.aidl b/biometrics/face/aidl/aidl_api/android.hardware.biometrics.face/current/android/hardware/biometrics/face/AuthenticationFrame.aidl new file mode 100644 index 0000000000..3043000f39 --- /dev/null +++ b/biometrics/face/aidl/aidl_api/android.hardware.biometrics.face/current/android/hardware/biometrics/face/AuthenticationFrame.aidl @@ -0,0 +1,23 @@ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. +// +// You must not make a backward incompatible change to any AIDL file built +// with the aidl_interface module type with versions property set. The module +// type is used to build AIDL files in a way that they can be used across +// independently updatable components of the system. If a device is shipped +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.hardware.biometrics.face; +@VintfStability +parcelable AuthenticationFrame { + android.hardware.biometrics.face.BaseFrame data; +} diff --git a/biometrics/face/aidl/aidl_api/android.hardware.biometrics.face/current/android/hardware/biometrics/face/BaseFrame.aidl b/biometrics/face/aidl/aidl_api/android.hardware.biometrics.face/current/android/hardware/biometrics/face/BaseFrame.aidl new file mode 100644 index 0000000000..27821e3ece --- /dev/null +++ b/biometrics/face/aidl/aidl_api/android.hardware.biometrics.face/current/android/hardware/biometrics/face/BaseFrame.aidl @@ -0,0 +1,28 @@ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. +// +// You must not make a backward incompatible change to any AIDL file built +// with the aidl_interface module type with versions property set. The module +// type is used to build AIDL files in a way that they can be used across +// independently updatable components of the system. If a device is shipped +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.hardware.biometrics.face; +@VintfStability +parcelable BaseFrame { + android.hardware.biometrics.face.AcquiredInfo acquiredInfo; + int vendorCode; + float pan; + float tilt; + float distance; + boolean isCancellable; +} diff --git a/biometrics/face/aidl/aidl_api/android.hardware.biometrics.face/current/android/hardware/biometrics/face/Cell.aidl b/biometrics/face/aidl/aidl_api/android.hardware.biometrics.face/current/android/hardware/biometrics/face/Cell.aidl new file mode 100644 index 0000000000..17c1ba3695 --- /dev/null +++ b/biometrics/face/aidl/aidl_api/android.hardware.biometrics.face/current/android/hardware/biometrics/face/Cell.aidl @@ -0,0 +1,25 @@ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. +// +// You must not make a backward incompatible change to any AIDL file built +// with the aidl_interface module type with versions property set. The module +// type is used to build AIDL files in a way that they can be used across +// independently updatable components of the system. If a device is shipped +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.hardware.biometrics.face; +@VintfStability +parcelable Cell { + int x; + int y; + int z; +} diff --git a/biometrics/face/aidl/aidl_api/android.hardware.biometrics.face/current/android/hardware/biometrics/face/EnrollmentFrame.aidl b/biometrics/face/aidl/aidl_api/android.hardware.biometrics.face/current/android/hardware/biometrics/face/EnrollmentFrame.aidl new file mode 100644 index 0000000000..c736e7e743 --- /dev/null +++ b/biometrics/face/aidl/aidl_api/android.hardware.biometrics.face/current/android/hardware/biometrics/face/EnrollmentFrame.aidl @@ -0,0 +1,25 @@ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. +// +// You must not make a backward incompatible change to any AIDL file built +// with the aidl_interface module type with versions property set. The module +// type is used to build AIDL files in a way that they can be used across +// independently updatable components of the system. If a device is shipped +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.hardware.biometrics.face; +@VintfStability +parcelable EnrollmentFrame { + @nullable android.hardware.biometrics.face.Cell cell; + android.hardware.biometrics.face.EnrollmentStage stage; + android.hardware.biometrics.face.BaseFrame data; +} diff --git a/biometrics/face/aidl/aidl_api/android.hardware.biometrics.face/current/android/hardware/biometrics/face/EnrollmentStage.aidl b/biometrics/face/aidl/aidl_api/android.hardware.biometrics.face/current/android/hardware/biometrics/face/EnrollmentStage.aidl new file mode 100644 index 0000000000..af6be9061a --- /dev/null +++ b/biometrics/face/aidl/aidl_api/android.hardware.biometrics.face/current/android/hardware/biometrics/face/EnrollmentStage.aidl @@ -0,0 +1,28 @@ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. +// +// You must not make a backward incompatible change to any AIDL file built +// with the aidl_interface module type with versions property set. The module +// type is used to build AIDL files in a way that they can be used across +// independently updatable components of the system. If a device is shipped +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.hardware.biometrics.face; +@Backing(type="byte") @VintfStability +enum EnrollmentStage { + FIRST_FRAME_RECEIVED = 0, + WAITING_FOR_CENTERING = 1, + HOLD_STILL_IN_CENTER = 2, + ENROLLING_MOVEMENT_1 = 3, + ENROLLING_MOVEMENT_2 = 4, + ENROLLMENT_FINISHED = 5, +} diff --git a/biometrics/face/aidl/aidl_api/android.hardware.biometrics.face/current/android/hardware/biometrics/face/EnrollmentStageConfig.aidl b/biometrics/face/aidl/aidl_api/android.hardware.biometrics.face/current/android/hardware/biometrics/face/EnrollmentStageConfig.aidl new file mode 100644 index 0000000000..268ce52e46 --- /dev/null +++ b/biometrics/face/aidl/aidl_api/android.hardware.biometrics.face/current/android/hardware/biometrics/face/EnrollmentStageConfig.aidl @@ -0,0 +1,24 @@ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. +// +// You must not make a backward incompatible change to any AIDL file built +// with the aidl_interface module type with versions property set. The module +// type is used to build AIDL files in a way that they can be used across +// independently updatable components of the system. If a device is shipped +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.hardware.biometrics.face; +@VintfStability +parcelable EnrollmentStageConfig { + android.hardware.biometrics.face.EnrollmentStage stage; + List<android.hardware.biometrics.face.Cell> cells; +} diff --git a/biometrics/face/aidl/aidl_api/android.hardware.biometrics.face/current/android/hardware/biometrics/face/EnrollmentType.aidl b/biometrics/face/aidl/aidl_api/android.hardware.biometrics.face/current/android/hardware/biometrics/face/EnrollmentType.aidl new file mode 100644 index 0000000000..7295b3b4ee --- /dev/null +++ b/biometrics/face/aidl/aidl_api/android.hardware.biometrics.face/current/android/hardware/biometrics/face/EnrollmentType.aidl @@ -0,0 +1,24 @@ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. +// +// You must not make a backward incompatible change to any AIDL file built +// with the aidl_interface module type with versions property set. The module +// type is used to build AIDL files in a way that they can be used across +// independently updatable components of the system. If a device is shipped +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.hardware.biometrics.face; +@Backing(type="byte") @VintfStability +enum EnrollmentType { + DEFAULT = 0, + ACCESSIBILITY = 1, +} diff --git a/biometrics/face/aidl/aidl_api/android.hardware.biometrics.face/current/android/hardware/biometrics/face/Error.aidl b/biometrics/face/aidl/aidl_api/android.hardware.biometrics.face/current/android/hardware/biometrics/face/Error.aidl index 15fcbf959a..cba6ec595f 100644 --- a/biometrics/face/aidl/aidl_api/android.hardware.biometrics.face/current/android/hardware/biometrics/face/Error.aidl +++ b/biometrics/face/aidl/aidl_api/android.hardware.biometrics.face/current/android/hardware/biometrics/face/Error.aidl @@ -2,13 +2,14 @@ // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // /////////////////////////////////////////////////////////////////////////////// -// This file is a snapshot of an AIDL interface (or parcelable). Do not try to -// edit this file. It looks like you are doing that because you have modified -// an AIDL interface in a backward-incompatible way, e.g., deleting a function -// from an interface or a field from a parcelable and it broke the build. That -// breakage is intended. +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. // -// You must not make a backward incompatible changes to the AIDL files built +// You must not make a backward incompatible change to any AIDL file built // with the aidl_interface module type with versions property set. The module // type is used to build AIDL files in a way that they can be used across // independently updatable components of the system. If a device is shipped diff --git a/biometrics/face/aidl/aidl_api/android.hardware.biometrics.face/current/android/hardware/biometrics/face/FaceSensorType.aidl b/biometrics/face/aidl/aidl_api/android.hardware.biometrics.face/current/android/hardware/biometrics/face/FaceSensorType.aidl index 943129ed8a..b08c345b5d 100644 --- a/biometrics/face/aidl/aidl_api/android.hardware.biometrics.face/current/android/hardware/biometrics/face/FaceSensorType.aidl +++ b/biometrics/face/aidl/aidl_api/android.hardware.biometrics.face/current/android/hardware/biometrics/face/FaceSensorType.aidl @@ -2,13 +2,14 @@ // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // /////////////////////////////////////////////////////////////////////////////// -// This file is a snapshot of an AIDL interface (or parcelable). Do not try to -// edit this file. It looks like you are doing that because you have modified -// an AIDL interface in a backward-incompatible way, e.g., deleting a function -// from an interface or a field from a parcelable and it broke the build. That -// breakage is intended. +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. // -// You must not make a backward incompatible changes to the AIDL files built +// You must not make a backward incompatible change to any AIDL file built // with the aidl_interface module type with versions property set. The module // type is used to build AIDL files in a way that they can be used across // independently updatable components of the system. If a device is shipped diff --git a/biometrics/face/aidl/aidl_api/android.hardware.biometrics.face/current/android/hardware/biometrics/face/IFace.aidl b/biometrics/face/aidl/aidl_api/android.hardware.biometrics.face/current/android/hardware/biometrics/face/IFace.aidl index 518fb147f4..65afaa493f 100644 --- a/biometrics/face/aidl/aidl_api/android.hardware.biometrics.face/current/android/hardware/biometrics/face/IFace.aidl +++ b/biometrics/face/aidl/aidl_api/android.hardware.biometrics.face/current/android/hardware/biometrics/face/IFace.aidl @@ -2,13 +2,14 @@ // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // /////////////////////////////////////////////////////////////////////////////// -// This file is a snapshot of an AIDL interface (or parcelable). Do not try to -// edit this file. It looks like you are doing that because you have modified -// an AIDL interface in a backward-incompatible way, e.g., deleting a function -// from an interface or a field from a parcelable and it broke the build. That -// breakage is intended. +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. // -// You must not make a backward incompatible changes to the AIDL files built +// You must not make a backward incompatible change to any AIDL file built // with the aidl_interface module type with versions property set. The module // type is used to build AIDL files in a way that they can be used across // independently updatable components of the system. If a device is shipped diff --git a/biometrics/face/aidl/aidl_api/android.hardware.biometrics.face/current/android/hardware/biometrics/face/ISession.aidl b/biometrics/face/aidl/aidl_api/android.hardware.biometrics.face/current/android/hardware/biometrics/face/ISession.aidl index 5d9a430779..203e2752b8 100644 --- a/biometrics/face/aidl/aidl_api/android.hardware.biometrics.face/current/android/hardware/biometrics/face/ISession.aidl +++ b/biometrics/face/aidl/aidl_api/android.hardware.biometrics.face/current/android/hardware/biometrics/face/ISession.aidl @@ -2,13 +2,14 @@ // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // /////////////////////////////////////////////////////////////////////////////// -// This file is a snapshot of an AIDL interface (or parcelable). Do not try to -// edit this file. It looks like you are doing that because you have modified -// an AIDL interface in a backward-incompatible way, e.g., deleting a function -// from an interface or a field from a parcelable and it broke the build. That -// breakage is intended. +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. // -// You must not make a backward incompatible changes to the AIDL files built +// You must not make a backward incompatible change to any AIDL file built // with the aidl_interface module type with versions property set. The module // type is used to build AIDL files in a way that they can be used across // independently updatable components of the system. If a device is shipped @@ -20,7 +21,7 @@ package android.hardware.biometrics.face; interface ISession { void generateChallenge(in int cookie, in int timeoutSec); void revokeChallenge(in int cookie, in long challenge); - android.hardware.biometrics.common.ICancellationSignal enroll(in int cookie, in android.hardware.keymaster.HardwareAuthToken hat, in android.hardware.common.NativeHandle previewSurface); + android.hardware.biometrics.common.ICancellationSignal enroll(in int cookie, in android.hardware.biometrics.face.EnrollmentType enrollmentType, in android.hardware.keymaster.HardwareAuthToken hat, in android.hardware.common.NativeHandle previewSurface); android.hardware.biometrics.common.ICancellationSignal authenticate(in int cookie, in long operationId); android.hardware.biometrics.common.ICancellationSignal detectInteraction(in int cookie); void enumerateEnrollments(in int cookie); diff --git a/biometrics/face/aidl/aidl_api/android.hardware.biometrics.face/current/android/hardware/biometrics/face/ISessionCallback.aidl b/biometrics/face/aidl/aidl_api/android.hardware.biometrics.face/current/android/hardware/biometrics/face/ISessionCallback.aidl index caf65aea1c..a81c79a42b 100644 --- a/biometrics/face/aidl/aidl_api/android.hardware.biometrics.face/current/android/hardware/biometrics/face/ISessionCallback.aidl +++ b/biometrics/face/aidl/aidl_api/android.hardware.biometrics.face/current/android/hardware/biometrics/face/ISessionCallback.aidl @@ -2,13 +2,14 @@ // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // /////////////////////////////////////////////////////////////////////////////// -// This file is a snapshot of an AIDL interface (or parcelable). Do not try to -// edit this file. It looks like you are doing that because you have modified -// an AIDL interface in a backward-incompatible way, e.g., deleting a function -// from an interface or a field from a parcelable and it broke the build. That -// breakage is intended. +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. // -// You must not make a backward incompatible changes to the AIDL files built +// You must not make a backward incompatible change to any AIDL file built // with the aidl_interface module type with versions property set. The module // type is used to build AIDL files in a way that they can be used across // independently updatable components of the system. If a device is shipped @@ -21,7 +22,8 @@ interface ISessionCallback { void onStateChanged(in int cookie, in android.hardware.biometrics.face.SessionState state); void onChallengeGenerated(in long challenge); void onChallengeRevoked(in long challenge); - void onAcquired(in android.hardware.biometrics.face.AcquiredInfo info, in int vendorCode); + void onAuthenticationFrame(in android.hardware.biometrics.face.AuthenticationFrame frame); + void onEnrollmentFrame(in android.hardware.biometrics.face.EnrollmentFrame frame); void onError(in android.hardware.biometrics.face.Error error, in int vendorCode); void onEnrollmentProgress(in int enrollmentId, int remaining); void onAuthenticationSucceeded(in int enrollmentId, in android.hardware.keymaster.HardwareAuthToken hat); @@ -33,5 +35,5 @@ interface ISessionCallback { void onEnrollmentsEnumerated(in int[] enrollmentIds); void onEnrollmentsRemoved(in int[] enrollmentIds); void onAuthenticatorIdRetrieved(in long authenticatorId); - void onAuthenticatorIdInvalidated(); + void onAuthenticatorIdInvalidated(in long newAuthenticatorId); } diff --git a/biometrics/face/aidl/aidl_api/android.hardware.biometrics.face/current/android/hardware/biometrics/face/SensorProps.aidl b/biometrics/face/aidl/aidl_api/android.hardware.biometrics.face/current/android/hardware/biometrics/face/SensorProps.aidl index 365ae58a46..a52829cc5b 100644 --- a/biometrics/face/aidl/aidl_api/android.hardware.biometrics.face/current/android/hardware/biometrics/face/SensorProps.aidl +++ b/biometrics/face/aidl/aidl_api/android.hardware.biometrics.face/current/android/hardware/biometrics/face/SensorProps.aidl @@ -2,13 +2,14 @@ // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // /////////////////////////////////////////////////////////////////////////////// -// This file is a snapshot of an AIDL interface (or parcelable). Do not try to -// edit this file. It looks like you are doing that because you have modified -// an AIDL interface in a backward-incompatible way, e.g., deleting a function -// from an interface or a field from a parcelable and it broke the build. That -// breakage is intended. +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. // -// You must not make a backward incompatible changes to the AIDL files built +// You must not make a backward incompatible change to any AIDL file built // with the aidl_interface module type with versions property set. The module // type is used to build AIDL files in a way that they can be used across // independently updatable components of the system. If a device is shipped diff --git a/biometrics/face/aidl/aidl_api/android.hardware.biometrics.face/current/android/hardware/biometrics/face/SessionState.aidl b/biometrics/face/aidl/aidl_api/android.hardware.biometrics.face/current/android/hardware/biometrics/face/SessionState.aidl index 8e5139b38b..12a5eabbc4 100644 --- a/biometrics/face/aidl/aidl_api/android.hardware.biometrics.face/current/android/hardware/biometrics/face/SessionState.aidl +++ b/biometrics/face/aidl/aidl_api/android.hardware.biometrics.face/current/android/hardware/biometrics/face/SessionState.aidl @@ -2,13 +2,14 @@ // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // /////////////////////////////////////////////////////////////////////////////// -// This file is a snapshot of an AIDL interface (or parcelable). Do not try to -// edit this file. It looks like you are doing that because you have modified -// an AIDL interface in a backward-incompatible way, e.g., deleting a function -// from an interface or a field from a parcelable and it broke the build. That -// breakage is intended. +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. // -// You must not make a backward incompatible changes to the AIDL files built +// You must not make a backward incompatible change to any AIDL file built // with the aidl_interface module type with versions property set. The module // type is used to build AIDL files in a way that they can be used across // independently updatable components of the system. If a device is shipped diff --git a/biometrics/face/aidl/android/hardware/biometrics/face/AcquiredInfo.aidl b/biometrics/face/aidl/android/hardware/biometrics/face/AcquiredInfo.aidl index 56a600f824..217a9bbfb1 100644 --- a/biometrics/face/aidl/android/hardware/biometrics/face/AcquiredInfo.aidl +++ b/biometrics/face/aidl/android/hardware/biometrics/face/AcquiredInfo.aidl @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 The Android Open Source Project + * Copyright (C) 2021 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/biometrics/face/aidl/android/hardware/biometrics/face/AuthenticationFrame.aidl b/biometrics/face/aidl/android/hardware/biometrics/face/AuthenticationFrame.aidl new file mode 100644 index 0000000000..47cad3c61d --- /dev/null +++ b/biometrics/face/aidl/android/hardware/biometrics/face/AuthenticationFrame.aidl @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * 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. + */ + +package android.hardware.biometrics.face; + +import android.hardware.biometrics.face.BaseFrame; + +/** + * Describes an individual frame captured during authentication. + */ +@VintfStability +parcelable AuthenticationFrame { + + /** + * The frame metadata. Can be used by the framework to provide user feedback. + */ + BaseFrame data; +} diff --git a/biometrics/face/aidl/android/hardware/biometrics/face/BaseFrame.aidl b/biometrics/face/aidl/android/hardware/biometrics/face/BaseFrame.aidl new file mode 100644 index 0000000000..9e6b98adfa --- /dev/null +++ b/biometrics/face/aidl/android/hardware/biometrics/face/BaseFrame.aidl @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * 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. + */ + +package android.hardware.biometrics.face; + +import android.hardware.biometrics.face.AcquiredInfo; + +/** + * Metadata of an individual frame. Can be used by the framework to provide user feedback. + * This parcelable is part of AuthenticationFrame and EnrollmentFrame, and shouldn't be used + * independently of those parcelables. + */ +@VintfStability +parcelable BaseFrame { + /** + * Information about the frame that can be used by the framework to provide feedback to the + * user, for example ask the user to move their face in a certain way. + */ + AcquiredInfo acquiredInfo; + + /** + * If acquiredInfo is set to AcquiredInfo::VENDOR. This is the index into the configuration + * "com.android.internal.R.array.face_acquired_vendor" that's installed on the vendor partition. + * Otherwise, this value must be ignored. + */ + int vendorCode; + + /** + * Pan value. It is recommended to use the range of [-1, 1] to represent valid values, and + * anything outside of that range to represent errors. However, vendors are free to define + * their own way of representing valid values and errors. + */ + float pan; + + /** + * Tilt value. It is recommended to use the range of [-1, 1] to represent valid values, and + * anything outside of that range to represent errors. However, vendors are free to define + * their own way of representing valid values and errors. + */ + float tilt; + + /** + * Distance value. It is recommended to use the range of [-1, 1] to represent valid values, and + * anything outside of that range to represent errors. However, vendors are free to define + * their own way of representing valid values and errors. + */ + float distance; + + /** + * Indicates that the HAL can no longer continue with authentication or enrollment. This allows + * the framework to correlate a failure condition with a particular AcquiredInfo, rather than + * having a sequence of AcquiredInfo + Error. + */ + boolean isCancellable; +} diff --git a/health/1.0/default/libhealthd/healthd_board_default.cpp b/biometrics/face/aidl/android/hardware/biometrics/face/Cell.aidl index 127f98e444..77f33b96f3 100644 --- a/health/1.0/default/libhealthd/healthd_board_default.cpp +++ b/biometrics/face/aidl/android/hardware/biometrics/face/Cell.aidl @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013 The Android Open Source Project + * Copyright (C) 2021 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,15 +14,14 @@ * limitations under the License. */ -#include <healthd/healthd.h> +package android.hardware.biometrics.face; -void healthd_board_init(struct healthd_config*) -{ - // use defaults -} - -int healthd_board_battery_update(struct android::BatteryProperties*) -{ - // return 0 to log periodic polled battery status to kernel log - return 0; +/** + * Coordinates of an enrollment UI cell in a vendor-defined coordinate system. + */ +@VintfStability +parcelable Cell { + int x; + int y; + int z; } diff --git a/biometrics/face/aidl/android/hardware/biometrics/face/EnrollmentFrame.aidl b/biometrics/face/aidl/android/hardware/biometrics/face/EnrollmentFrame.aidl new file mode 100644 index 0000000000..d4f9771d03 --- /dev/null +++ b/biometrics/face/aidl/android/hardware/biometrics/face/EnrollmentFrame.aidl @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * 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. + */ + +package android.hardware.biometrics.face; + +import android.hardware.biometrics.face.Cell; +import android.hardware.biometrics.face.EnrollmentStage; +import android.hardware.biometrics.face.BaseFrame; + +/** + * Describes an individual frame captured during enrollment. + */ +@VintfStability +parcelable EnrollmentFrame { + /** + * The enrollment UI cell that was captured in this frame, or null if no cell was captured. + */ + @nullable Cell cell; + + /** + * The enrollment stage for which this frame was captured. + */ + EnrollmentStage stage; + + /** + * The frame metadata. Can be used by the framework to provide user feedback. + */ + BaseFrame data; +} diff --git a/biometrics/face/aidl/android/hardware/biometrics/face/EnrollmentStage.aidl b/biometrics/face/aidl/android/hardware/biometrics/face/EnrollmentStage.aidl new file mode 100644 index 0000000000..bbc874fb0c --- /dev/null +++ b/biometrics/face/aidl/android/hardware/biometrics/face/EnrollmentStage.aidl @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * 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. + */ + +package android.hardware.biometrics.face; + +/** + * Enrollment stages that can be mapped to the enrollment UI actions in the framework. + */ +@VintfStability +@Backing(type="byte") +enum EnrollmentStage { + + /** + * HAL has obtained the first camera frame. + */ + FIRST_FRAME_RECEIVED, + + /** + * HAL is waiting for the user's face to be centered. + */ + WAITING_FOR_CENTERING, + + /** + * HAL is expecting the user's face to stay centered. + */ + HOLD_STILL_IN_CENTER, + + /** + * Vendor defined movement 1. + */ + ENROLLING_MOVEMENT_1, + + /** + * Vendor defined movement 2. + */ + ENROLLING_MOVEMENT_2, + + /** + * HAL has finished the enrollment. + */ + ENROLLMENT_FINISHED, +} diff --git a/biometrics/face/aidl/android/hardware/biometrics/face/EnrollmentStageConfig.aidl b/biometrics/face/aidl/android/hardware/biometrics/face/EnrollmentStageConfig.aidl new file mode 100644 index 0000000000..0b64e2bfbd --- /dev/null +++ b/biometrics/face/aidl/android/hardware/biometrics/face/EnrollmentStageConfig.aidl @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * 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. + */ + +package android.hardware.biometrics.face; + +import android.hardware.biometrics.face.Cell; +import android.hardware.biometrics.face.EnrollmentStage; + +@VintfStability +parcelable EnrollmentStageConfig { + /** + * The stage that's being configured. + */ + EnrollmentStage stage; + + /** + * Optional list of cells that must be completed to finish this stage. + */ + List<Cell> cells; +} diff --git a/health/1.0/default/HealthService.cpp b/biometrics/face/aidl/android/hardware/biometrics/face/EnrollmentType.aidl index 55848d2841..d7f31756bc 100644 --- a/health/1.0/default/HealthService.cpp +++ b/biometrics/face/aidl/android/hardware/biometrics/face/EnrollmentType.aidl @@ -1,5 +1,5 @@ /* - * Copyright 2016 The Android Open Source Project + * Copyright (C) 2021 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,14 +14,11 @@ * limitations under the License. */ -#define LOG_TAG "android.hardware.health@1.0-service" +package android.hardware.biometrics.face; -#include <android/hardware/health/1.0/IHealth.h> -#include <hidl/LegacySupport.h> - -using android::hardware::health::V1_0::IHealth; -using android::hardware::defaultPassthroughServiceImplementation; - -int main() { - return defaultPassthroughServiceImplementation<IHealth>(); +@VintfStability +@Backing(type="byte") +enum EnrollmentType { + DEFAULT, + ACCESSIBILITY, } diff --git a/biometrics/face/aidl/android/hardware/biometrics/face/Error.aidl b/biometrics/face/aidl/android/hardware/biometrics/face/Error.aidl index 1d02456f6d..d88370f129 100644 --- a/biometrics/face/aidl/android/hardware/biometrics/face/Error.aidl +++ b/biometrics/face/aidl/android/hardware/biometrics/face/Error.aidl @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 The Android Open Source Project + * Copyright (C) 2021 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/biometrics/face/aidl/android/hardware/biometrics/face/FaceSensorType.aidl b/biometrics/face/aidl/android/hardware/biometrics/face/FaceSensorType.aidl index 766f732a56..2a5dd20be1 100644 --- a/biometrics/face/aidl/android/hardware/biometrics/face/FaceSensorType.aidl +++ b/biometrics/face/aidl/android/hardware/biometrics/face/FaceSensorType.aidl @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 The Android Open Source Project + * Copyright (C) 2021 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/biometrics/face/aidl/android/hardware/biometrics/face/IFace.aidl b/biometrics/face/aidl/android/hardware/biometrics/face/IFace.aidl index e9a66e2ea3..f9ed4b1701 100644 --- a/biometrics/face/aidl/android/hardware/biometrics/face/IFace.aidl +++ b/biometrics/face/aidl/android/hardware/biometrics/face/IFace.aidl @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 The Android Open Source Project + * Copyright (C) 2021 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/biometrics/face/aidl/android/hardware/biometrics/face/ISession.aidl b/biometrics/face/aidl/android/hardware/biometrics/face/ISession.aidl index 425b3525eb..34a1f8b5a9 100644 --- a/biometrics/face/aidl/android/hardware/biometrics/face/ISession.aidl +++ b/biometrics/face/aidl/android/hardware/biometrics/face/ISession.aidl @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 The Android Open Source Project + * Copyright (C) 2021 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,6 +17,7 @@ package android.hardware.biometrics.face; import android.hardware.biometrics.common.ICancellationSignal; +import android.hardware.biometrics.face.EnrollmentType; import android.hardware.keymaster.HardwareAuthToken; import android.hardware.common.NativeHandle; @@ -85,6 +86,18 @@ interface ISession { void revokeChallenge(in int cookie, in long challenge); /** + * getEnrollmentConfig: + * + * Returns the enrollment configuration depending on the provided enrollment type. Enrollment + * configuration determines how many stages the enrollment will have and the requirements for + * each of the stages. + * + * @param enrollmentType See the EnrollmentType enum. + * @return A list of EnrollmentStageConfig that describes each enrollment stage. + * + List<EnrollmentStageConfig> getEnrollmentConfig(in EnrollmentType enrollmentType); + + /** * enroll: * * A request to add a face enrollment. @@ -118,6 +131,7 @@ interface ISession { * * @param cookie An identifier used to track subsystem operations related to this call path. The * client must guarantee that it is unique per ISession. + * @param enrollmentType See the EnrollmentType enum. * @param previewSurface A surface provided by the framework if SensorProps#halControlsPreview is * set to true. The HAL must send the preview frames to previewSurface if * it's not null. @@ -125,7 +139,8 @@ interface ISession { * @return ICancellationSignal An object that can be used by the framework to cancel this * operation. */ - ICancellationSignal enroll(in int cookie, in HardwareAuthToken hat, in NativeHandle previewSurface); + ICancellationSignal enroll(in int cookie, in EnrollmentType enrollmentType, + in HardwareAuthToken hat, in NativeHandle previewSurface); /** * authenticate: diff --git a/biometrics/face/aidl/android/hardware/biometrics/face/ISessionCallback.aidl b/biometrics/face/aidl/android/hardware/biometrics/face/ISessionCallback.aidl index fd4a648843..9178e3aa7b 100644 --- a/biometrics/face/aidl/android/hardware/biometrics/face/ISessionCallback.aidl +++ b/biometrics/face/aidl/android/hardware/biometrics/face/ISessionCallback.aidl @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 The Android Open Source Project + * Copyright (C) 2021 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,6 +17,8 @@ package android.hardware.biometrics.face; import android.hardware.biometrics.face.AcquiredInfo; +import android.hardware.biometrics.face.AuthenticationFrame; +import android.hardware.biometrics.face.EnrollmentFrame; import android.hardware.biometrics.face.Error; import android.hardware.biometrics.face.SessionState; import android.hardware.keymaster.HardwareAuthToken; @@ -40,20 +42,26 @@ interface ISessionCallback { /** * This method must only be used to notify the framework during the following states: - * 1) SessionState::ENROLLING - * 2) SessionState::AUTHENTICATING - * 3) SessionState::DETECTING_INTERACTION + * 1) SessionState::AUTHENTICATING + * 2) SessionState::DETECTING_INTERACTION * * These messages may be used to provide user guidance multiple times if necessary per * operation. * - * @param info See the AcquiredInfo enum. - * @param vendorCode Only valid if info == AcquiredInfo::VENDOR. The vendorCode must be used to - * index into the configuration - * com.android.internal.R.array.face_acquired_vendor that's installed - * on the vendor partition. + * @param frame See the AuthenticationFrame enum. */ - void onAcquired(in AcquiredInfo info, in int vendorCode); + void onAuthenticationFrame(in AuthenticationFrame frame); + + /** + * This method must only be used to notify the framework during the SessionState::ENROLLING + * state. + * + * These messages may be used to provide user guidance multiple times if necessary per + * operation. + * + * @param frame See the EnrollmentFrame enum. + */ + void onEnrollmentFrame(in EnrollmentFrame frame); /** * This method must only be used to notify the framework during the following states: @@ -195,6 +203,9 @@ interface ISessionCallback { * SessionState::INVALIDATING_AUTHENTICATOR_ID. * * See ISession#invalidateAuthenticatorId for more information. + * + * @param newAuthenticatorId The new entropy-encoded random identifier associated with the + * current set of enrollments. */ - void onAuthenticatorIdInvalidated(); + void onAuthenticatorIdInvalidated(in long newAuthenticatorId); } diff --git a/biometrics/face/aidl/android/hardware/biometrics/face/SensorProps.aidl b/biometrics/face/aidl/android/hardware/biometrics/face/SensorProps.aidl index 9c2f9a1734..335f2f9e52 100644 --- a/biometrics/face/aidl/android/hardware/biometrics/face/SensorProps.aidl +++ b/biometrics/face/aidl/android/hardware/biometrics/face/SensorProps.aidl @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 The Android Open Source Project + * Copyright (C) 2021 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/biometrics/face/aidl/android/hardware/biometrics/face/SessionState.aidl b/biometrics/face/aidl/android/hardware/biometrics/face/SessionState.aidl index 1878f7c823..e56f5d84fc 100644 --- a/biometrics/face/aidl/android/hardware/biometrics/face/SessionState.aidl +++ b/biometrics/face/aidl/android/hardware/biometrics/face/SessionState.aidl @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 The Android Open Source Project + * Copyright (C) 2021 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/biometrics/face/aidl/default/Session.cpp b/biometrics/face/aidl/default/Session.cpp index c5d7d23ecb..df5187955f 100644 --- a/biometrics/face/aidl/default/Session.cpp +++ b/biometrics/face/aidl/default/Session.cpp @@ -23,6 +23,7 @@ namespace aidl::android::hardware::biometrics::face { class CancellationSignal : public common::BnCancellationSignal { private: std::shared_ptr<ISessionCallback> cb_; + public: explicit CancellationSignal(std::shared_ptr<ISessionCallback> cb) : cb_(std::move(cb)) {} @@ -53,10 +54,10 @@ ndk::ScopedAStatus Session::revokeChallenge(int32_t /*cookie*/, int64_t challeng return ndk::ScopedAStatus::ok(); } -ndk::ScopedAStatus Session::enroll(int32_t /*cookie*/, const keymaster::HardwareAuthToken& /*hat*/, - const NativeHandle& /*previewSurface*/, - std::shared_ptr<biometrics::common::ICancellationSignal>* - /*returnVal*/) { +ndk::ScopedAStatus Session::enroll( + int32_t /*cookie*/, biometrics::face::EnrollmentType /*enrollmentType*/, + const keymaster::HardwareAuthToken& /*hat*/, const NativeHandle& /*previewSurface*/, + std::shared_ptr<biometrics::common::ICancellationSignal>* /*return_val*/) { return ndk::ScopedAStatus::ok(); } @@ -86,9 +87,9 @@ ndk::ScopedAStatus Session::enumerateEnrollments(int32_t /*cookie*/) { ndk::ScopedAStatus Session::removeEnrollments(int32_t /*cookie*/, const std::vector<int32_t>& /*enrollmentIds*/) { if (cb_) { - cb_->onStateChanged(0, SessionState::REMOVING_ENROLLMENTS); - cb_->onEnrollmentsRemoved(std::vector<int32_t>()); - cb_->onStateChanged(0, SessionState::IDLING); + cb_->onStateChanged(0, SessionState::REMOVING_ENROLLMENTS); + cb_->onEnrollmentsRemoved(std::vector<int32_t>()); + cb_->onStateChanged(0, SessionState::IDLING); } return ndk::ScopedAStatus::ok(); } @@ -115,4 +116,5 @@ ndk::ScopedAStatus Session::resetLockout(int32_t /*cookie*/, } return ndk::ScopedAStatus::ok(); } + } // namespace aidl::android::hardware::biometrics::face diff --git a/biometrics/face/aidl/default/Session.h b/biometrics/face/aidl/default/Session.h index 2bb5c42a99..347d2020fc 100644 --- a/biometrics/face/aidl/default/Session.h +++ b/biometrics/face/aidl/default/Session.h @@ -35,9 +35,9 @@ class Session : public BnSession { ndk::ScopedAStatus revokeChallenge(int32_t cookie, int64_t challenge) override; ndk::ScopedAStatus enroll( - int32_t cookie, const keymaster::HardwareAuthToken& hat, - const NativeHandle& previewSurface, - std::shared_ptr<biometrics::common::ICancellationSignal>* returnVal) override; + int32_t cookie, biometrics::face::EnrollmentType enrollmentType, + const keymaster::HardwareAuthToken& hat, const NativeHandle& previewSurface, + std::shared_ptr<biometrics::common::ICancellationSignal>* return_val) override; ndk::ScopedAStatus authenticate( int32_t cookie, int64_t keystoreOperationId, diff --git a/biometrics/face/aidl/vts/VtsHalBiometricsFaceTargetTest.cpp b/biometrics/face/aidl/vts/VtsHalBiometricsFaceTargetTest.cpp index 5f1030620d..5b02a57ce8 100644 --- a/biometrics/face/aidl/vts/VtsHalBiometricsFaceTargetTest.cpp +++ b/biometrics/face/aidl/vts/VtsHalBiometricsFaceTargetTest.cpp @@ -59,7 +59,11 @@ class SessionCallback : public BnSessionCallback { return ndk::ScopedAStatus::ok(); } - ndk::ScopedAStatus onAcquired(AcquiredInfo /*info*/, int32_t /*vendorCode*/) override { + ndk::ScopedAStatus onAuthenticationFrame(const AuthenticationFrame& /*frame*/) override { + return ndk::ScopedAStatus::ok(); + } + + ndk::ScopedAStatus onEnrollmentFrame(const EnrollmentFrame& /*frame*/) override { return ndk::ScopedAStatus::ok(); } @@ -103,7 +107,9 @@ class SessionCallback : public BnSessionCallback { return ndk::ScopedAStatus::ok(); } - ndk::ScopedAStatus onAuthenticatorIdInvalidated() override { return ndk::ScopedAStatus::ok(); } + ndk::ScopedAStatus onAuthenticatorIdInvalidated(int64_t /*newAuthenticatorId*/) override { + return ndk::ScopedAStatus::ok(); + } private: std::promise<SessionCallbackInvocation> invocation_promise_; diff --git a/biometrics/fingerprint/aidl/aidl_api/android.hardware.biometrics.fingerprint/current/android/hardware/biometrics/fingerprint/AcquiredInfo.aidl b/biometrics/fingerprint/aidl/aidl_api/android.hardware.biometrics.fingerprint/current/android/hardware/biometrics/fingerprint/AcquiredInfo.aidl index df30dcae47..d5f2ed27c6 100644 --- a/biometrics/fingerprint/aidl/aidl_api/android.hardware.biometrics.fingerprint/current/android/hardware/biometrics/fingerprint/AcquiredInfo.aidl +++ b/biometrics/fingerprint/aidl/aidl_api/android.hardware.biometrics.fingerprint/current/android/hardware/biometrics/fingerprint/AcquiredInfo.aidl @@ -2,13 +2,14 @@ // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // /////////////////////////////////////////////////////////////////////////////// -// This file is a snapshot of an AIDL interface (or parcelable). Do not try to -// edit this file. It looks like you are doing that because you have modified -// an AIDL interface in a backward-incompatible way, e.g., deleting a function -// from an interface or a field from a parcelable and it broke the build. That -// breakage is intended. +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. // -// You must not make a backward incompatible changes to the AIDL files built +// You must not make a backward incompatible change to any AIDL file built // with the aidl_interface module type with versions property set. The module // type is used to build AIDL files in a way that they can be used across // independently updatable components of the system. If a device is shipped diff --git a/biometrics/fingerprint/aidl/aidl_api/android.hardware.biometrics.fingerprint/current/android/hardware/biometrics/fingerprint/Error.aidl b/biometrics/fingerprint/aidl/aidl_api/android.hardware.biometrics.fingerprint/current/android/hardware/biometrics/fingerprint/Error.aidl index 6bd71b2d34..aec499f229 100644 --- a/biometrics/fingerprint/aidl/aidl_api/android.hardware.biometrics.fingerprint/current/android/hardware/biometrics/fingerprint/Error.aidl +++ b/biometrics/fingerprint/aidl/aidl_api/android.hardware.biometrics.fingerprint/current/android/hardware/biometrics/fingerprint/Error.aidl @@ -2,13 +2,14 @@ // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // /////////////////////////////////////////////////////////////////////////////// -// This file is a snapshot of an AIDL interface (or parcelable). Do not try to -// edit this file. It looks like you are doing that because you have modified -// an AIDL interface in a backward-incompatible way, e.g., deleting a function -// from an interface or a field from a parcelable and it broke the build. That -// breakage is intended. +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. // -// You must not make a backward incompatible changes to the AIDL files built +// You must not make a backward incompatible change to any AIDL file built // with the aidl_interface module type with versions property set. The module // type is used to build AIDL files in a way that they can be used across // independently updatable components of the system. If a device is shipped diff --git a/biometrics/fingerprint/aidl/aidl_api/android.hardware.biometrics.fingerprint/current/android/hardware/biometrics/fingerprint/FingerprintSensorType.aidl b/biometrics/fingerprint/aidl/aidl_api/android.hardware.biometrics.fingerprint/current/android/hardware/biometrics/fingerprint/FingerprintSensorType.aidl index 14bfece745..784e1d0d2f 100644 --- a/biometrics/fingerprint/aidl/aidl_api/android.hardware.biometrics.fingerprint/current/android/hardware/biometrics/fingerprint/FingerprintSensorType.aidl +++ b/biometrics/fingerprint/aidl/aidl_api/android.hardware.biometrics.fingerprint/current/android/hardware/biometrics/fingerprint/FingerprintSensorType.aidl @@ -2,13 +2,14 @@ // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // /////////////////////////////////////////////////////////////////////////////// -// This file is a snapshot of an AIDL interface (or parcelable). Do not try to -// edit this file. It looks like you are doing that because you have modified -// an AIDL interface in a backward-incompatible way, e.g., deleting a function -// from an interface or a field from a parcelable and it broke the build. That -// breakage is intended. +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. // -// You must not make a backward incompatible changes to the AIDL files built +// You must not make a backward incompatible change to any AIDL file built // with the aidl_interface module type with versions property set. The module // type is used to build AIDL files in a way that they can be used across // independently updatable components of the system. If a device is shipped diff --git a/biometrics/fingerprint/aidl/aidl_api/android.hardware.biometrics.fingerprint/current/android/hardware/biometrics/fingerprint/IFingerprint.aidl b/biometrics/fingerprint/aidl/aidl_api/android.hardware.biometrics.fingerprint/current/android/hardware/biometrics/fingerprint/IFingerprint.aidl index c5c6786542..51b4c63799 100644 --- a/biometrics/fingerprint/aidl/aidl_api/android.hardware.biometrics.fingerprint/current/android/hardware/biometrics/fingerprint/IFingerprint.aidl +++ b/biometrics/fingerprint/aidl/aidl_api/android.hardware.biometrics.fingerprint/current/android/hardware/biometrics/fingerprint/IFingerprint.aidl @@ -2,13 +2,14 @@ // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // /////////////////////////////////////////////////////////////////////////////// -// This file is a snapshot of an AIDL interface (or parcelable). Do not try to -// edit this file. It looks like you are doing that because you have modified -// an AIDL interface in a backward-incompatible way, e.g., deleting a function -// from an interface or a field from a parcelable and it broke the build. That -// breakage is intended. +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. // -// You must not make a backward incompatible changes to the AIDL files built +// You must not make a backward incompatible change to any AIDL file built // with the aidl_interface module type with versions property set. The module // type is used to build AIDL files in a way that they can be used across // independently updatable components of the system. If a device is shipped diff --git a/biometrics/fingerprint/aidl/aidl_api/android.hardware.biometrics.fingerprint/current/android/hardware/biometrics/fingerprint/ISession.aidl b/biometrics/fingerprint/aidl/aidl_api/android.hardware.biometrics.fingerprint/current/android/hardware/biometrics/fingerprint/ISession.aidl index 4df79817bf..185d86ff24 100644 --- a/biometrics/fingerprint/aidl/aidl_api/android.hardware.biometrics.fingerprint/current/android/hardware/biometrics/fingerprint/ISession.aidl +++ b/biometrics/fingerprint/aidl/aidl_api/android.hardware.biometrics.fingerprint/current/android/hardware/biometrics/fingerprint/ISession.aidl @@ -2,13 +2,14 @@ // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // /////////////////////////////////////////////////////////////////////////////// -// This file is a snapshot of an AIDL interface (or parcelable). Do not try to -// edit this file. It looks like you are doing that because you have modified -// an AIDL interface in a backward-incompatible way, e.g., deleting a function -// from an interface or a field from a parcelable and it broke the build. That -// breakage is intended. +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. // -// You must not make a backward incompatible changes to the AIDL files built +// You must not make a backward incompatible change to any AIDL file built // with the aidl_interface module type with versions property set. The module // type is used to build AIDL files in a way that they can be used across // independently updatable components of the system. If a device is shipped diff --git a/biometrics/fingerprint/aidl/aidl_api/android.hardware.biometrics.fingerprint/current/android/hardware/biometrics/fingerprint/ISessionCallback.aidl b/biometrics/fingerprint/aidl/aidl_api/android.hardware.biometrics.fingerprint/current/android/hardware/biometrics/fingerprint/ISessionCallback.aidl index b7a48a5186..cf663a5670 100644 --- a/biometrics/fingerprint/aidl/aidl_api/android.hardware.biometrics.fingerprint/current/android/hardware/biometrics/fingerprint/ISessionCallback.aidl +++ b/biometrics/fingerprint/aidl/aidl_api/android.hardware.biometrics.fingerprint/current/android/hardware/biometrics/fingerprint/ISessionCallback.aidl @@ -2,13 +2,14 @@ // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // /////////////////////////////////////////////////////////////////////////////// -// This file is a snapshot of an AIDL interface (or parcelable). Do not try to -// edit this file. It looks like you are doing that because you have modified -// an AIDL interface in a backward-incompatible way, e.g., deleting a function -// from an interface or a field from a parcelable and it broke the build. That -// breakage is intended. +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. // -// You must not make a backward incompatible changes to the AIDL files built +// You must not make a backward incompatible change to any AIDL file built // with the aidl_interface module type with versions property set. The module // type is used to build AIDL files in a way that they can be used across // independently updatable components of the system. If a device is shipped @@ -33,5 +34,5 @@ interface ISessionCallback { void onEnrollmentsEnumerated(in int[] enrollmentIds); void onEnrollmentsRemoved(in int[] enrollmentIds); void onAuthenticatorIdRetrieved(in long authenticatorId); - void onAuthenticatorIdInvalidated(); + void onAuthenticatorIdInvalidated(in long newAuthenticatorId); } diff --git a/biometrics/fingerprint/aidl/aidl_api/android.hardware.biometrics.fingerprint/current/android/hardware/biometrics/fingerprint/SensorProps.aidl b/biometrics/fingerprint/aidl/aidl_api/android.hardware.biometrics.fingerprint/current/android/hardware/biometrics/fingerprint/SensorProps.aidl index 8c779abc09..f8a40a964f 100644 --- a/biometrics/fingerprint/aidl/aidl_api/android.hardware.biometrics.fingerprint/current/android/hardware/biometrics/fingerprint/SensorProps.aidl +++ b/biometrics/fingerprint/aidl/aidl_api/android.hardware.biometrics.fingerprint/current/android/hardware/biometrics/fingerprint/SensorProps.aidl @@ -2,13 +2,14 @@ // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // /////////////////////////////////////////////////////////////////////////////// -// This file is a snapshot of an AIDL interface (or parcelable). Do not try to -// edit this file. It looks like you are doing that because you have modified -// an AIDL interface in a backward-incompatible way, e.g., deleting a function -// from an interface or a field from a parcelable and it broke the build. That -// breakage is intended. +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. // -// You must not make a backward incompatible changes to the AIDL files built +// You must not make a backward incompatible change to any AIDL file built // with the aidl_interface module type with versions property set. The module // type is used to build AIDL files in a way that they can be used across // independently updatable components of the system. If a device is shipped diff --git a/biometrics/fingerprint/aidl/aidl_api/android.hardware.biometrics.fingerprint/current/android/hardware/biometrics/fingerprint/SessionState.aidl b/biometrics/fingerprint/aidl/aidl_api/android.hardware.biometrics.fingerprint/current/android/hardware/biometrics/fingerprint/SessionState.aidl index e9daef1740..3453f93763 100644 --- a/biometrics/fingerprint/aidl/aidl_api/android.hardware.biometrics.fingerprint/current/android/hardware/biometrics/fingerprint/SessionState.aidl +++ b/biometrics/fingerprint/aidl/aidl_api/android.hardware.biometrics.fingerprint/current/android/hardware/biometrics/fingerprint/SessionState.aidl @@ -2,13 +2,14 @@ // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // /////////////////////////////////////////////////////////////////////////////// -// This file is a snapshot of an AIDL interface (or parcelable). Do not try to -// edit this file. It looks like you are doing that because you have modified -// an AIDL interface in a backward-incompatible way, e.g., deleting a function -// from an interface or a field from a parcelable and it broke the build. That -// breakage is intended. +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. // -// You must not make a backward incompatible changes to the AIDL files built +// You must not make a backward incompatible change to any AIDL file built // with the aidl_interface module type with versions property set. The module // type is used to build AIDL files in a way that they can be used across // independently updatable components of the system. If a device is shipped diff --git a/biometrics/fingerprint/aidl/android/hardware/biometrics/fingerprint/ISessionCallback.aidl b/biometrics/fingerprint/aidl/android/hardware/biometrics/fingerprint/ISessionCallback.aidl index 4387f988d0..fde1df753d 100644 --- a/biometrics/fingerprint/aidl/android/hardware/biometrics/fingerprint/ISessionCallback.aidl +++ b/biometrics/fingerprint/aidl/android/hardware/biometrics/fingerprint/ISessionCallback.aidl @@ -195,6 +195,9 @@ interface ISessionCallback { * SessionState::INVALIDATING_AUTHENTICATOR_ID. * * See ISession#invalidateAuthenticatorId for more information. + * + * @param newAuthenticatorId The new entropy-encoded random identifier associated with the + * current set of enrollments. */ - void onAuthenticatorIdInvalidated(); + void onAuthenticatorIdInvalidated(in long newAuthenticatorId); } diff --git a/biometrics/fingerprint/aidl/vts/VtsHalBiometricsFingerprintTargetTest.cpp b/biometrics/fingerprint/aidl/vts/VtsHalBiometricsFingerprintTargetTest.cpp index adb98ac46f..ddbc0fee5a 100644 --- a/biometrics/fingerprint/aidl/vts/VtsHalBiometricsFingerprintTargetTest.cpp +++ b/biometrics/fingerprint/aidl/vts/VtsHalBiometricsFingerprintTargetTest.cpp @@ -110,7 +110,7 @@ class SessionCallback : public BnSessionCallback { return ndk::ScopedAStatus::ok(); } - ndk::ScopedAStatus onAuthenticatorIdInvalidated() override { + ndk::ScopedAStatus onAuthenticatorIdInvalidated(int64_t /*newAuthenticatorId*/) override { return ndk::ScopedAStatus::ok(); } diff --git a/bluetooth/1.0/default/test/bluetooth_address_test.cc b/bluetooth/1.0/default/test/bluetooth_address_test.cc index ee52d3364f..422d6a3c78 100644 --- a/bluetooth/1.0/default/test/bluetooth_address_test.cc +++ b/bluetooth/1.0/default/test/bluetooth_address_test.cc @@ -14,7 +14,6 @@ // limitations under the License. // -#include <cutils/properties.h> #include <errno.h> #include <fcntl.h> #include <gtest/gtest.h> @@ -39,12 +38,6 @@ constexpr char kZeros[BluetoothAddress::kStringLength + 1] = "00:00:00:00:00:00"; constexpr uint8_t kZeros_bytes[BluetoothAddress::kBytes] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; -constexpr char kTestAddrBad1[BluetoothAddress::kStringLength + 1] = - "bb:aa:dd:00:00:01"; -constexpr uint8_t kTestAddrBad1_bytes[BluetoothAddress::kBytes] = { - 0xbb, 0xaa, 0xdd, 0x00, 0x00, 0x01}; - -constexpr char kAddrPath[] = "/tmp/my_address_in_a_file.txt"; class BluetoothAddressTest : public ::testing::Test { public: @@ -120,45 +113,6 @@ TEST_F(BluetoothAddressTest, bytes_to_string) { EXPECT_FALSE(memcmp(addrA, addrB, BluetoothAddress::kStringLength) == 0); } -TEST_F(BluetoothAddressTest, get_local_address) { - EXPECT_TRUE(property_set(PERSIST_BDADDR_PROPERTY, "") == 0); - EXPECT_TRUE(property_set(FACTORY_BDADDR_PROPERTY, "") == 0); - uint8_t address[BluetoothAddress::kBytes]; - - // File contains a non-zero Address. - FileWriteString(kAddrPath, kTestAddr1); - EXPECT_TRUE(property_set(PROPERTY_BT_BDADDR_PATH, kAddrPath) == 0); - EXPECT_TRUE(BluetoothAddress::get_local_address(address)); - EXPECT_TRUE(memcmp(address, kTestAddr1_bytes, BluetoothAddress::kBytes) == 0); - - // File contains a zero address. A random address will be generated. - FileWriteString(kAddrPath, kZeros); - EXPECT_TRUE(property_set(PROPERTY_BT_BDADDR_PATH, kAddrPath) == 0); - EXPECT_TRUE(property_set(PERSIST_BDADDR_PROPERTY, kTestAddrBad1) == 0); - EXPECT_TRUE(BluetoothAddress::get_local_address(address)); - EXPECT_TRUE(memcmp(address, kZeros_bytes, BluetoothAddress::kBytes) != 0); - char prop[PROP_VALUE_MAX] = "Before reading"; - EXPECT_TRUE(property_get(PERSIST_BDADDR_PROPERTY, prop, NULL) == - BluetoothAddress::kStringLength); - char address_str[BluetoothAddress::kStringLength + 1]; - BluetoothAddress::bytes_to_string(address, address_str); - EXPECT_TRUE(memcmp(address_str, prop, BluetoothAddress::kStringLength) == 0); - - // Factory property contains an address. - EXPECT_TRUE(property_set(PERSIST_BDADDR_PROPERTY, kTestAddrBad1) == 0); - EXPECT_TRUE(property_set(FACTORY_BDADDR_PROPERTY, kTestAddr1) == 0); - EXPECT_TRUE(BluetoothAddress::get_local_address(address)); - EXPECT_TRUE(memcmp(address, kTestAddr1_bytes, BluetoothAddress::kBytes) == 0); - - // Persistent property contains an address. - memcpy(address, kTestAddrBad1_bytes, BluetoothAddress::kBytes); - EXPECT_TRUE(property_set(PERSIST_BDADDR_PROPERTY, kTestAddr1) == 0); - EXPECT_TRUE(property_set(FACTORY_BDADDR_PROPERTY, "") == 0); - EXPECT_TRUE(property_set(PROPERTY_BT_BDADDR_PATH, "") == 0); - EXPECT_TRUE(BluetoothAddress::get_local_address(address)); - EXPECT_TRUE(memcmp(address, kTestAddr1_bytes, BluetoothAddress::kBytes) == 0); -} - } // namespace implementation } // namespace V1_0 } // namespace bluetooth diff --git a/bluetooth/1.0/vts/functional/VtsHalBluetoothV1_0TargetTest.cpp b/bluetooth/1.0/vts/functional/VtsHalBluetoothV1_0TargetTest.cpp index 0328af1d82..76777dcd73 100644 --- a/bluetooth/1.0/vts/functional/VtsHalBluetoothV1_0TargetTest.cpp +++ b/bluetooth/1.0/vts/functional/VtsHalBluetoothV1_0TargetTest.cpp @@ -50,7 +50,7 @@ using ::android::hardware::bluetooth::V1_0::Status; #define WAIT_FOR_HCI_EVENT_TIMEOUT std::chrono::milliseconds(2000) #define WAIT_FOR_SCO_DATA_TIMEOUT std::chrono::milliseconds(1000) #define WAIT_FOR_ACL_DATA_TIMEOUT std::chrono::milliseconds(1000) -#define INTERFACE_CLOSE_DELAY_MS std::chrono::milliseconds(200) +#define INTERFACE_CLOSE_DELAY_MS std::chrono::milliseconds(600) #define COMMAND_HCI_SHOULD_BE_UNKNOWN \ { 0xff, 0x3B, 0x08, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07 } diff --git a/bluetooth/audio/2.1/default/LeAudioAudioProvider.cpp b/bluetooth/audio/2.1/default/LeAudioAudioProvider.cpp index 1fa2dcea1f..9c2b4fe1db 100644 --- a/bluetooth/audio/2.1/default/LeAudioAudioProvider.cpp +++ b/bluetooth/audio/2.1/default/LeAudioAudioProvider.cpp @@ -91,35 +91,30 @@ Return<void> LeAudioAudioProvider::startSession_2_1( uint32_t kDataMqSize = 0; switch (audioConfig.pcmConfig().sampleRate) { + case SampleRate::RATE_8000: + kDataMqSize = 8000; + break; case SampleRate::RATE_16000: kDataMqSize = 16000; break; case SampleRate::RATE_24000: kDataMqSize = 24000; break; + case SampleRate::RATE_32000: + kDataMqSize = 32000; + break; case SampleRate::RATE_44100: kDataMqSize = 44100; break; case SampleRate::RATE_48000: kDataMqSize = 48000; break; - case SampleRate::RATE_88200: - kDataMqSize = 88200; - break; - case SampleRate::RATE_96000: - kDataMqSize = 96000; - break; - case SampleRate::RATE_176400: - kDataMqSize = 176400; - break; - case SampleRate::RATE_192000: - kDataMqSize = 192000; - break; default: - /* This should never happen it would be caught while validating - * parameters. - */ - break; + LOG(WARNING) << __func__ << " - Unsupported sampling frequency=" + << toString(audioConfig.pcmConfig()); + _hidl_cb(BluetoothAudioStatus::UNSUPPORTED_CODEC_CONFIGURATION, + DataMQ::Descriptor()); + return Void(); } /* Number of samples per millisecond */ diff --git a/bluetooth/audio/2.1/default/session/BluetoothAudioSupportedCodecsDB.cpp b/bluetooth/audio/2.1/default/session/BluetoothAudioSupportedCodecsDB.cpp index d15db49ff1..0937f441e3 100644 --- a/bluetooth/audio/2.1/default/session/BluetoothAudioSupportedCodecsDB.cpp +++ b/bluetooth/audio/2.1/default/session/BluetoothAudioSupportedCodecsDB.cpp @@ -409,12 +409,14 @@ bool IsSoftwarePcmConfigurationValid( } bool IsSoftwarePcmConfigurationValid_2_1(const PcmParameters& pcm_config) { - if ((pcm_config.sampleRate != SampleRate::RATE_44100 && - pcm_config.sampleRate != SampleRate::RATE_48000 && + if ((pcm_config.sampleRate != SampleRate::RATE_96000 && pcm_config.sampleRate != SampleRate::RATE_88200 && - pcm_config.sampleRate != SampleRate::RATE_96000 && + pcm_config.sampleRate != SampleRate::RATE_48000 && + pcm_config.sampleRate != SampleRate::RATE_44100 && + pcm_config.sampleRate != SampleRate::RATE_32000 && + pcm_config.sampleRate != SampleRate::RATE_24000 && pcm_config.sampleRate != SampleRate::RATE_16000 && - pcm_config.sampleRate != SampleRate::RATE_24000) || + pcm_config.sampleRate != SampleRate::RATE_8000) || (pcm_config.bitsPerSample != BitsPerSample::BITS_16 && pcm_config.bitsPerSample != BitsPerSample::BITS_24 && pcm_config.bitsPerSample != BitsPerSample::BITS_32) || diff --git a/bluetooth/audio/2.1/vts/functional/VtsHalBluetoothAudioV2_1TargetTest.cpp b/bluetooth/audio/2.1/vts/functional/VtsHalBluetoothAudioV2_1TargetTest.cpp index 37d1281cd7..95903d1f82 100644 --- a/bluetooth/audio/2.1/vts/functional/VtsHalBluetoothAudioV2_1TargetTest.cpp +++ b/bluetooth/audio/2.1/vts/functional/VtsHalBluetoothAudioV2_1TargetTest.cpp @@ -1005,8 +1005,10 @@ class BluetoothAudioProviderLeAudioOutputSoftwareHidlTest BluetoothAudioProvidersFactoryHidlTest::TearDown(); } - static constexpr SampleRate le_audio_output_sample_rates_[3] = { - SampleRate::RATE_UNKNOWN, SampleRate::RATE_16000, SampleRate::RATE_24000}; + static constexpr SampleRate le_audio_output_sample_rates_[11] = { + SampleRate::RATE_UNKNOWN, SampleRate::RATE_8000, SampleRate::RATE_16000, + SampleRate::RATE_24000, SampleRate::RATE_32000, SampleRate::RATE_44100, + SampleRate::RATE_48000}; static constexpr BitsPerSample le_audio_output_bits_per_samples_[3] = { BitsPerSample::BITS_UNKNOWN, BitsPerSample::BITS_16, BitsPerSample::BITS_24}; @@ -1097,8 +1099,10 @@ class BluetoothAudioProviderLeAudioInputSoftwareHidlTest BluetoothAudioProvidersFactoryHidlTest::TearDown(); } - static constexpr SampleRate le_audio_output_sample_rates_[3] = { - SampleRate::RATE_UNKNOWN, SampleRate::RATE_16000, SampleRate::RATE_24000}; + static constexpr SampleRate le_audio_output_sample_rates_[11] = { + SampleRate::RATE_UNKNOWN, SampleRate::RATE_8000, SampleRate::RATE_16000, + SampleRate::RATE_24000, SampleRate::RATE_32000, SampleRate::RATE_44100, + SampleRate::RATE_48000}; static constexpr BitsPerSample le_audio_output_bits_per_samples_[3] = { BitsPerSample::BITS_UNKNOWN, BitsPerSample::BITS_16, BitsPerSample::BITS_24}; diff --git a/broadcastradio/2.0/vts/functional/VtsHalBroadcastradioV2_0TargetTest.cpp b/broadcastradio/2.0/vts/functional/VtsHalBroadcastradioV2_0TargetTest.cpp index ca57243a6d..ce50f25f7c 100644 --- a/broadcastradio/2.0/vts/functional/VtsHalBroadcastradioV2_0TargetTest.cpp +++ b/broadcastradio/2.0/vts/functional/VtsHalBroadcastradioV2_0TargetTest.cpp @@ -415,7 +415,7 @@ TEST_P(BroadcastRadioHalTest, GetDabRegionConfig) { TEST_P(BroadcastRadioHalTest, FmTune) { ASSERT_TRUE(openSession()); - uint64_t freq = 100100; // 100.1 FM + uint64_t freq = 90900; // 90.9 FM auto sel = make_selector_amfm(freq); /* TODO(b/69958777): there is a race condition between tune() and onCurrentProgramInfoChanged diff --git a/cas/1.0/vts/functional/Android.bp b/cas/1.0/vts/functional/Android.bp index 82dc568fa6..a065b85906 100644 --- a/cas/1.0/vts/functional/Android.bp +++ b/cas/1.0/vts/functional/Android.bp @@ -20,6 +20,8 @@ cc_test { srcs: ["VtsHalCasV1_0TargetTest.cpp"], static_libs: [ "android.hardware.cas@1.0", + "android.hardware.cas@1.1", + "android.hardware.cas@1.2", "android.hardware.cas.native@1.0", "android.hidl.allocator@1.0", "android.hidl.memory@1.0", diff --git a/cas/1.0/vts/functional/VtsHalCasV1_0TargetTest.cpp b/cas/1.0/vts/functional/VtsHalCasV1_0TargetTest.cpp index 7f5d98888a..df0c85934f 100644 --- a/cas/1.0/vts/functional/VtsHalCasV1_0TargetTest.cpp +++ b/cas/1.0/vts/functional/VtsHalCasV1_0TargetTest.cpp @@ -22,6 +22,7 @@ #include <android/hardware/cas/1.0/IDescramblerBase.h> #include <android/hardware/cas/1.0/IMediaCasService.h> #include <android/hardware/cas/1.0/types.h> +#include <android/hardware/cas/1.2/IMediaCasService.h> #include <android/hardware/cas/native/1.0/IDescrambler.h> #include <android/hardware/cas/native/1.0/types.h> #include <binder/MemoryDealer.h> @@ -212,6 +213,10 @@ void MediaCasListener::testEventEcho(sp<ICas>& mediaCas, int32_t& event, int32_t class MediaCasHidlTest : public testing::TestWithParam<std::string> { public: virtual void SetUp() override { + if (android::hardware::cas::V1_2::IMediaCasService::getService(GetParam()) == nullptr) { + ALOGI("Descrambler is need to be tested before cas@1.2."); + mIsTestDescrambler = true; + } mService = IMediaCasService::getService(GetParam()); ASSERT_NE(mService, nullptr); } @@ -226,6 +231,7 @@ class MediaCasHidlTest : public testing::TestWithParam<std::string> { sp<ICas> mMediaCas; sp<IDescramblerBase> mDescramblerBase; sp<MediaCasListener> mCasListener; + bool mIsTestDescrambler = false; typedef struct _OobInputTestParams { const SubSample* subSamples; uint32_t numSubSamples; @@ -270,8 +276,14 @@ class MediaCasHidlTest : public testing::TestWithParam<std::string> { auto descramblerStatus = mService->createDescrambler(caSystemId); if (!descramblerStatus.isOk()) { - return ::testing::AssertionFailure(); + if (mIsTestDescrambler) { + return ::testing::AssertionFailure(); + } else { + ALOGI("Skip Descrambler test since it's not required in cas@1.2."); + return ::testing::AssertionSuccess(); + } } + mIsTestDescrambler = true; mDescramblerBase = descramblerStatus; return ::testing::AssertionResult(mDescramblerBase != nullptr); } @@ -494,14 +506,15 @@ TEST_P(MediaCasHidlTest, TestClearKeyApis) { returnStatus = mMediaCas->setSessionPrivateData(streamSessionId, hidlPvtData); EXPECT_TRUE(returnStatus.isOk()); EXPECT_EQ(Status::OK, returnStatus); - - returnStatus = mDescramblerBase->setMediaCasSession(sessionId); - EXPECT_TRUE(returnStatus.isOk()); - EXPECT_EQ(Status::OK, returnStatus); - - returnStatus = mDescramblerBase->setMediaCasSession(streamSessionId); - EXPECT_TRUE(returnStatus.isOk()); - EXPECT_EQ(Status::OK, returnStatus); + if (mIsTestDescrambler) { + returnStatus = mDescramblerBase->setMediaCasSession(sessionId); + EXPECT_TRUE(returnStatus.isOk()); + EXPECT_EQ(Status::OK, returnStatus); + + returnStatus = mDescramblerBase->setMediaCasSession(streamSessionId); + EXPECT_TRUE(returnStatus.isOk()); + EXPECT_EQ(Status::OK, returnStatus); + } hidl_vec<uint8_t> hidlNullPtr; hidlNullPtr.setToExternal(static_cast<uint8_t*>(nullptr), 0); @@ -543,29 +556,32 @@ TEST_P(MediaCasHidlTest, TestClearKeyApis) { EXPECT_TRUE(returnStatus.isOk()); EXPECT_EQ(Status::OK, returnStatus); - EXPECT_FALSE(mDescramblerBase->requiresSecureDecoderComponent("video/avc")); + if (mIsTestDescrambler) { + EXPECT_FALSE(mDescramblerBase->requiresSecureDecoderComponent("video/avc")); - sp<IDescrambler> descrambler; - descrambler = IDescrambler::castFrom(mDescramblerBase); - ASSERT_NE(descrambler, nullptr); + sp<IDescrambler> descrambler; + descrambler = IDescrambler::castFrom(mDescramblerBase); + ASSERT_NE(descrambler, nullptr); - Status descrambleStatus = Status::OK; - sp<IMemory> dataMemory; + Status descrambleStatus = Status::OK; + sp<IMemory> dataMemory; - ASSERT_TRUE(descrambleTestInputBuffer(descrambler, &descrambleStatus, &dataMemory)); - EXPECT_EQ(Status::OK, descrambleStatus); + ASSERT_TRUE(descrambleTestInputBuffer(descrambler, &descrambleStatus, &dataMemory)); + EXPECT_EQ(Status::OK, descrambleStatus); - ASSERT_NE(nullptr, dataMemory.get()); - uint8_t* opBuffer = static_cast<uint8_t*>(static_cast<void*>(dataMemory->unsecurePointer())); + ASSERT_NE(nullptr, dataMemory.get()); + uint8_t* opBuffer = + static_cast<uint8_t*>(static_cast<void*>(dataMemory->unsecurePointer())); - int compareResult = - memcmp(static_cast<const void*>(opBuffer), static_cast<const void*>(kOutRefBinaryBuffer), - sizeof(kOutRefBinaryBuffer)); - EXPECT_EQ(0, compareResult); + int compareResult = + memcmp(static_cast<const void*>(opBuffer), + static_cast<const void*>(kOutRefBinaryBuffer), sizeof(kOutRefBinaryBuffer)); + EXPECT_EQ(0, compareResult); - returnStatus = mDescramblerBase->release(); - EXPECT_TRUE(returnStatus.isOk()); - EXPECT_EQ(Status::OK, returnStatus); + returnStatus = mDescramblerBase->release(); + EXPECT_TRUE(returnStatus.isOk()); + EXPECT_EQ(Status::OK, returnStatus); + } returnStatus = mMediaCas->release(); EXPECT_TRUE(returnStatus.isOk()); @@ -590,13 +606,15 @@ TEST_P(MediaCasHidlTest, TestClearKeySessionClosedAfterRelease) { EXPECT_TRUE(returnStatus.isOk()); EXPECT_EQ(Status::OK, returnStatus); - returnStatus = mDescramblerBase->setMediaCasSession(sessionId); - EXPECT_TRUE(returnStatus.isOk()); - EXPECT_EQ(Status::ERROR_CAS_SESSION_NOT_OPENED, returnStatus); + if (mIsTestDescrambler) { + returnStatus = mDescramblerBase->setMediaCasSession(sessionId); + EXPECT_TRUE(returnStatus.isOk()); + EXPECT_EQ(Status::ERROR_CAS_SESSION_NOT_OPENED, returnStatus); - returnStatus = mDescramblerBase->setMediaCasSession(streamSessionId); - EXPECT_TRUE(returnStatus.isOk()); - EXPECT_EQ(Status::ERROR_CAS_SESSION_NOT_OPENED, returnStatus); + returnStatus = mDescramblerBase->setMediaCasSession(streamSessionId); + EXPECT_TRUE(returnStatus.isOk()); + EXPECT_EQ(Status::ERROR_CAS_SESSION_NOT_OPENED, returnStatus); + } } TEST_P(MediaCasHidlTest, TestClearKeyErrors) { @@ -654,38 +672,40 @@ TEST_P(MediaCasHidlTest, TestClearKeyErrors) { EXPECT_TRUE(returnStatus.isOk()); EXPECT_EQ(Status::ERROR_CAS_UNKNOWN, returnStatus); - /* - * Test MediaDescrambler error codes - */ - // setMediaCasSession should fail with an invalid session id - returnStatus = mDescramblerBase->setMediaCasSession(invalidSessionId); - EXPECT_TRUE(returnStatus.isOk()); - EXPECT_EQ(Status::ERROR_CAS_SESSION_NOT_OPENED, returnStatus); + if (mIsTestDescrambler) { + /* + * Test MediaDescrambler error codes + */ + // setMediaCasSession should fail with an invalid session id + returnStatus = mDescramblerBase->setMediaCasSession(invalidSessionId); + EXPECT_TRUE(returnStatus.isOk()); + EXPECT_EQ(Status::ERROR_CAS_SESSION_NOT_OPENED, returnStatus); - // descramble should fail without a valid session - sp<IDescrambler> descrambler; - descrambler = IDescrambler::castFrom(mDescramblerBase); - ASSERT_NE(descrambler, nullptr); + // descramble should fail without a valid session + sp<IDescrambler> descrambler; + descrambler = IDescrambler::castFrom(mDescramblerBase); + ASSERT_NE(descrambler, nullptr); - Status descrambleStatus = Status::OK; - sp<IMemory> dataMemory; + Status descrambleStatus = Status::OK; + sp<IMemory> dataMemory; - ASSERT_TRUE(descrambleTestInputBuffer(descrambler, &descrambleStatus, &dataMemory)); - EXPECT_EQ(Status::ERROR_CAS_DECRYPT_UNIT_NOT_INITIALIZED, descrambleStatus); + ASSERT_TRUE(descrambleTestInputBuffer(descrambler, &descrambleStatus, &dataMemory)); + EXPECT_EQ(Status::ERROR_CAS_DECRYPT_UNIT_NOT_INITIALIZED, descrambleStatus); - // Now set a valid session, should still fail because no valid ecm is processed - returnStatus = mDescramblerBase->setMediaCasSession(sessionId); - EXPECT_TRUE(returnStatus.isOk()); - EXPECT_EQ(Status::OK, returnStatus); + // Now set a valid session, should still fail because no valid ecm is processed + returnStatus = mDescramblerBase->setMediaCasSession(sessionId); + EXPECT_TRUE(returnStatus.isOk()); + EXPECT_EQ(Status::OK, returnStatus); - ASSERT_TRUE(descrambleTestInputBuffer(descrambler, &descrambleStatus, &dataMemory)); - EXPECT_EQ(Status::ERROR_CAS_DECRYPT, descrambleStatus); + ASSERT_TRUE(descrambleTestInputBuffer(descrambler, &descrambleStatus, &dataMemory)); + EXPECT_EQ(Status::ERROR_CAS_DECRYPT, descrambleStatus); - // Verify that requiresSecureDecoderComponent handles empty mime - EXPECT_FALSE(mDescramblerBase->requiresSecureDecoderComponent("")); + // Verify that requiresSecureDecoderComponent handles empty mime + EXPECT_FALSE(mDescramblerBase->requiresSecureDecoderComponent("")); - // Verify that requiresSecureDecoderComponent handles invalid mime - EXPECT_FALSE(mDescramblerBase->requiresSecureDecoderComponent("bad")); + // Verify that requiresSecureDecoderComponent handles invalid mime + EXPECT_FALSE(mDescramblerBase->requiresSecureDecoderComponent("bad")); + } } TEST_P(MediaCasHidlTest, TestClearKeyOobFails) { @@ -700,9 +720,11 @@ TEST_P(MediaCasHidlTest, TestClearKeyOobFails) { std::vector<uint8_t> sessionId; ASSERT_TRUE(openCasSession(&sessionId)); - returnStatus = mDescramblerBase->setMediaCasSession(sessionId); - EXPECT_TRUE(returnStatus.isOk()); - EXPECT_EQ(Status::OK, returnStatus); + if (mIsTestDescrambler) { + returnStatus = mDescramblerBase->setMediaCasSession(sessionId); + EXPECT_TRUE(returnStatus.isOk()); + EXPECT_EQ(Status::OK, returnStatus); + } hidl_vec<uint8_t> hidlEcm; hidlEcm.setToExternal(const_cast<uint8_t*>(kEcmBinaryBuffer), sizeof(kEcmBinaryBuffer)); @@ -710,126 +732,104 @@ TEST_P(MediaCasHidlTest, TestClearKeyOobFails) { EXPECT_TRUE(returnStatus.isOk()); EXPECT_EQ(Status::OK, returnStatus); - sp<IDescrambler> descrambler = IDescrambler::castFrom(mDescramblerBase); - ASSERT_NE(nullptr, descrambler.get()); - - Status descrambleStatus = Status::OK; - - // test invalid src buffer offset - ASSERT_TRUE(descrambleTestOobInput( - descrambler, - &descrambleStatus, - { - .subSamples = kSubSamples, - .numSubSamples = sizeof(kSubSamples)/sizeof(SubSample), - .imemSizeActual = sizeof(kInBinaryBuffer), - .imemOffset = 0xcccccc, - .imemSize = sizeof(kInBinaryBuffer), - .srcOffset = 0, - .dstOffset = 0 - })); - EXPECT_EQ(Status::BAD_VALUE, descrambleStatus); - - // test invalid src buffer size - ASSERT_TRUE(descrambleTestOobInput( - descrambler, - &descrambleStatus, - { - .subSamples = kSubSamples, - .numSubSamples = sizeof(kSubSamples)/sizeof(SubSample), - .imemSizeActual = sizeof(kInBinaryBuffer), - .imemOffset = 0, - .imemSize = 0xcccccc, - .srcOffset = 0, - .dstOffset = 0 - })); - EXPECT_EQ(Status::BAD_VALUE, descrambleStatus); - - // test invalid src buffer size - ASSERT_TRUE(descrambleTestOobInput( - descrambler, - &descrambleStatus, - { - .subSamples = kSubSamples, - .numSubSamples = sizeof(kSubSamples)/sizeof(SubSample), - .imemSizeActual = sizeof(kInBinaryBuffer), - .imemOffset = 1, - .imemSize = (uint64_t)-1, - .srcOffset = 0, - .dstOffset = 0 - })); - EXPECT_EQ(Status::BAD_VALUE, descrambleStatus); - - // test invalid srcOffset - ASSERT_TRUE(descrambleTestOobInput( - descrambler, - &descrambleStatus, - { - .subSamples = kSubSamples, - .numSubSamples = sizeof(kSubSamples)/sizeof(SubSample), - .imemSizeActual = sizeof(kInBinaryBuffer), - .imemOffset = 0, - .imemSize = sizeof(kInBinaryBuffer), - .srcOffset = 0xcccccc, - .dstOffset = 0 - })); - EXPECT_EQ(Status::BAD_VALUE, descrambleStatus); - - // test invalid dstOffset - ASSERT_TRUE(descrambleTestOobInput( - descrambler, - &descrambleStatus, - { - .subSamples = kSubSamples, - .numSubSamples = sizeof(kSubSamples)/sizeof(SubSample), - .imemSizeActual = sizeof(kInBinaryBuffer), - .imemOffset = 0, - .imemSize = sizeof(kInBinaryBuffer), - .srcOffset = 0, - .dstOffset = 0xcccccc - })); - EXPECT_EQ(Status::BAD_VALUE, descrambleStatus); - - // test detection of oob subsample sizes - const SubSample invalidSubSamples1[] = - {{162, 0}, {0, 184}, {0, 0xdddddd}}; - - ASSERT_TRUE(descrambleTestOobInput( - descrambler, - &descrambleStatus, - { - .subSamples = invalidSubSamples1, - .numSubSamples = sizeof(invalidSubSamples1)/sizeof(SubSample), - .imemSizeActual = sizeof(kInBinaryBuffer), - .imemOffset = 0, - .imemSize = sizeof(kInBinaryBuffer), - .srcOffset = 0, - .dstOffset = 0 - })); - EXPECT_EQ(Status::BAD_VALUE, descrambleStatus); - - // test detection of overflowing subsample sizes - const SubSample invalidSubSamples2[] = - {{162, 0}, {0, 184}, {2, (uint32_t)-1}}; - - ASSERT_TRUE(descrambleTestOobInput( - descrambler, - &descrambleStatus, - { - .subSamples = invalidSubSamples2, - .numSubSamples = sizeof(invalidSubSamples2)/sizeof(SubSample), - .imemSizeActual = sizeof(kInBinaryBuffer), - .imemOffset = 0, - .imemSize = sizeof(kInBinaryBuffer), - .srcOffset = 0, - .dstOffset = 0 - })); - EXPECT_EQ(Status::BAD_VALUE, descrambleStatus); - - returnStatus = mDescramblerBase->release(); - EXPECT_TRUE(returnStatus.isOk()); - EXPECT_EQ(Status::OK, returnStatus); - + if (mIsTestDescrambler) { + sp<IDescrambler> descrambler = IDescrambler::castFrom(mDescramblerBase); + ASSERT_NE(nullptr, descrambler.get()); + + Status descrambleStatus = Status::OK; + + // test invalid src buffer offset + ASSERT_TRUE( + descrambleTestOobInput(descrambler, &descrambleStatus, + {.subSamples = kSubSamples, + .numSubSamples = sizeof(kSubSamples) / sizeof(SubSample), + .imemSizeActual = sizeof(kInBinaryBuffer), + .imemOffset = 0xcccccc, + .imemSize = sizeof(kInBinaryBuffer), + .srcOffset = 0, + .dstOffset = 0})); + EXPECT_EQ(Status::BAD_VALUE, descrambleStatus); + + // test invalid src buffer size + ASSERT_TRUE( + descrambleTestOobInput(descrambler, &descrambleStatus, + {.subSamples = kSubSamples, + .numSubSamples = sizeof(kSubSamples) / sizeof(SubSample), + .imemSizeActual = sizeof(kInBinaryBuffer), + .imemOffset = 0, + .imemSize = 0xcccccc, + .srcOffset = 0, + .dstOffset = 0})); + EXPECT_EQ(Status::BAD_VALUE, descrambleStatus); + + // test invalid src buffer size + ASSERT_TRUE( + descrambleTestOobInput(descrambler, &descrambleStatus, + {.subSamples = kSubSamples, + .numSubSamples = sizeof(kSubSamples) / sizeof(SubSample), + .imemSizeActual = sizeof(kInBinaryBuffer), + .imemOffset = 1, + .imemSize = (uint64_t)-1, + .srcOffset = 0, + .dstOffset = 0})); + EXPECT_EQ(Status::BAD_VALUE, descrambleStatus); + + // test invalid srcOffset + ASSERT_TRUE( + descrambleTestOobInput(descrambler, &descrambleStatus, + {.subSamples = kSubSamples, + .numSubSamples = sizeof(kSubSamples) / sizeof(SubSample), + .imemSizeActual = sizeof(kInBinaryBuffer), + .imemOffset = 0, + .imemSize = sizeof(kInBinaryBuffer), + .srcOffset = 0xcccccc, + .dstOffset = 0})); + EXPECT_EQ(Status::BAD_VALUE, descrambleStatus); + + // test invalid dstOffset + ASSERT_TRUE( + descrambleTestOobInput(descrambler, &descrambleStatus, + {.subSamples = kSubSamples, + .numSubSamples = sizeof(kSubSamples) / sizeof(SubSample), + .imemSizeActual = sizeof(kInBinaryBuffer), + .imemOffset = 0, + .imemSize = sizeof(kInBinaryBuffer), + .srcOffset = 0, + .dstOffset = 0xcccccc})); + EXPECT_EQ(Status::BAD_VALUE, descrambleStatus); + + // test detection of oob subsample sizes + const SubSample invalidSubSamples1[] = {{162, 0}, {0, 184}, {0, 0xdddddd}}; + + ASSERT_TRUE(descrambleTestOobInput( + descrambler, &descrambleStatus, + {.subSamples = invalidSubSamples1, + .numSubSamples = sizeof(invalidSubSamples1) / sizeof(SubSample), + .imemSizeActual = sizeof(kInBinaryBuffer), + .imemOffset = 0, + .imemSize = sizeof(kInBinaryBuffer), + .srcOffset = 0, + .dstOffset = 0})); + EXPECT_EQ(Status::BAD_VALUE, descrambleStatus); + + // test detection of overflowing subsample sizes + const SubSample invalidSubSamples2[] = {{162, 0}, {0, 184}, {2, (uint32_t)-1}}; + + ASSERT_TRUE(descrambleTestOobInput( + descrambler, &descrambleStatus, + {.subSamples = invalidSubSamples2, + .numSubSamples = sizeof(invalidSubSamples2) / sizeof(SubSample), + .imemSizeActual = sizeof(kInBinaryBuffer), + .imemOffset = 0, + .imemSize = sizeof(kInBinaryBuffer), + .srcOffset = 0, + .dstOffset = 0})); + EXPECT_EQ(Status::BAD_VALUE, descrambleStatus); + + returnStatus = mDescramblerBase->release(); + EXPECT_TRUE(returnStatus.isOk()); + EXPECT_EQ(Status::OK, returnStatus); + } returnStatus = mMediaCas->release(); EXPECT_TRUE(returnStatus.isOk()); EXPECT_EQ(Status::OK, returnStatus); diff --git a/cas/1.1/vts/functional/Android.bp b/cas/1.1/vts/functional/Android.bp index de223c8ce2..0647d12a1c 100644 --- a/cas/1.1/vts/functional/Android.bp +++ b/cas/1.1/vts/functional/Android.bp @@ -21,6 +21,7 @@ cc_test { static_libs: [ "android.hardware.cas@1.0", "android.hardware.cas@1.1", + "android.hardware.cas@1.2", "android.hardware.cas.native@1.0", "android.hidl.allocator@1.0", "android.hidl.memory@1.0", diff --git a/cas/1.1/vts/functional/VtsHalCasV1_1TargetTest.cpp b/cas/1.1/vts/functional/VtsHalCasV1_1TargetTest.cpp index b657f07c1a..6797506642 100644 --- a/cas/1.1/vts/functional/VtsHalCasV1_1TargetTest.cpp +++ b/cas/1.1/vts/functional/VtsHalCasV1_1TargetTest.cpp @@ -22,6 +22,7 @@ #include <android/hardware/cas/1.1/ICas.h> #include <android/hardware/cas/1.1/ICasListener.h> #include <android/hardware/cas/1.1/IMediaCasService.h> +#include <android/hardware/cas/1.2/IMediaCasService.h> #include <android/hardware/cas/native/1.0/IDescrambler.h> #include <android/hardware/cas/native/1.0/types.h> #include <binder/MemoryDealer.h> @@ -255,6 +256,10 @@ void MediaCasListener::testSessionEventEcho(sp<ICas>& mediaCas, const hidl_vec<u class MediaCasHidlTest : public testing::TestWithParam<std::string> { public: virtual void SetUp() override { + if (android::hardware::cas::V1_2::IMediaCasService::getService(GetParam()) == nullptr) { + ALOGI("Descrambler is need to be tested before cas@1.2."); + mIsTestDescrambler = true; + } mService = IMediaCasService::getService(GetParam()); ASSERT_NE(mService, nullptr); } @@ -269,6 +274,7 @@ class MediaCasHidlTest : public testing::TestWithParam<std::string> { sp<ICas> mMediaCas; sp<IDescramblerBase> mDescramblerBase; sp<MediaCasListener> mCasListener; + bool mIsTestDescrambler = false; typedef struct _OobInputTestParams { const SubSample* subSamples; uint32_t numSubSamples; @@ -311,8 +317,15 @@ class MediaCasHidlTest : public testing::TestWithParam<std::string> { auto descramblerStatus = mService->createDescrambler(caSystemId); if (!descramblerStatus.isOk()) { - return ::testing::AssertionFailure(); + if (mIsTestDescrambler) { + return ::testing::AssertionFailure(); + } else { + ALOGI("Skip Descrambler test since it's not required in cas@1.2."); + return ::testing::AssertionSuccess(); + } } + mIsTestDescrambler = true; + mDescramblerBase = descramblerStatus; return ::testing::AssertionResult(mDescramblerBase != nullptr); } @@ -468,13 +481,15 @@ TEST_P(MediaCasHidlTest, TestClearKeyApisWithSession) { EXPECT_TRUE(returnStatus.isOk()); EXPECT_EQ(Status::OK, returnStatus); - returnStatus = mDescramblerBase->setMediaCasSession(sessionId); - EXPECT_TRUE(returnStatus.isOk()); - EXPECT_EQ(Status::OK, returnStatus); + if (mIsTestDescrambler) { + returnStatus = mDescramblerBase->setMediaCasSession(sessionId); + EXPECT_TRUE(returnStatus.isOk()); + EXPECT_EQ(Status::OK, returnStatus); - returnStatus = mDescramblerBase->setMediaCasSession(streamSessionId); - EXPECT_TRUE(returnStatus.isOk()); - EXPECT_EQ(Status::OK, returnStatus); + returnStatus = mDescramblerBase->setMediaCasSession(streamSessionId); + EXPECT_TRUE(returnStatus.isOk()); + EXPECT_EQ(Status::OK, returnStatus); + } hidl_vec<uint8_t> hidlNullPtr; hidlNullPtr.setToExternal(static_cast<uint8_t*>(nullptr), 0); @@ -518,29 +533,31 @@ TEST_P(MediaCasHidlTest, TestClearKeyApisWithSession) { EXPECT_TRUE(returnStatus.isOk()); EXPECT_EQ(Status::OK, returnStatus); - EXPECT_FALSE(mDescramblerBase->requiresSecureDecoderComponent("video/avc")); + if (mIsTestDescrambler) { + EXPECT_FALSE(mDescramblerBase->requiresSecureDecoderComponent("video/avc")); - sp<IDescrambler> descrambler; - descrambler = IDescrambler::castFrom(mDescramblerBase); - ASSERT_NE(descrambler, nullptr); + sp<IDescrambler> descrambler; + descrambler = IDescrambler::castFrom(mDescramblerBase); + ASSERT_NE(descrambler, nullptr); - Status descrambleStatus = Status::OK; - sp<IMemory> dataMemory; + Status descrambleStatus = Status::OK; + sp<IMemory> dataMemory; - ASSERT_TRUE(descrambleTestInputBuffer(descrambler, &descrambleStatus, &dataMemory)); - EXPECT_EQ(Status::OK, descrambleStatus); + ASSERT_TRUE(descrambleTestInputBuffer(descrambler, &descrambleStatus, &dataMemory)); + EXPECT_EQ(Status::OK, descrambleStatus); - ASSERT_NE(nullptr, dataMemory.get()); - uint8_t* opBuffer = static_cast<uint8_t*>(static_cast<void*>(dataMemory->unsecurePointer())); + ASSERT_NE(nullptr, dataMemory.get()); + uint8_t* opBuffer = static_cast<uint8_t*>(static_cast<void*>(dataMemory->unsecurePointer())); - int compareResult = - memcmp(static_cast<const void*>(opBuffer), - static_cast<const void*>(kOutRefBinaryBuffer), sizeof(kOutRefBinaryBuffer)); - EXPECT_EQ(0, compareResult); + int compareResult = + memcmp(static_cast<const void*>(opBuffer), + static_cast<const void*>(kOutRefBinaryBuffer), sizeof(kOutRefBinaryBuffer)); + EXPECT_EQ(0, compareResult); - returnStatus = mDescramblerBase->release(); - EXPECT_TRUE(returnStatus.isOk()); - EXPECT_EQ(Status::OK, returnStatus); + returnStatus = mDescramblerBase->release(); + EXPECT_TRUE(returnStatus.isOk()); + EXPECT_EQ(Status::OK, returnStatus); + } returnStatus = mMediaCas->release(); EXPECT_TRUE(returnStatus.isOk()); diff --git a/cas/1.2/vts/functional/VtsHalCasV1_2TargetTest.cpp b/cas/1.2/vts/functional/VtsHalCasV1_2TargetTest.cpp index f436b8b5ee..333dea61db 100644 --- a/cas/1.2/vts/functional/VtsHalCasV1_2TargetTest.cpp +++ b/cas/1.2/vts/functional/VtsHalCasV1_2TargetTest.cpp @@ -311,6 +311,7 @@ class MediaCasHidlTest : public testing::TestWithParam<std::string> { sp<ICas> mMediaCas; sp<IDescramblerBase> mDescramblerBase; sp<MediaCasListener> mCasListener; + bool mIsTestDescrambler = false; typedef struct _OobInputTestParams { const SubSample* subSamples; uint32_t numSubSamples; @@ -355,8 +356,11 @@ class MediaCasHidlTest : public testing::TestWithParam<std::string> { auto descramblerStatus = mService->createDescrambler(caSystemId); if (!descramblerStatus.isOk()) { - return ::testing::AssertionFailure(); + ALOGI("Skip Descrambler test since it's not required in cas@1.2."); + return ::testing::AssertionSuccess(); } + mIsTestDescrambler = true; + mDescramblerBase = descramblerStatus; return ::testing::AssertionResult(mDescramblerBase != nullptr); } @@ -512,14 +516,15 @@ TEST_P(MediaCasHidlTest, TestClearKeyApisWithSession) { EXPECT_TRUE(returnStatus.isOk()); EXPECT_EQ(Status::OK, returnStatus); - returnStatus = mDescramblerBase->setMediaCasSession(sessionId); - EXPECT_TRUE(returnStatus.isOk()); - EXPECT_EQ(Status::OK, returnStatus); - - returnStatus = mDescramblerBase->setMediaCasSession(streamSessionId); - EXPECT_TRUE(returnStatus.isOk()); - EXPECT_EQ(Status::OK, returnStatus); + if (mIsTestDescrambler) { + returnStatus = mDescramblerBase->setMediaCasSession(sessionId); + EXPECT_TRUE(returnStatus.isOk()); + EXPECT_EQ(Status::OK, returnStatus); + returnStatus = mDescramblerBase->setMediaCasSession(streamSessionId); + EXPECT_TRUE(returnStatus.isOk()); + EXPECT_EQ(Status::OK, returnStatus); + } hidl_vec<uint8_t> hidlNullPtr; hidlNullPtr.setToExternal(static_cast<uint8_t*>(nullptr), 0); returnStatus = mMediaCas->refreshEntitlements(3, hidlNullPtr); @@ -566,29 +571,31 @@ TEST_P(MediaCasHidlTest, TestClearKeyApisWithSession) { EXPECT_TRUE(returnStatus.isOk()); EXPECT_EQ(Status::OK, returnStatus); - EXPECT_FALSE(mDescramblerBase->requiresSecureDecoderComponent("video/avc")); + if (mIsTestDescrambler) { + EXPECT_FALSE(mDescramblerBase->requiresSecureDecoderComponent("video/avc")); - sp<IDescrambler> descrambler; - descrambler = IDescrambler::castFrom(mDescramblerBase); - ASSERT_NE(descrambler, nullptr); + sp<IDescrambler> descrambler; + descrambler = IDescrambler::castFrom(mDescramblerBase); + ASSERT_NE(descrambler, nullptr); - Status descrambleStatus = Status::OK; - sp<IMemory> dataMemory; + Status descrambleStatus = Status::OK; + sp<IMemory> dataMemory; - ASSERT_TRUE(descrambleTestInputBuffer(descrambler, &descrambleStatus, &dataMemory)); - EXPECT_EQ(Status::OK, descrambleStatus); + ASSERT_TRUE(descrambleTestInputBuffer(descrambler, &descrambleStatus, &dataMemory)); + EXPECT_EQ(Status::OK, descrambleStatus); - ASSERT_NE(nullptr, dataMemory.get()); - uint8_t* opBuffer = static_cast<uint8_t*>(static_cast<void*>(dataMemory->unsecurePointer())); + ASSERT_NE(nullptr, dataMemory.get()); + uint8_t* opBuffer = static_cast<uint8_t*>(static_cast<void*>(dataMemory->unsecurePointer())); - int compareResult = - memcmp(static_cast<const void*>(opBuffer), - static_cast<const void*>(kOutRefBinaryBuffer), sizeof(kOutRefBinaryBuffer)); - EXPECT_EQ(0, compareResult); + int compareResult = + memcmp(static_cast<const void*>(opBuffer), + static_cast<const void*>(kOutRefBinaryBuffer), sizeof(kOutRefBinaryBuffer)); + EXPECT_EQ(0, compareResult); - returnStatus = mDescramblerBase->release(); - EXPECT_TRUE(returnStatus.isOk()); - EXPECT_EQ(Status::OK, returnStatus); + returnStatus = mDescramblerBase->release(); + EXPECT_TRUE(returnStatus.isOk()); + EXPECT_EQ(Status::OK, returnStatus); + } returnStatus = mMediaCas->release(); EXPECT_TRUE(returnStatus.isOk()); diff --git a/compatibility_matrices/compatibility_matrix.current.xml b/compatibility_matrices/compatibility_matrix.current.xml index 4845c775e5..c616f8700a 100644 --- a/compatibility_matrices/compatibility_matrix.current.xml +++ b/compatibility_matrices/compatibility_matrix.current.xml @@ -27,6 +27,14 @@ <instance>default</instance> </interface> </hal> + <hal format="aidl" optional="true"> + <name>android.hardware.authsecret</name> + <version>1</version> + <interface> + <name>IAuthSecret</name> + <instance>default</instance> + </interface> + </hal> <hal format="hidl" optional="true"> <name>android.hardware.authsecret</name> <version>1.0</version> @@ -190,7 +198,7 @@ </hal> <hal format="hidl" optional="true"> <name>android.hardware.drm</name> - <version>1.3</version> + <version>1.3-4</version> <interface> <name>ICryptoFactory</name> <regex-instance>.*</regex-instance> @@ -279,12 +287,29 @@ </interface> </hal> <hal format="aidl" optional="true"> + <name>android.hardware.health.storage</name> + <version>1</version> + <interface> + <name>IStorage</name> + <instance>default</instance> + </interface> + </hal> + <hal format="aidl" optional="true"> <name>android.hardware.identity</name> + <version>1-2</version> <interface> <name>IIdentityCredentialStore</name> <instance>default</instance> </interface> </hal> + <hal format="aidl" optional="true"> + <name>android.hardware.oemlock</name> + <version>1</version> + <interface> + <name>IOemLock</name> + <instance>default</instance> + </interface> + </hal> <hal format="hidl" optional="true"> <name>android.hardware.ir</name> <version>1.0</version> @@ -468,6 +493,20 @@ <regex-instance>SIM[1-9][0-9]*</regex-instance> </interface> </hal> + <hal format="aidl" optional="true"> + <name>android.hardware.security.secureclock</name> + <interface> + <name>ISecureClock</name> + <instance>default</instance> + </interface> + </hal> + <hal format="aidl" optional="true"> + <name>android.hardware.security.sharedsecret</name> + <interface> + <name>ISharedSecret</name> + <instance>default</instance> + </interface> + </hal> <hal format="hidl" optional="true"> <name>android.hardware.sensors</name> <version>1.0</version> @@ -552,6 +591,7 @@ </hal> <hal format="aidl" optional="true"> <name>android.hardware.vibrator</name> + <version>1-2</version> <interface> <name>IVibrator</name> <instance>default</instance> @@ -559,20 +599,13 @@ </hal> <hal format="aidl" optional="true"> <name>android.hardware.vibrator</name> + <version>1-2</version> <interface> <name>IVibratorManager</name> <instance>default</instance> </interface> </hal> <hal format="hidl" optional="true"> - <name>android.hardware.vr</name> - <version>1.0</version> - <interface> - <name>IVr</name> - <instance>default</instance> - </interface> - </hal> - <hal format="hidl" optional="true"> <name>android.hardware.weaver</name> <version>1.0</version> <interface> diff --git a/contexthub/1.0/IContexthub.hal b/contexthub/1.0/IContexthub.hal index 8dccd67276..950e081326 100644 --- a/contexthub/1.0/IContexthub.hal +++ b/contexthub/1.0/IContexthub.hal @@ -41,7 +41,7 @@ interface IContexthub { * registration. * * @param hubId identifier for the hub - * callback an implementation of the IContextHubCallbacks + * @param callback an implementation of the IContextHubCallbacks * * @return result OK on success * BAD_VALUE if parameters are not sane @@ -53,7 +53,7 @@ interface IContexthub { * Send a message to a hub * * @param hubId identifier for hub to send message to - * msg message to be sent + * @param msg message to be sent * * @return result OK if successful, error code otherwise * BAD_VALUE if parameters are not sane @@ -77,9 +77,9 @@ interface IContexthub { * device. * * @param hubId identifer of the contextHub - * appBinary contains the binary representation of the nanoApp, plus + * @param appBinary contains the binary representation of the nanoApp, plus * metadata - * transactionId transactionId for this call + * @param transactionId transactionId for this call * * @return result OK if transation started * BAD_VALUE if parameters are not sane @@ -101,8 +101,8 @@ interface IContexthub { * Unloading a nanoapp must not take more than 5 seconds. * * @param hubId identifer of the contextHub - * appId appIdentifier returned by the HAL - * msg message to be sent + * @param appId appIdentifier returned by the HAL + * @param msg message to be sent * * @return result OK if transation started * BAD_VALUE if parameters are not sane @@ -122,8 +122,8 @@ interface IContexthub { * Enabling a nanoapp must not take more than 5 seconds. * * @param hubId identifer of the contextHub - * appId appIdentifier returned by the HAL - * msg message to be sent + * @param appId appIdentifier returned by the HAL + * @param msg message to be sent * * @return result OK if transation started * BAD_VALUE if parameters are not sane @@ -143,8 +143,8 @@ interface IContexthub { * Disabling a nanoapp must not take more than 5 seconds. * * @param hubId identifer of the contextHub - * appId appIdentifier returned by the HAL - * msg message to be sent + * @param appId appIdentifier returned by the HAL + * @param msg message to be sent * * @return result OK if transation started * BAD_VALUE if parameters are not sane diff --git a/contexthub/1.0/IContexthubCallback.hal b/contexthub/1.0/IContexthubCallback.hal index 264f84ca17..70750f808b 100644 --- a/contexthub/1.0/IContexthubCallback.hal +++ b/contexthub/1.0/IContexthubCallback.hal @@ -22,7 +22,7 @@ interface IContexthubCallback { * implementation to allow the HAL to send asynchronous messages back * to the service and registered clients of the ContextHub service. * - * @params msg : message + * @param msg message being sent from the contexthub * */ handleClientMsg(ContextHubMsg msg); @@ -32,9 +32,9 @@ interface IContexthubCallback { * implementation to allow the HAL to send the response for a * transaction. * - * @params txnId : transaction id whose result is being sent - * passed in by the service at start of transacation. - * result: result of transaction. + * @param txnId transaction id whose result is being sent + * passed in by the service at start of transacation. + * @param result result of transaction. * */ handleTxnResult(uint32_t txnId, TransactionResult result); @@ -44,7 +44,7 @@ interface IContexthubCallback { * implementation to allow the HAL to send an asynchronous event * to the ContextHub service. * - * @params msg : message + * @param evt event being sent from the contexthub * */ handleHubEvent(AsyncEventType evt); @@ -55,8 +55,8 @@ interface IContexthubCallback { * that a nanp-app has aborted. * This method must be called when a nanoapp invokes chreAbort(...)). * - * @params appId : app identifier - * : abortCode code passed by the nanoApp. + * @param appId app identifier + * @param abortCode code passed by the nanoApp. * * Also see chreAbort(...) * @@ -68,12 +68,11 @@ interface IContexthubCallback { * implementation to allow the HAL to send information about the * currently loaded and active nanoapps on the hub. * - * @params appInfo : vector of HubAppinfo structure for each nanoApp - * on the hub that can be enabled, disabled and - * unloaded by the service. Any nanoApps that cannot - * be controlled by the service must not be reported. - * All nanoApps that can be controlled by the service - * must be reported. + * @param appInfo vector of HubAppinfo structure for each nanoApp on the + * hub that can be enabled, disabled and unloaded by the + * service. Any nanoApps that cannot be controlled by the + * service must not be reported. All nanoApps that can be + * controlled by the service must be reported. */ handleAppsInfo(vec<HubAppInfo> appInfo); }; diff --git a/contexthub/1.1/default/Contexthub.cpp b/contexthub/1.1/default/Contexthub.cpp index e7fde84058..2d4fbaee24 100644 --- a/contexthub/1.1/default/Contexthub.cpp +++ b/contexthub/1.1/default/Contexthub.cpp @@ -23,10 +23,29 @@ namespace contexthub { namespace V1_1 { namespace implementation { +using ::android::hardware::contexthub::V1_0::Result; + Return<void> Contexthub::onSettingChanged(Setting /*setting*/, SettingValue /*newValue*/) { return Void(); } +Return<Result> Contexthub::registerCallback(uint32_t hubId, const sp<IContexthubCallback>& cb) { + if (hubId == kMockHubId) { + mCallback = cb; + return Result::OK; + } + return Result::BAD_PARAMS; +} + +Return<Result> Contexthub::queryApps(uint32_t hubId) { + if (hubId == kMockHubId && mCallback != nullptr) { + std::vector<HubAppInfo> nanoapps; + mCallback->handleAppsInfo(nanoapps); + return Result::OK; + } + return Result::BAD_PARAMS; +} + } // namespace implementation } // namespace V1_1 } // namespace contexthub diff --git a/contexthub/1.1/default/Contexthub.h b/contexthub/1.1/default/Contexthub.h index 1468fcfc0e..648749e27a 100644 --- a/contexthub/1.1/default/Contexthub.h +++ b/contexthub/1.1/default/Contexthub.h @@ -27,9 +27,19 @@ namespace implementation { class Contexthub : public ::android::hardware::contexthub::V1_X::implementation::ContextHub<IContexthub> { + using Result = ::android::hardware::contexthub::V1_0::Result; + public: + // Methods from V1_0::IContexthub + Return<Result> registerCallback(uint32_t hubId, const sp<IContexthubCallback>& cb) override; + + Return<Result> queryApps(uint32_t hubId) override; + // Methods from V1_1::IContexthub Return<void> onSettingChanged(Setting setting, SettingValue newValue) override; + + private: + sp<IContexthubCallback> mCallback; }; } // namespace implementation diff --git a/contexthub/1.2/Android.bp b/contexthub/1.2/Android.bp index e81948234a..9722a97ee6 100644 --- a/contexthub/1.2/Android.bp +++ b/contexthub/1.2/Android.bp @@ -6,6 +6,7 @@ hidl_interface { srcs: [ "types.hal", "IContexthub.hal", + "IContexthubCallback.hal", ], interfaces: [ "android.hardware.contexthub@1.0", diff --git a/contexthub/1.2/IContexthub.hal b/contexthub/1.2/IContexthub.hal index 819fc1d6e1..3488b7446c 100644 --- a/contexthub/1.2/IContexthub.hal +++ b/contexthub/1.2/IContexthub.hal @@ -16,11 +16,41 @@ package android.hardware.contexthub@1.2; +import @1.0::Result; import @1.1::IContexthub; import @1.1::SettingValue; +import IContexthubCallback; interface IContexthub extends @1.1::IContexthub { /** + * Register a callback for the HAL implementation to send asynchronous + * messages to the service from a context hub. There can be a maximum of + * one callback registered with the HAL. A call to this function when a + * callback has already been registered must override the previous + * registration. + * + * @param hubId identifier for the hub + * @param callback an implementation of the IContextHubCallbacks + * + * @return result OK on success + * BAD_VALUE if parameters are not valid + * + */ + registerCallback_1_2(uint32_t hubId, IContexthubCallback cb) generates (Result result); + + /** + * Send a message to a hub + * + * @param hubId identifier for hub to send message to + * @param msg message to be sent + * + * @return result OK if successful, error code otherwise + * BAD_VALUE if parameters are not valid + * TRANSACTION_FAILED if message send failed + */ + sendMessageToHub_1_2(uint32_t hubId, ContextHubMsg msg) generates (Result result); + + /** * Notification sent by the framework to indicate that the user * has changed a setting. * diff --git a/contexthub/1.2/IContexthubCallback.hal b/contexthub/1.2/IContexthubCallback.hal new file mode 100644 index 0000000000..0236160305 --- /dev/null +++ b/contexthub/1.2/IContexthubCallback.hal @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * 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. + */ + +package android.hardware.contexthub@1.2; + +import @1.0::IContexthubCallback; + +interface IContexthubCallback extends @1.0::IContexthubCallback { + /** + * This callback is passed by the Contexthub service to the HAL + * implementation to allow the HAL to send asynchronous messages back + * to the service and registered clients of the ContextHub service. + * + * @param msg message that should be delivered to host app clients + * + */ + handleClientMsg_1_2(ContextHubMsg msg); + + /** + * This callback is passed by the Contexthub service to the HAL + * implementation to allow the HAL to send information about the + * currently loaded and active nanoapps on the hub. + * + * @param appInfo vector of HubAppinfo structure for each nanoApp + * on the hub that can be enabled, disabled and + * unloaded by the service. Any nanoApps that cannot + * be controlled by the service must not be reported. + * All nanoApps that can be controlled by the service + * must be reported. + */ + handleAppsInfo_1_2(vec<HubAppInfo> appInfo); +}; diff --git a/contexthub/1.2/default/Android.bp b/contexthub/1.2/default/Android.bp index 49b54fc007..0a31325811 100644 --- a/contexthub/1.2/default/Android.bp +++ b/contexthub/1.2/default/Android.bp @@ -41,6 +41,7 @@ cc_binary { ], header_libs: [ "android.hardware.contexthub@1.X-common-impl", + "android.hardware.contexthub@1.X-common-utils", ], vintf_fragments: ["android.hardware.contexthub@1.2.xml"], } diff --git a/contexthub/1.2/default/Contexthub.cpp b/contexthub/1.2/default/Contexthub.cpp index d7ac7bf30e..db0c5bc3df 100644 --- a/contexthub/1.2/default/Contexthub.cpp +++ b/contexthub/1.2/default/Contexthub.cpp @@ -23,6 +23,43 @@ namespace contexthub { namespace V1_2 { namespace implementation { +using ::android::hardware::contexthub::V1_0::Result; +using ::android::hardware::contexthub::V1_X::implementation::IContextHubCallbackWrapperV1_0; +using ::android::hardware::contexthub::V1_X::implementation::IContextHubCallbackWrapperV1_2; + +Return<Result> Contexthub::registerCallback(uint32_t hubId, + const sp<V1_0::IContexthubCallback>& cb) { + if (hubId == kMockHubId) { + mCallback = new IContextHubCallbackWrapperV1_0(cb); + return Result::OK; + } + return Result::BAD_PARAMS; +} + +Return<Result> Contexthub::queryApps(uint32_t hubId) { + if (hubId == kMockHubId && mCallback != nullptr) { + std::vector<V1_2::HubAppInfo> nanoapps; + mCallback->handleAppsInfo(nanoapps); + return Result::OK; + } + return Result::BAD_PARAMS; +} + +Return<Result> Contexthub::registerCallback_1_2(uint32_t hubId, + const sp<V1_2::IContexthubCallback>& cb) { + if (hubId == kMockHubId) { + mCallback = new IContextHubCallbackWrapperV1_2(cb); + return Result::OK; + } + return Result::BAD_PARAMS; +} + +// We don't expose any nanoapps, therefore all nanoapp-related API calls return with BAD_PARAMS +Return<Result> Contexthub::sendMessageToHub_1_2(uint32_t /* hubId */, + const ContextHubMsg& /* msg */) { + return Result::BAD_PARAMS; +} + Return<void> Contexthub::onSettingChanged(SettingV1_1 /*setting*/, SettingValue /*newValue*/) { return Void(); } diff --git a/contexthub/1.2/default/Contexthub.h b/contexthub/1.2/default/Contexthub.h index d2f8d69afc..8b89824d6b 100644 --- a/contexthub/1.2/default/Contexthub.h +++ b/contexthub/1.2/default/Contexthub.h @@ -16,6 +16,7 @@ #pragma once #include "ContextHub.h" +#include "IContextHubCallbackWrapper.h" #include <android/hardware/contexthub/1.2/IContexthub.h> @@ -27,15 +28,34 @@ namespace implementation { class Contexthub : public ::android::hardware::contexthub::V1_X::implementation::ContextHub<IContexthub> { + using ContextHubMsg = ::android::hardware::contexthub::V1_2::ContextHubMsg; + using IContexthubCallback = ::android::hardware::contexthub::V1_2::IContexthubCallback; + using IContextHubCallbackWrapperBase = + ::android::hardware::contexthub::V1_X::implementation::IContextHubCallbackWrapperBase; + using Result = ::android::hardware::contexthub::V1_0::Result; using SettingValue = ::android::hardware::contexthub::V1_1::SettingValue; using SettingV1_1 = ::android::hardware::contexthub::V1_1::Setting; public: + // Methods from V1_0::IContexthub + Return<Result> registerCallback(uint32_t hubId, + const sp<V1_0::IContexthubCallback>& cb) override; + + Return<Result> queryApps(uint32_t hubId) override; + // Methods from V1_1::IContexthub Return<void> onSettingChanged(SettingV1_1 setting, SettingValue newValue) override; // Methods from V1_2::IContexthub Return<void> onSettingChanged_1_2(Setting setting, SettingValue newValue) override; + + Return<Result> registerCallback_1_2(uint32_t hubId, + const sp<V1_2::IContexthubCallback>& cb) override; + + Return<Result> sendMessageToHub_1_2(uint32_t hubId, const ContextHubMsg& msg) override; + + private: + sp<IContextHubCallbackWrapperBase> mCallback; }; } // namespace implementation diff --git a/contexthub/1.2/types.hal b/contexthub/1.2/types.hal index 38f9f7af55..5a11efea46 100644 --- a/contexthub/1.2/types.hal +++ b/contexthub/1.2/types.hal @@ -16,6 +16,8 @@ package android.hardware.contexthub@1.2; +import @1.0::ContextHubMsg; +import @1.0::HubAppInfo; import @1.1::Setting; /** @@ -31,4 +33,43 @@ enum Setting : @1.1::Setting { */ WIFI_AVAILABLE, AIRPLANE_MODE, + + /** + * Indicates if the microphone access was turned off globally by the user, + * in which case audio data cannot be used and propagated by CHRE. + */ + GLOBAL_MIC_DISABLE, +}; + +struct ContextHubMsg { + @1.0::ContextHubMsg msg_1_0; + + /** + * The list of Android permissions that the sender of this message has at + * the time the message was sent. + * + * The HAL MUST drop messages to nanoapps if this list of permissions is not + * a superset of those of the receiving nanoapp(s). + * + * The framework MUST drop messages to host apps that don't have a superset + * of the permissions that the sending nanoapp is using. + */ + vec<string> permissions; +}; + +struct HubAppInfo { + @1.0::HubAppInfo info_1_0; + + /** + * The list of Android permissions used by this nanoapp. This list MUST + * correspond to the permissions required for an equivalent Android app to + * sample similar signals through the Android framework. + * + * For example, if a nanoapp used location-based signals, the permissions + * list MUST contains android.permission.ACCESS_FINE_LOCATION and + * android.permission.ACCESS_BACKGROUND_LOCATION. If it were to also list to + * audio data, it would require adding android.permission.RECORD_AUDIO to + * this list. + */ + vec<string> permissions; }; diff --git a/contexthub/1.2/vts/functional/VtsHalContexthubV1_2TargetTest.cpp b/contexthub/1.2/vts/functional/VtsHalContexthubV1_2TargetTest.cpp index 77883c2423..782edae610 100644 --- a/contexthub/1.2/vts/functional/VtsHalContexthubV1_2TargetTest.cpp +++ b/contexthub/1.2/vts/functional/VtsHalContexthubV1_2TargetTest.cpp @@ -62,6 +62,13 @@ TEST_P(ContexthubHidlTest, TestOnAirplaneModeSettingChanged) { ASSERT_OK(registerCallback(nullptr)); } +TEST_P(ContexthubHidlTest, TestOnGlobalMicDisableSettingChanged) { + ASSERT_OK(registerCallback(new ContexthubCallbackBase())); + hubApi->onSettingChanged_1_2(Setting::GLOBAL_MIC_DISABLE, SettingValue::DISABLED); + hubApi->onSettingChanged_1_2(Setting::GLOBAL_MIC_DISABLE, SettingValue::ENABLED); + ASSERT_OK(registerCallback(nullptr)); +} + GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(ContexthubHidlTest); INSTANTIATE_TEST_SUITE_P(HubIdSpecificTests, ContexthubHidlTest, testing::ValuesIn(kTestParameters), android::hardware::PrintInstanceTupleNameToString<>); diff --git a/contexthub/common/default/1.X/ContextHub.h b/contexthub/common/default/1.X/ContextHub.h index 73d06319dd..00f74afa1b 100644 --- a/contexthub/common/default/1.X/ContextHub.h +++ b/contexthub/common/default/1.X/ContextHub.h @@ -60,14 +60,6 @@ struct ContextHub : public IContextHubInterface { return Void(); } - Return<Result> registerCallback(uint32_t hubId, const sp<IContexthubCallback>& cb) override { - if (hubId == kMockHubId) { - mCallback = cb; - return Result::OK; - } - return Result::BAD_PARAMS; - } - // We don't expose any nanoapps, therefore all nanoapp-related API calls return with BAD_PARAMS Return<Result> sendMessageToHub(uint32_t /*hubId*/, const ContextHubMsg& /*msg*/) override { return Result::BAD_PARAMS; @@ -93,19 +85,8 @@ struct ContextHub : public IContextHubInterface { return Result::BAD_PARAMS; } - Return<Result> queryApps(uint32_t hubId) override { - if (hubId == kMockHubId && mCallback != nullptr) { - std::vector<HubAppInfo> nanoapps; - mCallback->handleAppsInfo(nanoapps); - return Result::OK; - } - return Result::BAD_PARAMS; - } - - private: + protected: static constexpr uint32_t kMockHubId = 0; - - sp<IContexthubCallback> mCallback; }; } // namespace implementation diff --git a/contexthub/common/default/1.X/OWNERS b/contexthub/common/default/1.X/OWNERS new file mode 100644 index 0000000000..90c233030e --- /dev/null +++ b/contexthub/common/default/1.X/OWNERS @@ -0,0 +1,3 @@ +arthuri@google.com +bduddie@google.com +stange@google.com diff --git a/contexthub/common/default/1.X/utils/Android.bp b/contexthub/common/default/1.X/utils/Android.bp new file mode 100644 index 0000000000..c74b647413 --- /dev/null +++ b/contexthub/common/default/1.X/utils/Android.bp @@ -0,0 +1,30 @@ +// +// Copyright (C) 2020 The Android Open Source Project +// +// 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. + +cc_library_headers { + name: "android.hardware.contexthub@1.X-common-utils", + vendor_available: true, + defaults: ["hidl_defaults"], + export_include_dirs: ["."], + shared_libs: [ + "android.hardware.contexthub@1.0", + "android.hardware.contexthub@1.1", + "android.hardware.contexthub@1.2", + "libbinder", + "libcutils", + "libhidlbase", + "libutils", + ], +} diff --git a/contexthub/common/default/1.X/utils/IContextHubCallbackWrapper.h b/contexthub/common/default/1.X/utils/IContextHubCallbackWrapper.h new file mode 100644 index 0000000000..df78438750 --- /dev/null +++ b/contexthub/common/default/1.X/utils/IContextHubCallbackWrapper.h @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * 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 ANDROID_HARDWARE_CONTEXTHUB_V1_X_ICONTEXTHUBCALLBACKWRAPPER_H +#define ANDROID_HARDWARE_CONTEXTHUB_V1_X_ICONTEXTHUBCALLBACKWRAPPER_H + +#include "android/hardware/contexthub/1.0/IContexthub.h" +#include "android/hardware/contexthub/1.0/IContexthubCallback.h" +#include "android/hardware/contexthub/1.0/types.h" +#include "android/hardware/contexthub/1.2/IContexthubCallback.h" +#include "android/hardware/contexthub/1.2/types.h" + +#include <utils/LightRefBase.h> + +#include <cassert> + +namespace android { +namespace hardware { +namespace contexthub { +namespace V1_X { +namespace implementation { + +inline V1_0::ContextHubMsg convertToOldMsg(V1_2::ContextHubMsg msg) { + return msg.msg_1_0; +} + +inline hidl_vec<V1_0::HubAppInfo> convertToOldAppInfo(hidl_vec<V1_2::HubAppInfo> appInfos) { + hidl_vec<V1_0::HubAppInfo> convertedInfo(appInfos.size()); + for (int i = 0; i < appInfos.size(); ++i) { + convertedInfo[i] = appInfos[i].info_1_0; + } + + return convertedInfo; +} + +/** + * The IContexthubCallback classes below abstract away the common logic between both the V1.0, and + * V1.2 versions of the Contexthub HAL callback interface. This allows users of these classes to + * only care about the HAL version at init time and then interact with either version of the + * callback without worrying about the class type by utilizing the base class. + */ +class IContextHubCallbackWrapperBase : public VirtualLightRefBase { + public: + virtual Return<void> handleClientMsg(V1_2::ContextHubMsg msg) = 0; + + virtual Return<void> handleTxnResult(uint32_t txnId, V1_0::TransactionResult result) = 0; + + virtual Return<void> handleHubEvent(V1_0::AsyncEventType evt) = 0; + + virtual Return<void> handleAppAbort(uint64_t appId, uint32_t abortCode) = 0; + + virtual Return<void> handleAppsInfo(hidl_vec<V1_2::HubAppInfo> appInfo) = 0; +}; + +template <typename T> +class ContextHubCallbackWrapper : public IContextHubCallbackWrapperBase { + public: + ContextHubCallbackWrapper(sp<T> callback) : mCallback(callback){}; + + virtual Return<void> handleClientMsg(V1_2::ContextHubMsg msg) override { + return mCallback->handleClientMsg(convertToOldMsg(msg)); + } + + virtual Return<void> handleTxnResult(uint32_t txnId, V1_0::TransactionResult result) override { + return mCallback->handleTxnResult(txnId, result); + } + + virtual Return<void> handleHubEvent(V1_0::AsyncEventType evt) override { + return mCallback->handleHubEvent(evt); + } + + virtual Return<void> handleAppAbort(uint64_t appId, uint32_t abortCode) override { + return mCallback->handleAppAbort(appId, abortCode); + } + + virtual Return<void> handleAppsInfo(hidl_vec<V1_2::HubAppInfo> appInfo) override { + return mCallback->handleAppsInfo(convertToOldAppInfo(appInfo)); + } + + protected: + sp<T> mCallback; +}; + +class IContextHubCallbackWrapperV1_0 : public ContextHubCallbackWrapper<V1_0::IContexthubCallback> { + public: + IContextHubCallbackWrapperV1_0(sp<V1_0::IContexthubCallback> callback) + : ContextHubCallbackWrapper(callback){}; +}; + +class IContextHubCallbackWrapperV1_2 : public ContextHubCallbackWrapper<V1_2::IContexthubCallback> { + public: + IContextHubCallbackWrapperV1_2(sp<V1_2::IContexthubCallback> callback) + : ContextHubCallbackWrapper(callback){}; + + Return<void> handleClientMsg(V1_2::ContextHubMsg msg) override { + return mCallback->handleClientMsg_1_2(msg); + } + + Return<void> handleAppsInfo(hidl_vec<V1_2::HubAppInfo> appInfo) override { + return mCallback->handleAppsInfo_1_2(appInfo); + } +}; + +} // namespace implementation +} // namespace V1_X +} // namespace contexthub +} // namespace hardware +} // namespace android + +#endif // ANDROID_HARDWARE_CONTEXTHUB_V1_X_ICONTEXTHUBCALLBACKWRAPPER_H
\ No newline at end of file diff --git a/current.txt b/current.txt index 8623fc001b..bf6829a353 100644 --- a/current.txt +++ b/current.txt @@ -767,6 +767,8 @@ a64467bae843569f0d465c5be7f0c7a5b987985b55a3ef4794dd5afc68538650 android.hardwar 98592d193a717066facf91428426e5abe211e3bd718bc372e29fb944ddbe6e7c android.hardware.wifi.supplicant@1.3::types # ABI preserving changes to HALs during Android S +e042522daa4b5f7fd4a0a19bcdadb93c79a1b04c09ef2c9813a3a8941032f3f5 android.hardware.contexthub@1.0::IContexthub +c2f64133b83ede65c9939ef97ab5bd867b73faf3dba0e7e69f77c3c43d9e487e android.hardware.contexthub@1.0::IContexthubCallback 1ca372cd67d197df099e87616a613ba6ede6552638a603e18f86c8834302c3d1 android.hardware.gnss@1.0::IGnssMeasurementCallback 6a271e493907e8ba20912e42771bd0d99ae45431a851d5675ef9496d02510a34 android.hardware.gnss@1.1::IGnssMeasurementCallback 2c331a9605f3a08d9c1e0a36169ca57758bc43c11a78ef3f3730509885e52c15 android.hardware.graphics.composer@2.4::IComposerClient @@ -787,6 +789,4 @@ b9fbb6e2e061ed0960939d48b785e9700210add1f13ed32ecd688d0f1ca20ef7 android.hardwar # HALs released in Android S # NOTE: waiting to freeze HALs until later in the release -# NOTE: new HALs are recommended to be in AIDL -6e64b33f1b720b66b0deb5e08dee37a99deaa94e2e9ebf7806703cabab56e21d android.hardware.contexthub@1.2::IContexthub -3fb83f4539cab2c7bf9fdbecf7265d1c1dd6e8de9694046fe512b493c127ccea android.hardware.contexthub@1.2::types +# NOTE: new HALs are recommended to be in AIDL
\ No newline at end of file diff --git a/drm/1.4/Android.bp b/drm/1.4/Android.bp new file mode 100644 index 0000000000..8e1dc93c9f --- /dev/null +++ b/drm/1.4/Android.bp @@ -0,0 +1,20 @@ +// This file is autogenerated by hidl-gen -Landroidbp. + +hidl_interface { + name: "android.hardware.drm@1.4", + root: "android.hardware", + srcs: [ + "ICryptoFactory.hal", + "ICryptoPlugin.hal", + "IDrmFactory.hal", + "IDrmPlugin.hal", + ], + interfaces: [ + "android.hardware.drm@1.0", + "android.hardware.drm@1.1", + "android.hardware.drm@1.2", + "android.hardware.drm@1.3", + "android.hidl.base@1.0", + ], + gen_java: false, +} diff --git a/drm/1.4/ICryptoFactory.hal b/drm/1.4/ICryptoFactory.hal new file mode 100644 index 0000000000..6cbf9e3449 --- /dev/null +++ b/drm/1.4/ICryptoFactory.hal @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * 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. + */ +package android.hardware.drm@1.4; + +import @1.3::ICryptoFactory; + +/** + * ICryptoFactory is the main entry point for interacting with a vendor's + * crypto HAL to create crypto plugins. Crypto plugins create crypto sessions + * which are used by a codec to decrypt protected video content. + * + * The 1.4 factory must always create 1.4 ICryptoPlugin interfaces, which are + * returned via the 1.0 createPlugin method. + * + * To use 1.4 features the caller must cast the returned interface to a + * 1.4 HAL, using V1_4::ICryptoPlugin::castFrom(). + */ +interface ICryptoFactory extends @1.3::ICryptoFactory { +}; diff --git a/drm/1.4/ICryptoPlugin.hal b/drm/1.4/ICryptoPlugin.hal new file mode 100644 index 0000000000..874ef4cd3b --- /dev/null +++ b/drm/1.4/ICryptoPlugin.hal @@ -0,0 +1,26 @@ +/** + * Copyright (C) 2021 The Android Open Source Project + * + * 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. + */ +package android.hardware.drm@1.4; + +import @1.2::ICryptoPlugin; + +/** + * ICryptoPlugin is the HAL for vendor-provided crypto plugins. + * It allows crypto sessions to be opened and operated on, to + * load crypto keys for a codec to decrypt protected video content. + */ +interface ICryptoPlugin extends @1.2::ICryptoPlugin { +}; diff --git a/drm/1.4/IDrmFactory.hal b/drm/1.4/IDrmFactory.hal new file mode 100644 index 0000000000..035a2983ae --- /dev/null +++ b/drm/1.4/IDrmFactory.hal @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * 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. + */ +package android.hardware.drm@1.4; + +import @1.3::IDrmFactory; + +/** + * IDrmFactory is the main entry point for interacting with a vendor's + * drm HAL to create drm plugin instances. A drm plugin instance + * creates drm sessions which are used to obtain keys for a crypto + * session so it can decrypt protected video content. + * + * The 1.4 factory must always create 1.4 IDrmPlugin interfaces, which are + * returned via the 1.0 createPlugin method. + * + * To use 1.4 features the caller must cast the returned interface to a + * 1.4 HAL, using V1_4::IDrmPlugin::castFrom(). + */ + +interface IDrmFactory extends @1.3::IDrmFactory { +}; diff --git a/drm/1.4/IDrmPlugin.hal b/drm/1.4/IDrmPlugin.hal new file mode 100644 index 0000000000..e8af230033 --- /dev/null +++ b/drm/1.4/IDrmPlugin.hal @@ -0,0 +1,64 @@ +/** + * Copyright (C) 2021 The Android Open Source Project + * + * 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. + */ +package android.hardware.drm@1.4; + +import @1.0::Status; +import @1.0::SessionId; +import @1.1::SecurityLevel; +import @1.2::IDrmPlugin; + +/** + * IDrmPlugin is used to interact with a specific drm plugin that was + * created by IDrmFactory::createPlugin. A drm plugin provides methods for + * obtaining drm keys to be used by a codec to decrypt protected video + * content. + */ +interface IDrmPlugin extends @1.2::IDrmPlugin { + + /** + * Check if the specified mime-type & security level require a secure decoder + * component. + * + * @param mime The content mime-type + * @param level the requested security level + * @return secureRequired must be true if and only if a secure decoder is required + * for the specified mime-type & security level + */ + requiresSecureDecoder(string mime, @1.1::SecurityLevel level) generates (bool secureRequired); + + /** + * Check if the specified mime-type requires a secure decoder component + * at the highest security level supported on the device. + * + * @param mime The content mime-type + * @return secureRequired must be true if and only if a secure decoder is required + * for the specified mime-type + */ + requiresSecureDecoderDefault(string mime) generates (bool secureRequired); + + /** + * Set playback id of a drm session. The playback id can be used to join drm session metrics + * with metrics from other low level media components, e.g. codecs, or metrics from the high + * level player. + * + * @param sessionId drm session id + * @param playbackId high level playback id + * @return status the status of the call. The status must be OK on success, or + * ERROR_DRM_SESSION_NOT_OPENED if the drm session cannot be found + */ + setPlaybackId(SessionId sessionId, string playbackId) generates (@1.0::Status status); + +}; diff --git a/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/BlocklistedSource.aidl b/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/BlocklistedSource.aidl index 89f5d53106..03026761f8 100644 --- a/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/BlocklistedSource.aidl +++ b/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/BlocklistedSource.aidl @@ -2,13 +2,14 @@ // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // /////////////////////////////////////////////////////////////////////////////// -// This file is a snapshot of an AIDL interface (or parcelable). Do not try to -// edit this file. It looks like you are doing that because you have modified -// an AIDL interface in a backward-incompatible way, e.g., deleting a function -// from an interface or a field from a parcelable and it broke the build. That -// breakage is intended. +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. // -// You must not make a backward incompatible changes to the AIDL files built +// You must not make a backward incompatible change to any AIDL file built // with the aidl_interface module type with versions property set. The module // type is used to build AIDL files in a way that they can be used across // independently updatable components of the system. If a device is shipped diff --git a/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/CorrelationVector.aidl b/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/CorrelationVector.aidl new file mode 100644 index 0000000000..1f713fa4e0 --- /dev/null +++ b/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/CorrelationVector.aidl @@ -0,0 +1,26 @@ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. +// +// You must not make a backward incompatible change to any AIDL file built +// with the aidl_interface module type with versions property set. The module +// type is used to build AIDL files in a way that they can be used across +// independently updatable components of the system. If a device is shipped +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.hardware.gnss; +@VintfStability +parcelable CorrelationVector { + int frequencyOffsetMps; + double samplingWidthM; + double samplingStartM; + int[] magnitude; +} diff --git a/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/ElapsedRealtime.aidl b/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/ElapsedRealtime.aidl index a0e8de41a3..933f659c57 100644 --- a/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/ElapsedRealtime.aidl +++ b/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/ElapsedRealtime.aidl @@ -2,13 +2,14 @@ // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // /////////////////////////////////////////////////////////////////////////////// -// This file is a snapshot of an AIDL interface (or parcelable). Do not try to -// edit this file. It looks like you are doing that because you have modified -// an AIDL interface in a backward-incompatible way, e.g., deleting a function -// from an interface or a field from a parcelable and it broke the build. That -// breakage is intended. +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. // -// You must not make a backward incompatible changes to the AIDL files built +// You must not make a backward incompatible change to any AIDL file built // with the aidl_interface module type with versions property set. The module // type is used to build AIDL files in a way that they can be used across // independently updatable components of the system. If a device is shipped diff --git a/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/GnssClock.aidl b/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/GnssClock.aidl index 42b940e886..53ac0efa37 100644 --- a/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/GnssClock.aidl +++ b/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/GnssClock.aidl @@ -2,13 +2,14 @@ // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // /////////////////////////////////////////////////////////////////////////////// -// This file is a snapshot of an AIDL interface (or parcelable). Do not try to -// edit this file. It looks like you are doing that because you have modified -// an AIDL interface in a backward-incompatible way, e.g., deleting a function -// from an interface or a field from a parcelable and it broke the build. That -// breakage is intended. +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. // -// You must not make a backward incompatible changes to the AIDL files built +// You must not make a backward incompatible change to any AIDL file built // with the aidl_interface module type with versions property set. The module // type is used to build AIDL files in a way that they can be used across // independently updatable components of the system. If a device is shipped diff --git a/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/GnssConstellationType.aidl b/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/GnssConstellationType.aidl index 30d0227577..18fdfa91c2 100644 --- a/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/GnssConstellationType.aidl +++ b/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/GnssConstellationType.aidl @@ -2,13 +2,14 @@ // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // /////////////////////////////////////////////////////////////////////////////// -// This file is a snapshot of an AIDL interface (or parcelable). Do not try to -// edit this file. It looks like you are doing that because you have modified -// an AIDL interface in a backward-incompatible way, e.g., deleting a function -// from an interface or a field from a parcelable and it broke the build. That -// breakage is intended. +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. // -// You must not make a backward incompatible changes to the AIDL files built +// You must not make a backward incompatible change to any AIDL file built // with the aidl_interface module type with versions property set. The module // type is used to build AIDL files in a way that they can be used across // independently updatable components of the system. If a device is shipped diff --git a/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/GnssData.aidl b/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/GnssData.aidl index 7ffabd2a3c..73ead10ff7 100644 --- a/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/GnssData.aidl +++ b/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/GnssData.aidl @@ -2,13 +2,14 @@ // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // /////////////////////////////////////////////////////////////////////////////// -// This file is a snapshot of an AIDL interface (or parcelable). Do not try to -// edit this file. It looks like you are doing that because you have modified -// an AIDL interface in a backward-incompatible way, e.g., deleting a function -// from an interface or a field from a parcelable and it broke the build. That -// breakage is intended. +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. // -// You must not make a backward incompatible changes to the AIDL files built +// You must not make a backward incompatible change to any AIDL file built // with the aidl_interface module type with versions property set. The module // type is used to build AIDL files in a way that they can be used across // independently updatable components of the system. If a device is shipped diff --git a/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/GnssMeasurement.aidl b/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/GnssMeasurement.aidl index 73d8a86187..3d287e4a1a 100644 --- a/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/GnssMeasurement.aidl +++ b/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/GnssMeasurement.aidl @@ -2,13 +2,14 @@ // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // /////////////////////////////////////////////////////////////////////////////// -// This file is a snapshot of an AIDL interface (or parcelable). Do not try to -// edit this file. It looks like you are doing that because you have modified -// an AIDL interface in a backward-incompatible way, e.g., deleting a function -// from an interface or a field from a parcelable and it broke the build. That -// breakage is intended. +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. // -// You must not make a backward incompatible changes to the AIDL files built +// You must not make a backward incompatible change to any AIDL file built // with the aidl_interface module type with versions property set. The module // type is used to build AIDL files in a way that they can be used across // independently updatable components of the system. If a device is shipped @@ -42,6 +43,8 @@ parcelable GnssMeasurement { double fullInterSignalBiasUncertaintyNs; double satelliteInterSignalBiasNs; double satelliteInterSignalBiasUncertaintyNs; + android.hardware.gnss.SatellitePvt satellitePvt; + android.hardware.gnss.CorrelationVector[] correlationVectors; const int HAS_SNR = 1; const int HAS_CARRIER_FREQUENCY = 512; const int HAS_CARRIER_CYCLES = 1024; @@ -52,6 +55,8 @@ parcelable GnssMeasurement { const int HAS_FULL_ISB_UNCERTAINTY = 131072; const int HAS_SATELLITE_ISB = 262144; const int HAS_SATELLITE_ISB_UNCERTAINTY = 524288; + const int HAS_SATELLITE_PVT = 1048576; + const int HAS_CORRELATION_VECTOR = 2097152; const int STATE_UNKNOWN = 0; const int STATE_CODE_LOCK = 1; const int STATE_BIT_SYNC = 2; diff --git a/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/GnssMultipathIndicator.aidl b/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/GnssMultipathIndicator.aidl index 75ca3afbc3..5da60f7a6e 100644 --- a/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/GnssMultipathIndicator.aidl +++ b/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/GnssMultipathIndicator.aidl @@ -2,13 +2,14 @@ // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // /////////////////////////////////////////////////////////////////////////////// -// This file is a snapshot of an AIDL interface (or parcelable). Do not try to -// edit this file. It looks like you are doing that because you have modified -// an AIDL interface in a backward-incompatible way, e.g., deleting a function -// from an interface or a field from a parcelable and it broke the build. That -// breakage is intended. +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. // -// You must not make a backward incompatible changes to the AIDL files built +// You must not make a backward incompatible change to any AIDL file built // with the aidl_interface module type with versions property set. The module // type is used to build AIDL files in a way that they can be used across // independently updatable components of the system. If a device is shipped diff --git a/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/GnssPowerStats.aidl b/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/GnssPowerStats.aidl index d385fd488c..358b570157 100644 --- a/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/GnssPowerStats.aidl +++ b/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/GnssPowerStats.aidl @@ -2,13 +2,14 @@ // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // /////////////////////////////////////////////////////////////////////////////// -// This file is a snapshot of an AIDL interface (or parcelable). Do not try to -// edit this file. It looks like you are doing that because you have modified -// an AIDL interface in a backward-incompatible way, e.g., deleting a function -// from an interface or a field from a parcelable and it broke the build. That -// breakage is intended. +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. // -// You must not make a backward incompatible changes to the AIDL files built +// You must not make a backward incompatible change to any AIDL file built // with the aidl_interface module type with versions property set. The module // type is used to build AIDL files in a way that they can be used across // independently updatable components of the system. If a device is shipped diff --git a/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/GnssSignalType.aidl b/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/GnssSignalType.aidl index f10b9430dd..b2a498d009 100644 --- a/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/GnssSignalType.aidl +++ b/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/GnssSignalType.aidl @@ -2,13 +2,14 @@ // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // /////////////////////////////////////////////////////////////////////////////// -// This file is a snapshot of an AIDL interface (or parcelable). Do not try to -// edit this file. It looks like you are doing that because you have modified -// an AIDL interface in a backward-incompatible way, e.g., deleting a function -// from an interface or a field from a parcelable and it broke the build. That -// breakage is intended. +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. // -// You must not make a backward incompatible changes to the AIDL files built +// You must not make a backward incompatible change to any AIDL file built // with the aidl_interface module type with versions property set. The module // type is used to build AIDL files in a way that they can be used across // independently updatable components of the system. If a device is shipped diff --git a/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/IGnss.aidl b/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/IGnss.aidl index 10ac150a53..bd6f1ff34d 100644 --- a/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/IGnss.aidl +++ b/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/IGnss.aidl @@ -2,13 +2,14 @@ // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // /////////////////////////////////////////////////////////////////////////////// -// This file is a snapshot of an AIDL interface (or parcelable). Do not try to -// edit this file. It looks like you are doing that because you have modified -// an AIDL interface in a backward-incompatible way, e.g., deleting a function -// from an interface or a field from a parcelable and it broke the build. That -// breakage is intended. +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. // -// You must not make a backward incompatible changes to the AIDL files built +// You must not make a backward incompatible change to any AIDL file built // with the aidl_interface module type with versions property set. The module // type is used to build AIDL files in a way that they can be used across // independently updatable components of the system. If a device is shipped diff --git a/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/IGnssCallback.aidl b/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/IGnssCallback.aidl index 62870d6871..a0c4255df2 100644 --- a/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/IGnssCallback.aidl +++ b/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/IGnssCallback.aidl @@ -2,13 +2,14 @@ // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // /////////////////////////////////////////////////////////////////////////////// -// This file is a snapshot of an AIDL interface (or parcelable). Do not try to -// edit this file. It looks like you are doing that because you have modified -// an AIDL interface in a backward-incompatible way, e.g., deleting a function -// from an interface or a field from a parcelable and it broke the build. That -// breakage is intended. +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. // -// You must not make a backward incompatible changes to the AIDL files built +// You must not make a backward incompatible change to any AIDL file built // with the aidl_interface module type with versions property set. The module // type is used to build AIDL files in a way that they can be used across // independently updatable components of the system. If a device is shipped @@ -19,5 +20,7 @@ package android.hardware.gnss; @VintfStability interface IGnssCallback { void gnssSetCapabilitiesCb(in int capabilities); - const int CAPABILITY_SATELLITE_BLOCKLIST = 1; + const int CAPABILITY_SATELLITE_BLOCKLIST = 512; + const int CAPABILITY_CORRELATION_VECTOR = 4096; + const int CAPABILITY_SATELLITE_PVT = 8192; } diff --git a/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/IGnssConfiguration.aidl b/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/IGnssConfiguration.aidl index 5af30cf237..eb4ad82653 100644 --- a/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/IGnssConfiguration.aidl +++ b/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/IGnssConfiguration.aidl @@ -2,13 +2,14 @@ // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // /////////////////////////////////////////////////////////////////////////////// -// This file is a snapshot of an AIDL interface (or parcelable). Do not try to -// edit this file. It looks like you are doing that because you have modified -// an AIDL interface in a backward-incompatible way, e.g., deleting a function -// from an interface or a field from a parcelable and it broke the build. That -// breakage is intended. +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. // -// You must not make a backward incompatible changes to the AIDL files built +// You must not make a backward incompatible change to any AIDL file built // with the aidl_interface module type with versions property set. The module // type is used to build AIDL files in a way that they can be used across // independently updatable components of the system. If a device is shipped diff --git a/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/IGnssMeasurementCallback.aidl b/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/IGnssMeasurementCallback.aidl index e05e9b9954..764b896955 100644 --- a/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/IGnssMeasurementCallback.aidl +++ b/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/IGnssMeasurementCallback.aidl @@ -2,13 +2,14 @@ // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // /////////////////////////////////////////////////////////////////////////////// -// This file is a snapshot of an AIDL interface (or parcelable). Do not try to -// edit this file. It looks like you are doing that because you have modified -// an AIDL interface in a backward-incompatible way, e.g., deleting a function -// from an interface or a field from a parcelable and it broke the build. That -// breakage is intended. +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. // -// You must not make a backward incompatible changes to the AIDL files built +// You must not make a backward incompatible change to any AIDL file built // with the aidl_interface module type with versions property set. The module // type is used to build AIDL files in a way that they can be used across // independently updatable components of the system. If a device is shipped diff --git a/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/IGnssMeasurementInterface.aidl b/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/IGnssMeasurementInterface.aidl index 9576205772..7cb7395c18 100644 --- a/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/IGnssMeasurementInterface.aidl +++ b/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/IGnssMeasurementInterface.aidl @@ -2,13 +2,14 @@ // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // /////////////////////////////////////////////////////////////////////////////// -// This file is a snapshot of an AIDL interface (or parcelable). Do not try to -// edit this file. It looks like you are doing that because you have modified -// an AIDL interface in a backward-incompatible way, e.g., deleting a function -// from an interface or a field from a parcelable and it broke the build. That -// breakage is intended. +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. // -// You must not make a backward incompatible changes to the AIDL files built +// You must not make a backward incompatible change to any AIDL file built // with the aidl_interface module type with versions property set. The module // type is used to build AIDL files in a way that they can be used across // independently updatable components of the system. If a device is shipped @@ -18,6 +19,6 @@ package android.hardware.gnss; @VintfStability interface IGnssMeasurementInterface { - void setCallback(in android.hardware.gnss.IGnssMeasurementCallback callback, in boolean enableFullTracking); + void setCallback(in android.hardware.gnss.IGnssMeasurementCallback callback, in boolean enableFullTracking, in boolean enableCorrVecOutputs); void close(); } diff --git a/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/IGnssPowerIndication.aidl b/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/IGnssPowerIndication.aidl index 843489e941..c44903eadb 100644 --- a/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/IGnssPowerIndication.aidl +++ b/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/IGnssPowerIndication.aidl @@ -2,13 +2,14 @@ // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // /////////////////////////////////////////////////////////////////////////////// -// This file is a snapshot of an AIDL interface (or parcelable). Do not try to -// edit this file. It looks like you are doing that because you have modified -// an AIDL interface in a backward-incompatible way, e.g., deleting a function -// from an interface or a field from a parcelable and it broke the build. That -// breakage is intended. +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. // -// You must not make a backward incompatible changes to the AIDL files built +// You must not make a backward incompatible change to any AIDL file built // with the aidl_interface module type with versions property set. The module // type is used to build AIDL files in a way that they can be used across // independently updatable components of the system. If a device is shipped diff --git a/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/IGnssPowerIndicationCallback.aidl b/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/IGnssPowerIndicationCallback.aidl index 5281d29bc6..12e6762d2b 100644 --- a/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/IGnssPowerIndicationCallback.aidl +++ b/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/IGnssPowerIndicationCallback.aidl @@ -2,13 +2,14 @@ // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // /////////////////////////////////////////////////////////////////////////////// -// This file is a snapshot of an AIDL interface (or parcelable). Do not try to -// edit this file. It looks like you are doing that because you have modified -// an AIDL interface in a backward-incompatible way, e.g., deleting a function -// from an interface or a field from a parcelable and it broke the build. That -// breakage is intended. +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. // -// You must not make a backward incompatible changes to the AIDL files built +// You must not make a backward incompatible change to any AIDL file built // with the aidl_interface module type with versions property set. The module // type is used to build AIDL files in a way that they can be used across // independently updatable components of the system. If a device is shipped diff --git a/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/IGnssPsds.aidl b/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/IGnssPsds.aidl index ddef9280ed..cae2ea6432 100644 --- a/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/IGnssPsds.aidl +++ b/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/IGnssPsds.aidl @@ -2,13 +2,14 @@ // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // /////////////////////////////////////////////////////////////////////////////// -// This file is a snapshot of an AIDL interface (or parcelable). Do not try to -// edit this file. It looks like you are doing that because you have modified -// an AIDL interface in a backward-incompatible way, e.g., deleting a function -// from an interface or a field from a parcelable and it broke the build. That -// breakage is intended. +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. // -// You must not make a backward incompatible changes to the AIDL files built +// You must not make a backward incompatible change to any AIDL file built // with the aidl_interface module type with versions property set. The module // type is used to build AIDL files in a way that they can be used across // independently updatable components of the system. If a device is shipped diff --git a/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/IGnssPsdsCallback.aidl b/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/IGnssPsdsCallback.aidl index 8413d2cc77..6888632fa2 100644 --- a/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/IGnssPsdsCallback.aidl +++ b/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/IGnssPsdsCallback.aidl @@ -2,13 +2,14 @@ // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // /////////////////////////////////////////////////////////////////////////////// -// This file is a snapshot of an AIDL interface (or parcelable). Do not try to -// edit this file. It looks like you are doing that because you have modified -// an AIDL interface in a backward-incompatible way, e.g., deleting a function -// from an interface or a field from a parcelable and it broke the build. That -// breakage is intended. +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. // -// You must not make a backward incompatible changes to the AIDL files built +// You must not make a backward incompatible change to any AIDL file built // with the aidl_interface module type with versions property set. The module // type is used to build AIDL files in a way that they can be used across // independently updatable components of the system. If a device is shipped diff --git a/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/PsdsType.aidl b/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/PsdsType.aidl index 9d1984e292..d348c633d0 100644 --- a/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/PsdsType.aidl +++ b/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/PsdsType.aidl @@ -2,13 +2,14 @@ // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // /////////////////////////////////////////////////////////////////////////////// -// This file is a snapshot of an AIDL interface (or parcelable). Do not try to -// edit this file. It looks like you are doing that because you have modified -// an AIDL interface in a backward-incompatible way, e.g., deleting a function -// from an interface or a field from a parcelable and it broke the build. That -// breakage is intended. +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. // -// You must not make a backward incompatible changes to the AIDL files built +// You must not make a backward incompatible change to any AIDL file built // with the aidl_interface module type with versions property set. The module // type is used to build AIDL files in a way that they can be used across // independently updatable components of the system. If a device is shipped diff --git a/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/SatelliteClockInfo.aidl b/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/SatelliteClockInfo.aidl new file mode 100644 index 0000000000..bdba6673ea --- /dev/null +++ b/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/SatelliteClockInfo.aidl @@ -0,0 +1,25 @@ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. +// +// You must not make a backward incompatible change to any AIDL file built +// with the aidl_interface module type with versions property set. The module +// type is used to build AIDL files in a way that they can be used across +// independently updatable components of the system. If a device is shipped +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.hardware.gnss; +@VintfStability +parcelable SatelliteClockInfo { + double satHardwareCodeBiasMeters; + double satTimeCorrectionMeters; + double satClkDriftMps; +} diff --git a/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/SatellitePositionEcef.aidl b/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/SatellitePositionEcef.aidl new file mode 100644 index 0000000000..550fa4dbe9 --- /dev/null +++ b/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/SatellitePositionEcef.aidl @@ -0,0 +1,26 @@ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. +// +// You must not make a backward incompatible change to any AIDL file built +// with the aidl_interface module type with versions property set. The module +// type is used to build AIDL files in a way that they can be used across +// independently updatable components of the system. If a device is shipped +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.hardware.gnss; +@VintfStability +parcelable SatellitePositionEcef { + double posXMeters; + double posYMeters; + double posZMeters; + double ureMeters; +} diff --git a/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/SatellitePvt.aidl b/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/SatellitePvt.aidl new file mode 100644 index 0000000000..4ff025eff8 --- /dev/null +++ b/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/SatellitePvt.aidl @@ -0,0 +1,27 @@ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. +// +// You must not make a backward incompatible change to any AIDL file built +// with the aidl_interface module type with versions property set. The module +// type is used to build AIDL files in a way that they can be used across +// independently updatable components of the system. If a device is shipped +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.hardware.gnss; +@VintfStability +parcelable SatellitePvt { + android.hardware.gnss.SatellitePositionEcef satPosEcef; + android.hardware.gnss.SatelliteVelocityEcef satVelEcef; + android.hardware.gnss.SatelliteClockInfo satClockInfo; + double ionoDelayMeters; + double tropoDelayMeters; +} diff --git a/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/SatelliteVelocityEcef.aidl b/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/SatelliteVelocityEcef.aidl new file mode 100644 index 0000000000..7db7ee6b5d --- /dev/null +++ b/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/SatelliteVelocityEcef.aidl @@ -0,0 +1,26 @@ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. +// +// You must not make a backward incompatible change to any AIDL file built +// with the aidl_interface module type with versions property set. The module +// type is used to build AIDL files in a way that they can be used across +// independently updatable components of the system. If a device is shipped +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.hardware.gnss; +@VintfStability +parcelable SatelliteVelocityEcef { + double velXMps; + double velYMps; + double velZMps; + double ureRateMps; +} diff --git a/gnss/aidl/android/hardware/gnss/CorrelationVector.aidl b/gnss/aidl/android/hardware/gnss/CorrelationVector.aidl new file mode 100644 index 0000000000..22a80cec57 --- /dev/null +++ b/gnss/aidl/android/hardware/gnss/CorrelationVector.aidl @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * 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. + */ + +package android.hardware.gnss; + +/** + * Contains info about the correlation output of incoming GNSS signal and a local copy of + * its corresponding spreading code at a given frequency offset. + */ +@VintfStability +parcelable CorrelationVector { + + /** + * Frequency offset from reported pseudorange rate for this Correlation Vector. + */ + int frequencyOffsetMps; + + /** + * Space between correlation samples in meters. + */ + double samplingWidthM; + + /** + * Offset of the first sampling bin in meters. + * The following sampling bins are located at positive offsets from this value as follows: + * samplingStartM, samplingStartM + samplingWidthM, ... , samplingStartM + + * (magnitude.size-1) * samplingWidthM. + */ + double samplingStartM; + + /** + * Normalized correlation magnitude values from -1 to 1, the reported value must be encoded as + * signed 16 bit integer where 1 is represented by 32767 and -1 is represented by -32768. + * + * The length of the array is defined by the GNSS chipset. + */ + int[] magnitude; +}
\ No newline at end of file diff --git a/gnss/aidl/android/hardware/gnss/GnssMeasurement.aidl b/gnss/aidl/android/hardware/gnss/GnssMeasurement.aidl index ce88647162..2c56a41643 100644 --- a/gnss/aidl/android/hardware/gnss/GnssMeasurement.aidl +++ b/gnss/aidl/android/hardware/gnss/GnssMeasurement.aidl @@ -16,8 +16,10 @@ package android.hardware.gnss; +import android.hardware.gnss.CorrelationVector; import android.hardware.gnss.GnssSignalType; import android.hardware.gnss.GnssMultipathIndicator; +import android.hardware.gnss.SatellitePvt; /** * Represents a GNSS Measurement, it contains raw and computed information. @@ -57,6 +59,14 @@ parcelable GnssMeasurement { * GnssMeasurement. */ const int HAS_SATELLITE_ISB_UNCERTAINTY = 1 << 19; + /** + * Bit mask indicating a valid satellite PVT is stored in the GnssMeasurement. + */ + const int HAS_SATELLITE_PVT = 1 << 20; + /** + * Bit mask indicating valid correlation vectors are stored in the GnssMeasurement. + */ + const int HAS_CORRELATION_VECTOR = 1 << 21; /** * A bitfield of flags indicating the validity of the fields in this GnssMeasurement. The bit @@ -612,4 +622,21 @@ parcelable GnssMeasurement { * 1-sigma uncertainty associated with the satellite inter-signal bias in nanoseconds. */ double satelliteInterSignalBiasUncertaintyNs; + + /** + * The GNSS satellite position, velocity and time information at the signal transmission time + * receivedSvTimeInNs. + * + * If the data is available, gnssMeasurementFlags must contain HAS_SATELLITE_PVT. + */ + SatellitePvt satellitePvt; + + /** + * A list of Correlation Vectors with each vector corresponding to a frequency offset. + * + * To represent correlation values over a 2D spaces (delay and frequency), a CorrelationVector + * is required per frequency offset, and each CorrelationVector contains correlation values + * at equally spaced spatial offsets. + */ + CorrelationVector[] correlationVectors; }
\ No newline at end of file diff --git a/gnss/aidl/android/hardware/gnss/IGnssCallback.aidl b/gnss/aidl/android/hardware/gnss/IGnssCallback.aidl index a46a018141..8881ea7486 100644 --- a/gnss/aidl/android/hardware/gnss/IGnssCallback.aidl +++ b/gnss/aidl/android/hardware/gnss/IGnssCallback.aidl @@ -27,8 +27,14 @@ import android.hardware.gnss.IGnssConfiguration; @VintfStability interface IGnssCallback { - /** Capability bit mask indicating GNSS supports blocklisting satellites */ - const int CAPABILITY_SATELLITE_BLOCKLIST = 1 << 0; + /** Capability bit mask indicating that GNSS supports blocklisting satellites */ + const int CAPABILITY_SATELLITE_BLOCKLIST = 1 << 9; + + /** Capability bit mask indicating that GNSS supports correlation vector */ + const int CAPABILITY_CORRELATION_VECTOR = 1 << 12; + + /** Capability bit mask indicating that GNSS supports satellite PVT */ + const int CAPABILITY_SATELLITE_PVT = 1 << 13; /** * Callback to inform framework of the GNSS HAL implementation's capabilities. @@ -36,4 +42,4 @@ interface IGnssCallback { * @param capabilities Capability parameter is a bit field of the Capability bit masks. */ void gnssSetCapabilitiesCb(in int capabilities); -}
\ No newline at end of file +} diff --git a/gnss/aidl/android/hardware/gnss/IGnssMeasurementInterface.aidl b/gnss/aidl/android/hardware/gnss/IGnssMeasurementInterface.aidl index fdeebde8ae..04cdf6417b 100644 --- a/gnss/aidl/android/hardware/gnss/IGnssMeasurementInterface.aidl +++ b/gnss/aidl/android/hardware/gnss/IGnssMeasurementInterface.aidl @@ -37,12 +37,16 @@ interface IGnssMeasurementInterface { * The GNSS chipset is allowed to consume more power in this mode. If false, API must * optimize power via duty cycling, constellations and frequency limits, etc. * + * @param enableCorrVecOutputs If true, enable correlation vectors as part of the raw GNSS + * measurements outputs. If false, disable correlation vectors. + * * @return initRet Returns SUCCESS if successful. Returns ERROR_ALREADY_INIT if a callback has * already been registered without a corresponding call to 'close'. Returns ERROR_GENERIC * for any other error. The HAL must not generate any other updates upon returning this * error code. */ - void setCallback(in IGnssMeasurementCallback callback, in boolean enableFullTracking); + void setCallback(in IGnssMeasurementCallback callback, in boolean enableFullTracking, + in boolean enableCorrVecOutputs); /** * Stops updates from the HAL, and unregisters the callback routines. After a call to close(), diff --git a/gnss/aidl/android/hardware/gnss/SatelliteClockInfo.aidl b/gnss/aidl/android/hardware/gnss/SatelliteClockInfo.aidl new file mode 100644 index 0000000000..844fd1c47d --- /dev/null +++ b/gnss/aidl/android/hardware/gnss/SatelliteClockInfo.aidl @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * 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. + */ + +package android.hardware.gnss; + +/** + * Contains estimates of the satellite clock info. + */ +@VintfStability +parcelable SatelliteClockInfo { + /** + * Satellite hardware code bias of the reported code type w.r.t + * ionosphere-free measurement in meters. + */ + double satHardwareCodeBiasMeters; + + /** + * Satellite time correction for ionospheric-free signal measurement + * (meters). The satellite clock correction for the given signal type + * = satTimeCorrectionMeters - satHardwareCodeBiasMeters. + */ + double satTimeCorrectionMeters; + + /** Satellite clock drift (meters per second). */ + double satClkDriftMps; +}
\ No newline at end of file diff --git a/gnss/aidl/android/hardware/gnss/SatellitePositionEcef.aidl b/gnss/aidl/android/hardware/gnss/SatellitePositionEcef.aidl new file mode 100644 index 0000000000..4b3615e29a --- /dev/null +++ b/gnss/aidl/android/hardware/gnss/SatellitePositionEcef.aidl @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * 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. + */ + +package android.hardware.gnss; + +/** + * Contains estimates of the satellite position fields in ECEF coordinate frame. + */ +@VintfStability +parcelable SatellitePositionEcef { + /** Satellite position X in WGS84 ECEF (meters). */ + double posXMeters; + + /** Satellite position Y in WGS84 ECEF (meters). */ + double posYMeters; + + /** Satellite position Z in WGS84 ECEF (meters). */ + double posZMeters; + + /** + * The Signal in Space User Range Error (URE) (meters). + * + * It covers satellite position and clock errors projected to the pseudorange measurements. + */ + double ureMeters; +}
\ No newline at end of file diff --git a/gnss/aidl/android/hardware/gnss/SatellitePvt.aidl b/gnss/aidl/android/hardware/gnss/SatellitePvt.aidl new file mode 100644 index 0000000000..ea55f0c257 --- /dev/null +++ b/gnss/aidl/android/hardware/gnss/SatellitePvt.aidl @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * 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. + */ + +package android.hardware.gnss; + +import android.hardware.gnss.SatellitePositionEcef; +import android.hardware.gnss.SatelliteVelocityEcef; +import android.hardware.gnss.SatelliteClockInfo; + +/** + * Contains estimates of the satellite position, velocity and time in the + * ECEF coordinate frame. + */ +@VintfStability +parcelable SatellitePvt { + /** + * Satellite position in WGS84 ECEF. See comments of + * SatellitePositionEcef for units. + */ + SatellitePositionEcef satPosEcef; + + /** + * Satellite velocity in WGS84 ECEF. See comments of + * SatelliteVelocityEcef for units. + */ + SatelliteVelocityEcef satVelEcef; + + /** Satellite clock bias and drift info. */ + SatelliteClockInfo satClockInfo; + + /** Ionospheric delay in meters. */ + double ionoDelayMeters; + + /** Tropospheric delay in meters. */ + double tropoDelayMeters; +}
\ No newline at end of file diff --git a/gnss/aidl/android/hardware/gnss/SatelliteVelocityEcef.aidl b/gnss/aidl/android/hardware/gnss/SatelliteVelocityEcef.aidl new file mode 100644 index 0000000000..25ece3a3a7 --- /dev/null +++ b/gnss/aidl/android/hardware/gnss/SatelliteVelocityEcef.aidl @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * 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. + */ + +package android.hardware.gnss; + +/** + * Contains estimates of the satellite velocity fields in the ECEF coordinate frame. + */ +@VintfStability +parcelable SatelliteVelocityEcef { + /** Satellite velocity X in WGS84 ECEF (meters per second). */ + double velXMps; + + /** Satellite velocity Y in WGS84 ECEF (meters per second). */ + double velYMps; + + /** Satellite velocity Z in WGS84 ECEF (meters per second). */ + double velZMps; + + /** + * The Signal in Space User Range Error Rate (URE Rate) (meters per second). + * + * It covers satellite velocity error and Satellite clock drift + * projected to the pseudorange rate measurements. + */ + double ureRateMps; +}
\ No newline at end of file diff --git a/gnss/aidl/default/Gnss.cpp b/gnss/aidl/default/Gnss.cpp index 02bad6031d..435afa3576 100644 --- a/gnss/aidl/default/Gnss.cpp +++ b/gnss/aidl/default/Gnss.cpp @@ -36,7 +36,10 @@ ndk::ScopedAStatus Gnss::setCallback(const std::shared_ptr<IGnssCallback>& callb sGnssCallback = callback; - int capabilities = (int)IGnssCallback::CAPABILITY_SATELLITE_BLOCKLIST; + int capabilities = (int)(IGnssCallback::CAPABILITY_SATELLITE_BLOCKLIST | + IGnssCallback::CAPABILITY_SATELLITE_PVT | + IGnssCallback::CAPABILITY_CORRELATION_VECTOR); + auto status = sGnssCallback->gnssSetCapabilitiesCb(capabilities); if (!status.isOk()) { ALOGE("%s: Unable to invoke callback.gnssSetCapabilities", __func__); diff --git a/gnss/aidl/default/GnssMeasurementInterface.cpp b/gnss/aidl/default/GnssMeasurementInterface.cpp index d726d9502f..cae9499077 100644 --- a/gnss/aidl/default/GnssMeasurementInterface.cpp +++ b/gnss/aidl/default/GnssMeasurementInterface.cpp @@ -34,8 +34,10 @@ GnssMeasurementInterface::~GnssMeasurementInterface() { } ndk::ScopedAStatus GnssMeasurementInterface::setCallback( - const std::shared_ptr<IGnssMeasurementCallback>& callback, const bool enableFullTracking) { - ALOGD("setCallback: enableFullTracking: %d", (int)enableFullTracking); + const std::shared_ptr<IGnssMeasurementCallback>& callback, const bool enableFullTracking, + const bool enableCorrVecOutputs) { + ALOGD("setCallback: enableFullTracking: %d enableCorrVecOutputs: %d", (int)enableFullTracking, + (int)enableCorrVecOutputs); std::unique_lock<std::mutex> lock(mMutex); sCallback = callback; @@ -43,7 +45,7 @@ ndk::ScopedAStatus GnssMeasurementInterface::setCallback( ALOGW("GnssMeasurement callback already set. Resetting the callback..."); stop(); } - start(); + start(enableCorrVecOutputs); return ndk::ScopedAStatus::ok(); } @@ -56,12 +58,12 @@ ndk::ScopedAStatus GnssMeasurementInterface::close() { return ndk::ScopedAStatus::ok(); } -void GnssMeasurementInterface::start() { +void GnssMeasurementInterface::start(const bool enableCorrVecOutputs) { ALOGD("start"); mIsActive = true; - mThread = std::thread([this]() { + mThread = std::thread([this, enableCorrVecOutputs]() { while (mIsActive == true) { - auto measurement = Utils::getMockMeasurement(); + auto measurement = Utils::getMockMeasurement(enableCorrVecOutputs); this->reportMeasurement(measurement); std::this_thread::sleep_for(std::chrono::milliseconds(mMinIntervalMillis)); diff --git a/gnss/aidl/default/GnssMeasurementInterface.h b/gnss/aidl/default/GnssMeasurementInterface.h index 69cd871c96..db6351555d 100644 --- a/gnss/aidl/default/GnssMeasurementInterface.h +++ b/gnss/aidl/default/GnssMeasurementInterface.h @@ -29,11 +29,12 @@ struct GnssMeasurementInterface : public BnGnssMeasurementInterface { GnssMeasurementInterface(); ~GnssMeasurementInterface(); ndk::ScopedAStatus setCallback(const std::shared_ptr<IGnssMeasurementCallback>& callback, - const bool enableFullTracking) override; + const bool enableFullTracking, + const bool enableCorrVecOutputs) override; ndk::ScopedAStatus close() override; private: - void start(); + void start(const bool enableCorrVecOutputs); void stop(); void reportMeasurement(const GnssData&); diff --git a/gnss/aidl/vts/gnss_hal_test_cases.cpp b/gnss/aidl/vts/gnss_hal_test_cases.cpp index 857c74208b..ae0551d63d 100644 --- a/gnss/aidl/vts/gnss_hal_test_cases.cpp +++ b/gnss/aidl/vts/gnss_hal_test_cases.cpp @@ -69,14 +69,21 @@ TEST_P(GnssHalTest, TestPsdsExtension) { * 2. Sets a GnssMeasurementCallback, waits for a measurement, and verifies fields are valid. */ TEST_P(GnssHalTest, TestGnssMeasurementExtension) { + const bool kIsCorrelationVectorSupported = aidl_gnss_cb_->last_capabilities_ & + (int)GnssCallbackAidl::CAPABILITY_CORRELATION_VECTOR; const int kFirstGnssMeasurementTimeoutSeconds = 10; + + bool has_capability_satpvt = false; + sp<IGnssMeasurementInterface> iGnssMeasurement; auto status = aidl_gnss_hal_->getExtensionGnssMeasurement(&iGnssMeasurement); ASSERT_TRUE(status.isOk()); ASSERT_TRUE(iGnssMeasurement != nullptr); auto callback = sp<GnssMeasurementCallbackAidl>::make(); - status = iGnssMeasurement->setCallback(callback, /* enableFullTracking= */ true); + status = + iGnssMeasurement->setCallback(callback, /* enableFullTracking= */ true, + /* enableCorrVecOutputs */ kIsCorrelationVectorSupported); ASSERT_TRUE(status.isOk()); android::hardware::gnss::GnssData lastMeasurement; @@ -102,6 +109,10 @@ TEST_P(GnssHalTest, TestGnssMeasurementExtension) { GnssClock::HAS_FULL_BIAS | GnssClock::HAS_BIAS | GnssClock::HAS_BIAS_UNCERTAINTY | GnssClock::HAS_DRIFT | GnssClock::HAS_DRIFT_UNCERTAINTY)); + + if (aidl_gnss_cb_->last_capabilities_ & (int)GnssCallbackAidl::CAPABILITY_SATELLITE_PVT) { + has_capability_satpvt = true; + } for (const auto& measurement : lastMeasurement.measurements) { ASSERT_TRUE( measurement.flags >= 0 && @@ -112,7 +123,41 @@ TEST_P(GnssHalTest, TestGnssMeasurementExtension) { GnssMeasurement::HAS_AUTOMATIC_GAIN_CONTROL | GnssMeasurement::HAS_FULL_ISB | GnssMeasurement::HAS_FULL_ISB_UNCERTAINTY | GnssMeasurement::HAS_SATELLITE_ISB | - GnssMeasurement::HAS_SATELLITE_ISB_UNCERTAINTY)); + GnssMeasurement::HAS_SATELLITE_ISB_UNCERTAINTY | + GnssMeasurement::HAS_SATELLITE_PVT | + GnssMeasurement::HAS_CORRELATION_VECTOR)); + + if ((measurement.flags & GnssMeasurement::HAS_SATELLITE_PVT) && + (has_capability_satpvt == true)) { + ASSERT_TRUE(measurement.satellitePvt.satPosEcef.posXMeters >= -43000000 && + measurement.satellitePvt.satPosEcef.posXMeters <= 43000000); + ASSERT_TRUE(measurement.satellitePvt.satPosEcef.posYMeters >= -43000000 && + measurement.satellitePvt.satPosEcef.posYMeters <= 43000000); + ASSERT_TRUE(measurement.satellitePvt.satPosEcef.posZMeters >= -43000000 && + measurement.satellitePvt.satPosEcef.posZMeters <= 43000000); + ASSERT_TRUE(measurement.satellitePvt.satPosEcef.ureMeters > 0); + ASSERT_TRUE(measurement.satellitePvt.satVelEcef.velXMps >= -4000 && + measurement.satellitePvt.satVelEcef.velXMps <= 4000); + ASSERT_TRUE(measurement.satellitePvt.satVelEcef.velYMps >= -4000 && + measurement.satellitePvt.satVelEcef.velYMps <= 4000); + ASSERT_TRUE(measurement.satellitePvt.satVelEcef.velZMps >= -4000 && + measurement.satellitePvt.satVelEcef.velZMps <= 4000); + ASSERT_TRUE(measurement.satellitePvt.satVelEcef.ureRateMps > 0); + } + + if (kIsCorrelationVectorSupported && + measurement.flags & GnssMeasurement::HAS_CORRELATION_VECTOR) { + ASSERT_TRUE(measurement.correlationVectors.size() > 0); + for (const auto& correlationVector : measurement.correlationVectors) { + ASSERT_GE(correlationVector.frequencyOffsetMps, 0); + ASSERT_GT(correlationVector.samplingWidthM, 0); + ASSERT_GE(correlationVector.samplingStartM, 0); + ASSERT_TRUE(correlationVector.magnitude.size() > 0); + for (const auto& magnitude : correlationVector.magnitude) { + ASSERT_TRUE(magnitude >= -32768 && magnitude <= 32767); + } + } + } } status = iGnssMeasurement->close(); diff --git a/gnss/common/utils/default/Utils.cpp b/gnss/common/utils/default/Utils.cpp index 21282f443d..ccc7145d2c 100644 --- a/gnss/common/utils/default/Utils.cpp +++ b/gnss/common/utils/default/Utils.cpp @@ -140,7 +140,7 @@ GnssDataV2_0 Utils::getMockMeasurementV2_0() { return gnssData; } -GnssData Utils::getMockMeasurement() { +GnssData Utils::getMockMeasurement(const bool enableCorrVecOutputs) { aidl::android::hardware::gnss::GnssSignalType signalType = { .constellation = aidl::android::hardware::gnss::GnssConstellationType::GLONASS, .carrierFrequencyHz = 1.59975e+09, @@ -152,10 +152,10 @@ GnssData Utils::getMockMeasurement() { GnssMeasurement::HAS_CARRIER_PHASE_UNCERTAINTY | GnssMeasurement::HAS_FULL_ISB | GnssMeasurement::HAS_FULL_ISB_UNCERTAINTY | GnssMeasurement::HAS_SATELLITE_ISB | - GnssMeasurement::HAS_SATELLITE_ISB_UNCERTAINTY, + GnssMeasurement::HAS_SATELLITE_ISB_UNCERTAINTY | + GnssMeasurement::HAS_SATELLITE_PVT, .svid = 13, .signalType = signalType, - .timeOffsetNs = 0.0, .receivedSvTimeInNs = 8195997131077, .receivedSvTimeUncertaintyInNs = 15, .antennaCN0DbHz = 30.0, @@ -175,7 +175,20 @@ GnssData Utils::getMockMeasurement() { .fullInterSignalBiasUncertaintyNs = 792.0, .satelliteInterSignalBiasNs = 233.9, .satelliteInterSignalBiasUncertaintyNs = 921.2, - }; + .satellitePvt = {.satPosEcef = {.posXMeters = 10442993.1153328, + .posYMeters = -19926932.8051666, + .posZMeters = -12034295.0216203, + .ureMeters = 1000.2345678}, + .satVelEcef = {.velXMps = -478.667183715732, + .velYMps = 1580.68371984114, + .velZMps = -3030.52994449997, + .ureRateMps = 10.2345678}, + .satClockInfo = {.satHardwareCodeBiasMeters = 1.396983861923e-09, + .satTimeCorrectionMeters = -7113.08964331, + .satClkDriftMps = 0}, + .ionoDelayMeters = 3.069949602639317e-08, + .tropoDelayMeters = 3.882265204404031}, + .correlationVectors = {}}; GnssClock clock = {.gnssClockFlags = GnssClock::HAS_FULL_BIAS | GnssClock::HAS_FULL_BIAS | GnssClock::HAS_BIAS_UNCERTAINTY | GnssClock::HAS_DRIFT | @@ -196,6 +209,21 @@ GnssData Utils::getMockMeasurement() { // or don't set the field. .timeUncertaintyNs = 1020400}; + if (enableCorrVecOutputs) { + aidl::android::hardware::gnss::CorrelationVector correlationVector1 = { + .frequencyOffsetMps = 10, + .samplingWidthM = 30, + .samplingStartM = 0, + .magnitude = {0, 5000, 10000, 5000, 0, 0, 3000, 0}}; + aidl::android::hardware::gnss::CorrelationVector correlationVector2 = { + .frequencyOffsetMps = 20, + .samplingWidthM = 30, + .samplingStartM = 0, + .magnitude = {0, 3000, 5000, 3000, 0, 0, 1000, 0}}; + measurement.correlationVectors = {correlationVector1, correlationVector2}; + measurement.flags |= GnssMeasurement::HAS_CORRELATION_VECTOR; + } + GnssData gnssData = { .measurements = {measurement}, .clock = clock, .elapsedRealtime = timestamp}; return gnssData; diff --git a/gnss/common/utils/default/include/Utils.h b/gnss/common/utils/default/include/Utils.h index 0ca1b00e32..771d39dbd1 100644 --- a/gnss/common/utils/default/include/Utils.h +++ b/gnss/common/utils/default/include/Utils.h @@ -30,7 +30,8 @@ namespace gnss { namespace common { struct Utils { - static aidl::android::hardware::gnss::GnssData getMockMeasurement(); + static aidl::android::hardware::gnss::GnssData getMockMeasurement( + const bool enableCorrVecOutputs); static V2_0::IGnssMeasurementCallback::GnssData getMockMeasurementV2_0(); static V2_1::IGnssMeasurementCallback::GnssData getMockMeasurementV2_1(); static V2_0::GnssLocation getMockLocationV2_0(); diff --git a/health/1.0/default/Android.bp b/health/1.0/default/Android.bp index aab9cc72b5..ff4b8759b8 100644 --- a/health/1.0/default/Android.bp +++ b/health/1.0/default/Android.bp @@ -17,62 +17,3 @@ cc_library_static { ], } - -cc_library_static { - name: "android.hardware.health@1.0-impl-helper", - vendor: true, - srcs: ["Health.cpp"], - - header_libs: [ - "libbase_headers", - "libhealthd_headers", - ], - - shared_libs: [ - "libcutils", - "libhidlbase", - "liblog", - "libutils", - "android.hardware.health@1.0", - ], - - static_libs: [ - "android.hardware.health@1.0-convert", - ], -} - -cc_library_shared { - name: "android.hardware.health@1.0-impl", - vendor: true, - relative_install_path: "hw", - - static_libs: [ - "android.hardware.health@1.0-impl-helper", - "android.hardware.health@1.0-convert", - "libhealthd.default", - ], - - shared_libs: [ - "libhidlbase", - "libutils", - "android.hardware.health@1.0", - ], -} - -cc_binary { - name: "android.hardware.health@1.0-service", - vendor: true, - relative_install_path: "hw", - init_rc: ["android.hardware.health@1.0-service.rc"], - srcs: ["HealthService.cpp"], - - shared_libs: [ - "liblog", - "libcutils", - "libdl", - "libbase", - "libutils", - "libhidlbase", - "android.hardware.health@1.0", - ], -} diff --git a/health/1.0/default/Health.cpp b/health/1.0/default/Health.cpp deleted file mode 100644 index 1a02956f11..0000000000 --- a/health/1.0/default/Health.cpp +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * 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. - */ - -#define LOG_TAG "health-hal" - -#include <Health.h> -#include <include/hal_conversion.h> - -namespace android { -namespace hardware { -namespace health { -namespace V1_0 { -namespace implementation { - -using ::android::hardware::health::V1_0::hal_conversion::convertToHealthConfig; -using ::android::hardware::health::V1_0::hal_conversion::convertFromHealthConfig; -using ::android::hardware::health::V1_0::hal_conversion::convertToHealthInfo; -using ::android::hardware::health::V1_0::hal_conversion::convertFromHealthInfo; - -// Methods from ::android::hardware::health::V1_0::IHealth follow. -Return<void> Health::init(const HealthConfig& config, init_cb _hidl_cb) { - struct healthd_config healthd_config = {}; - HealthConfig configOut; - - // To keep working with existing healthd static HALs, - // convert the new HealthConfig to the old healthd_config - // and back. - - convertFromHealthConfig(config, &healthd_config); - healthd_board_init(&healthd_config); - mGetEnergyCounter = healthd_config.energyCounter; - convertToHealthConfig(&healthd_config, configOut); - - _hidl_cb(configOut); - - return Void(); -} - -Return<void> Health::update(const HealthInfo& info, update_cb _hidl_cb) { - struct android::BatteryProperties p = {}; - HealthInfo infoOut; - - // To keep working with existing healthd static HALs, - // convert the new HealthInfo to android::Batteryproperties - // and back. - - convertFromHealthInfo(info, &p); - int skipLogging = healthd_board_battery_update(&p); - convertToHealthInfo(&p, infoOut); - - _hidl_cb(!!skipLogging, infoOut); - - return Void(); -} - -Return<void> Health::energyCounter(energyCounter_cb _hidl_cb) { - int64_t energy = 0; - Result result = Result::NOT_SUPPORTED; - - if (mGetEnergyCounter) { - int status = mGetEnergyCounter(&energy); - if (status == 0) { - result = Result::SUCCESS; - } - } - - _hidl_cb(result, energy); - - return Void(); -} - -IHealth* HIDL_FETCH_IHealth(const char* /* name */) { - return new Health(); -} - -} // namespace implementation -} // namespace V1_0 -} // namespace health -} // namespace hardware -} // namespace android diff --git a/health/1.0/default/Health.h b/health/1.0/default/Health.h deleted file mode 100644 index ed364c1132..0000000000 --- a/health/1.0/default/Health.h +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * 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 ANDROID_HARDWARE_HEALTH_V1_0_HEALTH_H -#define ANDROID_HARDWARE_HEALTH_V1_0_HEALTH_H - -#include <android/hardware/health/1.0/IHealth.h> -#include <hidl/Status.h> -#include <hidl/MQDescriptor.h> -#include <healthd/healthd.h> -#include <utils/String8.h> - -namespace android { -namespace hardware { -namespace health { -namespace V1_0 { -namespace implementation { - -using ::android::hardware::health::V1_0::HealthInfo; -using ::android::hardware::health::V1_0::HealthConfig; -using ::android::hardware::health::V1_0::IHealth; -using ::android::hardware::Return; -using ::android::hardware::Void; -using ::android::hardware::hidl_vec; -using ::android::hardware::hidl_string; -using ::android::sp; - -struct Health : public IHealth { - // Methods from ::android::hardware::health::V1_0::IHealth follow. - Return<void> init(const HealthConfig& config, init_cb _hidl_cb) override; - Return<void> update(const HealthInfo& info, update_cb _hidl_cb) override; - Return<void> energyCounter(energyCounter_cb _hidl_cb) override; -private: - std::function<int(int64_t *)> mGetEnergyCounter; -}; - -extern "C" IHealth* HIDL_FETCH_IHealth(const char* name); - -} // namespace implementation -} // namespace V1_0 -} // namespace health -} // namespace hardware -} // namespace android - -#endif // ANDROID_HARDWARE_HEALTH_V1_0_HEALTH_H diff --git a/health/1.0/default/README.md b/health/1.0/default/README.md deleted file mode 100644 index 1ded7dede5..0000000000 --- a/health/1.0/default/README.md +++ /dev/null @@ -1,66 +0,0 @@ -# Implement the 2.1 HAL instead! - -It is strongly recommended that you implement the 2.1 HAL directly. See -`hardware/interfaces/health/2.1/README.md` for more details. - -# Implement Health 1.0 HAL - -1. Install common binderized service. The binderized service `dlopen()`s - passthrough implementations on the device, so there is no need to write - your own. - - ```mk - # Install default binderized implementation to vendor. - PRODUCT_PACKAGES += android.hardware.health@1.0-service - ``` - -1. Add proper VINTF manifest entry to your device manifest. Example: - - ```xml - <hal format="hidl"> - <name>android.hardware.health</name> - <transport>hwbinder</transport> - <version>1.0</version> - <interface> - <name>IHealth</name> - <instance>default</instance> - </interface> - </hal> - ``` - -1. Install the proper passthrough implemetation. - - 1. If you want to use the default implementation (with default `libhealthd`), - add the following to `device.mk`: - - ```mk - PRODUCT_PACKAGES += \ - android.hardware.health@1.0-impl - ``` - - 1. Otherwise, if you have a customized `libhealthd.<board>`: - - 1. Define your passthrough implementation. Example (replace `<device>` - and `<board>` accordingly): - - ```bp - cc_library_shared { - name: "android.hardware.health@1.0-impl-<device>", - vendor: true, - relative_install_path: "hw", - - static_libs: [ - "android.hardware.health@1.0-impl-helper", - "android.hardware.health@1.0-convert", - "libhealthd.<board>", - ], - } - ``` - - 1. Add to `device.mk`. - - ``` - PRODUCT_PACKAGES += android.hardware.health@1.0-impl-<device> - ``` - - 1. Define appropriate SELinux permissions. diff --git a/health/1.0/default/android.hardware.health@1.0-service.rc b/health/1.0/default/android.hardware.health@1.0-service.rc deleted file mode 100644 index 569dc8851a..0000000000 --- a/health/1.0/default/android.hardware.health@1.0-service.rc +++ /dev/null @@ -1,5 +0,0 @@ -service vendor.health-hal-1-0 /vendor/bin/hw/android.hardware.health@1.0-service - class hal - user system - group system - capabilities WAKE_ALARM BLOCK_SUSPEND diff --git a/health/1.0/default/libhealthd/Android.bp b/health/1.0/default/libhealthd/Android.bp deleted file mode 100644 index 43463eb4bd..0000000000 --- a/health/1.0/default/libhealthd/Android.bp +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright 2016 The Android Open Source Project - -cc_library_static { - srcs: ["healthd_board_default.cpp"], - name: "libhealthd.default", - vendor_available: true, - recovery_available: true, - cflags: ["-Werror"], - include_dirs: ["system/libbase/include"], - header_libs: ["libhealthd_headers"], -} diff --git a/health/1.0/vts/functional/VtsHalHealthV1_0TargetTest.cpp b/health/1.0/vts/functional/VtsHalHealthV1_0TargetTest.cpp deleted file mode 100644 index 8b3dcc1f5d..0000000000 --- a/health/1.0/vts/functional/VtsHalHealthV1_0TargetTest.cpp +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * 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. - */ - -#define LOG_TAG "health_hidl_hal_test" - -#include <android/hardware/health/1.0/IHealth.h> -#include <android/hardware/health/1.0/types.h> -#include <gtest/gtest.h> -#include <hidl/GtestPrinter.h> -#include <hidl/ServiceManagement.h> -#include <log/log.h> - -using HealthConfig = ::android::hardware::health::V1_0::HealthConfig; -using HealthInfo = ::android::hardware::health::V1_0::HealthInfo; -using IHealth = ::android::hardware::health::V1_0::IHealth; -using Result = ::android::hardware::health::V1_0::Result; - -using ::android::sp; - -class HealthHidlTest : public ::testing::TestWithParam<std::string> { - public: - virtual void SetUp() override { - health = IHealth::getService(GetParam()); - ASSERT_NE(health, nullptr); - health->init(config, - [&](const auto& halConfigOut) { config = halConfigOut; }); - } - - sp<IHealth> health; - HealthConfig config; -}; - -/** - * Ensure EnergyCounter call returns positive energy counter or NOT_SUPPORTED - */ -TEST_P(HealthHidlTest, TestEnergyCounter) { - Result result; - int64_t energy = 0; - health->energyCounter([&](Result ret, int64_t energyOut) { - result = ret; - energy = energyOut; - }); - - ASSERT_TRUE(result == Result::SUCCESS || result == Result::NOT_SUPPORTED); - ASSERT_TRUE(result != Result::SUCCESS || energy > 0); -} - -GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(HealthHidlTest); -INSTANTIATE_TEST_SUITE_P( - PerInstance, HealthHidlTest, - testing::ValuesIn(android::hardware::getAllHalInstanceNames(IHealth::descriptor)), - android::hardware::PrintInstanceNameToString); diff --git a/health/storage/1.0/default/Android.bp b/health/storage/1.0/default/Android.bp index 3156dfef1c..3834244cea 100644 --- a/health/storage/1.0/default/Android.bp +++ b/health/storage/1.0/default/Android.bp @@ -38,6 +38,7 @@ cc_binary { ], static_libs: [ + "libhealth_storage_impl_common", "libfstab", ], diff --git a/health/storage/1.0/default/Storage.cpp b/health/storage/1.0/default/Storage.cpp index 561deaaed6..02b6a3dbf0 100644 --- a/health/storage/1.0/default/Storage.cpp +++ b/health/storage/1.0/default/Storage.cpp @@ -18,11 +18,8 @@ #include <sstream> -#include <android-base/chrono_utils.h> -#include <android-base/file.h> #include <android-base/logging.h> -#include <android-base/strings.h> -#include <fstab/fstab.h> +#include <health-storage-impl/common.h> namespace android { namespace hardware { @@ -31,69 +28,9 @@ namespace storage { namespace V1_0 { namespace implementation { -using base::ReadFileToString; -using base::Timer; -using base::Trim; -using base::WriteStringToFd; -using base::WriteStringToFile; -using fs_mgr::Fstab; -using fs_mgr::ReadDefaultFstab; - -std::string getGarbageCollectPath() { - Fstab fstab; - ReadDefaultFstab(&fstab); - - for (const auto& entry : fstab) { - if (!entry.sysfs_path.empty()) { - return entry.sysfs_path + "/manual_gc"; - } - } - - return ""; -} - Return<void> Storage::garbageCollect(uint64_t timeoutSeconds, const sp<IGarbageCollectCallback>& cb) { - Result result = Result::SUCCESS; - std::string path = getGarbageCollectPath(); - - if (path.empty()) { - LOG(WARNING) << "Cannot find Dev GC path"; - result = Result::UNKNOWN_ERROR; - } else { - Timer timer; - LOG(INFO) << "Start Dev GC on " << path; - while (1) { - std::string require; - if (!ReadFileToString(path, &require)) { - PLOG(WARNING) << "Reading manual_gc failed in " << path; - result = Result::IO_ERROR; - break; - } - require = Trim(require); - if (require == "" || require == "off" || require == "disabled") { - LOG(DEBUG) << "No more to do Dev GC"; - break; - } - LOG(DEBUG) << "Trigger Dev GC on " << path; - if (!WriteStringToFile("1", path)) { - PLOG(WARNING) << "Start Dev GC failed on " << path; - result = Result::IO_ERROR; - break; - } - if (timer.duration() >= std::chrono::seconds(timeoutSeconds)) { - LOG(WARNING) << "Dev GC timeout"; - // Timeout is not treated as an error. Try next time. - break; - } - sleep(2); - } - LOG(INFO) << "Stop Dev GC on " << path; - if (!WriteStringToFile("0", path)) { - PLOG(WARNING) << "Stop Dev GC failed on " << path; - result = Result::IO_ERROR; - } - } + Result result = GarbageCollect(timeoutSeconds); if (cb != nullptr) { auto ret = cb->onFinish(result); @@ -110,28 +47,7 @@ Return<void> Storage::debug(const hidl_handle& handle, const hidl_vec<hidl_strin } int fd = handle->data[0]; - std::stringstream output; - - std::string path = getGarbageCollectPath(); - if (path.empty()) { - output << "Cannot find Dev GC path"; - } else { - std::string require; - - if (ReadFileToString(path, &require)) { - output << path << ":" << require << std::endl; - } - - if (WriteStringToFile("0", path)) { - output << "stop success" << std::endl; - } - } - - if (!WriteStringToFd(output.str(), fd)) { - PLOG(WARNING) << "debug: cannot write to fd"; - } - - fsync(fd); + DebugDump(fd); return Void(); } diff --git a/health/storage/1.0/vts/functional/Android.bp b/health/storage/1.0/vts/functional/Android.bp index 22010319a2..731ad62434 100644 --- a/health/storage/1.0/vts/functional/Android.bp +++ b/health/storage/1.0/vts/functional/Android.bp @@ -19,6 +19,9 @@ cc_test { defaults: ["VtsHalTargetTestDefaults"], srcs: ["VtsHalHealthStorageV1_0TargetTest.cpp"], static_libs: ["android.hardware.health.storage@1.0"], + header_libs: [ + "libhealth_storage_test_common_headers", + ], shared_libs: [ "libhidlbase", ], diff --git a/health/storage/1.0/vts/functional/VtsHalHealthStorageV1_0TargetTest.cpp b/health/storage/1.0/vts/functional/VtsHalHealthStorageV1_0TargetTest.cpp index 24ddc5d814..ddb6b5a1fd 100644 --- a/health/storage/1.0/vts/functional/VtsHalHealthStorageV1_0TargetTest.cpp +++ b/health/storage/1.0/vts/functional/VtsHalHealthStorageV1_0TargetTest.cpp @@ -14,14 +14,17 @@ * limitations under the License. */ +#include <unistd.h> + +#include <thread> + #include <android-base/logging.h> #include <android/hardware/health/storage/1.0/IStorage.h> #include <gtest/gtest.h> +#include <health-storage-test/common.h> #include <hidl/GtestPrinter.h> #include <hidl/HidlTransportSupport.h> #include <hidl/ServiceManagement.h> -#include <unistd.h> -#include <thread> namespace android { namespace hardware { @@ -29,61 +32,17 @@ namespace health { namespace storage { namespace V1_0 { +using namespace ::android::hardware::health::storage::test; using ::std::literals::chrono_literals::operator""ms; #define ASSERT_OK(ret) ASSERT_TRUE(ret.isOk()) << ret.description() -// Dev GC timeout. This is the timeout used by vold. -const uint64_t kDevGcTimeoutSec = 120; -const std::chrono::seconds kDevGcTimeout{kDevGcTimeoutSec}; -// Dev GC timeout tolerance. The HAL may not immediately return after the -// timeout, so include an acceptable tolerance. -const std::chrono::seconds kDevGcTolerance{3}; -// Time accounted for RPC calls. -const std::chrono::milliseconds kRpcTime{1000}; - -template <typename R> -std::string toString(std::chrono::duration<R, std::milli> time) { - return std::to_string(time.count()) + "ms"; -} - -/** An atomic boolean flag that indicates whether a task has finished. */ -class Flag { - public: - void onFinish() { - std::unique_lock<std::mutex> lock(mMutex); - onFinishLocked(&lock); - } - template <typename R, typename P> - bool wait(std::chrono::duration<R, P> duration) { - std::unique_lock<std::mutex> lock(mMutex); - return waitLocked(&lock, duration); - } - - protected: - /** Will unlock. */ - void onFinishLocked(std::unique_lock<std::mutex>* lock) { - mFinished = true; - lock->unlock(); - mCv.notify_all(); - } - template <typename R, typename P> - bool waitLocked(std::unique_lock<std::mutex>* lock, std::chrono::duration<R, P> duration) { - mCv.wait_for(*lock, duration, [this] { return mFinished; }); - return mFinished; - } - - bool mFinished{false}; - std::mutex mMutex; - std::condition_variable mCv; -}; - class GcCallback : public IGarbageCollectCallback, public Flag { - public: + public: Return<void> onFinish(Result result) override { - std::unique_lock<std::mutex> lock(mMutex); - mResult = result; - Flag::onFinishLocked(&lock); + std::unique_lock<std::mutex> lock(mutex_); + result_ = result; + Flag::OnFinishLocked(&lock); return Void(); } @@ -93,13 +52,13 @@ class GcCallback : public IGarbageCollectCallback, public Flag { */ template <typename R, typename P> void waitForResult(std::chrono::duration<R, P> timeout, Result expected) { - std::unique_lock<std::mutex> lock(mMutex); - ASSERT_TRUE(waitLocked(&lock, timeout)) << "timeout after " << toString(timeout); - EXPECT_EQ(expected, mResult); + std::unique_lock<std::mutex> lock(mutex_); + ASSERT_TRUE(WaitLocked(&lock, timeout)) << "timeout after " << to_string(timeout); + EXPECT_EQ(expected, result_); } - private: - Result mResult{Result::UNKNOWN_ERROR}; + private: + Result result_{Result::UNKNOWN_ERROR}; }; class HealthStorageHidlTest : public ::testing::TestWithParam<std::string> { @@ -127,10 +86,10 @@ class HealthStorageHidlTest : public ::testing::TestWithParam<std::string> { auto pingFlag = std::make_shared<Flag>(); std::thread([service, pingFlag] { service->ping(); - pingFlag->onFinish(); + pingFlag->OnFinish(); }) .detach(); - return pingFlag->wait(timeout); + return pingFlag->Wait(timeout); } sp<IStorage> fs; @@ -147,7 +106,7 @@ TEST_P(HealthStorageHidlTest, GcNullCallback) { // Hold test process because HAL can be single-threaded and doing GC. ASSERT_TRUE(ping(kDevGcTimeout + kDevGcTolerance + kRpcTime)) << "Service must be available after " - << toString(kDevGcTimeout + kDevGcTolerance + kRpcTime); + << to_string(kDevGcTimeout + kDevGcTolerance + kRpcTime); } /** diff --git a/health/storage/aidl/Android.bp b/health/storage/aidl/Android.bp new file mode 100644 index 0000000000..c39a46d48e --- /dev/null +++ b/health/storage/aidl/Android.bp @@ -0,0 +1,33 @@ +// Copyright (C) 2021 The Android Open Source Project +// +// 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. + +aidl_interface { + name: "android.hardware.health.storage", + vendor_available: true, + srcs: ["android/hardware/health/storage/*.aidl"], + stability: "vintf", + backend: { + cpp: { + enabled: false, + }, + java: { + enabled: false, + }, + ndk: { + vndk: { + enabled: true, + }, + }, + }, +} diff --git a/health/storage/aidl/aidl_api/android.hardware.health.storage/current/android/hardware/health/storage/IGarbageCollectCallback.aidl b/health/storage/aidl/aidl_api/android.hardware.health.storage/current/android/hardware/health/storage/IGarbageCollectCallback.aidl new file mode 100644 index 0000000000..0f382d7e6d --- /dev/null +++ b/health/storage/aidl/aidl_api/android.hardware.health.storage/current/android/hardware/health/storage/IGarbageCollectCallback.aidl @@ -0,0 +1,23 @@ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. +// +// You must not make a backward incompatible change to any AIDL file built +// with the aidl_interface module type with versions property set. The module +// type is used to build AIDL files in a way that they can be used across +// independently updatable components of the system. If a device is shipped +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.hardware.health.storage; +@VintfStability +interface IGarbageCollectCallback { + oneway void onFinish(in android.hardware.health.storage.Result result); +} diff --git a/health/storage/aidl/aidl_api/android.hardware.health.storage/current/android/hardware/health/storage/IStorage.aidl b/health/storage/aidl/aidl_api/android.hardware.health.storage/current/android/hardware/health/storage/IStorage.aidl new file mode 100644 index 0000000000..61f838addd --- /dev/null +++ b/health/storage/aidl/aidl_api/android.hardware.health.storage/current/android/hardware/health/storage/IStorage.aidl @@ -0,0 +1,23 @@ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. +// +// You must not make a backward incompatible change to any AIDL file built +// with the aidl_interface module type with versions property set. The module +// type is used to build AIDL files in a way that they can be used across +// independently updatable components of the system. If a device is shipped +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.hardware.health.storage; +@VintfStability +interface IStorage { + oneway void garbageCollect(in long timeoutSeconds, in android.hardware.health.storage.IGarbageCollectCallback callback); +} diff --git a/health/storage/aidl/aidl_api/android.hardware.health.storage/current/android/hardware/health/storage/Result.aidl b/health/storage/aidl/aidl_api/android.hardware.health.storage/current/android/hardware/health/storage/Result.aidl new file mode 100644 index 0000000000..a34580855b --- /dev/null +++ b/health/storage/aidl/aidl_api/android.hardware.health.storage/current/android/hardware/health/storage/Result.aidl @@ -0,0 +1,25 @@ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. +// +// You must not make a backward incompatible change to any AIDL file built +// with the aidl_interface module type with versions property set. The module +// type is used to build AIDL files in a way that they can be used across +// independently updatable components of the system. If a device is shipped +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.hardware.health.storage; +@Backing(type="int") @VintfStability +enum Result { + SUCCESS = 0, + IO_ERROR = 1, + UNKNOWN_ERROR = 2, +} diff --git a/health/storage/aidl/android/hardware/health/storage/IGarbageCollectCallback.aidl b/health/storage/aidl/android/hardware/health/storage/IGarbageCollectCallback.aidl new file mode 100644 index 0000000000..ccd1b44829 --- /dev/null +++ b/health/storage/aidl/android/hardware/health/storage/IGarbageCollectCallback.aidl @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * 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. + */ + +package android.hardware.health.storage; + +import android.hardware.health.storage.Result; + +/** + * Callback interface to IStorage.garbageCollect. + */ +@VintfStability +interface IGarbageCollectCallback { + /** + * When garbage collection has finished, the implementation must + * invoke this function to indicate the result of the garbage collection. + * + * @param out result Execution result. See documentation for Result for + * details. + */ + oneway void onFinish(in Result result); +} diff --git a/health/storage/aidl/android/hardware/health/storage/IStorage.aidl b/health/storage/aidl/android/hardware/health/storage/IStorage.aidl new file mode 100644 index 0000000000..78992a2bd8 --- /dev/null +++ b/health/storage/aidl/android/hardware/health/storage/IStorage.aidl @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * 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. + */ + +package android.hardware.health.storage; + +import android.hardware.health.storage.IGarbageCollectCallback; + +/** + * IStorage is an interface that provides operations on underlying storage + * devices, including flash memory. + */ +@VintfStability +interface IStorage { + /** + * Start garbage collection on the driver of storage devices. + * + * Garbage collection must be started at regular intervals when it is a good + * time for a longer-running cleanup tasks, roughly daily. + * + * When garbage collection finishes or encounters an error before the + * specified timeout, the implementation must call IGarbageCollect.finish + * immediately with appropriate result. + * + * If garbage collection does not finish within the specified timeout, + * the implementation must stop garbage collection, and must not call + * IGarbageCollect.finish. + * + * @param timeoutSeconds timeout in seconds. The implementation must + * return after the timeout is reached. + * + * @param callback callback interface. Callback must be null if the client + * does not need to receive any callbacks. + * + */ + oneway void garbageCollect(in long timeoutSeconds, in IGarbageCollectCallback callback); +} diff --git a/health/storage/aidl/android/hardware/health/storage/Result.aidl b/health/storage/aidl/android/hardware/health/storage/Result.aidl new file mode 100644 index 0000000000..73bb779f20 --- /dev/null +++ b/health/storage/aidl/android/hardware/health/storage/Result.aidl @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * 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. + */ + +package android.hardware.health.storage; + +/** + * Status values for HAL methods. + */ +@VintfStability +@Backing(type="int") +enum Result { + /** + * Execution of the method is successful. + */ + SUCCESS = 0, + /** + * An IO error is encountered when the HAL communicates with the device. + */ + IO_ERROR, + /** + * An unknown error is encountered. + */ + UNKNOWN_ERROR, +} diff --git a/health/storage/aidl/default/Android.bp b/health/storage/aidl/default/Android.bp new file mode 100644 index 0000000000..68a8ee267f --- /dev/null +++ b/health/storage/aidl/default/Android.bp @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * 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. + */ + +cc_defaults { + name: "libhealth_storage_impl_defaults", + vendor: true, + shared_libs: [ + "libbase", + "libbinder_ndk", + "android.hardware.health.storage-unstable-ndk_platform", + ], + static_libs: [ + "libfstab", + "libhealth_storage_impl_common", + ], +} + +cc_library_static { + name: "libhealth_storage_default_impl", + defaults: ["libhealth_storage_impl_defaults"], + srcs: [ + "Storage.cpp", + ], + visibility: [ + ":__subpackages__", + "//hardware/interfaces/tests/extension/health/storage:__subpackages__", + ], +} + +cc_binary { + name: "android.hardware.health.storage-service.default", + defaults: ["libhealth_storage_impl_defaults"], + relative_install_path: "hw", + init_rc: ["health-storage-default.rc"], + vintf_fragments: ["health-storage-default.xml"], + srcs: ["main.cpp"], + static_libs: [ + "libhealth_storage_default_impl", + ], +} diff --git a/health/storage/aidl/default/Storage.cpp b/health/storage/aidl/default/Storage.cpp new file mode 100644 index 0000000000..faa4ff6e60 --- /dev/null +++ b/health/storage/aidl/default/Storage.cpp @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * 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 "Storage.h" + +#include <sstream> + +#include <android-base/logging.h> +#include <health-storage-impl/common.h> + +using ::android::hardware::health::storage::DebugDump; +using ::android::hardware::health::storage::GarbageCollect; + +using HResult = android::hardware::health::storage::V1_0::Result; +using AResult = aidl::android::hardware::health::storage::Result; +// Ensure static_cast<AResult>(any HResult) works +static_assert(static_cast<AResult>(HResult::SUCCESS) == AResult::SUCCESS); +static_assert(static_cast<AResult>(HResult::IO_ERROR) == AResult::IO_ERROR); +static_assert(static_cast<AResult>(HResult::UNKNOWN_ERROR) == AResult::UNKNOWN_ERROR); + +namespace aidl::android::hardware::health::storage { + +ndk::ScopedAStatus Storage::garbageCollect( + int64_t timeout_seconds, const std::shared_ptr<IGarbageCollectCallback>& callback) { + AResult result = static_cast<AResult>(GarbageCollect(static_cast<uint64_t>(timeout_seconds))); + if (callback != nullptr) { + auto status = callback->onFinish(result); + if (!status.isOk()) { + LOG(WARNING) << "Cannot return result " << toString(result) + << " to callback: " << status.getDescription(); + } + } + return ndk::ScopedAStatus::ok(); +} + +binder_status_t Storage::dump(int fd, const char**, uint32_t) { + DebugDump(fd); + return STATUS_OK; +} + +} // namespace aidl::android::hardware::health::storage diff --git a/health/storage/aidl/default/Storage.h b/health/storage/aidl/default/Storage.h new file mode 100644 index 0000000000..049991b679 --- /dev/null +++ b/health/storage/aidl/default/Storage.h @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * 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. + */ + +#pragma once + +#include <aidl/android/hardware/health/storage/BnStorage.h> + +namespace aidl::android::hardware::health::storage { + +class Storage : public BnStorage { + ndk::ScopedAStatus garbageCollect( + int64_t timeout_seconds, + const std::shared_ptr<IGarbageCollectCallback>& callback) override; + binder_status_t dump(int fd, const char** args, uint32_t num_args) override; +}; + +} // namespace aidl::android::hardware::health::storage diff --git a/health/storage/aidl/default/health-storage-default.rc b/health/storage/aidl/default/health-storage-default.rc new file mode 100644 index 0000000000..fc1cc8b3a5 --- /dev/null +++ b/health/storage/aidl/default/health-storage-default.rc @@ -0,0 +1,7 @@ +service vendor.health-storage-default /vendor/bin/hw/android.hardware.health.storage-service.default + interface aidl android.hardware.health.storage.IStorage/default + oneshot + disabled + class hal + user system + group system diff --git a/health/storage/aidl/default/health-storage-default.xml b/health/storage/aidl/default/health-storage-default.xml new file mode 100644 index 0000000000..14d4901823 --- /dev/null +++ b/health/storage/aidl/default/health-storage-default.xml @@ -0,0 +1,7 @@ +<manifest version="1.0" type="device"> + <hal format="aidl"> + <name>android.hardware.health.storage</name> + <version>1</version> + <fqname>IStorage/default</fqname> + </hal> +</manifest> diff --git a/health/storage/aidl/default/main.cpp b/health/storage/aidl/default/main.cpp new file mode 100644 index 0000000000..186b64c0c5 --- /dev/null +++ b/health/storage/aidl/default/main.cpp @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * 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 <android-base/logging.h> +#include <android/binder_manager.h> +#include <android/binder_process.h> + +#include "Storage.h" + +using aidl::android::hardware::health::storage::Storage; +using std::string_literals::operator""s; + +int main() { + ABinderProcess_setThreadPoolMaxThreadCount(0); + + // make a default storage service + auto storage = ndk::SharedRefBase::make<Storage>(); + const std::string name = Storage::descriptor + "/default"s; + CHECK_EQ(STATUS_OK, + AServiceManager_registerLazyService(storage->asBinder().get(), name.c_str())); + + ABinderProcess_joinThreadPool(); + return EXIT_FAILURE; // should not reach +} diff --git a/health/storage/aidl/vts/functional/Android.bp b/health/storage/aidl/vts/functional/Android.bp new file mode 100644 index 0000000000..86b72a712d --- /dev/null +++ b/health/storage/aidl/vts/functional/Android.bp @@ -0,0 +1,37 @@ +// Copyright (C) 2021 The Android Open Source Project +// +// 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. + +cc_test { + name: "VtsHalHealthStorageTargetTest", + defaults: [ + "VtsHalTargetTestDefaults", + "use_libaidlvintf_gtest_helper_static", + ], + srcs: [ + "VtsHalHealthStorageTargetTest.cpp", + ], + shared_libs: [ + "libbinder_ndk", + ], + static_libs: [ + "android.hardware.health.storage-ndk_platform", + ], + header_libs: [ + "libhealth_storage_test_common_headers", + ], + test_suites: [ + "vts", + ], + test_config: "VtsHalHealthStorageTargetTest.xml", +} diff --git a/health/storage/aidl/vts/functional/VtsHalHealthStorageTargetTest.cpp b/health/storage/aidl/vts/functional/VtsHalHealthStorageTargetTest.cpp new file mode 100644 index 0000000000..3b6b6b4864 --- /dev/null +++ b/health/storage/aidl/vts/functional/VtsHalHealthStorageTargetTest.cpp @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * 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 <chrono> +#include <set> +#include <string> +#include <thread> + +#include <aidl/Gtest.h> +#include <aidl/Vintf.h> +#include <aidl/android/hardware/health/storage/BnGarbageCollectCallback.h> +#include <aidl/android/hardware/health/storage/IStorage.h> +#include <android-base/logging.h> +#include <android/binder_ibinder.h> +#include <android/binder_manager.h> +#include <android/binder_process.h> +#include <gtest/gtest.h> +#include <health-storage-test/common.h> + +namespace aidl::android::hardware::health::storage { + +using namespace ::android::hardware::health::storage::test; +using std::chrono_literals::operator""ms; + +#define ASSERT_OK(ret) ASSERT_TRUE(ret.isOk()) << ret.getDescription() +#define EXPECT_OK(ret) EXPECT_TRUE(ret.isOk()) << ret.getDescription() + +class GcCallback : public BnGarbageCollectCallback, public Flag { + public: + ndk::ScopedAStatus onFinish(Result result) override { + std::unique_lock<std::mutex> lock(mutex_); + result_ = result; + OnFinishLocked(&lock); + return ndk::ScopedAStatus::ok(); + } + + /** + * Wait for a specific "timeout". If GC has finished, test that the result + * is equal to the "expected" value. + */ + template <typename R, typename P> + void WaitForResult(std::chrono::duration<R, P> timeout, Result expected) { + std::unique_lock<std::mutex> lock(mutex_); + ASSERT_TRUE(WaitLocked(&lock, timeout)) << "timeout after " << to_string(timeout); + EXPECT_EQ(expected, result_); + } + + private: + Result result_{Result::UNKNOWN_ERROR}; +}; + +class HealthStorageAidl : public testing::TestWithParam<std::string> { + public: + virtual void SetUp() override { + std::string name = GetParam(); + ASSERT_TRUE(AServiceManager_isDeclared(name.c_str())) << name; + ndk::SpAIBinder binder(AServiceManager_waitForService(name.c_str())); + ASSERT_NE(binder, nullptr); + storage_ = IStorage::fromBinder(binder); + ASSERT_NE(storage_, nullptr); + } + + virtual void TearDown() override { + EXPECT_TRUE(ping(kRpcTime)) + << "Service is not responsive; expect subsequent tests to fail."; + } + + /** + * Ping the service and expect it to return after "timeout". Return true + * iff the service is responsive within "timeout". + */ + template <typename R, typename P> + bool ping(std::chrono::duration<R, P> timeout) { + // Ensure the service is responsive after the test. + std::shared_ptr<IStorage> service = storage_; + auto ping_flag = std::make_shared<Flag>(); + std::thread([service, ping_flag] { + EXPECT_EQ(STATUS_OK, AIBinder_ping(service->asBinder().get())); + ping_flag->OnFinish(); + }).detach(); + return ping_flag->Wait(timeout); + } + + std::shared_ptr<IStorage> storage_; +}; + +/** + * Ensure garbage collection works on null callback. + */ +TEST_P(HealthStorageAidl, GcNullCallback) { + ASSERT_OK(storage_->garbageCollect(kDevGcTimeoutSec, nullptr)); + + // Hold test process because HAL can be single-threaded and doing GC. + ASSERT_TRUE(ping(kDevGcTimeout + kDevGcTolerance + kRpcTime)) + << "Service must be available after " + << to_string(kDevGcTimeout + kDevGcTolerance + kRpcTime); +} + +/** + * Ensure garbage collection works on non-null callback. + */ +TEST_P(HealthStorageAidl, GcNonNullCallback) { + std::shared_ptr<GcCallback> cb = ndk::SharedRefBase::make<GcCallback>(); + ASSERT_OK(storage_->garbageCollect(kDevGcTimeoutSec, cb)); + cb->WaitForResult(kDevGcTimeout + kDevGcTolerance + kRpcTime, Result::SUCCESS); +} + +GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(HealthStorageAidl); +INSTANTIATE_TEST_SUITE_P( + HealthStorage, HealthStorageAidl, + testing::ValuesIn(::android::getAidlHalInstanceNames(IStorage::descriptor)), + ::android::PrintInstanceNameToString); + +} // namespace aidl::android::hardware::health::storage + +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + ABinderProcess_setThreadPoolMaxThreadCount(1); + ABinderProcess_startThreadPool(); + return RUN_ALL_TESTS(); +} diff --git a/health/storage/aidl/vts/functional/VtsHalHealthStorageTargetTest.xml b/health/storage/aidl/vts/functional/VtsHalHealthStorageTargetTest.xml new file mode 100644 index 0000000000..f8a1c87a34 --- /dev/null +++ b/health/storage/aidl/vts/functional/VtsHalHealthStorageTargetTest.xml @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2021 The Android Open Source Project + + 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. +--> +<configuration description="Runs VtsHalHealthStorageTargetTest."> + <option name="test-suite-tag" value="apct" /> + <option name="test-suite-tag" value="apct-native" /> + + <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"> + </target_preparer> + + <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer"> + <option name="cleanup" value="true" /> + <option name="push" value="VtsHalHealthStorageTargetTest->/data/local/tmp/VtsHalHealthStorageTargetTest" /> + </target_preparer> + + <test class="com.android.tradefed.testtype.GTest" > + <option name="native-test-device-path" value="/data/local/tmp" /> + <option name="module-name" value="VtsHalHealthStorageTargetTest" /> + <option name="native-test-timeout" value="3m" /> + </test> +</configuration> diff --git a/health/storage/impl_common/Android.bp b/health/storage/impl_common/Android.bp new file mode 100644 index 0000000000..e1149c0cb7 --- /dev/null +++ b/health/storage/impl_common/Android.bp @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * 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. + */ + +// Common implementation between HIDL and AIDL HAL. +cc_library_static { + name: "libhealth_storage_impl_common", + vendor: true, + srcs: [ + "impl_common.cpp", + ], + export_include_dirs: [ + "include", + ], + + cflags: [ + "-Wall", + "-Werror", + ], + + shared_libs: [ + "libbase", + "libhidlbase", + "liblog", + "android.hardware.health.storage@1.0", + ], + + static_libs: [ + "libfstab", + ], + + export_shared_lib_headers: [ + "android.hardware.health.storage@1.0", + ], +} diff --git a/health/storage/impl_common/impl_common.cpp b/health/storage/impl_common/impl_common.cpp new file mode 100644 index 0000000000..6e753d4655 --- /dev/null +++ b/health/storage/impl_common/impl_common.cpp @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * 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 <health-storage-impl/common.h> + +#include <android-base/chrono_utils.h> +#include <android-base/file.h> +#include <android-base/logging.h> +#include <android-base/strings.h> +#include <fstab/fstab.h> + +using ::android::base::ReadFileToString; +using ::android::base::Timer; +using ::android::base::Trim; +using ::android::base::WriteStringToFd; +using ::android::base::WriteStringToFile; +using ::android::fs_mgr::Fstab; +using ::android::fs_mgr::ReadDefaultFstab; +using ::android::hardware::health::storage::V1_0::Result; + +namespace android::hardware::health::storage { + +static std::string GetGarbageCollectPath() { + Fstab fstab; + ReadDefaultFstab(&fstab); + + for (const auto& entry : fstab) { + if (!entry.sysfs_path.empty()) { + return entry.sysfs_path + "/manual_gc"; + } + } + + return ""; +} + +Result GarbageCollect(uint64_t timeout_seconds) { + std::string path = GetGarbageCollectPath(); + + if (path.empty()) { + LOG(WARNING) << "Cannot find Dev GC path"; + return Result::UNKNOWN_ERROR; + } + + Result result = Result::SUCCESS; + Timer timer; + LOG(INFO) << "Start Dev GC on " << path; + while (1) { + std::string require; + if (!ReadFileToString(path, &require)) { + PLOG(WARNING) << "Reading manual_gc failed in " << path; + result = Result::IO_ERROR; + break; + } + require = Trim(require); + if (require == "" || require == "off" || require == "disabled") { + LOG(DEBUG) << "No more to do Dev GC"; + break; + } + LOG(DEBUG) << "Trigger Dev GC on " << path; + if (!WriteStringToFile("1", path)) { + PLOG(WARNING) << "Start Dev GC failed on " << path; + result = Result::IO_ERROR; + break; + } + if (timer.duration() >= std::chrono::seconds(timeout_seconds)) { + LOG(WARNING) << "Dev GC timeout"; + // Timeout is not treated as an error. Try next time. + break; + } + sleep(2); + } + LOG(INFO) << "Stop Dev GC on " << path; + if (!WriteStringToFile("0", path)) { + PLOG(WARNING) << "Stop Dev GC failed on " << path; + result = Result::IO_ERROR; + } + + return result; +} + +void DebugDump(int fd) { + std::stringstream output; + + std::string path = GetGarbageCollectPath(); + if (path.empty()) { + output << "Cannot find Dev GC path"; + } else { + std::string require; + + if (ReadFileToString(path, &require)) { + output << path << ":" << require << std::endl; + } + + if (WriteStringToFile("0", path)) { + output << "stop success" << std::endl; + } + } + + if (!WriteStringToFd(output.str(), fd)) { + PLOG(WARNING) << "debug: cannot write to fd"; + } + + fsync(fd); +} + +} // namespace android::hardware::health::storage diff --git a/health/storage/impl_common/include/health-storage-impl/common.h b/health/storage/impl_common/include/health-storage-impl/common.h new file mode 100644 index 0000000000..c84a6a94a4 --- /dev/null +++ b/health/storage/impl_common/include/health-storage-impl/common.h @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * 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. + */ + +#pragma once + +#include <android/hardware/health/storage/1.0/types.h> +#include <string> + +namespace android::hardware::health::storage { + +// Run debug on fd +void DebugDump(int fd); + +// Run garbage collection on GetGarbageCollectPath(). Blocks until garbage +// collect finishes or |timeout_seconds| has reached. +V1_0::Result GarbageCollect(uint64_t timeout_seconds); + +} // namespace android::hardware::health::storage diff --git a/health/storage/test_common/Android.bp b/health/storage/test_common/Android.bp new file mode 100644 index 0000000000..7c6bef414c --- /dev/null +++ b/health/storage/test_common/Android.bp @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * 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. + */ + +cc_library_headers { + name: "libhealth_storage_test_common_headers", + export_include_dirs: ["include"], +} diff --git a/health/storage/test_common/include/health-storage-test/common.h b/health/storage/test_common/include/health-storage-test/common.h new file mode 100644 index 0000000000..dfda83051d --- /dev/null +++ b/health/storage/test_common/include/health-storage-test/common.h @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * 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. + */ + +#pragma once + +#include <chrono> +#include <string> + +namespace android::hardware::health::storage::test { + +// Dev GC timeout. This is the timeout used by vold. +const uint64_t kDevGcTimeoutSec = 120; +const std::chrono::seconds kDevGcTimeout{kDevGcTimeoutSec}; +// Dev GC timeout tolerance. The HAL may not immediately return after the +// timeout, so include an acceptable tolerance. +const std::chrono::seconds kDevGcTolerance{3}; +// Time accounted for RPC calls. +const std::chrono::milliseconds kRpcTime{1000}; + +template <typename R> +std::string to_string(std::chrono::duration<R, std::milli> time) { + return std::to_string(time.count()) + "ms"; +} + +/** An atomic boolean flag that indicates whether a task has finished. */ +class Flag { + public: + void OnFinish() { + std::unique_lock<std::mutex> lock(mutex_); + OnFinishLocked(&lock); + } + template <typename R, typename P> + bool Wait(std::chrono::duration<R, P> duration) { + std::unique_lock<std::mutex> lock(mutex_); + return WaitLocked(&lock, duration); + } + + protected: + /** Will unlock. */ + void OnFinishLocked(std::unique_lock<std::mutex>* lock) { + finished_ = true; + lock->unlock(); + cv_.notify_all(); + } + template <typename R, typename P> + bool WaitLocked(std::unique_lock<std::mutex>* lock, std::chrono::duration<R, P> duration) { + cv_.wait_for(*lock, duration, [this] { return finished_; }); + return finished_; + } + + bool finished_{false}; + std::mutex mutex_; + std::condition_variable cv_; +}; + +} // namespace android::hardware::health::storage::test diff --git a/identity/aidl/default/Android.bp b/identity/aidl/default/Android.bp index 2eb0faa034..7f342d087a 100644 --- a/identity/aidl/default/Android.bp +++ b/identity/aidl/default/Android.bp @@ -1,3 +1,67 @@ +cc_library_static { + name: "android.hardware.identity-libeic-hal-common", + vendor_available: true, + srcs: [ + "common/IdentityCredential.cpp", + "common/IdentityCredentialStore.cpp", + "common/WritableIdentityCredential.cpp", + ], + export_include_dirs: [ + "common", + ], + cflags: [ + "-Wall", + "-Wextra", + ], + shared_libs: [ + "liblog", + "libcrypto", + "libbinder_ndk", + "libkeymaster_messages", + ], + static_libs: [ + "libbase", + "libcppbor", + "libutils", + "libsoft_attestation_cert", + "libkeymaster_portable", + "libsoft_attestation_cert", + "libpuresoftkeymasterdevice", + "android.hardware.identity-support-lib", + "android.hardware.identity-ndk_platform", + "android.hardware.keymaster-ndk_platform", + ], +} + +cc_library_static { + name: "android.hardware.identity-libeic-library", + vendor_available: true, + srcs: [ + "libeic/EicCbor.c", + "libeic/EicPresentation.c", + "libeic/EicProvisioning.c", + "EicOpsImpl.cc", + ], + export_include_dirs: [ + "libeic", + ], + cflags: [ + "-DEIC_COMPILATION", + "-Wall", + "-Wextra", + "-DEIC_DEBUG", + // Allow using C2x extensions such as omitting parameter names + "-Wno-c2x-extensions", + ], + shared_libs: [ + "libbase", + "libcrypto", + ], + static_libs: [ + "android.hardware.identity-support-lib", + ], +} + cc_binary { name: "android.hardware.identity-service.example", relative_install_path: "hw", @@ -7,23 +71,30 @@ cc_binary { cflags: [ "-Wall", "-Wextra", + "-g", ], shared_libs: [ - "libbase", + "liblog", + "libcrypto", "libbinder_ndk", + "libkeymaster_messages", + ], + static_libs: [ + "libbase", "libcppbor", - "libcrypto", - "liblog", "libutils", + "libsoft_attestation_cert", + "libkeymaster_portable", + "libsoft_attestation_cert", + "libpuresoftkeymasterdevice", "android.hardware.identity-support-lib", "android.hardware.identity-ndk_platform", "android.hardware.keymaster-ndk_platform", + "android.hardware.identity-libeic-hal-common", + "android.hardware.identity-libeic-library", ], srcs: [ - "IdentityCredential.cpp", - "IdentityCredentialStore.cpp", - "WritableIdentityCredential.cpp", - "Util.cpp", "service.cpp", + "FakeSecureHardwareProxy.cpp", ], } diff --git a/identity/aidl/default/EicOpsImpl.cc b/identity/aidl/default/EicOpsImpl.cc new file mode 100644 index 0000000000..3f2ec8b763 --- /dev/null +++ b/identity/aidl/default/EicOpsImpl.cc @@ -0,0 +1,506 @@ +/* + * Copyright 2020, The Android Open Source Project + * + * 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. + */ + +#define LOG_TAG "EicOpsImpl" + +#include <optional> +#include <tuple> +#include <vector> + +#include <android-base/logging.h> +#include <android-base/stringprintf.h> +#include <string.h> + +#include <android/hardware/identity/support/IdentityCredentialSupport.h> + +#include <openssl/sha.h> + +#include <openssl/aes.h> +#include <openssl/bn.h> +#include <openssl/crypto.h> +#include <openssl/ec.h> +#include <openssl/err.h> +#include <openssl/evp.h> +#include <openssl/hkdf.h> +#include <openssl/hmac.h> +#include <openssl/objects.h> +#include <openssl/pem.h> +#include <openssl/pkcs12.h> +#include <openssl/rand.h> +#include <openssl/x509.h> +#include <openssl/x509_vfy.h> + +#include "EicOps.h" + +using ::std::optional; +using ::std::string; +using ::std::tuple; +using ::std::vector; + +void* eicMemSet(void* s, int c, size_t n) { + return memset(s, c, n); +} + +void* eicMemCpy(void* dest, const void* src, size_t n) { + return memcpy(dest, src, n); +} + +size_t eicStrLen(const char* s) { + return strlen(s); +} + +int eicCryptoMemCmp(const void* s1, const void* s2, size_t n) { + return CRYPTO_memcmp(s1, s2, n); +} + +void eicOpsHmacSha256Init(EicHmacSha256Ctx* ctx, const uint8_t* key, size_t keySize) { + HMAC_CTX* realCtx = (HMAC_CTX*)ctx; + HMAC_CTX_init(realCtx); + if (HMAC_Init_ex(realCtx, key, keySize, EVP_sha256(), nullptr /* impl */) != 1) { + LOG(ERROR) << "Error initializing HMAC_CTX"; + } +} + +void eicOpsHmacSha256Update(EicHmacSha256Ctx* ctx, const uint8_t* data, size_t len) { + HMAC_CTX* realCtx = (HMAC_CTX*)ctx; + if (HMAC_Update(realCtx, data, len) != 1) { + LOG(ERROR) << "Error updating HMAC_CTX"; + } +} + +void eicOpsHmacSha256Final(EicHmacSha256Ctx* ctx, uint8_t digest[EIC_SHA256_DIGEST_SIZE]) { + HMAC_CTX* realCtx = (HMAC_CTX*)ctx; + unsigned int size = 0; + if (HMAC_Final(realCtx, digest, &size) != 1) { + LOG(ERROR) << "Error finalizing HMAC_CTX"; + } + if (size != EIC_SHA256_DIGEST_SIZE) { + LOG(ERROR) << "Expected 32 bytes from HMAC_Final, got " << size; + } +} + +void eicOpsSha256Init(EicSha256Ctx* ctx) { + SHA256_CTX* realCtx = (SHA256_CTX*)ctx; + SHA256_Init(realCtx); +} + +void eicOpsSha256Update(EicSha256Ctx* ctx, const uint8_t* data, size_t len) { + SHA256_CTX* realCtx = (SHA256_CTX*)ctx; + SHA256_Update(realCtx, data, len); +} + +void eicOpsSha256Final(EicSha256Ctx* ctx, uint8_t digest[EIC_SHA256_DIGEST_SIZE]) { + SHA256_CTX* realCtx = (SHA256_CTX*)ctx; + SHA256_Final(digest, realCtx); +} + +bool eicOpsRandom(uint8_t* buf, size_t numBytes) { + optional<vector<uint8_t>> bytes = ::android::hardware::identity::support::getRandom(numBytes); + if (!bytes.has_value()) { + return false; + } + memcpy(buf, bytes.value().data(), numBytes); + return true; +} + +bool eicOpsEncryptAes128Gcm( + const uint8_t* key, // Must be 16 bytes + const uint8_t* nonce, // Must be 12 bytes + const uint8_t* data, // May be NULL if size is 0 + size_t dataSize, + const uint8_t* additionalAuthenticationData, // May be NULL if size is 0 + size_t additionalAuthenticationDataSize, uint8_t* encryptedData) { + vector<uint8_t> cppKey; + cppKey.resize(16); + memcpy(cppKey.data(), key, 16); + + vector<uint8_t> cppData; + cppData.resize(dataSize); + if (dataSize > 0) { + memcpy(cppData.data(), data, dataSize); + } + + vector<uint8_t> cppAAD; + cppAAD.resize(additionalAuthenticationDataSize); + if (additionalAuthenticationDataSize > 0) { + memcpy(cppAAD.data(), additionalAuthenticationData, additionalAuthenticationDataSize); + } + + vector<uint8_t> cppNonce; + cppNonce.resize(12); + memcpy(cppNonce.data(), nonce, 12); + + optional<vector<uint8_t>> cppEncryptedData = + android::hardware::identity::support::encryptAes128Gcm(cppKey, cppNonce, cppData, + cppAAD); + if (!cppEncryptedData.has_value()) { + return false; + } + + memcpy(encryptedData, cppEncryptedData.value().data(), cppEncryptedData.value().size()); + return true; +} + +// Decrypts |encryptedData| using |key| and |additionalAuthenticatedData|, +// returns resulting plaintext in |data| must be of size |encryptedDataSize| - 28. +// +// The format of |encryptedData| must be as specified in the +// encryptAes128Gcm() function. +bool eicOpsDecryptAes128Gcm(const uint8_t* key, // Must be 16 bytes + const uint8_t* encryptedData, size_t encryptedDataSize, + const uint8_t* additionalAuthenticationData, + size_t additionalAuthenticationDataSize, uint8_t* data) { + vector<uint8_t> keyVec; + keyVec.resize(16); + memcpy(keyVec.data(), key, 16); + + vector<uint8_t> encryptedDataVec; + encryptedDataVec.resize(encryptedDataSize); + if (encryptedDataSize > 0) { + memcpy(encryptedDataVec.data(), encryptedData, encryptedDataSize); + } + + vector<uint8_t> aadVec; + aadVec.resize(additionalAuthenticationDataSize); + if (additionalAuthenticationDataSize > 0) { + memcpy(aadVec.data(), additionalAuthenticationData, additionalAuthenticationDataSize); + } + + optional<vector<uint8_t>> decryptedDataVec = + android::hardware::identity::support::decryptAes128Gcm(keyVec, encryptedDataVec, + aadVec); + if (!decryptedDataVec.has_value()) { + eicDebug("Error decrypting data"); + return false; + } + if (decryptedDataVec.value().size() != encryptedDataSize - 28) { + eicDebug("Decrypted data is size %zd, expected %zd", decryptedDataVec.value().size(), + encryptedDataSize - 28); + return false; + } + + if (decryptedDataVec.value().size() > 0) { + memcpy(data, decryptedDataVec.value().data(), decryptedDataVec.value().size()); + } + return true; +} + +bool eicOpsCreateEcKey(uint8_t privateKey[EIC_P256_PRIV_KEY_SIZE], + uint8_t publicKey[EIC_P256_PUB_KEY_SIZE]) { + optional<vector<uint8_t>> keyPair = android::hardware::identity::support::createEcKeyPair(); + if (!keyPair) { + eicDebug("Error creating EC keypair"); + return false; + } + optional<vector<uint8_t>> privKey = + android::hardware::identity::support::ecKeyPairGetPrivateKey(keyPair.value()); + if (!privKey) { + eicDebug("Error extracting private key"); + return false; + } + if (privKey.value().size() != EIC_P256_PRIV_KEY_SIZE) { + eicDebug("Private key is not %zd bytes long as expected", (size_t)EIC_P256_PRIV_KEY_SIZE); + return false; + } + + optional<vector<uint8_t>> pubKey = + android::hardware::identity::support::ecKeyPairGetPublicKey(keyPair.value()); + if (!pubKey) { + eicDebug("Error extracting public key"); + return false; + } + // ecKeyPairGetPublicKey() returns 0x04 | x | y, we don't want the leading 0x04. + if (pubKey.value().size() != EIC_P256_PUB_KEY_SIZE + 1) { + eicDebug("Private key is %zd bytes long, expected %zd", pubKey.value().size(), + (size_t)EIC_P256_PRIV_KEY_SIZE + 1); + return false; + } + + memcpy(privateKey, privKey.value().data(), EIC_P256_PRIV_KEY_SIZE); + memcpy(publicKey, pubKey.value().data() + 1, EIC_P256_PUB_KEY_SIZE); + + return true; +} + +bool eicOpsCreateCredentialKey(uint8_t privateKey[EIC_P256_PRIV_KEY_SIZE], const uint8_t* challenge, + size_t challengeSize, const uint8_t* applicationId, + size_t applicationIdSize, bool testCredential, uint8_t* cert, + size_t* certSize) { + vector<uint8_t> challengeVec(challengeSize); + memcpy(challengeVec.data(), challenge, challengeSize); + + vector<uint8_t> applicationIdVec(applicationIdSize); + memcpy(applicationIdVec.data(), applicationId, applicationIdSize); + + optional<std::pair<vector<uint8_t>, vector<vector<uint8_t>>>> ret = + android::hardware::identity::support::createEcKeyPairAndAttestation( + challengeVec, applicationIdVec, testCredential); + if (!ret) { + eicDebug("Error generating CredentialKey and attestation"); + return false; + } + + // Extract certificate chain. + vector<uint8_t> flatChain = + android::hardware::identity::support::certificateChainJoin(ret.value().second); + if (*certSize < flatChain.size()) { + eicDebug("Buffer for certificate is only %zd bytes long, need %zd bytes", *certSize, + flatChain.size()); + return false; + } + memcpy(cert, flatChain.data(), flatChain.size()); + *certSize = flatChain.size(); + + // Extract private key. + optional<vector<uint8_t>> privKey = + android::hardware::identity::support::ecKeyPairGetPrivateKey(ret.value().first); + if (!privKey) { + eicDebug("Error extracting private key"); + return false; + } + if (privKey.value().size() != EIC_P256_PRIV_KEY_SIZE) { + eicDebug("Private key is not %zd bytes long as expected", (size_t)EIC_P256_PRIV_KEY_SIZE); + return false; + } + + memcpy(privateKey, privKey.value().data(), EIC_P256_PRIV_KEY_SIZE); + + return true; +} + +bool eicOpsSignEcKey(const uint8_t publicKey[EIC_P256_PUB_KEY_SIZE], + const uint8_t signingKey[EIC_P256_PRIV_KEY_SIZE], unsigned int serial, + const char* issuerName, const char* subjectName, time_t validityNotBefore, + time_t validityNotAfter, uint8_t* cert, + size_t* certSize) { // inout + vector<uint8_t> signingKeyVec(EIC_P256_PRIV_KEY_SIZE); + memcpy(signingKeyVec.data(), signingKey, EIC_P256_PRIV_KEY_SIZE); + + vector<uint8_t> pubKeyVec(EIC_P256_PUB_KEY_SIZE + 1); + pubKeyVec[0] = 0x04; + memcpy(pubKeyVec.data() + 1, publicKey, EIC_P256_PUB_KEY_SIZE); + + std::string serialDecimal = android::base::StringPrintf("%d", serial); + + optional<vector<uint8_t>> certVec = + android::hardware::identity::support::ecPublicKeyGenerateCertificate( + pubKeyVec, signingKeyVec, serialDecimal, issuerName, subjectName, + validityNotBefore, validityNotAfter); + if (!certVec) { + eicDebug("Error generating certificate"); + return false; + } + + if (*certSize < certVec.value().size()) { + eicDebug("Buffer for certificate is only %zd bytes long, need %zd bytes", *certSize, + certVec.value().size()); + return false; + } + memcpy(cert, certVec.value().data(), certVec.value().size()); + *certSize = certVec.value().size(); + + return true; +} + +bool eicOpsEcDsa(const uint8_t privateKey[EIC_P256_PRIV_KEY_SIZE], + const uint8_t digestOfData[EIC_SHA256_DIGEST_SIZE], + uint8_t signature[EIC_ECDSA_P256_SIGNATURE_SIZE]) { + vector<uint8_t> privKeyVec(EIC_P256_PRIV_KEY_SIZE); + memcpy(privKeyVec.data(), privateKey, EIC_P256_PRIV_KEY_SIZE); + + vector<uint8_t> digestVec(EIC_SHA256_DIGEST_SIZE); + memcpy(digestVec.data(), digestOfData, EIC_SHA256_DIGEST_SIZE); + + optional<vector<uint8_t>> derSignature = + android::hardware::identity::support::signEcDsaDigest(privKeyVec, digestVec); + if (!derSignature) { + eicDebug("Error signing data"); + return false; + } + + ECDSA_SIG* sig; + const unsigned char* p = derSignature.value().data(); + sig = d2i_ECDSA_SIG(nullptr, &p, derSignature.value().size()); + if (sig == nullptr) { + eicDebug("Error decoding DER signature"); + return false; + } + + if (BN_bn2binpad(sig->r, signature, 32) != 32) { + eicDebug("Error encoding r"); + return false; + } + if (BN_bn2binpad(sig->s, signature + 32, 32) != 32) { + eicDebug("Error encoding s"); + return false; + } + + return true; +} + +static const uint8_t hbkTest[16] = {0}; +static const uint8_t hbkReal[16] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}; + +const uint8_t* eicOpsGetHardwareBoundKey(bool testCredential) { + if (testCredential) { + return hbkTest; + } + return hbkReal; +} + +bool eicOpsValidateAuthToken(uint64_t /* challenge */, uint64_t /* secureUserId */, + uint64_t /* authenticatorId */, int /* hardwareAuthenticatorType */, + uint64_t /* timeStamp */, const uint8_t* /* mac */, + size_t /* macSize */, uint64_t /* verificationTokenChallenge */, + uint64_t /* verificationTokenTimeStamp */, + int /* verificationTokenSecurityLevel */, + const uint8_t* /* verificationTokenMac */, + size_t /* verificationTokenMacSize */) { + // Here's where we would validate the passed-in |authToken| to assure ourselves + // that it comes from the e.g. biometric hardware and wasn't made up by an attacker. + // + // However this involves calculating the MAC which requires access to the to + // a pre-shared key which we don't have... + // + return true; +} + +bool eicOpsX509GetPublicKey(const uint8_t* x509Cert, size_t x509CertSize, uint8_t* publicKey, + size_t* publicKeySize) { + vector<uint8_t> chain; + chain.resize(x509CertSize); + memcpy(chain.data(), x509Cert, x509CertSize); + optional<vector<uint8_t>> res = + android::hardware::identity::support::certificateChainGetTopMostKey(chain); + if (!res) { + return false; + } + if (res.value().size() > *publicKeySize) { + eicDebug("Public key size is %zd but buffer only has room for %zd bytes", + res.value().size(), *publicKeySize); + return false; + } + *publicKeySize = res.value().size(); + memcpy(publicKey, res.value().data(), *publicKeySize); + eicDebug("Extracted %zd bytes public key from %zd bytes X.509 cert", *publicKeySize, + x509CertSize); + return true; +} + +bool eicOpsX509CertSignedByPublicKey(const uint8_t* x509Cert, size_t x509CertSize, + const uint8_t* publicKey, size_t publicKeySize) { + vector<uint8_t> certVec(x509Cert, x509Cert + x509CertSize); + vector<uint8_t> publicKeyVec(publicKey, publicKey + publicKeySize); + return android::hardware::identity::support::certificateSignedByPublicKey(certVec, + publicKeyVec); +} + +bool eicOpsEcDsaVerifyWithPublicKey(const uint8_t* digest, size_t digestSize, + const uint8_t* signature, size_t signatureSize, + const uint8_t* publicKey, size_t publicKeySize) { + vector<uint8_t> digestVec(digest, digest + digestSize); + vector<uint8_t> signatureVec(signature, signature + signatureSize); + vector<uint8_t> publicKeyVec(publicKey, publicKey + publicKeySize); + + vector<uint8_t> derSignature; + if (!android::hardware::identity::support::ecdsaSignatureCoseToDer(signatureVec, + derSignature)) { + LOG(ERROR) << "Error convering signature to DER format"; + return false; + } + + if (!android::hardware::identity::support::checkEcDsaSignature(digestVec, derSignature, + publicKeyVec)) { + LOG(ERROR) << "Signature check failed"; + return false; + } + return true; +} + +bool eicOpsEcdh(const uint8_t publicKey[EIC_P256_PUB_KEY_SIZE], + const uint8_t privateKey[EIC_P256_PUB_KEY_SIZE], + uint8_t sharedSecret[EIC_P256_COORDINATE_SIZE]) { + vector<uint8_t> pubKeyVec(EIC_P256_PUB_KEY_SIZE + 1); + pubKeyVec[0] = 0x04; + memcpy(pubKeyVec.data() + 1, publicKey, EIC_P256_PUB_KEY_SIZE); + + vector<uint8_t> privKeyVec(EIC_P256_PRIV_KEY_SIZE); + memcpy(privKeyVec.data(), privateKey, EIC_P256_PRIV_KEY_SIZE); + + optional<vector<uint8_t>> shared = + android::hardware::identity::support::ecdh(pubKeyVec, privKeyVec); + if (!shared) { + LOG(ERROR) << "Error performing ECDH"; + return false; + } + if (shared.value().size() != EIC_P256_COORDINATE_SIZE) { + LOG(ERROR) << "Unexpected size of shared secret " << shared.value().size() << " expected " + << EIC_P256_COORDINATE_SIZE << " bytes"; + return false; + } + memcpy(sharedSecret, shared.value().data(), EIC_P256_COORDINATE_SIZE); + return true; +} + +bool eicOpsHkdf(const uint8_t* sharedSecret, size_t sharedSecretSize, const uint8_t* salt, + size_t saltSize, const uint8_t* info, size_t infoSize, uint8_t* output, + size_t outputSize) { + vector<uint8_t> sharedSecretVec(sharedSecretSize); + memcpy(sharedSecretVec.data(), sharedSecret, sharedSecretSize); + vector<uint8_t> saltVec(saltSize); + memcpy(saltVec.data(), salt, saltSize); + vector<uint8_t> infoVec(infoSize); + memcpy(infoVec.data(), info, infoSize); + + optional<vector<uint8_t>> result = android::hardware::identity::support::hkdf( + sharedSecretVec, saltVec, infoVec, outputSize); + if (!result) { + LOG(ERROR) << "Error performing HKDF"; + return false; + } + if (result.value().size() != outputSize) { + LOG(ERROR) << "Unexpected size of HKDF " << result.value().size() << " expected " + << outputSize; + return false; + } + memcpy(output, result.value().data(), outputSize); + return true; +} + +#ifdef EIC_DEBUG + +void eicPrint(const char* format, ...) { + va_list args; + va_start(args, format); + vfprintf(stderr, format, args); + va_end(args); +} + +void eicHexdump(const char* message, const uint8_t* data, size_t dataSize) { + vector<uint8_t> dataVec(dataSize); + memcpy(dataVec.data(), data, dataSize); + android::hardware::identity::support::hexdump(message, dataVec); +} + +void eicCborPrettyPrint(const uint8_t* cborData, size_t cborDataSize, size_t maxBStrSize) { + vector<uint8_t> cborDataVec(cborDataSize); + memcpy(cborDataVec.data(), cborData, cborDataSize); + string str = + android::hardware::identity::support::cborPrettyPrint(cborDataVec, maxBStrSize, {}); + fprintf(stderr, "%s\n", str.c_str()); +} + +#endif // EIC_DEBUG diff --git a/identity/aidl/default/EicOpsImpl.h b/identity/aidl/default/EicOpsImpl.h new file mode 100644 index 0000000000..333cdce63f --- /dev/null +++ b/identity/aidl/default/EicOpsImpl.h @@ -0,0 +1,46 @@ +/* + * Copyright 2020, The Android Open Source Project + * + * 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 ANDROID_HARDWARE_IDENTITY_EIC_OPS_IMPL_H +#define ANDROID_HARDWARE_IDENTITY_EIC_OPS_IMPL_H + +#include <stdbool.h> +#include <stddef.h> +#include <stdlib.h> + +// Add whatever includes are needed for definitions below. +// + +#include <openssl/hmac.h> +#include <openssl/sha.h> + +#ifdef __cplusplus +extern "C" { +#endif + +// Set the following defines to match the implementation of the supplied +// eicOps*() operations. See EicOps.h for details. +// + +#define EIC_SHA256_CONTEXT_SIZE sizeof(SHA256_CTX) + +#define EIC_HMAC_SHA256_CONTEXT_SIZE sizeof(HMAC_CTX) + +#ifdef __cplusplus +} +#endif + +#endif // ANDROID_HARDWARE_IDENTITY_EMBEDDED_IC_H diff --git a/identity/aidl/default/FakeSecureHardwareProxy.cpp b/identity/aidl/default/FakeSecureHardwareProxy.cpp new file mode 100644 index 0000000000..de6762fc2e --- /dev/null +++ b/identity/aidl/default/FakeSecureHardwareProxy.cpp @@ -0,0 +1,324 @@ +/* + * Copyright 2020, The Android Open Source Project + * + * 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. + */ + +#define LOG_TAG "FakeSecureHardwareProxy" + +#include "FakeSecureHardwareProxy.h" + +#include <android/hardware/identity/support/IdentityCredentialSupport.h> + +#include <android-base/logging.h> +#include <android-base/stringprintf.h> +#include <string.h> + +#include <openssl/sha.h> + +#include <openssl/aes.h> +#include <openssl/bn.h> +#include <openssl/crypto.h> +#include <openssl/ec.h> +#include <openssl/err.h> +#include <openssl/evp.h> +#include <openssl/hkdf.h> +#include <openssl/hmac.h> +#include <openssl/objects.h> +#include <openssl/pem.h> +#include <openssl/pkcs12.h> +#include <openssl/rand.h> +#include <openssl/x509.h> +#include <openssl/x509_vfy.h> + +#include <libeic.h> + +using ::std::optional; +using ::std::string; +using ::std::tuple; +using ::std::vector; + +namespace android::hardware::identity { + +// ---------------------------------------------------------------------- + +FakeSecureHardwareProvisioningProxy::FakeSecureHardwareProvisioningProxy() {} + +FakeSecureHardwareProvisioningProxy::~FakeSecureHardwareProvisioningProxy() {} + +bool FakeSecureHardwareProvisioningProxy::shutdown() { + LOG(INFO) << "FakeSecureHardwarePresentationProxy shutdown"; + return true; +} + +bool FakeSecureHardwareProvisioningProxy::initialize(bool testCredential) { + LOG(INFO) << "FakeSecureHardwareProvisioningProxy created, sizeof(EicProvisioning): " + << sizeof(EicProvisioning); + return eicProvisioningInit(&ctx_, testCredential); +} + +// Returns public key certificate. +optional<vector<uint8_t>> FakeSecureHardwareProvisioningProxy::createCredentialKey( + const vector<uint8_t>& challenge, const vector<uint8_t>& applicationId) { + uint8_t publicKeyCert[4096]; + size_t publicKeyCertSize = sizeof publicKeyCert; + if (!eicProvisioningCreateCredentialKey(&ctx_, challenge.data(), challenge.size(), + applicationId.data(), applicationId.size(), + publicKeyCert, &publicKeyCertSize)) { + return {}; + } + vector<uint8_t> pubKeyCert(publicKeyCertSize); + memcpy(pubKeyCert.data(), publicKeyCert, publicKeyCertSize); + return pubKeyCert; +} + +bool FakeSecureHardwareProvisioningProxy::startPersonalization( + int accessControlProfileCount, vector<int> entryCounts, const string& docType, + size_t expectedProofOfProvisioningSize) { + if (!eicProvisioningStartPersonalization(&ctx_, accessControlProfileCount, entryCounts.data(), + entryCounts.size(), docType.c_str(), + expectedProofOfProvisioningSize)) { + return false; + } + return true; +} + +// Returns MAC (28 bytes). +optional<vector<uint8_t>> FakeSecureHardwareProvisioningProxy::addAccessControlProfile( + int id, const vector<uint8_t>& readerCertificate, bool userAuthenticationRequired, + uint64_t timeoutMillis, uint64_t secureUserId) { + vector<uint8_t> mac(28); + if (!eicProvisioningAddAccessControlProfile( + &ctx_, id, readerCertificate.data(), readerCertificate.size(), + userAuthenticationRequired, timeoutMillis, secureUserId, mac.data())) { + return {}; + } + return mac; +} + +bool FakeSecureHardwareProvisioningProxy::beginAddEntry(const vector<int>& accessControlProfileIds, + const string& nameSpace, const string& name, + uint64_t entrySize) { + uint8_t scratchSpace[512]; + return eicProvisioningBeginAddEntry(&ctx_, accessControlProfileIds.data(), + accessControlProfileIds.size(), nameSpace.c_str(), + name.c_str(), entrySize, scratchSpace, sizeof scratchSpace); +} + +// Returns encryptedContent. +optional<vector<uint8_t>> FakeSecureHardwareProvisioningProxy::addEntryValue( + const vector<int>& accessControlProfileIds, const string& nameSpace, const string& name, + const vector<uint8_t>& content) { + vector<uint8_t> eicEncryptedContent; + uint8_t scratchSpace[512]; + eicEncryptedContent.resize(content.size() + 28); + if (!eicProvisioningAddEntryValue( + &ctx_, accessControlProfileIds.data(), accessControlProfileIds.size(), + nameSpace.c_str(), name.c_str(), content.data(), content.size(), + eicEncryptedContent.data(), scratchSpace, sizeof scratchSpace)) { + return {}; + } + return eicEncryptedContent; +} + +// Returns signatureOfToBeSigned (EIC_ECDSA_P256_SIGNATURE_SIZE bytes). +optional<vector<uint8_t>> FakeSecureHardwareProvisioningProxy::finishAddingEntries() { + vector<uint8_t> signatureOfToBeSigned(EIC_ECDSA_P256_SIGNATURE_SIZE); + if (!eicProvisioningFinishAddingEntries(&ctx_, signatureOfToBeSigned.data())) { + return {}; + } + return signatureOfToBeSigned; +} + +// Returns encryptedCredentialKeys (80 bytes). +optional<vector<uint8_t>> FakeSecureHardwareProvisioningProxy::finishGetCredentialData( + const string& docType) { + vector<uint8_t> encryptedCredentialKeys(80); + if (!eicProvisioningFinishGetCredentialData(&ctx_, docType.c_str(), + encryptedCredentialKeys.data())) { + return {}; + } + return encryptedCredentialKeys; +} + +// ---------------------------------------------------------------------- + +FakeSecureHardwarePresentationProxy::FakeSecureHardwarePresentationProxy() {} + +FakeSecureHardwarePresentationProxy::~FakeSecureHardwarePresentationProxy() {} + +bool FakeSecureHardwarePresentationProxy::initialize(bool testCredential, string docType, + vector<uint8_t> encryptedCredentialKeys) { + LOG(INFO) << "FakeSecureHardwarePresentationProxy created, sizeof(EicPresentation): " + << sizeof(EicPresentation); + return eicPresentationInit(&ctx_, testCredential, docType.c_str(), + encryptedCredentialKeys.data()); +} + +// Returns publicKeyCert (1st component) and signingKeyBlob (2nd component) +optional<pair<vector<uint8_t>, vector<uint8_t>>> +FakeSecureHardwarePresentationProxy::generateSigningKeyPair(string docType, time_t now) { + uint8_t publicKeyCert[512]; + size_t publicKeyCertSize = sizeof(publicKeyCert); + vector<uint8_t> signingKeyBlob(60); + + if (!eicPresentationGenerateSigningKeyPair(&ctx_, docType.c_str(), now, publicKeyCert, + &publicKeyCertSize, signingKeyBlob.data())) { + return {}; + } + + vector<uint8_t> cert; + cert.resize(publicKeyCertSize); + memcpy(cert.data(), publicKeyCert, publicKeyCertSize); + + return std::make_pair(cert, signingKeyBlob); +} + +// Returns private key +optional<vector<uint8_t>> FakeSecureHardwarePresentationProxy::createEphemeralKeyPair() { + vector<uint8_t> priv(EIC_P256_PRIV_KEY_SIZE); + if (!eicPresentationCreateEphemeralKeyPair(&ctx_, priv.data())) { + return {}; + } + return priv; +} + +optional<uint64_t> FakeSecureHardwarePresentationProxy::createAuthChallenge() { + uint64_t challenge; + if (!eicPresentationCreateAuthChallenge(&ctx_, &challenge)) { + return {}; + } + return challenge; +} + +bool FakeSecureHardwarePresentationProxy::shutdown() { + LOG(INFO) << "FakeSecureHardwarePresentationProxy shutdown"; + return true; +} + +bool FakeSecureHardwarePresentationProxy::pushReaderCert(const vector<uint8_t>& certX509) { + return eicPresentationPushReaderCert(&ctx_, certX509.data(), certX509.size()); +} + +bool FakeSecureHardwarePresentationProxy::validateRequestMessage( + const vector<uint8_t>& sessionTranscript, const vector<uint8_t>& requestMessage, + int coseSignAlg, const vector<uint8_t>& readerSignatureOfToBeSigned) { + return eicPresentationValidateRequestMessage( + &ctx_, sessionTranscript.data(), sessionTranscript.size(), requestMessage.data(), + requestMessage.size(), coseSignAlg, readerSignatureOfToBeSigned.data(), + readerSignatureOfToBeSigned.size()); +} + +bool FakeSecureHardwarePresentationProxy::setAuthToken( + uint64_t challenge, uint64_t secureUserId, uint64_t authenticatorId, + int hardwareAuthenticatorType, uint64_t timeStamp, const vector<uint8_t>& mac, + uint64_t verificationTokenChallenge, uint64_t verificationTokenTimestamp, + int verificationTokenSecurityLevel, const vector<uint8_t>& verificationTokenMac) { + return eicPresentationSetAuthToken(&ctx_, challenge, secureUserId, authenticatorId, + hardwareAuthenticatorType, timeStamp, mac.data(), mac.size(), + verificationTokenChallenge, verificationTokenTimestamp, + verificationTokenSecurityLevel, verificationTokenMac.data(), + verificationTokenMac.size()); +} + +optional<bool> FakeSecureHardwarePresentationProxy::validateAccessControlProfile( + int id, const vector<uint8_t>& readerCertificate, bool userAuthenticationRequired, + int timeoutMillis, uint64_t secureUserId, const vector<uint8_t>& mac) { + bool accessGranted = false; + if (!eicPresentationValidateAccessControlProfile(&ctx_, id, readerCertificate.data(), + readerCertificate.size(), + userAuthenticationRequired, timeoutMillis, + secureUserId, mac.data(), &accessGranted)) { + return {}; + } + return accessGranted; +} + +bool FakeSecureHardwarePresentationProxy::startRetrieveEntries() { + return eicPresentationStartRetrieveEntries(&ctx_); +} + +bool FakeSecureHardwarePresentationProxy::calcMacKey( + const vector<uint8_t>& sessionTranscript, const vector<uint8_t>& readerEphemeralPublicKey, + const vector<uint8_t>& signingKeyBlob, const string& docType, + unsigned int numNamespacesWithValues, size_t expectedProofOfProvisioningSize) { + if (signingKeyBlob.size() != 60) { + eicDebug("Unexpected size %zd of signingKeyBlob, expected 60", signingKeyBlob.size()); + return false; + } + return eicPresentationCalcMacKey(&ctx_, sessionTranscript.data(), sessionTranscript.size(), + readerEphemeralPublicKey.data(), signingKeyBlob.data(), + docType.c_str(), numNamespacesWithValues, + expectedProofOfProvisioningSize); +} + +AccessCheckResult FakeSecureHardwarePresentationProxy::startRetrieveEntryValue( + const string& nameSpace, const string& name, unsigned int newNamespaceNumEntries, + int32_t entrySize, const vector<int32_t>& accessControlProfileIds) { + uint8_t scratchSpace[512]; + EicAccessCheckResult result = eicPresentationStartRetrieveEntryValue( + &ctx_, nameSpace.c_str(), name.c_str(), newNamespaceNumEntries, entrySize, + accessControlProfileIds.data(), accessControlProfileIds.size(), scratchSpace, + sizeof scratchSpace); + switch (result) { + case EIC_ACCESS_CHECK_RESULT_OK: + return AccessCheckResult::kOk; + case EIC_ACCESS_CHECK_RESULT_NO_ACCESS_CONTROL_PROFILES: + return AccessCheckResult::kNoAccessControlProfiles; + case EIC_ACCESS_CHECK_RESULT_FAILED: + return AccessCheckResult::kFailed; + case EIC_ACCESS_CHECK_RESULT_USER_AUTHENTICATION_FAILED: + return AccessCheckResult::kUserAuthenticationFailed; + case EIC_ACCESS_CHECK_RESULT_READER_AUTHENTICATION_FAILED: + return AccessCheckResult::kReaderAuthenticationFailed; + } + eicDebug("Unknown result with code %d, returning kFailed", (int)result); + return AccessCheckResult::kFailed; +} + +optional<vector<uint8_t>> FakeSecureHardwarePresentationProxy::retrieveEntryValue( + const vector<uint8_t>& encryptedContent, const string& nameSpace, const string& name, + const vector<int32_t>& accessControlProfileIds) { + uint8_t scratchSpace[512]; + vector<uint8_t> content; + content.resize(encryptedContent.size() - 28); + if (!eicPresentationRetrieveEntryValue( + &ctx_, encryptedContent.data(), encryptedContent.size(), content.data(), + nameSpace.c_str(), name.c_str(), accessControlProfileIds.data(), + accessControlProfileIds.size(), scratchSpace, sizeof scratchSpace)) { + return {}; + } + return content; +} + +optional<vector<uint8_t>> FakeSecureHardwarePresentationProxy::finishRetrieval() { + vector<uint8_t> mac(32); + size_t macSize = 32; + if (!eicPresentationFinishRetrieval(&ctx_, mac.data(), &macSize)) { + return {}; + } + mac.resize(macSize); + return mac; +} + +optional<vector<uint8_t>> FakeSecureHardwarePresentationProxy::deleteCredential( + const string& docType, size_t proofOfDeletionCborSize) { + vector<uint8_t> signatureOfToBeSigned(EIC_ECDSA_P256_SIGNATURE_SIZE); + if (!eicPresentationDeleteCredential(&ctx_, docType.c_str(), proofOfDeletionCborSize, + signatureOfToBeSigned.data())) { + return {}; + } + return signatureOfToBeSigned; +} + +} // namespace android::hardware::identity diff --git a/identity/aidl/default/FakeSecureHardwareProxy.h b/identity/aidl/default/FakeSecureHardwareProxy.h new file mode 100644 index 0000000000..b858dd409d --- /dev/null +++ b/identity/aidl/default/FakeSecureHardwareProxy.h @@ -0,0 +1,151 @@ +/* + * Copyright 2020, The Android Open Source Project + * + * 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 ANDROID_HARDWARE_IDENTITY_FAKESECUREHARDWAREPROXY_H +#define ANDROID_HARDWARE_IDENTITY_FAKESECUREHARDWAREPROXY_H + +#include <libeic/libeic.h> + +#include "SecureHardwareProxy.h" + +namespace android::hardware::identity { + +// This implementation uses libEmbeddedIC in-process. +// +class FakeSecureHardwareProvisioningProxy : public SecureHardwareProvisioningProxy { + public: + FakeSecureHardwareProvisioningProxy(); + virtual ~FakeSecureHardwareProvisioningProxy(); + + bool initialize(bool testCredential) override; + + bool shutdown() override; + + // Returns public key certificate. + optional<vector<uint8_t>> createCredentialKey(const vector<uint8_t>& challenge, + const vector<uint8_t>& applicationId) override; + + bool startPersonalization(int accessControlProfileCount, vector<int> entryCounts, + const string& docType, + size_t expectedProofOfProvisioningSize) override; + + // Returns MAC (28 bytes). + optional<vector<uint8_t>> addAccessControlProfile(int id, + const vector<uint8_t>& readerCertificate, + bool userAuthenticationRequired, + uint64_t timeoutMillis, + uint64_t secureUserId) override; + + bool beginAddEntry(const vector<int>& accessControlProfileIds, const string& nameSpace, + const string& name, uint64_t entrySize) override; + + // Returns encryptedContent. + optional<vector<uint8_t>> addEntryValue(const vector<int>& accessControlProfileIds, + const string& nameSpace, const string& name, + const vector<uint8_t>& content) override; + + // Returns signatureOfToBeSigned (EIC_ECDSA_P256_SIGNATURE_SIZE bytes). + optional<vector<uint8_t>> finishAddingEntries() override; + + // Returns encryptedCredentialKeys (80 bytes). + optional<vector<uint8_t>> finishGetCredentialData(const string& docType) override; + + protected: + EicProvisioning ctx_; +}; + +// This implementation uses libEmbeddedIC in-process. +// +class FakeSecureHardwarePresentationProxy : public SecureHardwarePresentationProxy { + public: + FakeSecureHardwarePresentationProxy(); + virtual ~FakeSecureHardwarePresentationProxy(); + + bool initialize(bool testCredential, string docType, + vector<uint8_t> encryptedCredentialKeys) override; + + // Returns publicKeyCert (1st component) and signingKeyBlob (2nd component) + optional<pair<vector<uint8_t>, vector<uint8_t>>> generateSigningKeyPair(string docType, + time_t now) override; + + // Returns private key + optional<vector<uint8_t>> createEphemeralKeyPair() override; + + optional<uint64_t> createAuthChallenge() override; + + bool startRetrieveEntries() override; + + bool setAuthToken(uint64_t challenge, uint64_t secureUserId, uint64_t authenticatorId, + int hardwareAuthenticatorType, uint64_t timeStamp, const vector<uint8_t>& mac, + uint64_t verificationTokenChallenge, uint64_t verificationTokenTimestamp, + int verificationTokenSecurityLevel, + const vector<uint8_t>& verificationTokenMac) override; + + bool pushReaderCert(const vector<uint8_t>& certX509) override; + + optional<bool> validateAccessControlProfile(int id, const vector<uint8_t>& readerCertificate, + bool userAuthenticationRequired, int timeoutMillis, + uint64_t secureUserId, + const vector<uint8_t>& mac) override; + + bool validateRequestMessage(const vector<uint8_t>& sessionTranscript, + const vector<uint8_t>& requestMessage, int coseSignAlg, + const vector<uint8_t>& readerSignatureOfToBeSigned) override; + + bool calcMacKey(const vector<uint8_t>& sessionTranscript, + const vector<uint8_t>& readerEphemeralPublicKey, + const vector<uint8_t>& signingKeyBlob, const string& docType, + unsigned int numNamespacesWithValues, + size_t expectedProofOfProvisioningSize) override; + + AccessCheckResult startRetrieveEntryValue( + const string& nameSpace, const string& name, unsigned int newNamespaceNumEntries, + int32_t entrySize, const vector<int32_t>& accessControlProfileIds) override; + + optional<vector<uint8_t>> retrieveEntryValue( + const vector<uint8_t>& encryptedContent, const string& nameSpace, const string& name, + const vector<int32_t>& accessControlProfileIds) override; + + optional<vector<uint8_t>> finishRetrieval() override; + + optional<vector<uint8_t>> deleteCredential(const string& docType, + size_t proofOfDeletionCborSize) override; + + bool shutdown() override; + + protected: + EicPresentation ctx_; +}; + +// Factory implementation. +// +class FakeSecureHardwareProxyFactory : public SecureHardwareProxyFactory { + public: + FakeSecureHardwareProxyFactory() {} + virtual ~FakeSecureHardwareProxyFactory() {} + + sp<SecureHardwareProvisioningProxy> createProvisioningProxy() override { + return new FakeSecureHardwareProvisioningProxy(); + } + + sp<SecureHardwarePresentationProxy> createPresentationProxy() override { + return new FakeSecureHardwarePresentationProxy(); + } +}; + +} // namespace android::hardware::identity + +#endif // ANDROID_HARDWARE_IDENTITY_FAKESECUREHARDWAREPROXY_H diff --git a/identity/aidl/default/Util.cpp b/identity/aidl/default/Util.cpp deleted file mode 100644 index 66b9f13d89..0000000000 --- a/identity/aidl/default/Util.cpp +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Copyright 2019, The Android Open Source Project - * - * 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. - */ - -#define LOG_TAG "Util" - -#include "Util.h" - -#include <android/hardware/identity/support/IdentityCredentialSupport.h> - -#include <string.h> - -#include <android-base/logging.h> - -#include <cppbor.h> -#include <cppbor_parse.h> - -namespace aidl::android::hardware::identity { - -using namespace ::android::hardware::identity; - -// This is not a very random HBK but that's OK because this is the SW -// implementation where it can't be kept secret. -vector<uint8_t> hardwareBoundKey = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}; - -const vector<uint8_t>& getHardwareBoundKey() { - return hardwareBoundKey; -} - -vector<uint8_t> secureAccessControlProfileEncodeCbor(const SecureAccessControlProfile& profile) { - cppbor::Map map; - map.add("id", profile.id); - - if (profile.readerCertificate.encodedCertificate.size() > 0) { - map.add("readerCertificate", cppbor::Bstr(profile.readerCertificate.encodedCertificate)); - } - - if (profile.userAuthenticationRequired) { - map.add("userAuthenticationRequired", profile.userAuthenticationRequired); - map.add("timeoutMillis", profile.timeoutMillis); - map.add("secureUserId", profile.secureUserId); - } - - return map.encode(); -} - -optional<vector<uint8_t>> secureAccessControlProfileCalcMac( - const SecureAccessControlProfile& profile, const vector<uint8_t>& storageKey) { - vector<uint8_t> cborData = secureAccessControlProfileEncodeCbor(profile); - - optional<vector<uint8_t>> nonce = support::getRandom(12); - if (!nonce) { - return {}; - } - optional<vector<uint8_t>> macO = - support::encryptAes128Gcm(storageKey, nonce.value(), {}, cborData); - if (!macO) { - return {}; - } - return macO.value(); -} - -bool secureAccessControlProfileCheckMac(const SecureAccessControlProfile& profile, - const vector<uint8_t>& storageKey) { - vector<uint8_t> cborData = secureAccessControlProfileEncodeCbor(profile); - - if (profile.mac.size() < support::kAesGcmIvSize) { - return false; - } - vector<uint8_t> nonce = - vector<uint8_t>(profile.mac.begin(), profile.mac.begin() + support::kAesGcmIvSize); - optional<vector<uint8_t>> mac = support::encryptAes128Gcm(storageKey, nonce, {}, cborData); - if (!mac) { - return false; - } - if (mac.value() != profile.mac) { - return false; - } - return true; -} - -vector<uint8_t> entryCreateAdditionalData(const string& nameSpace, const string& name, - const vector<int32_t> accessControlProfileIds) { - cppbor::Map map; - map.add("Namespace", nameSpace); - map.add("Name", name); - - cppbor::Array acpIds; - for (auto id : accessControlProfileIds) { - acpIds.add(id); - } - map.add("AccessControlProfileIds", std::move(acpIds)); - return map.encode(); -} - -} // namespace aidl::android::hardware::identity diff --git a/identity/aidl/default/Util.h b/identity/aidl/default/Util.h deleted file mode 100644 index 9fccba2c72..0000000000 --- a/identity/aidl/default/Util.h +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright 2019, The Android Open Source Project - * - * 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 ANDROID_HARDWARE_IDENTITY_UTIL_H -#define ANDROID_HARDWARE_IDENTITY_UTIL_H - -#include <aidl/android/hardware/identity/BnIdentityCredential.h> -#include <android/hardware/identity/support/IdentityCredentialSupport.h> - -#include <map> -#include <optional> -#include <set> -#include <string> -#include <vector> - -#include <cppbor/cppbor.h> - -namespace aidl::android::hardware::identity { - -using ::std::optional; -using ::std::string; -using ::std::vector; - -// Returns the hardware-bound AES-128 key. -const vector<uint8_t>& getHardwareBoundKey(); - -// Calculates the MAC for |profile| using |storageKey|. -optional<vector<uint8_t>> secureAccessControlProfileCalcMac( - const SecureAccessControlProfile& profile, const vector<uint8_t>& storageKey); - -// Checks authenticity of the MAC in |profile| using |storageKey|. -bool secureAccessControlProfileCheckMac(const SecureAccessControlProfile& profile, - const vector<uint8_t>& storageKey); - -// Creates the AdditionalData CBOR used in the addEntryValue() HIDL method. -vector<uint8_t> entryCreateAdditionalData(const string& nameSpace, const string& name, - const vector<int32_t> accessControlProfileIds); - -} // namespace aidl::android::hardware::identity - -#endif // ANDROID_HARDWARE_IDENTITY_UTIL_H diff --git a/identity/aidl/default/IdentityCredential.cpp b/identity/aidl/default/common/IdentityCredential.cpp index dfcd4f557f..270fcfa8d0 100644 --- a/identity/aidl/default/IdentityCredential.cpp +++ b/identity/aidl/default/common/IdentityCredential.cpp @@ -18,7 +18,6 @@ #include "IdentityCredential.h" #include "IdentityCredentialStore.h" -#include "Util.h" #include <android/hardware/identity/support/IdentityCredentialSupport.h> @@ -30,6 +29,8 @@ #include <cppbor.h> #include <cppbor_parse.h> +#include "FakeSecureHardwareProxy.h" + namespace aidl::android::hardware::identity { using ::aidl::android::hardware::keymaster::Timestamp; @@ -69,40 +70,17 @@ int IdentityCredential::initialize() { docType_ = docTypeItem->value(); testCredential_ = testCredentialItem->value(); - vector<uint8_t> hardwareBoundKey; - if (testCredential_) { - hardwareBoundKey = support::getTestHardwareBoundKey(); - } else { - hardwareBoundKey = getHardwareBoundKey(); - } - const vector<uint8_t>& encryptedCredentialKeys = encryptedCredentialKeysItem->value(); - const vector<uint8_t> docTypeVec(docType_.begin(), docType_.end()); - optional<vector<uint8_t>> decryptedCredentialKeys = - support::decryptAes128Gcm(hardwareBoundKey, encryptedCredentialKeys, docTypeVec); - if (!decryptedCredentialKeys) { - LOG(ERROR) << "Error decrypting CredentialKeys"; - return IIdentityCredentialStore::STATUS_INVALID_DATA; - } - auto [dckItem, dckPos, dckMessage] = cppbor::parse(decryptedCredentialKeys.value()); - if (dckItem == nullptr) { - LOG(ERROR) << "Decrypted CredentialKeys is not valid CBOR: " << dckMessage; + if (encryptedCredentialKeys.size() != 80) { + LOG(ERROR) << "Unexpected size for encrypted CredentialKeys"; return IIdentityCredentialStore::STATUS_INVALID_DATA; } - const cppbor::Array* dckArrayItem = dckItem->asArray(); - if (dckArrayItem == nullptr || dckArrayItem->size() != 2) { - LOG(ERROR) << "Decrypted CredentialKeys is not an array with two elements"; - return IIdentityCredentialStore::STATUS_INVALID_DATA; - } - const cppbor::Bstr* storageKeyItem = (*dckArrayItem)[0]->asBstr(); - const cppbor::Bstr* credentialPrivKeyItem = (*dckArrayItem)[1]->asBstr(); - if (storageKeyItem == nullptr || credentialPrivKeyItem == nullptr) { - LOG(ERROR) << "CredentialKeys unexpected item types"; - return IIdentityCredentialStore::STATUS_INVALID_DATA; + + if (!hwProxy_->initialize(testCredential_, docType_, encryptedCredentialKeys)) { + LOG(ERROR) << "hwProxy->initialize failed"; + return false; } - storageKey_ = storageKeyItem->value(); - credentialPrivKey_ = credentialPrivKeyItem->value(); return IIdentityCredentialStore::STATUS_OK; } @@ -110,12 +88,20 @@ int IdentityCredential::initialize() { ndk::ScopedAStatus IdentityCredential::deleteCredential( vector<uint8_t>* outProofOfDeletionSignature) { cppbor::Array array = {"ProofOfDeletion", docType_, testCredential_}; - vector<uint8_t> proofOfDeletion = array.encode(); + vector<uint8_t> proofOfDeletionCbor = array.encode(); + vector<uint8_t> podDigest = support::sha256(proofOfDeletionCbor); - optional<vector<uint8_t>> signature = support::coseSignEcDsa(credentialPrivKey_, - proofOfDeletion, // payload - {}, // additionalData - {}); // certificateChain + optional<vector<uint8_t>> signatureOfToBeSigned = + hwProxy_->deleteCredential(docType_, proofOfDeletionCbor.size()); + if (!signatureOfToBeSigned) { + return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( + IIdentityCredentialStore::STATUS_FAILED, "Error signing ProofOfDeletion")); + } + + optional<vector<uint8_t>> signature = + support::coseSignEcDsaWithSignature(signatureOfToBeSigned.value(), + proofOfDeletionCbor, // data + {}); // certificateChain if (!signature) { return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( IIdentityCredentialStore::STATUS_FAILED, "Error signing data")); @@ -126,22 +112,28 @@ ndk::ScopedAStatus IdentityCredential::deleteCredential( } ndk::ScopedAStatus IdentityCredential::createEphemeralKeyPair(vector<uint8_t>* outKeyPair) { - optional<vector<uint8_t>> kp = support::createEcKeyPair(); - if (!kp) { + optional<vector<uint8_t>> ephemeralPriv = hwProxy_->createEphemeralKeyPair(); + if (!ephemeralPriv) { + return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( + IIdentityCredentialStore::STATUS_FAILED, "Error creating ephemeral key")); + } + optional<vector<uint8_t>> keyPair = support::ecPrivateKeyToKeyPair(ephemeralPriv.value()); + if (!keyPair) { return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( - IIdentityCredentialStore::STATUS_FAILED, "Error creating ephemeral key pair")); + IIdentityCredentialStore::STATUS_FAILED, "Error creating ephemeral key-pair")); } // Stash public key of this key-pair for later check in startRetrieval(). - optional<vector<uint8_t>> publicKey = support::ecKeyPairGetPublicKey(kp.value()); + optional<vector<uint8_t>> publicKey = support::ecKeyPairGetPublicKey(keyPair.value()); if (!publicKey) { + LOG(ERROR) << "Error getting public part of ephemeral key pair"; return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( IIdentityCredentialStore::STATUS_FAILED, "Error getting public part of ephemeral key pair")); } ephemeralPublicKey_ = publicKey.value(); - *outKeyPair = kp.value(); + *outKeyPair = keyPair.value(); return ndk::ScopedAStatus::ok(); } @@ -152,109 +144,15 @@ ndk::ScopedAStatus IdentityCredential::setReaderEphemeralPublicKey( } ndk::ScopedAStatus IdentityCredential::createAuthChallenge(int64_t* outChallenge) { - uint64_t challenge = 0; - while (challenge == 0) { - optional<vector<uint8_t>> bytes = support::getRandom(8); - if (!bytes) { - return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( - IIdentityCredentialStore::STATUS_FAILED, - "Error getting random data for challenge")); - } - - challenge = 0; - for (size_t n = 0; n < bytes.value().size(); n++) { - challenge |= ((bytes.value())[n] << (n * 8)); - } + optional<uint64_t> challenge = hwProxy_->createAuthChallenge(); + if (!challenge) { + return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( + IIdentityCredentialStore::STATUS_FAILED, "Error generating challenge")); } - - *outChallenge = challenge; - authChallenge_ = challenge; + *outChallenge = challenge.value(); return ndk::ScopedAStatus::ok(); } -// TODO: this could be a lot faster if we did all the splitting and pubkey extraction -// ahead of time. -bool checkReaderAuthentication(const SecureAccessControlProfile& profile, - const vector<uint8_t>& readerCertificateChain) { - optional<vector<uint8_t>> acpPubKey = - support::certificateChainGetTopMostKey(profile.readerCertificate.encodedCertificate); - if (!acpPubKey) { - LOG(ERROR) << "Error extracting public key from readerCertificate in profile"; - return false; - } - - optional<vector<vector<uint8_t>>> certificatesInChain = - support::certificateChainSplit(readerCertificateChain); - if (!certificatesInChain) { - LOG(ERROR) << "Error splitting readerCertificateChain"; - return false; - } - for (const vector<uint8_t>& certInChain : certificatesInChain.value()) { - optional<vector<uint8_t>> certPubKey = support::certificateChainGetTopMostKey(certInChain); - if (!certPubKey) { - LOG(ERROR) - << "Error extracting public key from certificate in chain presented by reader"; - return false; - } - if (acpPubKey.value() == certPubKey.value()) { - return true; - } - } - return false; -} - -bool checkUserAuthentication(const SecureAccessControlProfile& profile, - const VerificationToken& verificationToken, - const HardwareAuthToken& authToken, uint64_t authChallenge) { - if (profile.secureUserId != authToken.userId) { - LOG(ERROR) << "secureUserId in profile (" << profile.secureUserId - << ") differs from userId in authToken (" << authToken.userId << ")"; - return false; - } - - if (verificationToken.timestamp.milliSeconds == 0) { - LOG(ERROR) << "VerificationToken is not set"; - return false; - } - if (authToken.timestamp.milliSeconds == 0) { - LOG(ERROR) << "AuthToken is not set"; - return false; - } - - if (profile.timeoutMillis == 0) { - if (authToken.challenge == 0) { - LOG(ERROR) << "No challenge in authToken"; - return false; - } - - if (authToken.challenge != int64_t(authChallenge)) { - LOG(ERROR) << "Challenge in authToken (" << uint64_t(authToken.challenge) << ") " - << "doesn't match the challenge we created (" << authChallenge << ")"; - return false; - } - return true; - } - - // Timeout-based user auth follows. The verification token conveys what the - // time is right now in the environment which generated the auth token. This - // is what makes it possible to do timeout-based checks. - // - const Timestamp now = verificationToken.timestamp; - if (authToken.timestamp.milliSeconds > now.milliSeconds) { - LOG(ERROR) << "Timestamp in authToken (" << authToken.timestamp.milliSeconds - << ") is in the future (now: " << now.milliSeconds << ")"; - return false; - } - if (now.milliSeconds > authToken.timestamp.milliSeconds + profile.timeoutMillis) { - LOG(ERROR) << "Deadline for authToken (" << authToken.timestamp.milliSeconds << " + " - << profile.timeoutMillis << " = " - << (authToken.timestamp.milliSeconds + profile.timeoutMillis) - << ") is in the past (now: " << now.milliSeconds << ")"; - return false; - } - return true; -} - ndk::ScopedAStatus IdentityCredential::setRequestedNamespaces( const vector<RequestNamespace>& requestNamespaces) { requestNamespaces_ = requestNamespaces; @@ -284,6 +182,7 @@ ndk::ScopedAStatus IdentityCredential::startRetrieval( } if (numStartRetrievalCalls_ > 0) { if (sessionTranscript_ != sessionTranscript) { + LOG(ERROR) << "Session Transcript changed"; return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( IIdentityCredentialStore::STATUS_SESSION_TRANSCRIPT_MISMATCH, "Passed-in SessionTranscript doesn't match previously used SessionTranscript")); @@ -291,6 +190,40 @@ ndk::ScopedAStatus IdentityCredential::startRetrieval( } sessionTranscript_ = sessionTranscript; + // This resets various state in the TA... + if (!hwProxy_->startRetrieveEntries()) { + return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( + IIdentityCredentialStore::STATUS_FAILED, "Error starting retrieving entries")); + } + + optional<vector<uint8_t>> signatureOfToBeSigned; + if (readerSignature.size() > 0) { + signatureOfToBeSigned = support::coseSignGetSignature(readerSignature); + if (!signatureOfToBeSigned) { + return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( + IIdentityCredentialStore::STATUS_READER_SIGNATURE_CHECK_FAILED, + "Error extracting signatureOfToBeSigned from COSE_Sign1")); + } + } + + // Feed the auth token to secure hardware. + if (!hwProxy_->setAuthToken(authToken.challenge, authToken.userId, authToken.authenticatorId, + int(authToken.authenticatorType), authToken.timestamp.milliSeconds, + authToken.mac, verificationToken_.challenge, + verificationToken_.timestamp.milliSeconds, + int(verificationToken_.securityLevel), verificationToken_.mac)) { + return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( + IIdentityCredentialStore::STATUS_INVALID_DATA, "Invalid Auth Token")); + } + + // We'll be feeding ACPs interleaved with certificates from the reader + // certificate chain... + vector<SecureAccessControlProfile> remainingAcps = accessControlProfiles; + + // ... and we'll use those ACPs to build up a 32-bit mask indicating which + // of the possible 32 ACPs grants access. + uint32_t accessControlProfileMask = 0; + // If there is a signature, validate that it was made with the top-most key in the // certificate chain embedded in the COSE_Sign1 structure. optional<vector<uint8_t>> readerCertificateChain; @@ -302,45 +235,113 @@ ndk::ScopedAStatus IdentityCredential::startRetrieval( "Unable to get reader certificate chain from COSE_Sign1")); } - if (!support::certificateChainValidate(readerCertificateChain.value())) { + // First, feed all the reader certificates to the secure hardware. We start + // at the end.. + optional<vector<vector<uint8_t>>> splitCerts = + support::certificateChainSplit(readerCertificateChain.value()); + if (!splitCerts || splitCerts.value().size() == 0) { return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( IIdentityCredentialStore::STATUS_READER_SIGNATURE_CHECK_FAILED, - "Error validating reader certificate chain")); + "Error splitting certificate chain from COSE_Sign1")); } + for (ssize_t n = splitCerts.value().size() - 1; n >= 0; --n) { + const vector<uint8_t>& x509Cert = splitCerts.value()[n]; + if (!hwProxy_->pushReaderCert(x509Cert)) { + return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( + IIdentityCredentialStore::STATUS_READER_SIGNATURE_CHECK_FAILED, + StringPrintf("Error validating reader certificate %zd", n).c_str())); + } - optional<vector<uint8_t>> readerPublicKey = - support::certificateChainGetTopMostKey(readerCertificateChain.value()); - if (!readerPublicKey) { - return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( - IIdentityCredentialStore::STATUS_READER_SIGNATURE_CHECK_FAILED, - "Unable to get public key from reader certificate chain")); + // If we have ACPs for that particular certificate, send them to the + // TA right now... + // + // Remember in this case certificate equality is done by comparing public keys, + // not bitwise comparison of the certificates. + // + optional<vector<uint8_t>> x509CertPubKey = + support::certificateChainGetTopMostKey(x509Cert); + if (!x509CertPubKey) { + return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( + IIdentityCredentialStore::STATUS_FAILED, + StringPrintf("Error getting public key from reader certificate %zd", n) + .c_str())); + } + vector<SecureAccessControlProfile>::iterator it = remainingAcps.begin(); + while (it != remainingAcps.end()) { + const SecureAccessControlProfile& profile = *it; + if (profile.readerCertificate.encodedCertificate.size() == 0) { + ++it; + continue; + } + optional<vector<uint8_t>> profilePubKey = support::certificateChainGetTopMostKey( + profile.readerCertificate.encodedCertificate); + if (!profilePubKey) { + return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( + IIdentityCredentialStore::STATUS_FAILED, + "Error getting public key from profile")); + } + if (profilePubKey.value() == x509CertPubKey.value()) { + optional<bool> res = hwProxy_->validateAccessControlProfile( + profile.id, profile.readerCertificate.encodedCertificate, + profile.userAuthenticationRequired, profile.timeoutMillis, + profile.secureUserId, profile.mac); + if (!res) { + return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( + IIdentityCredentialStore::STATUS_INVALID_DATA, + "Error validating access control profile")); + } + if (res.value()) { + accessControlProfileMask |= (1 << profile.id); + } + it = remainingAcps.erase(it); + } else { + ++it; + } + } } - const vector<uint8_t>& itemsRequestBytes = itemsRequest; - vector<uint8_t> encodedReaderAuthentication = - cppbor::Array() - .add("ReaderAuthentication") - .add(std::move(sessionTranscriptItem)) - .add(cppbor::Semantic(24, itemsRequestBytes)) - .encode(); - vector<uint8_t> encodedReaderAuthenticationBytes = - cppbor::Semantic(24, encodedReaderAuthentication).encode(); - if (!support::coseCheckEcDsaSignature(readerSignature, - encodedReaderAuthenticationBytes, // detached content - readerPublicKey.value())) { - return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( - IIdentityCredentialStore::STATUS_READER_SIGNATURE_CHECK_FAILED, - "readerSignature check failed")); + // ... then pass the request message and have the TA check it's signed by the + // key in last certificate we pushed. + if (sessionTranscript.size() > 0 && itemsRequest.size() > 0 && readerSignature.size() > 0) { + optional<vector<uint8_t>> tbsSignature = support::coseSignGetSignature(readerSignature); + if (!tbsSignature) { + return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( + IIdentityCredentialStore::STATUS_READER_SIGNATURE_CHECK_FAILED, + "Error extracting toBeSigned from COSE_Sign1")); + } + optional<int> coseSignAlg = support::coseSignGetAlg(readerSignature); + if (!coseSignAlg) { + return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( + IIdentityCredentialStore::STATUS_READER_SIGNATURE_CHECK_FAILED, + "Error extracting signature algorithm from COSE_Sign1")); + } + if (!hwProxy_->validateRequestMessage(sessionTranscript, itemsRequest, + coseSignAlg.value(), tbsSignature.value())) { + return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( + IIdentityCredentialStore::STATUS_READER_SIGNATURE_CHECK_FAILED, + "readerMessage is not signed by top-level certificate")); + } } } - // Here's where we would validate the passed-in |authToken| to assure ourselves - // that it comes from the e.g. biometric hardware and wasn't made up by an attacker. - // - // However this involves calculating the MAC. However this requires access - // to the key needed to a pre-shared key which we don't have... - // + // Feed remaining access control profiles... + for (const SecureAccessControlProfile& profile : remainingAcps) { + optional<bool> res = hwProxy_->validateAccessControlProfile( + profile.id, profile.readerCertificate.encodedCertificate, + profile.userAuthenticationRequired, profile.timeoutMillis, profile.secureUserId, + profile.mac); + if (!res) { + return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( + IIdentityCredentialStore::STATUS_INVALID_DATA, + "Error validating access control profile")); + } + if (res.value()) { + accessControlProfileMask |= (1 << profile.id); + } + } + // TODO: move this check to the TA +#if 1 // To prevent replay-attacks, we check that the public part of the ephemeral // key we previously created, is present in the DeviceEngagement part of // SessionTranscript as a COSE_Key, in uncompressed form. @@ -364,6 +365,7 @@ ndk::ScopedAStatus IdentityCredential::startRetrieval( "SessionTranscript (make sure leading zeroes are not used)")); } } +#endif // itemsRequest: If non-empty, contains request data that may be signed by the // reader. The content can be defined in the way appropriate for the @@ -463,30 +465,6 @@ ndk::ScopedAStatus IdentityCredential::startRetrieval( } } - // Validate all the access control profiles in the requestData. - bool haveAuthToken = (authToken.timestamp.milliSeconds != int64_t(0)); - for (const auto& profile : accessControlProfiles) { - if (!secureAccessControlProfileCheckMac(profile, storageKey_)) { - LOG(ERROR) << "Error checking MAC for profile"; - return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( - IIdentityCredentialStore::STATUS_INVALID_DATA, - "Error checking MAC for profile")); - } - int accessControlCheck = IIdentityCredentialStore::STATUS_OK; - if (profile.userAuthenticationRequired) { - if (!haveAuthToken || - !checkUserAuthentication(profile, verificationToken_, authToken, authChallenge_)) { - accessControlCheck = IIdentityCredentialStore::STATUS_USER_AUTHENTICATION_FAILED; - } - } else if (profile.readerCertificate.encodedCertificate.size() > 0) { - if (!readerCertificateChain || - !checkReaderAuthentication(profile, readerCertificateChain.value())) { - accessControlCheck = IIdentityCredentialStore::STATUS_READER_AUTHENTICATION_FAILED; - } - } - profileIdToAccessCheckResult_[profile.id] = accessControlCheck; - } - deviceNameSpacesMap_ = cppbor::Map(); currentNameSpaceDeviceNameSpacesMap_ = cppbor::Map(); @@ -496,8 +474,36 @@ ndk::ScopedAStatus IdentityCredential::startRetrieval( itemsRequest_ = itemsRequest; signingKeyBlob_ = signingKeyBlob; - // Finally, calculate the size of DeviceNameSpaces. We need to know it ahead of time. - expectedDeviceNameSpacesSize_ = calcDeviceNameSpacesSize(); + // calculate the size of DeviceNameSpaces. We need to know it ahead of time. + calcDeviceNameSpacesSize(accessControlProfileMask); + + // Count the number of non-empty namespaces + size_t numNamespacesWithValues = 0; + for (size_t n = 0; n < expectedNumEntriesPerNamespace_.size(); n++) { + if (expectedNumEntriesPerNamespace_[n] > 0) { + numNamespacesWithValues += 1; + } + } + + // Finally, pass info so the HMAC key can be derived and the TA can start + // creating the DeviceNameSpaces CBOR... + if (sessionTranscript_.size() > 0 && readerPublicKey_.size() > 0 && signingKeyBlob.size() > 0) { + // We expect the reader ephemeral public key to be same size and curve + // as the ephemeral key we generated (e.g. P-256 key), otherwise ECDH + // won't work. So its length should be 65 bytes and it should be + // starting with 0x04. + if (readerPublicKey_.size() != 65 || readerPublicKey_[0] != 0x04) { + return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( + IIdentityCredentialStore::STATUS_FAILED, + "Reader public key is not in expected format")); + } + vector<uint8_t> pubKeyP256(readerPublicKey_.begin() + 1, readerPublicKey_.end()); + if (!hwProxy_->calcMacKey(sessionTranscript_, pubKeyP256, signingKeyBlob, docType_, + numNamespacesWithValues, expectedDeviceNameSpacesSize_)) { + return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( + IIdentityCredentialStore::STATUS_FAILED, "Error starting retrieving entries")); + } + } numStartRetrievalCalls_ += 1; return ndk::ScopedAStatus::ok(); @@ -520,7 +526,7 @@ size_t cborNumBytesForTstr(const string& value) { return 1 + cborNumBytesForLength(value.size()) + value.size(); } -size_t IdentityCredential::calcDeviceNameSpacesSize() { +void IdentityCredential::calcDeviceNameSpacesSize(uint32_t accessControlProfileMask) { /* * This is how DeviceNameSpaces is defined: * @@ -539,7 +545,7 @@ size_t IdentityCredential::calcDeviceNameSpacesSize() { * encoded. */ size_t ret = 0; - size_t numNamespacesWithValues = 0; + vector<unsigned int> numEntriesPerNamespace; for (const RequestNamespace& rns : requestNamespaces_) { vector<RequestDataItem> itemsToInclude; @@ -562,13 +568,9 @@ size_t IdentityCredential::calcDeviceNameSpacesSize() { // bool authorized = false; for (auto id : rdi.accessControlProfileIds) { - auto it = profileIdToAccessCheckResult_.find(id); - if (it != profileIdToAccessCheckResult_.end()) { - int accessControlForProfile = it->second; - if (accessControlForProfile == IIdentityCredentialStore::STATUS_OK) { - authorized = true; - break; - } + if (accessControlProfileMask & (1 << id)) { + authorized = true; + break; } } if (!authorized) { @@ -578,7 +580,10 @@ size_t IdentityCredential::calcDeviceNameSpacesSize() { itemsToInclude.push_back(rdi); } - // If no entries are to be in the namespace, we don't include it... + numEntriesPerNamespace.push_back(itemsToInclude.size()); + + // If no entries are to be in the namespace, we don't include it in + // the CBOR... if (itemsToInclude.size() == 0) { continue; } @@ -597,15 +602,14 @@ size_t IdentityCredential::calcDeviceNameSpacesSize() { // that. ret += item.size; } - - numNamespacesWithValues++; } - // Now that we now the nunber of namespaces with values, we know how many + // Now that we know the number of namespaces with values, we know how many // bytes the DeviceNamespaces map in the beginning is going to take up. - ret += 1 + cborNumBytesForLength(numNamespacesWithValues); + ret += 1 + cborNumBytesForLength(numEntriesPerNamespace.size()); - return ret; + expectedDeviceNameSpacesSize_ = ret; + expectedNumEntriesPerNamespace_ = numEntriesPerNamespace; } ndk::ScopedAStatus IdentityCredential::startRetrieveEntryValue( @@ -626,9 +630,11 @@ ndk::ScopedAStatus IdentityCredential::startRetrieveEntryValue( "No more name spaces left to go through")); } + bool newNamespace; if (currentNameSpace_ == "") { // First call. currentNameSpace_ = nameSpace; + newNamespace = true; } if (nameSpace == currentNameSpace_) { @@ -655,6 +661,7 @@ ndk::ScopedAStatus IdentityCredential::startRetrieveEntryValue( requestCountsRemaining_.erase(requestCountsRemaining_.begin()); currentNameSpace_ = nameSpace; + newNamespace = true; } // It's permissible to have an empty itemsRequest... but if non-empty you can @@ -674,35 +681,52 @@ ndk::ScopedAStatus IdentityCredential::startRetrieveEntryValue( } } - // Enforce access control. - // - // Access is granted if at least one of the profiles grants access. - // - // If an item is configured without any profiles, access is denied. - // - int accessControl = IIdentityCredentialStore::STATUS_NO_ACCESS_CONTROL_PROFILES; - for (auto id : accessControlProfileIds) { - auto search = profileIdToAccessCheckResult_.find(id); - if (search == profileIdToAccessCheckResult_.end()) { + unsigned int newNamespaceNumEntries = 0; + if (newNamespace) { + if (expectedNumEntriesPerNamespace_.size() == 0) { return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( IIdentityCredentialStore::STATUS_INVALID_DATA, - "Requested entry with unvalidated profile id")); - } - int accessControlForProfile = search->second; - if (accessControlForProfile == IIdentityCredentialStore::STATUS_OK) { - accessControl = IIdentityCredentialStore::STATUS_OK; - break; + "No more populated name spaces left to go through")); } - accessControl = accessControlForProfile; - } - if (accessControl != IIdentityCredentialStore::STATUS_OK) { - return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( - int(accessControl), "Access control check failed")); + newNamespaceNumEntries = expectedNumEntriesPerNamespace_[0]; + expectedNumEntriesPerNamespace_.erase(expectedNumEntriesPerNamespace_.begin()); } - entryAdditionalData_ = entryCreateAdditionalData(nameSpace, name, accessControlProfileIds); + // Access control is enforced in the secure hardware. + // + // ... except for STATUS_NOT_IN_REQUEST_MESSAGE, that's handled above (TODO: + // consolidate). + // + AccessCheckResult res = hwProxy_->startRetrieveEntryValue( + nameSpace, name, newNamespaceNumEntries, entrySize, accessControlProfileIds); + switch (res) { + case AccessCheckResult::kOk: + /* Do nothing. */ + break; + case AccessCheckResult::kFailed: + return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( + IIdentityCredentialStore::STATUS_FAILED, + "Access control check failed (failed)")); + break; + case AccessCheckResult::kNoAccessControlProfiles: + return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( + IIdentityCredentialStore::STATUS_NO_ACCESS_CONTROL_PROFILES, + "Access control check failed (no access control profiles)")); + break; + case AccessCheckResult::kUserAuthenticationFailed: + return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( + IIdentityCredentialStore::STATUS_USER_AUTHENTICATION_FAILED, + "Access control check failed (user auth)")); + break; + case AccessCheckResult::kReaderAuthenticationFailed: + return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( + IIdentityCredentialStore::STATUS_READER_AUTHENTICATION_FAILED, + "Access control check failed (reader auth)")); + break; + } currentName_ = name; + currentAccessControlProfileIds_ = accessControlProfileIds; entryRemainingBytes_ = entrySize; entryValue_.resize(0); @@ -711,8 +735,8 @@ ndk::ScopedAStatus IdentityCredential::startRetrieveEntryValue( ndk::ScopedAStatus IdentityCredential::retrieveEntryValue(const vector<uint8_t>& encryptedContent, vector<uint8_t>* outContent) { - optional<vector<uint8_t>> content = - support::decryptAes128Gcm(storageKey_, encryptedContent, entryAdditionalData_); + optional<vector<uint8_t>> content = hwProxy_->retrieveEntryValue( + encryptedContent, currentNameSpace_, currentName_, currentAccessControlProfileIds_); if (!content) { return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( IIdentityCredentialStore::STATUS_INVALID_DATA, "Error decrypting data")); @@ -777,28 +801,14 @@ ndk::ScopedAStatus IdentityCredential::finishRetrieval(vector<uint8_t>* outMac, optional<vector<uint8_t>> mac; if (signingKeyBlob_.size() > 0 && sessionTranscript_.size() > 0 && readerPublicKey_.size() > 0) { - vector<uint8_t> docTypeAsBlob(docType_.begin(), docType_.end()); - optional<vector<uint8_t>> signingKey = - support::decryptAes128Gcm(storageKey_, signingKeyBlob_, docTypeAsBlob); - if (!signingKey) { + optional<vector<uint8_t>> digestToBeMaced = hwProxy_->finishRetrieval(); + if (!digestToBeMaced || digestToBeMaced.value().size() != 32) { return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( IIdentityCredentialStore::STATUS_INVALID_DATA, - "Error decrypting signingKeyBlob")); - } - - vector<uint8_t> sessionTranscriptBytes = cppbor::Semantic(24, sessionTranscript_).encode(); - optional<vector<uint8_t>> eMacKey = - support::calcEMacKey(signingKey.value(), readerPublicKey_, sessionTranscriptBytes); - if (!eMacKey) { - return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( - IIdentityCredentialStore::STATUS_FAILED, "Error calculating EMacKey")); - } - mac = support::calcMac(sessionTranscript_, docType_, encodedDeviceNameSpaces, - eMacKey.value()); - if (!mac) { - return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( - IIdentityCredentialStore::STATUS_FAILED, "Error MACing data")); + "Error generating digestToBeMaced")); } + // Now construct COSE_Mac0 from the returned MAC... + mac = support::coseMacWithDigest(digestToBeMaced.value(), {} /* data */); } *outMac = mac.value_or(vector<uint8_t>({})); @@ -808,56 +818,18 @@ ndk::ScopedAStatus IdentityCredential::finishRetrieval(vector<uint8_t>* outMac, ndk::ScopedAStatus IdentityCredential::generateSigningKeyPair( vector<uint8_t>* outSigningKeyBlob, Certificate* outSigningKeyCertificate) { - string serialDecimal = "1"; - string issuer = "Android Identity Credential Key"; - string subject = "Android Identity Credential Authentication Key"; - time_t validityNotBefore = time(nullptr); - time_t validityNotAfter = validityNotBefore + 365 * 24 * 3600; - - optional<vector<uint8_t>> signingKeyPKCS8 = support::createEcKeyPair(); - if (!signingKeyPKCS8) { - return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( - IIdentityCredentialStore::STATUS_FAILED, "Error creating signingKey")); - } - - optional<vector<uint8_t>> signingPublicKey = - support::ecKeyPairGetPublicKey(signingKeyPKCS8.value()); - if (!signingPublicKey) { - return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( - IIdentityCredentialStore::STATUS_FAILED, - "Error getting public part of signingKey")); - } - - optional<vector<uint8_t>> signingKey = support::ecKeyPairGetPrivateKey(signingKeyPKCS8.value()); - if (!signingKey) { - return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( - IIdentityCredentialStore::STATUS_FAILED, - "Error getting private part of signingKey")); - } - - optional<vector<uint8_t>> certificate = support::ecPublicKeyGenerateCertificate( - signingPublicKey.value(), credentialPrivKey_, serialDecimal, issuer, subject, - validityNotBefore, validityNotAfter); - if (!certificate) { + time_t now = time(NULL); + optional<pair<vector<uint8_t>, vector<uint8_t>>> pair = + hwProxy_->generateSigningKeyPair(docType_, now); + if (!pair) { return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( IIdentityCredentialStore::STATUS_FAILED, "Error creating signingKey")); } - optional<vector<uint8_t>> nonce = support::getRandom(12); - if (!nonce) { - return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( - IIdentityCredentialStore::STATUS_FAILED, "Error getting random")); - } - vector<uint8_t> docTypeAsBlob(docType_.begin(), docType_.end()); - optional<vector<uint8_t>> encryptedSigningKey = support::encryptAes128Gcm( - storageKey_, nonce.value(), signingKey.value(), docTypeAsBlob); - if (!encryptedSigningKey) { - return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( - IIdentityCredentialStore::STATUS_FAILED, "Error encrypting signingKey")); - } - *outSigningKeyBlob = encryptedSigningKey.value(); *outSigningKeyCertificate = Certificate(); - outSigningKeyCertificate->encodedCertificate = certificate.value(); + outSigningKeyCertificate->encodedCertificate = pair->first; + + *outSigningKeyBlob = pair->second; return ndk::ScopedAStatus::ok(); } diff --git a/identity/aidl/default/IdentityCredential.h b/identity/aidl/default/common/IdentityCredential.h index a8a6409ca0..228182160a 100644 --- a/identity/aidl/default/IdentityCredential.h +++ b/identity/aidl/default/common/IdentityCredential.h @@ -29,10 +29,15 @@ #include <cppbor/cppbor.h> +#include "IdentityCredentialStore.h" +#include "SecureHardwareProxy.h" + namespace aidl::android::hardware::identity { using ::aidl::android::hardware::keymaster::HardwareAuthToken; using ::aidl::android::hardware::keymaster::VerificationToken; +using ::android::sp; +using ::android::hardware::identity::SecureHardwarePresentationProxy; using ::std::map; using ::std::set; using ::std::string; @@ -40,10 +45,11 @@ using ::std::vector; class IdentityCredential : public BnIdentityCredential { public: - IdentityCredential(const vector<uint8_t>& credentialData) - : credentialData_(credentialData), + IdentityCredential(sp<SecureHardwarePresentationProxy> hwProxy, + const vector<uint8_t>& credentialData) + : hwProxy_(hwProxy), + credentialData_(credentialData), numStartRetrievalCalls_(0), - authChallenge_(0), expectedDeviceNameSpacesSize_(0) {} // Parses and decrypts credentialData_, return a status code from @@ -75,14 +81,13 @@ class IdentityCredential : public BnIdentityCredential { private: // Set by constructor + sp<SecureHardwarePresentationProxy> hwProxy_; vector<uint8_t> credentialData_; int numStartRetrievalCalls_; // Set by initialize() string docType_; bool testCredential_; - vector<uint8_t> storageKey_; - vector<uint8_t> credentialPrivKey_; // Set by createEphemeralKeyPair() vector<uint8_t> ephemeralPublicKey_; @@ -90,9 +95,6 @@ class IdentityCredential : public BnIdentityCredential { // Set by setReaderEphemeralPublicKey() vector<uint8_t> readerPublicKey_; - // Set by createAuthChallenge() - uint64_t authChallenge_; - // Set by setRequestedNamespaces() vector<RequestNamespace> requestNamespaces_; @@ -100,7 +102,6 @@ class IdentityCredential : public BnIdentityCredential { VerificationToken verificationToken_; // Set at startRetrieval() time. - map<int32_t, int> profileIdToAccessCheckResult_; vector<uint8_t> signingKeyBlob_; vector<uint8_t> sessionTranscript_; vector<uint8_t> itemsRequest_; @@ -111,15 +112,16 @@ class IdentityCredential : public BnIdentityCredential { // Calculated at startRetrieval() time. size_t expectedDeviceNameSpacesSize_; + vector<unsigned int> expectedNumEntriesPerNamespace_; // Set at startRetrieveEntryValue() time. string currentNameSpace_; string currentName_; + vector<int32_t> currentAccessControlProfileIds_; size_t entryRemainingBytes_; vector<uint8_t> entryValue_; - vector<uint8_t> entryAdditionalData_; - size_t calcDeviceNameSpacesSize(); + void calcDeviceNameSpacesSize(uint32_t accessControlProfileMask); }; } // namespace aidl::android::hardware::identity diff --git a/identity/aidl/default/IdentityCredentialStore.cpp b/identity/aidl/default/common/IdentityCredentialStore.cpp index 30dc6f330b..13f91aacf9 100644 --- a/identity/aidl/default/IdentityCredentialStore.cpp +++ b/identity/aidl/default/common/IdentityCredentialStore.cpp @@ -39,8 +39,9 @@ ndk::ScopedAStatus IdentityCredentialStore::getHardwareInformation( ndk::ScopedAStatus IdentityCredentialStore::createCredential( const string& docType, bool testCredential, shared_ptr<IWritableIdentityCredential>* outWritableCredential) { + sp<SecureHardwareProvisioningProxy> hwProxy = hwProxyFactory_->createProvisioningProxy(); shared_ptr<WritableIdentityCredential> wc = - ndk::SharedRefBase::make<WritableIdentityCredential>(docType, testCredential); + ndk::SharedRefBase::make<WritableIdentityCredential>(hwProxy, docType, testCredential); if (!wc->initialize()) { return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( IIdentityCredentialStore::STATUS_FAILED, @@ -60,8 +61,9 @@ ndk::ScopedAStatus IdentityCredentialStore::getCredential( "Unsupported cipher suite")); } + sp<SecureHardwarePresentationProxy> hwProxy = hwProxyFactory_->createPresentationProxy(); shared_ptr<IdentityCredential> credential = - ndk::SharedRefBase::make<IdentityCredential>(credentialData); + ndk::SharedRefBase::make<IdentityCredential>(hwProxy, credentialData); auto ret = credential->initialize(); if (ret != IIdentityCredentialStore::STATUS_OK) { return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( diff --git a/identity/aidl/default/IdentityCredentialStore.h b/identity/aidl/default/common/IdentityCredentialStore.h index 4f3a42139f..d35e632984 100644 --- a/identity/aidl/default/IdentityCredentialStore.h +++ b/identity/aidl/default/common/IdentityCredentialStore.h @@ -19,15 +19,20 @@ #include <aidl/android/hardware/identity/BnIdentityCredentialStore.h> +#include "SecureHardwareProxy.h" + namespace aidl::android::hardware::identity { +using ::android::sp; +using ::android::hardware::identity::SecureHardwareProxyFactory; using ::std::shared_ptr; using ::std::string; using ::std::vector; class IdentityCredentialStore : public BnIdentityCredentialStore { public: - IdentityCredentialStore() {} + IdentityCredentialStore(sp<SecureHardwareProxyFactory> hwProxyFactory) + : hwProxyFactory_(hwProxyFactory) {} // The GCM chunk size used by this implementation is 64 KiB. static constexpr size_t kGcmChunkSize = 64 * 1024; @@ -41,6 +46,9 @@ class IdentityCredentialStore : public BnIdentityCredentialStore { ndk::ScopedAStatus getCredential(CipherSuite cipherSuite, const vector<uint8_t>& credentialData, shared_ptr<IIdentityCredential>* outCredential) override; + + private: + sp<SecureHardwareProxyFactory> hwProxyFactory_; }; } // namespace aidl::android::hardware::identity diff --git a/identity/aidl/default/common/SecureHardwareProxy.h b/identity/aidl/default/common/SecureHardwareProxy.h new file mode 100644 index 0000000000..b89ad8781f --- /dev/null +++ b/identity/aidl/default/common/SecureHardwareProxy.h @@ -0,0 +1,174 @@ +/* + * Copyright 2020, The Android Open Source Project + * + * 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 ANDROID_HARDWARE_IDENTITY_SECUREHARDWAREPROXY_H +#define ANDROID_HARDWARE_IDENTITY_SECUREHARDWAREPROXY_H + +#include <utils/RefBase.h> +#include <optional> +#include <string> +#include <utility> +#include <vector> + +namespace android::hardware::identity { + +using ::android::RefBase; +using ::std::optional; +using ::std::pair; +using ::std::string; +using ::std::vector; + +// These classes are used to communicate with Secure Hardware. They mimic the +// API in libEmbeddedIC 1:1 (except for using C++ types) as each call is intended +// to be forwarded to the Secure Hardware. +// +// Instances are instantiated when a provisioning or presentation session +// starts. When the session is complete, the shutdown() method is called. +// + +// Forward declare. +// +class SecureHardwareProvisioningProxy; +class SecureHardwarePresentationProxy; + +// This is a class used to create proxies. +// +class SecureHardwareProxyFactory : public RefBase { + public: + SecureHardwareProxyFactory() {} + virtual ~SecureHardwareProxyFactory() {} + + virtual sp<SecureHardwareProvisioningProxy> createProvisioningProxy() = 0; + virtual sp<SecureHardwarePresentationProxy> createPresentationProxy() = 0; +}; + +// The proxy used for provisioning. +// +class SecureHardwareProvisioningProxy : public RefBase { + public: + SecureHardwareProvisioningProxy() {} + virtual ~SecureHardwareProvisioningProxy() {} + + virtual bool initialize(bool testCredential) = 0; + + // Returns public key certificate chain with attestation. + // + // This must return an entire certificate chain and its implementation must + // be coordinated with the implementation of eicOpsCreateCredentialKey() on + // the TA side (which may return just a single certificate or the entire + // chain). + virtual optional<vector<uint8_t>> createCredentialKey(const vector<uint8_t>& challenge, + const vector<uint8_t>& applicationId) = 0; + + virtual bool startPersonalization(int accessControlProfileCount, vector<int> entryCounts, + const string& docType, + size_t expectedProofOfProvisioningSize) = 0; + + // Returns MAC (28 bytes). + virtual optional<vector<uint8_t>> addAccessControlProfile( + int id, const vector<uint8_t>& readerCertificate, bool userAuthenticationRequired, + uint64_t timeoutMillis, uint64_t secureUserId) = 0; + + virtual bool beginAddEntry(const vector<int>& accessControlProfileIds, const string& nameSpace, + const string& name, uint64_t entrySize) = 0; + + // Returns encryptedContent. + virtual optional<vector<uint8_t>> addEntryValue(const vector<int>& accessControlProfileIds, + const string& nameSpace, const string& name, + const vector<uint8_t>& content) = 0; + + // Returns signatureOfToBeSigned (EIC_ECDSA_P256_SIGNATURE_SIZE bytes). + virtual optional<vector<uint8_t>> finishAddingEntries() = 0; + + // Returns encryptedCredentialKeys (80 bytes). + virtual optional<vector<uint8_t>> finishGetCredentialData(const string& docType) = 0; + + virtual bool shutdown() = 0; +}; + +enum AccessCheckResult { + kOk, + kFailed, + kNoAccessControlProfiles, + kUserAuthenticationFailed, + kReaderAuthenticationFailed, +}; + +// The proxy used for presentation. +// +class SecureHardwarePresentationProxy : public RefBase { + public: + SecureHardwarePresentationProxy() {} + virtual ~SecureHardwarePresentationProxy() {} + + virtual bool initialize(bool testCredential, string docType, + vector<uint8_t> encryptedCredentialKeys) = 0; + + // Returns publicKeyCert (1st component) and signingKeyBlob (2nd component) + virtual optional<pair<vector<uint8_t>, vector<uint8_t>>> generateSigningKeyPair(string docType, + time_t now) = 0; + + // Returns private key + virtual optional<vector<uint8_t>> createEphemeralKeyPair() = 0; + + virtual optional<uint64_t> createAuthChallenge() = 0; + + virtual bool startRetrieveEntries() = 0; + + virtual bool setAuthToken(uint64_t challenge, uint64_t secureUserId, uint64_t authenticatorId, + int hardwareAuthenticatorType, uint64_t timeStamp, + const vector<uint8_t>& mac, uint64_t verificationTokenChallenge, + uint64_t verificationTokenTimestamp, + int verificationTokenSecurityLevel, + const vector<uint8_t>& verificationTokenMac) = 0; + + virtual bool pushReaderCert(const vector<uint8_t>& certX509) = 0; + + virtual optional<bool> validateAccessControlProfile(int id, + const vector<uint8_t>& readerCertificate, + bool userAuthenticationRequired, + int timeoutMillis, uint64_t secureUserId, + const vector<uint8_t>& mac) = 0; + + virtual bool validateRequestMessage(const vector<uint8_t>& sessionTranscript, + const vector<uint8_t>& requestMessage, int coseSignAlg, + const vector<uint8_t>& readerSignatureOfToBeSigned) = 0; + + virtual bool calcMacKey(const vector<uint8_t>& sessionTranscript, + const vector<uint8_t>& readerEphemeralPublicKey, + const vector<uint8_t>& signingKeyBlob, const string& docType, + unsigned int numNamespacesWithValues, + size_t expectedProofOfProvisioningSize) = 0; + + virtual AccessCheckResult startRetrieveEntryValue( + const string& nameSpace, const string& name, unsigned int newNamespaceNumEntries, + int32_t entrySize, const vector<int32_t>& accessControlProfileIds) = 0; + + virtual optional<vector<uint8_t>> retrieveEntryValue( + const vector<uint8_t>& encryptedContent, const string& nameSpace, const string& name, + const vector<int32_t>& accessControlProfileIds) = 0; + + virtual optional<vector<uint8_t>> finishRetrieval(); + + virtual optional<vector<uint8_t>> deleteCredential(const string& docType, + size_t proofOfDeletionCborSize) = 0; + + virtual bool shutdown() = 0; +}; + +} // namespace android::hardware::identity + +#endif // ANDROID_HARDWARE_IDENTITY_SECUREHARDWAREPROXY_H diff --git a/identity/aidl/default/WritableIdentityCredential.cpp b/identity/aidl/default/common/WritableIdentityCredential.cpp index 141b4deaef..1328f3629e 100644 --- a/identity/aidl/default/WritableIdentityCredential.cpp +++ b/identity/aidl/default/common/WritableIdentityCredential.cpp @@ -17,7 +17,6 @@ #define LOG_TAG "WritableIdentityCredential" #include "WritableIdentityCredential.h" -#include "IdentityCredentialStore.h" #include <android/hardware/identity/support/IdentityCredentialSupport.h> @@ -30,8 +29,8 @@ #include <utility> #include "IdentityCredentialStore.h" -#include "Util.h" -#include "WritableIdentityCredential.h" + +#include "FakeSecureHardwareProxy.h" namespace aidl::android::hardware::identity { @@ -40,74 +39,55 @@ using ::std::optional; using namespace ::android::hardware::identity; bool WritableIdentityCredential::initialize() { - optional<vector<uint8_t>> random = support::getRandom(16); - if (!random) { - LOG(ERROR) << "Error creating storageKey"; + if (!hwProxy_->initialize(testCredential_)) { + LOG(ERROR) << "hwProxy->initialize failed"; return false; } - storageKey_ = random.value(); startPersonalizationCalled_ = false; firstEntry_ = true; return true; } -// This function generates the attestation certificate using the passed in -// |attestationApplicationId| and |attestationChallenge|. It will generate an -// attestation certificate with current time and expires one year from now. The -// certificate shall contain all values as specified in hal. +WritableIdentityCredential::~WritableIdentityCredential() {} + ndk::ScopedAStatus WritableIdentityCredential::getAttestationCertificate( - const vector<uint8_t>& attestationApplicationId, // - const vector<uint8_t>& attestationChallenge, // - vector<Certificate>* outCertificateChain) { - if (!credentialPrivKey_.empty() || !credentialPubKey_.empty() || !certificateChain_.empty()) { + const vector<uint8_t>& attestationApplicationId, + const vector<uint8_t>& attestationChallenge, vector<Certificate>* outCertificateChain) { + if (getAttestationCertificateAlreadyCalled_) { return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( IIdentityCredentialStore::STATUS_FAILED, "Error attestation certificate previously generated")); } + getAttestationCertificateAlreadyCalled_ = true; + if (attestationChallenge.empty()) { return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( IIdentityCredentialStore::STATUS_INVALID_DATA, "Challenge can not be empty")); } - vector<uint8_t> challenge(attestationChallenge.begin(), attestationChallenge.end()); - vector<uint8_t> appId(attestationApplicationId.begin(), attestationApplicationId.end()); - - optional<std::pair<vector<uint8_t>, vector<vector<uint8_t>>>> keyAttestationPair = - support::createEcKeyPairAndAttestation(challenge, appId, testCredential_); - if (!keyAttestationPair) { - LOG(ERROR) << "Error creating credentialKey and attestation"; - return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( - IIdentityCredentialStore::STATUS_FAILED, - "Error creating credentialKey and attestation")); - } - - vector<uint8_t> keyPair = keyAttestationPair.value().first; - certificateChain_ = keyAttestationPair.value().second; - - optional<vector<uint8_t>> pubKey = support::ecKeyPairGetPublicKey(keyPair); - if (!pubKey) { + optional<vector<uint8_t>> certChain = + hwProxy_->createCredentialKey(attestationChallenge, attestationApplicationId); + if (!certChain) { return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( IIdentityCredentialStore::STATUS_FAILED, - "Error getting public part of credentialKey")); + "Error generating attestation certificate chain")); } - credentialPubKey_ = pubKey.value(); - optional<vector<uint8_t>> privKey = support::ecKeyPairGetPrivateKey(keyPair); - if (!privKey) { + optional<vector<vector<uint8_t>>> certs = support::certificateChainSplit(certChain.value()); + if (!certs) { return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( IIdentityCredentialStore::STATUS_FAILED, - "Error getting private part of credentialKey")); + "Error splitting chain into separate certificates")); } - credentialPrivKey_ = privKey.value(); - // convert from vector<vector<uint8_t>>> to vector<Certificate>* *outCertificateChain = vector<Certificate>(); - for (const vector<uint8_t>& cert : certificateChain_) { + for (const vector<uint8_t>& cert : certs.value()) { Certificate c = Certificate(); c.encodedCertificate = cert; outCertificateChain->push_back(std::move(c)); } + return ndk::ScopedAStatus::ok(); } @@ -123,8 +103,8 @@ ndk::ScopedAStatus WritableIdentityCredential::startPersonalization( return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( IIdentityCredentialStore::STATUS_FAILED, "startPersonalization called already")); } - startPersonalizationCalled_ = true; + numAccessControlProfileRemaining_ = accessControlProfileCount; remainingEntryCounts_ = entryCounts; entryNameSpace_ = ""; @@ -133,6 +113,12 @@ ndk::ScopedAStatus WritableIdentityCredential::startPersonalization( signedDataNamespaces_ = cppbor::Map(); signedDataCurrentNamespace_ = cppbor::Array(); + if (!hwProxy_->startPersonalization(accessControlProfileCount, entryCounts, docType_, + expectedProofOfProvisioningSize_)) { + return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( + IIdentityCredentialStore::STATUS_FAILED, "eicStartPersonalization")); + } + return ndk::ScopedAStatus::ok(); } @@ -140,8 +126,6 @@ ndk::ScopedAStatus WritableIdentityCredential::addAccessControlProfile( int32_t id, const Certificate& readerCertificate, bool userAuthenticationRequired, int64_t timeoutMillis, int64_t secureUserId, SecureAccessControlProfile* outSecureAccessControlProfile) { - SecureAccessControlProfile profile; - if (numAccessControlProfileRemaining_ == 0) { return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( IIdentityCredentialStore::STATUS_INVALID_DATA, @@ -169,25 +153,21 @@ ndk::ScopedAStatus WritableIdentityCredential::addAccessControlProfile( "userAuthenticationRequired is false but timeout is non-zero")); } - // If |userAuthenticationRequired| is true, then |secureUserId| must be non-zero. - if (userAuthenticationRequired && secureUserId == 0) { + optional<vector<uint8_t>> mac = hwProxy_->addAccessControlProfile( + id, readerCertificate.encodedCertificate, userAuthenticationRequired, timeoutMillis, + secureUserId); + if (!mac) { return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( - IIdentityCredentialStore::STATUS_INVALID_DATA, - "userAuthenticationRequired is true but secureUserId is zero")); + IIdentityCredentialStore::STATUS_FAILED, "eicAddAccessControlProfile")); } + SecureAccessControlProfile profile; profile.id = id; profile.readerCertificate = readerCertificate; profile.userAuthenticationRequired = userAuthenticationRequired; profile.timeoutMillis = timeoutMillis; profile.secureUserId = secureUserId; - optional<vector<uint8_t>> mac = secureAccessControlProfileCalcMac(profile, storageKey_); - if (!mac) { - return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( - IIdentityCredentialStore::STATUS_FAILED, "Error calculating MAC for profile")); - } profile.mac = mac.value(); - cppbor::Map profileMap; profileMap.add("id", profile.id); if (profile.readerCertificate.encodedCertificate.size() > 0) { @@ -261,14 +241,18 @@ ndk::ScopedAStatus WritableIdentityCredential::beginAddEntry( remainingEntryCounts_[0] -= 1; } - entryAdditionalData_ = entryCreateAdditionalData(nameSpace, name, accessControlProfileIds); - entryRemainingBytes_ = entrySize; entryNameSpace_ = nameSpace; entryName_ = name; entryAccessControlProfileIds_ = accessControlProfileIds; entryBytes_.resize(0); // LOG(INFO) << "name=" << name << " entrySize=" << entrySize; + + if (!hwProxy_->beginAddEntry(accessControlProfileIds, nameSpace, name, entrySize)) { + return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( + IIdentityCredentialStore::STATUS_FAILED, "eicBeginAddEntry")); + } + return ndk::ScopedAStatus::ok(); } @@ -297,16 +281,11 @@ ndk::ScopedAStatus WritableIdentityCredential::addEntryValue(const vector<uint8_ } } - optional<vector<uint8_t>> nonce = support::getRandom(12); - if (!nonce) { - return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( - IIdentityCredentialStore::STATUS_FAILED, "Error getting nonce")); - } - optional<vector<uint8_t>> encryptedContent = - support::encryptAes128Gcm(storageKey_, nonce.value(), content, entryAdditionalData_); + optional<vector<uint8_t>> encryptedContent = hwProxy_->addEntryValue( + entryAccessControlProfileIds_, entryNameSpace_, entryName_, content); if (!encryptedContent) { return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( - IIdentityCredentialStore::STATUS_FAILED, "Error encrypting content")); + IIdentityCredentialStore::STATUS_FAILED, "eicAddEntryValue")); } if (entryRemainingBytes_ == 0) { @@ -332,50 +311,6 @@ ndk::ScopedAStatus WritableIdentityCredential::addEntryValue(const vector<uint8_ return ndk::ScopedAStatus::ok(); } -// Writes CBOR-encoded structure to |credentialKeys| containing |storageKey| and -// |credentialPrivKey|. -static bool generateCredentialKeys(const vector<uint8_t>& storageKey, - const vector<uint8_t>& credentialPrivKey, - vector<uint8_t>& credentialKeys) { - if (storageKey.size() != 16) { - LOG(ERROR) << "Size of storageKey is not 16"; - return false; - } - - cppbor::Array array; - array.add(cppbor::Bstr(storageKey)); - array.add(cppbor::Bstr(credentialPrivKey)); - credentialKeys = array.encode(); - return true; -} - -// Writes CBOR-encoded structure to |credentialData| containing |docType|, -// |testCredential| and |credentialKeys|. The latter element will be stored in -// encrypted form, using |hardwareBoundKey| as the encryption key. -bool generateCredentialData(const vector<uint8_t>& hardwareBoundKey, const string& docType, - bool testCredential, const vector<uint8_t>& credentialKeys, - vector<uint8_t>& credentialData) { - optional<vector<uint8_t>> nonce = support::getRandom(12); - if (!nonce) { - LOG(ERROR) << "Error getting random"; - return false; - } - vector<uint8_t> docTypeAsVec(docType.begin(), docType.end()); - optional<vector<uint8_t>> credentialBlob = support::encryptAes128Gcm( - hardwareBoundKey, nonce.value(), credentialKeys, docTypeAsVec); - if (!credentialBlob) { - LOG(ERROR) << "Error encrypting CredentialKeys blob"; - return false; - } - - cppbor::Array array; - array.add(docType); - array.add(testCredential); - array.add(cppbor::Bstr(credentialBlob.value())); - credentialData = array.encode(); - return true; -} - ndk::ScopedAStatus WritableIdentityCredential::finishAddingEntries( vector<uint8_t>* outCredentialData, vector<uint8_t>* outProofOfProvisioningSignature) { if (numAccessControlProfileRemaining_ != 0) { @@ -411,31 +346,37 @@ ndk::ScopedAStatus WritableIdentityCredential::finishAddingEntries( .c_str())); } - optional<vector<uint8_t>> signature = support::coseSignEcDsa(credentialPrivKey_, - encodedCbor, // payload - {}, // additionalData - {}); // certificateChain - if (!signature) { + optional<vector<uint8_t>> signatureOfToBeSigned = hwProxy_->finishAddingEntries(); + if (!signatureOfToBeSigned) { return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( - IIdentityCredentialStore::STATUS_FAILED, "Error signing data")); + IIdentityCredentialStore::STATUS_FAILED, "eicFinishAddingEntries")); } - vector<uint8_t> credentialKeys; - if (!generateCredentialKeys(storageKey_, credentialPrivKey_, credentialKeys)) { + optional<vector<uint8_t>> signature = + support::coseSignEcDsaWithSignature(signatureOfToBeSigned.value(), + encodedCbor, // data + {}); // certificateChain + if (!signature) { return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( - IIdentityCredentialStore::STATUS_FAILED, "Error generating CredentialKeys")); + IIdentityCredentialStore::STATUS_FAILED, "Error signing data")); } - vector<uint8_t> credentialData; - if (!generateCredentialData( - testCredential_ ? support::getTestHardwareBoundKey() : getHardwareBoundKey(), - docType_, testCredential_, credentialKeys, credentialData)) { + optional<vector<uint8_t>> encryptedCredentialKeys = hwProxy_->finishGetCredentialData(docType_); + if (!encryptedCredentialKeys) { return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( - IIdentityCredentialStore::STATUS_FAILED, "Error generating CredentialData")); + IIdentityCredentialStore::STATUS_FAILED, + "Error generating encrypted CredentialKeys")); } + cppbor::Array array; + array.add(docType_); + array.add(testCredential_); + array.add(encryptedCredentialKeys.value()); + vector<uint8_t> credentialData = array.encode(); *outCredentialData = credentialData; *outProofOfProvisioningSignature = signature.value(); + hwProxy_->shutdown(); + return ndk::ScopedAStatus::ok(); } diff --git a/identity/aidl/default/WritableIdentityCredential.h b/identity/aidl/default/common/WritableIdentityCredential.h index 56458520c1..c6f0628cae 100644 --- a/identity/aidl/default/WritableIdentityCredential.h +++ b/identity/aidl/default/common/WritableIdentityCredential.h @@ -23,16 +23,24 @@ #include <cppbor.h> #include <set> +#include "IdentityCredentialStore.h" +#include "SecureHardwareProxy.h" + namespace aidl::android::hardware::identity { +using ::android::sp; +using ::android::hardware::identity::SecureHardwareProvisioningProxy; using ::std::set; using ::std::string; using ::std::vector; class WritableIdentityCredential : public BnWritableIdentityCredential { public: - WritableIdentityCredential(const string& docType, bool testCredential) - : docType_(docType), testCredential_(testCredential) {} + WritableIdentityCredential(sp<SecureHardwareProvisioningProxy> hwProxy, const string& docType, + bool testCredential) + : hwProxy_(hwProxy), docType_(docType), testCredential_(testCredential) {} + + ~WritableIdentityCredential(); // Creates the Credential Key. Returns false on failure. Must be called // right after construction. @@ -57,7 +65,6 @@ class WritableIdentityCredential : public BnWritableIdentityCredential { ndk::ScopedAStatus beginAddEntry(const vector<int32_t>& accessControlProfileIds, const string& nameSpace, const string& name, int32_t entrySize) override; - ndk::ScopedAStatus addEntryValue(const vector<uint8_t>& content, vector<uint8_t>* outEncryptedContent) override; @@ -66,18 +73,17 @@ class WritableIdentityCredential : public BnWritableIdentityCredential { vector<uint8_t>* outProofOfProvisioningSignature) override; private: + // Set by constructor. + sp<SecureHardwareProvisioningProxy> hwProxy_; string docType_; bool testCredential_; // This is set in initialize(). - vector<uint8_t> storageKey_; bool startPersonalizationCalled_; bool firstEntry_; - // These are set in getAttestationCertificate(). - vector<uint8_t> credentialPrivKey_; - vector<uint8_t> credentialPubKey_; - vector<vector<uint8_t>> certificateChain_; + // This is set in getAttestationCertificate(). + bool getAttestationCertificateAlreadyCalled_ = false; // These fields are initialized during startPersonalization() size_t numAccessControlProfileRemaining_; @@ -92,7 +98,6 @@ class WritableIdentityCredential : public BnWritableIdentityCredential { // These fields are initialized during beginAddEntry() size_t entryRemainingBytes_; - vector<uint8_t> entryAdditionalData_; string entryNameSpace_; string entryName_; vector<int32_t> entryAccessControlProfileIds_; diff --git a/identity/aidl/default/identity-default.xml b/identity/aidl/default/identity-default.xml index a47d354ce5..37d5b81c21 100644 --- a/identity/aidl/default/identity-default.xml +++ b/identity/aidl/default/identity-default.xml @@ -1,6 +1,7 @@ <manifest version="1.0" type="device"> <hal format="aidl"> <name>android.hardware.identity</name> + <version>2</version> <interface> <name>IIdentityCredentialStore</name> <instance>default</instance> diff --git a/identity/aidl/default/libeic/EicCbor.c b/identity/aidl/default/libeic/EicCbor.c new file mode 100644 index 0000000000..ec049b1c0d --- /dev/null +++ b/identity/aidl/default/libeic/EicCbor.c @@ -0,0 +1,236 @@ +/* + * Copyright 2020, The Android Open Source Project + * + * 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 "EicCbor.h" + +void eicCborInit(EicCbor* cbor, uint8_t* buffer, size_t bufferSize) { + cbor->size = 0; + cbor->bufferSize = bufferSize; + cbor->buffer = buffer; + cbor->digestType = EIC_CBOR_DIGEST_TYPE_SHA256; + eicOpsSha256Init(&cbor->digester.sha256); +} + +void eicCborInitHmacSha256(EicCbor* cbor, uint8_t* buffer, size_t bufferSize, + const uint8_t* hmacKey, size_t hmacKeySize) { + cbor->size = 0; + cbor->bufferSize = bufferSize; + cbor->buffer = buffer; + cbor->digestType = EIC_CBOR_DIGEST_TYPE_HMAC_SHA256; + eicOpsHmacSha256Init(&cbor->digester.hmacSha256, hmacKey, hmacKeySize); +} + +void eicCborFinal(EicCbor* cbor, uint8_t digest[EIC_SHA256_DIGEST_SIZE]) { + switch (cbor->digestType) { + case EIC_CBOR_DIGEST_TYPE_SHA256: + eicOpsSha256Final(&cbor->digester.sha256, digest); + break; + case EIC_CBOR_DIGEST_TYPE_HMAC_SHA256: + eicOpsHmacSha256Final(&cbor->digester.hmacSha256, digest); + break; + } +} + +void eicCborAppend(EicCbor* cbor, const uint8_t* data, size_t size) { + switch (cbor->digestType) { + case EIC_CBOR_DIGEST_TYPE_SHA256: + eicOpsSha256Update(&cbor->digester.sha256, data, size); + break; + case EIC_CBOR_DIGEST_TYPE_HMAC_SHA256: + eicOpsHmacSha256Update(&cbor->digester.hmacSha256, data, size); + break; + } + + if (cbor->size >= cbor->bufferSize) { + cbor->size += size; + return; + } + + size_t numBytesLeft = cbor->bufferSize - cbor->size; + size_t numBytesToCopy = size; + if (numBytesToCopy > numBytesLeft) { + numBytesToCopy = numBytesLeft; + } + eicMemCpy(cbor->buffer + cbor->size, data, numBytesToCopy); + + cbor->size += size; +} + +size_t eicCborAdditionalLengthBytesFor(size_t size) { + if (size < 24) { + return 0; + } else if (size <= 0xff) { + return 1; + } else if (size <= 0xffff) { + return 2; + } else if (size <= 0xffffffff) { + return 4; + } + return 8; +} + +void eicCborBegin(EicCbor* cbor, int majorType, size_t size) { + uint8_t data[9]; + + if (size < 24) { + data[0] = (majorType << 5) | size; + eicCborAppend(cbor, data, 1); + } else if (size <= 0xff) { + data[0] = (majorType << 5) | 24; + data[1] = size; + eicCborAppend(cbor, data, 2); + } else if (size <= 0xffff) { + data[0] = (majorType << 5) | 25; + data[1] = size >> 8; + data[2] = size & 0xff; + eicCborAppend(cbor, data, 3); + } else if (size <= 0xffffffff) { + data[0] = (majorType << 5) | 26; + data[1] = (size >> 24) & 0xff; + data[2] = (size >> 16) & 0xff; + data[3] = (size >> 8) & 0xff; + data[4] = size & 0xff; + eicCborAppend(cbor, data, 5); + } else { + data[0] = (majorType << 5) | 24; + data[1] = (((uint64_t)size) >> 56) & 0xff; + data[2] = (((uint64_t)size) >> 48) & 0xff; + data[3] = (((uint64_t)size) >> 40) & 0xff; + data[4] = (((uint64_t)size) >> 32) & 0xff; + data[5] = (((uint64_t)size) >> 24) & 0xff; + data[6] = (((uint64_t)size) >> 16) & 0xff; + data[7] = (((uint64_t)size) >> 8) & 0xff; + data[8] = ((uint64_t)size) & 0xff; + eicCborAppend(cbor, data, 9); + } +} + +void eicCborAppendByteString(EicCbor* cbor, const uint8_t* data, size_t dataSize) { + eicCborBegin(cbor, EIC_CBOR_MAJOR_TYPE_BYTE_STRING, dataSize); + eicCborAppend(cbor, data, dataSize); +} + +void eicCborAppendString(EicCbor* cbor, const char* str) { + size_t length = eicStrLen(str); + eicCborBegin(cbor, EIC_CBOR_MAJOR_TYPE_STRING, length); + eicCborAppend(cbor, (const uint8_t*)str, length); +} + +void eicCborAppendSimple(EicCbor* cbor, uint8_t simpleValue) { + eicCborBegin(cbor, EIC_CBOR_MAJOR_TYPE_SIMPLE, simpleValue); +} + +void eicCborAppendBool(EicCbor* cbor, bool value) { + uint8_t simpleValue = value ? EIC_CBOR_SIMPLE_VALUE_TRUE : EIC_CBOR_SIMPLE_VALUE_FALSE; + eicCborAppendSimple(cbor, simpleValue); +} + +void eicCborAppendSemantic(EicCbor* cbor, uint64_t value) { + size_t encoded = value; + eicCborBegin(cbor, EIC_CBOR_MAJOR_TYPE_SEMANTIC, encoded); +} + +void eicCborAppendUnsigned(EicCbor* cbor, uint64_t value) { + size_t encoded = value; + eicCborBegin(cbor, EIC_CBOR_MAJOR_TYPE_UNSIGNED, encoded); +} + +void eicCborAppendNumber(EicCbor* cbor, int64_t value) { + if (value < 0) { + size_t encoded = -1 - value; + eicCborBegin(cbor, EIC_CBOR_MAJOR_TYPE_NEGATIVE, encoded); + } else { + eicCborAppendUnsigned(cbor, value); + } +} + +void eicCborAppendArray(EicCbor* cbor, size_t numElements) { + eicCborBegin(cbor, EIC_CBOR_MAJOR_TYPE_ARRAY, numElements); +} + +void eicCborAppendMap(EicCbor* cbor, size_t numPairs) { + eicCborBegin(cbor, EIC_CBOR_MAJOR_TYPE_MAP, numPairs); +} + +bool eicCborCalcAccessControl(EicCbor* cborBuilder, int id, const uint8_t* readerCertificate, + size_t readerCertificateSize, bool userAuthenticationRequired, + uint64_t timeoutMillis, uint64_t secureUserId) { + size_t numPairs = 1; + if (readerCertificateSize > 0) { + numPairs += 1; + } + if (userAuthenticationRequired) { + numPairs += 2; + if (secureUserId > 0) { + numPairs += 1; + } + } + eicCborAppendMap(cborBuilder, numPairs); + eicCborAppendString(cborBuilder, "id"); + eicCborAppendUnsigned(cborBuilder, id); + if (readerCertificateSize > 0) { + eicCborAppendString(cborBuilder, "readerCertificate"); + eicCborAppendByteString(cborBuilder, readerCertificate, readerCertificateSize); + } + if (userAuthenticationRequired) { + eicCborAppendString(cborBuilder, "userAuthenticationRequired"); + eicCborAppendBool(cborBuilder, userAuthenticationRequired); + eicCborAppendString(cborBuilder, "timeoutMillis"); + eicCborAppendUnsigned(cborBuilder, timeoutMillis); + if (secureUserId > 0) { + eicCborAppendString(cborBuilder, "secureUserId"); + eicCborAppendUnsigned(cborBuilder, secureUserId); + } + } + + if (cborBuilder->size > cborBuilder->bufferSize) { + eicDebug("Buffer for ACP CBOR is too small (%zd) - need %zd bytes", cborBuilder->bufferSize, + cborBuilder->size); + return false; + } + + return true; +} + +bool eicCborCalcEntryAdditionalData(const int* accessControlProfileIds, + size_t numAccessControlProfileIds, const char* nameSpace, + const char* name, uint8_t* cborBuffer, size_t cborBufferSize, + size_t* outAdditionalDataCborSize, + uint8_t additionalDataSha256[EIC_SHA256_DIGEST_SIZE]) { + EicCbor cborBuilder; + + eicCborInit(&cborBuilder, cborBuffer, cborBufferSize); + eicCborAppendMap(&cborBuilder, 3); + eicCborAppendString(&cborBuilder, "Namespace"); + eicCborAppendString(&cborBuilder, nameSpace); + eicCborAppendString(&cborBuilder, "Name"); + eicCborAppendString(&cborBuilder, name); + eicCborAppendString(&cborBuilder, "AccessControlProfileIds"); + eicCborAppendArray(&cborBuilder, numAccessControlProfileIds); + for (size_t n = 0; n < numAccessControlProfileIds; n++) { + eicCborAppendNumber(&cborBuilder, accessControlProfileIds[n]); + } + if (cborBuilder.size > cborBufferSize) { + eicDebug("Not enough space for additionalData - buffer is only %zd bytes, content is %zd", + cborBufferSize, cborBuilder.size); + return false; + } + if (outAdditionalDataCborSize != NULL) { + *outAdditionalDataCborSize = cborBuilder.size; + } + eicCborFinal(&cborBuilder, additionalDataSha256); + return true; +} diff --git a/identity/aidl/default/libeic/EicCbor.h b/identity/aidl/default/libeic/EicCbor.h new file mode 100644 index 0000000000..4686b38447 --- /dev/null +++ b/identity/aidl/default/libeic/EicCbor.h @@ -0,0 +1,156 @@ +/* + * Copyright 2020, The Android Open Source Project + * + * 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. + */ + +#if !defined(EIC_INSIDE_LIBEIC_H) && !defined(EIC_COMPILATION) +#error "Never include this file directly, include libeic.h instead." +#endif + +#ifndef ANDROID_HARDWARE_IDENTITY_EIC_CBOR_H +#define ANDROID_HARDWARE_IDENTITY_EIC_CBOR_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "EicOps.h" + +typedef enum { + EIC_CBOR_DIGEST_TYPE_SHA256, + EIC_CBOR_DIGEST_TYPE_HMAC_SHA256, +} EicCborDigestType; + +/* EicCbor is a utility class to build CBOR data structures and calculate + * digests on the fly. + */ +typedef struct { + // Contains the size of the built CBOR, even if it exceeds bufferSize (will + // never write to buffer beyond bufferSize though) + size_t size; + + // The size of the buffer. Is zero if no data is recorded in which case + // only digesting is performed. + size_t bufferSize; + + // Whether we're producing a SHA-256 or HMAC-SHA256 digest. + EicCborDigestType digestType; + + // The SHA-256 digester object. + union { + EicSha256Ctx sha256; + EicHmacSha256Ctx hmacSha256; + } digester; + + // The buffer used for building up CBOR or NULL if bufferSize is 0. + uint8_t* buffer; +} EicCbor; + +/* Initializes an EicCbor. + * + * The given buffer will be used, up to bufferSize. + * + * If bufferSize is 0, buffer may be NULL. + */ +void eicCborInit(EicCbor* cbor, uint8_t* buffer, size_t bufferSize); + +/* Like eicCborInit() but uses HMAC-SHA256 instead of SHA-256. + */ +void eicCborInitHmacSha256(EicCbor* cbor, uint8_t* buffer, size_t bufferSize, + const uint8_t* hmacKey, size_t hmacKeySize); + +/* Finishes building CBOR and returns the digest. */ +void eicCborFinal(EicCbor* cbor, uint8_t digest[EIC_SHA256_DIGEST_SIZE]); + +/* Appends CBOR data to the EicCbor. */ +void eicCborAppend(EicCbor* cbor, const uint8_t* data, size_t size); + +#define EIC_CBOR_MAJOR_TYPE_UNSIGNED 0 +#define EIC_CBOR_MAJOR_TYPE_NEGATIVE 1 +#define EIC_CBOR_MAJOR_TYPE_BYTE_STRING 2 +#define EIC_CBOR_MAJOR_TYPE_STRING 3 +#define EIC_CBOR_MAJOR_TYPE_ARRAY 4 +#define EIC_CBOR_MAJOR_TYPE_MAP 5 +#define EIC_CBOR_MAJOR_TYPE_SEMANTIC 6 +#define EIC_CBOR_MAJOR_TYPE_SIMPLE 7 + +#define EIC_CBOR_SIMPLE_VALUE_FALSE 20 +#define EIC_CBOR_SIMPLE_VALUE_TRUE 21 + +#define EIC_CBOR_SEMANTIC_TAG_ENCODED_CBOR 24 + +/* Begins a new CBOR value. */ +void eicCborBegin(EicCbor* cbor, int majorType, size_t size); + +/* Appends a bytestring. */ +void eicCborAppendByteString(EicCbor* cbor, const uint8_t* data, size_t dataSize); + +/* Appends a NUL-terminated UTF-8 string. */ +void eicCborAppendString(EicCbor* cbor, const char* str); + +/* Appends a simple value. */ +void eicCborAppendSimple(EicCbor* cbor, uint8_t simpleValue); + +/* Appends a boolean. */ +void eicCborAppendBool(EicCbor* cbor, bool value); + +/* Appends a semantic */ +void eicCborAppendSemantic(EicCbor* cbor, uint64_t value); + +/* Appends an unsigned number. */ +void eicCborAppendUnsigned(EicCbor* cbor, uint64_t value); + +/* Appends a number. */ +void eicCborAppendNumber(EicCbor* cbor, int64_t value); + +/* Starts appending an array. + * + * After this numElements CBOR elements must follow. + */ +void eicCborAppendArray(EicCbor* cbor, size_t numElements); + +/* Starts appending a map. + * + * After this numPairs pairs of CBOR elements must follow. + */ +void eicCborAppendMap(EicCbor* cbor, size_t numPairs); + +/* Calculates how many bytes are needed to store a size. */ +size_t eicCborAdditionalLengthBytesFor(size_t size); + +bool eicCborCalcAccessControl(EicCbor* cborBuilder, int id, const uint8_t* readerCertificate, + size_t readerCertificateSize, bool userAuthenticationRequired, + uint64_t timeoutMillis, uint64_t secureUserId); + +bool eicCborCalcEntryAdditionalData(const int* accessControlProfileIds, + size_t numAccessControlProfileIds, const char* nameSpace, + const char* name, uint8_t* cborBuffer, size_t cborBufferSize, + size_t* outAdditionalDataCborSize, + uint8_t additionalDataSha256[EIC_SHA256_DIGEST_SIZE]); + +// The maximum size of an encoded Secure Access Control Profile that we +// support. Since the SACP may contain a reader certificate chain these can get +// pretty big. +// +// Currently we allocate space on the stack for this structure which is why we +// have a maximum size. We can get rid of the maximum size by incrementally +// building/verifying the SACP. TODO: actually do this. +// +#define EIC_MAX_CBOR_SIZE_FOR_ACCESS_CONTROL_PROFILE 512 + +#ifdef __cplusplus +} +#endif + +#endif // ANDROID_HARDWARE_IDENTITY_EIC_CBOR_H diff --git a/identity/aidl/default/libeic/EicOps.h b/identity/aidl/default/libeic/EicOps.h new file mode 100644 index 0000000000..da4dabf879 --- /dev/null +++ b/identity/aidl/default/libeic/EicOps.h @@ -0,0 +1,299 @@ +/* + * Copyright 2020, The Android Open Source Project + * + * 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. + */ + +#if !defined(EIC_INSIDE_LIBEIC_H) && !defined(EIC_COMPILATION) +#error "Never include this file directly, include libeic.h instead." +#endif + +#ifndef ANDROID_HARDWARE_IDENTITY_EIC_OPS_H +#define ANDROID_HARDWARE_IDENTITY_EIC_OPS_H + +#include <stdarg.h> +#include <stdbool.h> +#include <stddef.h> +#include <stdlib.h> + +// Uncomment or define if debug messages are needed. +// +//#define EIC_DEBUG + +#ifdef __cplusplus +extern "C" { +#endif + +// The following defines must be set to something appropriate +// +// EIC_SHA256_CONTEXT_SIZE - the size of EicSha256Ctx +// EIC_HMAC_SHA256_CONTEXT_SIZE - the size of EicHmacSha256Ctx +// +// For example, if EicSha256Ctx is implemented using BoringSSL this would be defined +// as sizeof(SHA256_CTX). +// +// We expect the implementation to provide a header file with the name +// EicOpsImpl.h to do all this. +// +#include "EicOpsImpl.h" + +#define EIC_SHA256_DIGEST_SIZE 32 + +// The size of a P-256 private key. +// +#define EIC_P256_PRIV_KEY_SIZE 32 + +// The size of a P-256 public key in uncompressed form. +// +// The public key is stored in uncompressed form, first the X coordinate, then +// the Y coordinate. +// +#define EIC_P256_PUB_KEY_SIZE 64 + +// Size of one of the coordinates in a curve-point. +// +#define EIC_P256_COORDINATE_SIZE 32 + +// The size of an ECSDA signature using P-256. +// +// The R and S values are stored here, first R then S. +// +#define EIC_ECDSA_P256_SIGNATURE_SIZE 64 + +#define EIC_AES_128_KEY_SIZE 16 + +// The following are definitions of implementation functions the +// underlying platform must provide. +// + +struct EicSha256Ctx { + uint8_t reserved[EIC_SHA256_CONTEXT_SIZE]; +}; +typedef struct EicSha256Ctx EicSha256Ctx; + +struct EicHmacSha256Ctx { + uint8_t reserved[EIC_HMAC_SHA256_CONTEXT_SIZE]; +}; +typedef struct EicHmacSha256Ctx EicHmacSha256Ctx; + +#ifdef EIC_DEBUG +// Debug macro. Don't include a new-line in message. +// +#define eicDebug(...) \ + do { \ + eicPrint("%s:%d: ", __FILE__, __LINE__); \ + eicPrint(__VA_ARGS__); \ + eicPrint("\n"); \ + } while (0) +#else +#define eicDebug(...) \ + do { \ + } while (0) +#endif + +// Prints message which should include new-line character. Can be no-op. +// +// Don't use this from code, use eicDebug() instead. +// +#ifdef EIC_DEBUG +void eicPrint(const char* format, ...); +#else +inline void eicPrint(const char*, ...) {} +#endif + +// Dumps data as pretty-printed hex. Can be no-op. +// +#ifdef EIC_DEBUG +void eicHexdump(const char* message, const uint8_t* data, size_t dataSize); +#else +inline void eicHexdump(const char*, const uint8_t*, size_t) {} +#endif + +// Pretty-prints encoded CBOR. Can be no-op. +// +// If a byte-string is larger than |maxBStrSize| its contents will not be +// printed, instead the value of the form "<bstr size=1099016 +// sha1=ef549cca331f73dfae2090e6a37c04c23f84b07b>" will be printed. Pass zero +// for |maxBStrSize| to disable this. +// +#ifdef EIC_DEBUG +void eicCborPrettyPrint(const uint8_t* cborData, size_t cborDataSize, size_t maxBStrSize); +#else +inline void eicCborPrettyPrint(const uint8_t*, size_t, size_t) {} +#endif + +// Memory setting, see memset(3). +void* eicMemSet(void* s, int c, size_t n); + +// Memory copying, see memcpy(3). +void* eicMemCpy(void* dest, const void* src, size_t n); + +// String length, see strlen(3). +size_t eicStrLen(const char* s); + +// Memory compare, see CRYPTO_memcmp(3SSL) +// +// It takes an amount of time dependent on len, but independent of the contents of the +// memory regions pointed to by s1 and s2. +// +int eicCryptoMemCmp(const void* s1, const void* s2, size_t n); + +// Random number generation. +bool eicOpsRandom(uint8_t* buf, size_t numBytes); + +// If |testCredential| is true, returns the 128-bit AES Hardware-Bound Key (16 bytes). +// +// Otherwise returns all zeroes (16 bytes). +// +const uint8_t* eicOpsGetHardwareBoundKey(bool testCredential); + +// Encrypts |data| with |key| and |additionalAuthenticatedData| using |nonce|, +// returns the resulting (nonce || ciphertext || tag) in |encryptedData| which +// must be of size |dataSize| + 28. +bool eicOpsEncryptAes128Gcm( + const uint8_t* key, // Must be 16 bytes + const uint8_t* nonce, // Must be 12 bytes + const uint8_t* data, // May be NULL if size is 0 + size_t dataSize, + const uint8_t* additionalAuthenticationData, // May be NULL if size is 0 + size_t additionalAuthenticationDataSize, uint8_t* encryptedData); + +// Decrypts |encryptedData| using |key| and |additionalAuthenticatedData|, +// returns resulting plaintext in |data| must be of size |encryptedDataSize| - 28. +// +// The format of |encryptedData| must be as specified in the +// encryptAes128Gcm() function. +bool eicOpsDecryptAes128Gcm(const uint8_t* key, // Must be 16 bytes + const uint8_t* encryptedData, size_t encryptedDataSize, + const uint8_t* additionalAuthenticationData, + size_t additionalAuthenticationDataSize, uint8_t* data); + +// Creates an EC key using the P-256 curve. The private key is written to +// |privateKey|. The public key is written to |publicKey|. +// +bool eicOpsCreateEcKey(uint8_t privateKey[EIC_P256_PRIV_KEY_SIZE], + uint8_t publicKey[EIC_P256_PUB_KEY_SIZE]); + +// Generates CredentialKey plus an attestation certificate. +// +// The attestation certificate will be signed by the attestation keys the secure +// area has been provisioned with. The given |challenge| and |applicationId| +// will be used as will |testCredential|. +// +// The generated certificate will be in X.509 format and returned in |cert| +// and |certSize| must be set to the size of this array and this function will +// set it to the size of the certification chain on successfully return. +// +// This may return either a single certificate or an entire certificate +// chain. If it returns only a single certificate, the implementation of +// SecureHardwareProvisioningProxy::createCredentialKey() should amend the +// remainder of the certificate chain on the HAL side. +// +bool eicOpsCreateCredentialKey(uint8_t privateKey[EIC_P256_PRIV_KEY_SIZE], const uint8_t* challenge, + size_t challengeSize, const uint8_t* applicationId, + size_t applicationIdSize, bool testCredential, uint8_t* cert, + size_t* certSize); // inout + +// Generate an X.509 certificate for the key identified by |publicKey| which +// must be of the form returned by eicOpsCreateEcKey(). +// +// The certificate will be signed by the key identified by |signingKey| which +// must be of the form returned by eicOpsCreateEcKey(). +// +bool eicOpsSignEcKey(const uint8_t publicKey[EIC_P256_PUB_KEY_SIZE], + const uint8_t signingKey[EIC_P256_PRIV_KEY_SIZE], unsigned int serial, + const char* issuerName, const char* subjectName, time_t validityNotBefore, + time_t validityNotAfter, uint8_t* cert, + size_t* certSize); // inout + +// Uses |privateKey| to create an ECDSA signature of some data (the SHA-256 must +// be given by |digestOfData|). Returns the signature in |signature|. +// +bool eicOpsEcDsa(const uint8_t privateKey[EIC_P256_PRIV_KEY_SIZE], + const uint8_t digestOfData[EIC_SHA256_DIGEST_SIZE], + uint8_t signature[EIC_ECDSA_P256_SIGNATURE_SIZE]); + +// Performs Elliptic Curve Diffie-Helman. +// +bool eicOpsEcdh(const uint8_t publicKey[EIC_P256_PUB_KEY_SIZE], + const uint8_t privateKey[EIC_P256_PRIV_KEY_SIZE], + uint8_t sharedSecret[EIC_P256_COORDINATE_SIZE]); + +// Performs HKDF. +// +bool eicOpsHkdf(const uint8_t* sharedSecret, size_t sharedSecretSize, const uint8_t* salt, + size_t saltSize, const uint8_t* info, size_t infoSize, uint8_t* output, + size_t outputSize); + +// SHA-256 functions. +void eicOpsSha256Init(EicSha256Ctx* ctx); +void eicOpsSha256Update(EicSha256Ctx* ctx, const uint8_t* data, size_t len); +void eicOpsSha256Final(EicSha256Ctx* ctx, uint8_t digest[EIC_SHA256_DIGEST_SIZE]); + +// HMAC SHA-256 functions. +void eicOpsHmacSha256Init(EicHmacSha256Ctx* ctx, const uint8_t* key, size_t keySize); +void eicOpsHmacSha256Update(EicHmacSha256Ctx* ctx, const uint8_t* data, size_t len); +void eicOpsHmacSha256Final(EicHmacSha256Ctx* ctx, uint8_t digest[EIC_SHA256_DIGEST_SIZE]); + +// Extracts the public key in the given X.509 certificate. +// +// If the key is not an EC key, this function fails. +// +// Otherwise the public key is stored in uncompressed form in |publicKey| which +// size should be set in |publicKeySize|. On successful return |publicKeySize| +// is set to the length of the key. If there is not enough space, the function +// fails. +// +// (The public key returned is not necessarily a P-256 key, even if it is note +// that its size is not EIC_P256_PUBLIC_KEY_SIZE because of the leading 0x04.) +// +bool eicOpsX509GetPublicKey(const uint8_t* x509Cert, size_t x509CertSize, uint8_t* publicKey, + size_t* publicKeySize); + +// Checks that the X.509 certificate given by |x509Cert| is signed by the public +// key given by |publicKey| which must be an EC key in uncompressed form (e.g. +// same formatt as returned by eicOpsX509GetPublicKey()). +// +bool eicOpsX509CertSignedByPublicKey(const uint8_t* x509Cert, size_t x509CertSize, + const uint8_t* publicKey, size_t publicKeySize); + +// Checks that |signature| is a signature of some data (given by |digest|), +// signed by the public key given by |publicKey|. +// +// The key must be an EC key in uncompressed form (e.g. same format as returned +// by eicOpsX509GetPublicKey()). +// +// The format of the signature is the same encoding as the 'signature' field of +// COSE_Sign1 - that is, it's the R and S integers both with the same length as +// the key-size. +// +// The size of digest must match the size of the key. +// +bool eicOpsEcDsaVerifyWithPublicKey(const uint8_t* digest, size_t digestSize, + const uint8_t* signature, size_t signatureSize, + const uint8_t* publicKey, size_t publicKeySize); + +// Validates that the passed in data constitutes a valid auth- and verification tokens. +// +bool eicOpsValidateAuthToken(uint64_t challenge, uint64_t secureUserId, uint64_t authenticatorId, + int hardwareAuthenticatorType, uint64_t timeStamp, const uint8_t* mac, + size_t macSize, uint64_t verificationTokenChallenge, + uint64_t verificationTokenTimeStamp, + int verificationTokenSecurityLevel, + const uint8_t* verificationTokenMac, size_t verificationTokenMacSize); + +#ifdef __cplusplus +} +#endif + +#endif // ANDROID_HARDWARE_IDENTITY_EIC_OPS_H diff --git a/identity/aidl/default/libeic/EicPresentation.c b/identity/aidl/default/libeic/EicPresentation.c new file mode 100644 index 0000000000..d3f5556f66 --- /dev/null +++ b/identity/aidl/default/libeic/EicPresentation.c @@ -0,0 +1,728 @@ +/* + * Copyright 2020, The Android Open Source Project + * + * 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 "EicPresentation.h" + +#include <inttypes.h> + +bool eicPresentationInit(EicPresentation* ctx, bool testCredential, const char* docType, + const uint8_t encryptedCredentialKeys[80]) { + uint8_t credentialKeys[52]; + + eicMemSet(ctx, '\0', sizeof(EicPresentation)); + + if (!eicOpsDecryptAes128Gcm(eicOpsGetHardwareBoundKey(testCredential), encryptedCredentialKeys, + 80, + // DocType is the additionalAuthenticatedData + (const uint8_t*)docType, eicStrLen(docType), credentialKeys)) { + eicDebug("Error decrypting CredentialKeys"); + return false; + } + + // It's supposed to look like this; + // + // CredentialKeys = [ + // bstr, ; storageKey, a 128-bit AES key + // bstr ; credentialPrivKey, the private key for credentialKey + // ] + // + // where storageKey is 16 bytes and credentialPrivateKey is 32 bytes. + // + // So the first two bytes will be 0x82 0x50 indicating resp. an array of two elements + // and a bstr of 16 elements. Sixteen bytes later (offset 18 and 19) there will be + // a bstr of 32 bytes. It's encoded as two bytes 0x58 and 0x20. + // + if (credentialKeys[0] != 0x82 || credentialKeys[1] != 0x50 || credentialKeys[18] != 0x58 || + credentialKeys[19] != 0x20) { + eicDebug("Invalid CBOR for CredentialKeys"); + return false; + } + eicMemCpy(ctx->storageKey, credentialKeys + 2, EIC_AES_128_KEY_SIZE); + eicMemCpy(ctx->credentialPrivateKey, credentialKeys + 20, EIC_P256_PRIV_KEY_SIZE); + ctx->testCredential = testCredential; + return true; +} + +bool eicPresentationGenerateSigningKeyPair(EicPresentation* ctx, const char* docType, time_t now, + uint8_t* publicKeyCert, size_t* publicKeyCertSize, + uint8_t signingKeyBlob[60]) { + uint8_t signingKeyPriv[EIC_P256_PRIV_KEY_SIZE]; + uint8_t signingKeyPub[EIC_P256_PUB_KEY_SIZE]; + + if (!eicOpsCreateEcKey(signingKeyPriv, signingKeyPub)) { + eicDebug("Error creating signing key"); + return false; + } + + const int secondsInOneYear = 365 * 24 * 60 * 60; + time_t validityNotBefore = now; + time_t validityNotAfter = now + secondsInOneYear; // One year from now. + if (!eicOpsSignEcKey(signingKeyPub, ctx->credentialPrivateKey, 1, + "Android Identity Credential Key", // issuer CN + "Android Identity Credential Authentication Key", // subject CN + validityNotBefore, validityNotAfter, publicKeyCert, publicKeyCertSize)) { + eicDebug("Error creating certificate for signing key"); + return false; + } + + uint8_t nonce[12]; + if (!eicOpsRandom(nonce, 12)) { + eicDebug("Error getting random"); + return false; + } + if (!eicOpsEncryptAes128Gcm(ctx->storageKey, nonce, signingKeyPriv, sizeof(signingKeyPriv), + // DocType is the additionalAuthenticatedData + (const uint8_t*)docType, eicStrLen(docType), signingKeyBlob)) { + eicDebug("Error encrypting signing key"); + return false; + } + + return true; +} + +bool eicPresentationCreateEphemeralKeyPair(EicPresentation* ctx, + uint8_t ephemeralPrivateKey[EIC_P256_PRIV_KEY_SIZE]) { + uint8_t ephemeralPublicKey[EIC_P256_PUB_KEY_SIZE]; + if (!eicOpsCreateEcKey(ctx->ephemeralPrivateKey, ephemeralPublicKey)) { + eicDebug("Error creating ephemeral key"); + return false; + } + eicMemCpy(ephemeralPrivateKey, ctx->ephemeralPrivateKey, EIC_P256_PRIV_KEY_SIZE); + return true; +} + +bool eicPresentationCreateAuthChallenge(EicPresentation* ctx, uint64_t* authChallenge) { + do { + if (!eicOpsRandom((uint8_t*)&(ctx->authChallenge), sizeof(uint64_t))) { + eicDebug("Failed generating random challenge"); + return false; + } + } while (ctx->authChallenge == 0); + eicDebug("Created auth challenge %" PRIu64, ctx->authChallenge); + *authChallenge = ctx->authChallenge; + return true; +} + +// From "COSE Algorithms" registry +// +#define COSE_ALG_ECDSA_256 -7 + +bool eicPresentationValidateRequestMessage(EicPresentation* ctx, const uint8_t* sessionTranscript, + size_t sessionTranscriptSize, + const uint8_t* requestMessage, size_t requestMessageSize, + int coseSignAlg, + const uint8_t* readerSignatureOfToBeSigned, + size_t readerSignatureOfToBeSignedSize) { + if (ctx->readerPublicKeySize == 0) { + eicDebug("No public key for reader"); + return false; + } + + // Right now we only support ECDSA with SHA-256 (e.g. ES256). + // + if (coseSignAlg != COSE_ALG_ECDSA_256) { + eicDebug( + "COSE Signature algorithm for reader signature is %d, " + "only ECDSA with SHA-256 is supported right now", + coseSignAlg); + return false; + } + + // What we're going to verify is the COSE ToBeSigned structure which + // looks like the following: + // + // Sig_structure = [ + // context : "Signature" / "Signature1" / "CounterSignature", + // body_protected : empty_or_serialized_map, + // ? sign_protected : empty_or_serialized_map, + // external_aad : bstr, + // payload : bstr + // ] + // + // So we're going to build that CBOR... + // + EicCbor cbor; + eicCborInit(&cbor, NULL, 0); + eicCborAppendArray(&cbor, 4); + eicCborAppendString(&cbor, "Signature1"); + + // The COSE Encoded protected headers is just a single field with + // COSE_LABEL_ALG (1) -> coseSignAlg (e.g. -7). For simplicitly we just + // hard-code the CBOR encoding: + static const uint8_t coseEncodedProtectedHeaders[] = {0xa1, 0x01, 0x26}; + eicCborAppendByteString(&cbor, coseEncodedProtectedHeaders, + sizeof(coseEncodedProtectedHeaders)); + + // External_aad is the empty bstr + static const uint8_t externalAad[0] = {}; + eicCborAppendByteString(&cbor, externalAad, sizeof(externalAad)); + + // For the payload, the _encoded_ form follows here. We handle this by simply + // opening a bstr, and then writing the CBOR. This requires us to know the + // size of said bstr, ahead of time... the CBOR to be written is + // + // ReaderAuthentication = [ + // "ReaderAuthentication", + // SessionTranscript, + // ItemsRequestBytes + // ] + // + // ItemsRequestBytes = #6.24(bstr .cbor ItemsRequest) + // + // ReaderAuthenticationBytes = #6.24(bstr .cbor ReaderAuthentication) + // + // which is easily calculated below + // + size_t calculatedSize = 0; + calculatedSize += 1; // Array of size 3 + calculatedSize += 1; // "ReaderAuthentication" less than 24 bytes + calculatedSize += sizeof("ReaderAuthentication") - 1; // Don't include trailing NUL + calculatedSize += sessionTranscriptSize; // Already CBOR encoded + calculatedSize += 2; // Semantic tag EIC_CBOR_SEMANTIC_TAG_ENCODED_CBOR (24) + calculatedSize += 1 + eicCborAdditionalLengthBytesFor(requestMessageSize); + calculatedSize += requestMessageSize; + + // However note that we're authenticating ReaderAuthenticationBytes which + // is a tagged bstr of the bytes of ReaderAuthentication. So need to get + // that in front. + size_t rabCalculatedSize = 0; + rabCalculatedSize += 2; // Semantic tag EIC_CBOR_SEMANTIC_TAG_ENCODED_CBOR (24) + rabCalculatedSize += 1 + eicCborAdditionalLengthBytesFor(calculatedSize); + rabCalculatedSize += calculatedSize; + + // Begin the bytestring for ReaderAuthenticationBytes; + eicCborBegin(&cbor, EIC_CBOR_MAJOR_TYPE_BYTE_STRING, rabCalculatedSize); + + eicCborAppendSemantic(&cbor, EIC_CBOR_SEMANTIC_TAG_ENCODED_CBOR); + + // Begins the bytestring for ReaderAuthentication; + eicCborBegin(&cbor, EIC_CBOR_MAJOR_TYPE_BYTE_STRING, calculatedSize); + + // And now that we know the size, let's fill it in... + // + size_t payloadOffset = cbor.size; + eicCborBegin(&cbor, EIC_CBOR_MAJOR_TYPE_ARRAY, 3); + eicCborAppendString(&cbor, "ReaderAuthentication"); + eicCborAppend(&cbor, sessionTranscript, sessionTranscriptSize); + eicCborAppendSemantic(&cbor, EIC_CBOR_SEMANTIC_TAG_ENCODED_CBOR); + eicCborBegin(&cbor, EIC_CBOR_MAJOR_TYPE_BYTE_STRING, requestMessageSize); + eicCborAppend(&cbor, requestMessage, requestMessageSize); + + if (cbor.size != payloadOffset + calculatedSize) { + eicDebug("CBOR size is %zd but we expected %zd", cbor.size, payloadOffset + calculatedSize); + return false; + } + uint8_t toBeSignedDigest[EIC_SHA256_DIGEST_SIZE]; + eicCborFinal(&cbor, toBeSignedDigest); + + if (!eicOpsEcDsaVerifyWithPublicKey( + toBeSignedDigest, EIC_SHA256_DIGEST_SIZE, readerSignatureOfToBeSigned, + readerSignatureOfToBeSignedSize, ctx->readerPublicKey, ctx->readerPublicKeySize)) { + eicDebug("Request message is not signed by public key"); + return false; + } + ctx->requestMessageValidated = true; + return true; +} + +// Validates the next certificate in the reader certificate chain. +bool eicPresentationPushReaderCert(EicPresentation* ctx, const uint8_t* certX509, + size_t certX509Size) { + // If we had a previous certificate, use its public key to validate this certificate. + if (ctx->readerPublicKeySize > 0) { + if (!eicOpsX509CertSignedByPublicKey(certX509, certX509Size, ctx->readerPublicKey, + ctx->readerPublicKeySize)) { + eicDebug("Certificate is not signed by public key in the previous certificate"); + return false; + } + } + + // Store the key of this certificate, this is used to validate the next certificate + // and also ACPs with certificates that use the same public key... + ctx->readerPublicKeySize = EIC_PRESENTATION_MAX_READER_PUBLIC_KEY_SIZE; + if (!eicOpsX509GetPublicKey(certX509, certX509Size, ctx->readerPublicKey, + &ctx->readerPublicKeySize)) { + eicDebug("Error extracting public key from certificate"); + return false; + } + if (ctx->readerPublicKeySize == 0) { + eicDebug("Zero-length public key in certificate"); + return false; + } + + return true; +} + +bool eicPresentationSetAuthToken(EicPresentation* ctx, uint64_t challenge, uint64_t secureUserId, + uint64_t authenticatorId, int hardwareAuthenticatorType, + uint64_t timeStamp, const uint8_t* mac, size_t macSize, + uint64_t verificationTokenChallenge, + uint64_t verificationTokenTimestamp, + int verificationTokenSecurityLevel, + const uint8_t* verificationTokenMac, + size_t verificationTokenMacSize) { + if (!eicOpsValidateAuthToken( + challenge, secureUserId, authenticatorId, hardwareAuthenticatorType, timeStamp, mac, + macSize, verificationTokenChallenge, verificationTokenTimestamp, + verificationTokenSecurityLevel, verificationTokenMac, verificationTokenMacSize)) { + return false; + } + ctx->authTokenChallenge = challenge; + ctx->authTokenSecureUserId = secureUserId; + ctx->authTokenTimestamp = timeStamp; + ctx->verificationTokenTimestamp = verificationTokenTimestamp; + return true; +} + +static bool checkUserAuth(EicPresentation* ctx, bool userAuthenticationRequired, int timeoutMillis, + uint64_t secureUserId) { + if (!userAuthenticationRequired) { + return true; + } + + if (secureUserId != ctx->authTokenSecureUserId) { + eicDebug("secureUserId in profile differs from userId in authToken"); + return false; + } + + if (timeoutMillis == 0) { + if (ctx->authTokenChallenge == 0) { + eicDebug("No challenge in authToken"); + return false; + } + + // If we didn't create a challenge, too bad but user auth with + // timeoutMillis set to 0 needs it. + if (ctx->authChallenge == 0) { + eicDebug("No challenge was created for this session"); + return false; + } + if (ctx->authTokenChallenge != ctx->authChallenge) { + eicDebug("Challenge in authToken (%" PRIu64 + ") doesn't match the challenge " + "that was created (%" PRIu64 ") for this session", + ctx->authTokenChallenge, ctx->authChallenge); + return false; + } + } + + uint64_t now = ctx->verificationTokenTimestamp; + if (ctx->authTokenTimestamp > now) { + eicDebug("Timestamp in authToken is in the future"); + return false; + } + + if (timeoutMillis > 0) { + if (now > ctx->authTokenTimestamp + timeoutMillis) { + eicDebug("Deadline for authToken is in the past"); + return false; + } + } + + return true; +} + +static bool checkReaderAuth(EicPresentation* ctx, const uint8_t* readerCertificate, + size_t readerCertificateSize) { + uint8_t publicKey[EIC_PRESENTATION_MAX_READER_PUBLIC_KEY_SIZE]; + size_t publicKeySize; + + if (readerCertificateSize == 0) { + return true; + } + + // Remember in this case certificate equality is done by comparing public + // keys, not bitwise comparison of the certificates. + // + publicKeySize = EIC_PRESENTATION_MAX_READER_PUBLIC_KEY_SIZE; + if (!eicOpsX509GetPublicKey(readerCertificate, readerCertificateSize, publicKey, + &publicKeySize)) { + eicDebug("Error extracting public key from certificate"); + return false; + } + if (publicKeySize == 0) { + eicDebug("Zero-length public key in certificate"); + return false; + } + + if ((ctx->readerPublicKeySize != publicKeySize) || + (eicCryptoMemCmp(ctx->readerPublicKey, publicKey, ctx->readerPublicKeySize) != 0)) { + return false; + } + return true; +} + +// Note: This function returns false _only_ if an error occurred check for access, _not_ +// whether access is granted. Whether access is granted is returned in |accessGranted|. +// +bool eicPresentationValidateAccessControlProfile(EicPresentation* ctx, int id, + const uint8_t* readerCertificate, + size_t readerCertificateSize, + bool userAuthenticationRequired, int timeoutMillis, + uint64_t secureUserId, const uint8_t mac[28], + bool* accessGranted) { + *accessGranted = false; + + if (id < 0 || id >= 32) { + eicDebug("id value of %d is out of allowed range [0, 32[", id); + return false; + } + + // Validate the MAC + uint8_t cborBuffer[EIC_MAX_CBOR_SIZE_FOR_ACCESS_CONTROL_PROFILE]; + EicCbor cborBuilder; + eicCborInit(&cborBuilder, cborBuffer, EIC_MAX_CBOR_SIZE_FOR_ACCESS_CONTROL_PROFILE); + if (!eicCborCalcAccessControl(&cborBuilder, id, readerCertificate, readerCertificateSize, + userAuthenticationRequired, timeoutMillis, secureUserId)) { + return false; + } + if (!eicOpsDecryptAes128Gcm(ctx->storageKey, mac, 28, cborBuilder.buffer, cborBuilder.size, + NULL)) { + eicDebug("MAC for AccessControlProfile doesn't match"); + return false; + } + + bool passedUserAuth = + checkUserAuth(ctx, userAuthenticationRequired, timeoutMillis, secureUserId); + bool passedReaderAuth = checkReaderAuth(ctx, readerCertificate, readerCertificateSize); + + ctx->accessControlProfileMaskValidated |= (1 << id); + if (readerCertificateSize > 0) { + ctx->accessControlProfileMaskUsesReaderAuth |= (1 << id); + } + if (!passedReaderAuth) { + ctx->accessControlProfileMaskFailedReaderAuth |= (1 << id); + } + if (!passedUserAuth) { + ctx->accessControlProfileMaskFailedUserAuth |= (1 << id); + } + + if (passedUserAuth && passedReaderAuth) { + *accessGranted = true; + eicDebug("Access granted for id %d", id); + } + return true; +} + +bool eicPresentationCalcMacKey(EicPresentation* ctx, const uint8_t* sessionTranscript, + size_t sessionTranscriptSize, + const uint8_t readerEphemeralPublicKey[EIC_P256_PUB_KEY_SIZE], + const uint8_t signingKeyBlob[60], const char* docType, + unsigned int numNamespacesWithValues, + size_t expectedDeviceNamespacesSize) { + uint8_t signingKeyPriv[EIC_P256_PRIV_KEY_SIZE]; + if (!eicOpsDecryptAes128Gcm(ctx->storageKey, signingKeyBlob, 60, (const uint8_t*)docType, + eicStrLen(docType), signingKeyPriv)) { + eicDebug("Error decrypting signingKeyBlob"); + return false; + } + + uint8_t sharedSecret[EIC_P256_COORDINATE_SIZE]; + if (!eicOpsEcdh(readerEphemeralPublicKey, signingKeyPriv, sharedSecret)) { + eicDebug("ECDH failed"); + return false; + } + + EicCbor cbor; + eicCborInit(&cbor, NULL, 0); + eicCborAppendSemantic(&cbor, EIC_CBOR_SEMANTIC_TAG_ENCODED_CBOR); + eicCborAppendByteString(&cbor, sessionTranscript, sessionTranscriptSize); + uint8_t salt[EIC_SHA256_DIGEST_SIZE]; + eicCborFinal(&cbor, salt); + + const uint8_t info[7] = {'E', 'M', 'a', 'c', 'K', 'e', 'y'}; + uint8_t derivedKey[32]; + if (!eicOpsHkdf(sharedSecret, EIC_P256_COORDINATE_SIZE, salt, sizeof(salt), info, sizeof(info), + derivedKey, sizeof(derivedKey))) { + eicDebug("HKDF failed"); + return false; + } + + eicCborInitHmacSha256(&ctx->cbor, NULL, 0, derivedKey, sizeof(derivedKey)); + ctx->buildCbor = true; + + // What we're going to calculate the HMAC-SHA256 is the COSE ToBeMaced + // structure which looks like the following: + // + // MAC_structure = [ + // context : "MAC" / "MAC0", + // protected : empty_or_serialized_map, + // external_aad : bstr, + // payload : bstr + // ] + // + eicCborAppendArray(&ctx->cbor, 4); + eicCborAppendString(&ctx->cbor, "MAC0"); + + // The COSE Encoded protected headers is just a single field with + // COSE_LABEL_ALG (1) -> COSE_ALG_HMAC_256_256 (5). For simplicitly we just + // hard-code the CBOR encoding: + static const uint8_t coseEncodedProtectedHeaders[] = {0xa1, 0x01, 0x05}; + eicCborAppendByteString(&ctx->cbor, coseEncodedProtectedHeaders, + sizeof(coseEncodedProtectedHeaders)); + + // We currently don't support Externally Supplied Data (RFC 8152 section 4.3) + // so external_aad is the empty bstr + static const uint8_t externalAad[0] = {}; + eicCborAppendByteString(&ctx->cbor, externalAad, sizeof(externalAad)); + + // For the payload, the _encoded_ form follows here. We handle this by simply + // opening a bstr, and then writing the CBOR. This requires us to know the + // size of said bstr, ahead of time... the CBOR to be written is + // + // DeviceAuthentication = [ + // "DeviceAuthentication", + // SessionTranscript, + // DocType, ; DocType as used in Documents structure in OfflineResponse + // DeviceNameSpacesBytes + // ] + // + // DeviceNameSpacesBytes = #6.24(bstr .cbor DeviceNameSpaces) + // + // DeviceAuthenticationBytes = #6.24(bstr .cbor DeviceAuthentication) + // + // which is easily calculated below + // + size_t calculatedSize = 0; + calculatedSize += 1; // Array of size 4 + calculatedSize += 1; // "DeviceAuthentication" less than 24 bytes + calculatedSize += sizeof("DeviceAuthentication") - 1; // Don't include trailing NUL + calculatedSize += sessionTranscriptSize; // Already CBOR encoded + size_t docTypeLen = eicStrLen(docType); + calculatedSize += 1 + eicCborAdditionalLengthBytesFor(docTypeLen) + docTypeLen; + calculatedSize += 2; // Semantic tag EIC_CBOR_SEMANTIC_TAG_ENCODED_CBOR (24) + calculatedSize += 1 + eicCborAdditionalLengthBytesFor(expectedDeviceNamespacesSize); + calculatedSize += expectedDeviceNamespacesSize; + + // However note that we're authenticating DeviceAuthenticationBytes which + // is a tagged bstr of the bytes of DeviceAuthentication. So need to get + // that in front. + size_t dabCalculatedSize = 0; + dabCalculatedSize += 2; // Semantic tag EIC_CBOR_SEMANTIC_TAG_ENCODED_CBOR (24) + dabCalculatedSize += 1 + eicCborAdditionalLengthBytesFor(calculatedSize); + dabCalculatedSize += calculatedSize; + + // Begin the bytestring for DeviceAuthenticationBytes; + eicCborBegin(&ctx->cbor, EIC_CBOR_MAJOR_TYPE_BYTE_STRING, dabCalculatedSize); + + eicCborAppendSemantic(&ctx->cbor, EIC_CBOR_SEMANTIC_TAG_ENCODED_CBOR); + + // Begins the bytestring for DeviceAuthentication; + eicCborBegin(&ctx->cbor, EIC_CBOR_MAJOR_TYPE_BYTE_STRING, calculatedSize); + + eicCborAppendArray(&ctx->cbor, 4); + eicCborAppendString(&ctx->cbor, "DeviceAuthentication"); + eicCborAppend(&ctx->cbor, sessionTranscript, sessionTranscriptSize); + eicCborAppendString(&ctx->cbor, docType); + + // For the payload, the _encoded_ form follows here. We handle this by simply + // opening a bstr, and then writing the CBOR. This requires us to know the + // size of said bstr, ahead of time. + eicCborAppendSemantic(&ctx->cbor, EIC_CBOR_SEMANTIC_TAG_ENCODED_CBOR); + eicCborBegin(&ctx->cbor, EIC_CBOR_MAJOR_TYPE_BYTE_STRING, expectedDeviceNamespacesSize); + ctx->expectedCborSizeAtEnd = expectedDeviceNamespacesSize + ctx->cbor.size; + + eicCborAppendMap(&ctx->cbor, numNamespacesWithValues); + return true; +} + +bool eicPresentationStartRetrieveEntries(EicPresentation* ctx) { + // HAL may use this object multiple times to retrieve data so need to reset various + // state objects here. + ctx->requestMessageValidated = false; + ctx->buildCbor = false; + ctx->accessControlProfileMaskValidated = 0; + ctx->accessControlProfileMaskUsesReaderAuth = 0; + ctx->accessControlProfileMaskFailedReaderAuth = 0; + ctx->accessControlProfileMaskFailedUserAuth = 0; + ctx->readerPublicKeySize = 0; + return true; +} + +EicAccessCheckResult eicPresentationStartRetrieveEntryValue( + EicPresentation* ctx, const char* nameSpace, const char* name, + unsigned int newNamespaceNumEntries, int32_t /* entrySize */, + const int* accessControlProfileIds, size_t numAccessControlProfileIds, + uint8_t* scratchSpace, size_t scratchSpaceSize) { + uint8_t* additionalDataCbor = scratchSpace; + const size_t additionalDataCborBufSize = scratchSpaceSize; + size_t additionalDataCborSize; + + if (newNamespaceNumEntries > 0) { + eicCborAppendString(&ctx->cbor, nameSpace); + eicCborAppendMap(&ctx->cbor, newNamespaceNumEntries); + } + + // We'll need to calc and store a digest of additionalData to check that it's the same + // additionalData being passed in for every eicPresentationRetrieveEntryValue() call... + if (!eicCborCalcEntryAdditionalData(accessControlProfileIds, numAccessControlProfileIds, + nameSpace, name, additionalDataCbor, + additionalDataCborBufSize, &additionalDataCborSize, + ctx->additionalDataSha256)) { + return EIC_ACCESS_CHECK_RESULT_FAILED; + } + + if (numAccessControlProfileIds == 0) { + return EIC_ACCESS_CHECK_RESULT_NO_ACCESS_CONTROL_PROFILES; + } + + // Access is granted if at least one of the profiles grants access. + // + // If an item is configured without any profiles, access is denied. + // + EicAccessCheckResult result = EIC_ACCESS_CHECK_RESULT_FAILED; + for (size_t n = 0; n < numAccessControlProfileIds; n++) { + int id = accessControlProfileIds[n]; + uint32_t idBitMask = (1 << id); + + // If the access control profile wasn't validated, this is an error and we + // fail immediately. + bool validated = ((ctx->accessControlProfileMaskValidated & idBitMask) != 0); + if (!validated) { + eicDebug("No ACP for profile id %d", id); + return EIC_ACCESS_CHECK_RESULT_FAILED; + } + + // Otherwise, we _did_ validate the profile. If none of the checks + // failed, we're done + bool failedUserAuth = ((ctx->accessControlProfileMaskFailedUserAuth & idBitMask) != 0); + bool failedReaderAuth = ((ctx->accessControlProfileMaskFailedReaderAuth & idBitMask) != 0); + if (!failedUserAuth && !failedReaderAuth) { + result = EIC_ACCESS_CHECK_RESULT_OK; + break; + } + // One of the checks failed, convey which one + if (failedUserAuth) { + result = EIC_ACCESS_CHECK_RESULT_USER_AUTHENTICATION_FAILED; + } else { + result = EIC_ACCESS_CHECK_RESULT_READER_AUTHENTICATION_FAILED; + } + } + eicDebug("Result %d for name %s", result, name); + + if (result == EIC_ACCESS_CHECK_RESULT_OK) { + eicCborAppendString(&ctx->cbor, name); + } + return result; +} + +// Note: |content| must be big enough to hold |encryptedContentSize| - 28 bytes. +bool eicPresentationRetrieveEntryValue(EicPresentation* ctx, const uint8_t* encryptedContent, + size_t encryptedContentSize, uint8_t* content, + const char* nameSpace, const char* name, + const int* accessControlProfileIds, + size_t numAccessControlProfileIds, uint8_t* scratchSpace, + size_t scratchSpaceSize) { + uint8_t* additionalDataCbor = scratchSpace; + const size_t additionalDataCborBufSize = scratchSpaceSize; + size_t additionalDataCborSize; + + uint8_t calculatedSha256[EIC_SHA256_DIGEST_SIZE]; + if (!eicCborCalcEntryAdditionalData(accessControlProfileIds, numAccessControlProfileIds, + nameSpace, name, additionalDataCbor, + additionalDataCborBufSize, &additionalDataCborSize, + calculatedSha256)) { + return false; + } + if (eicCryptoMemCmp(calculatedSha256, ctx->additionalDataSha256, EIC_SHA256_DIGEST_SIZE) != 0) { + eicDebug("SHA-256 mismatch of additionalData"); + return false; + } + + if (!eicOpsDecryptAes128Gcm(ctx->storageKey, encryptedContent, encryptedContentSize, + additionalDataCbor, additionalDataCborSize, content)) { + eicDebug("Error decrypting content"); + return false; + } + + eicCborAppend(&ctx->cbor, content, encryptedContentSize - 28); + + return true; +} + +bool eicPresentationFinishRetrieval(EicPresentation* ctx, uint8_t* digestToBeMaced, + size_t* digestToBeMacedSize) { + if (!ctx->buildCbor) { + *digestToBeMacedSize = 0; + return true; + } + if (*digestToBeMacedSize != 32) { + return false; + } + + // This verifies that the correct expectedDeviceNamespacesSize value was + // passed in at eicPresentationCalcMacKey() time. + if (ctx->cbor.size != ctx->expectedCborSizeAtEnd) { + eicDebug("CBOR size is %zd, was expecting %zd", ctx->cbor.size, ctx->expectedCborSizeAtEnd); + return false; + } + eicCborFinal(&ctx->cbor, digestToBeMaced); + return true; +} + +bool eicPresentationDeleteCredential(EicPresentation* ctx, const char* docType, + size_t proofOfDeletionCborSize, + uint8_t signatureOfToBeSigned[EIC_ECDSA_P256_SIGNATURE_SIZE]) { + EicCbor cbor; + + eicCborInit(&cbor, NULL, 0); + + // What we're going to sign is the COSE ToBeSigned structure which + // looks like the following: + // + // Sig_structure = [ + // context : "Signature" / "Signature1" / "CounterSignature", + // body_protected : empty_or_serialized_map, + // ? sign_protected : empty_or_serialized_map, + // external_aad : bstr, + // payload : bstr + // ] + // + eicCborAppendArray(&cbor, 4); + eicCborAppendString(&cbor, "Signature1"); + + // The COSE Encoded protected headers is just a single field with + // COSE_LABEL_ALG (1) -> COSE_ALG_ECSDA_256 (-7). For simplicitly we just + // hard-code the CBOR encoding: + static const uint8_t coseEncodedProtectedHeaders[] = {0xa1, 0x01, 0x26}; + eicCborAppendByteString(&cbor, coseEncodedProtectedHeaders, + sizeof(coseEncodedProtectedHeaders)); + + // We currently don't support Externally Supplied Data (RFC 8152 section 4.3) + // so external_aad is the empty bstr + static const uint8_t externalAad[0] = {}; + eicCborAppendByteString(&cbor, externalAad, sizeof(externalAad)); + + // For the payload, the _encoded_ form follows here. We handle this by simply + // opening a bstr, and then writing the CBOR. This requires us to know the + // size of said bstr, ahead of time. + eicCborBegin(&cbor, EIC_CBOR_MAJOR_TYPE_BYTE_STRING, proofOfDeletionCborSize); + + // Finally, the CBOR that we're actually signing. + eicCborAppendArray(&cbor, 3); + eicCborAppendString(&cbor, "ProofOfDeletion"); + eicCborAppendString(&cbor, docType); + eicCborAppendBool(&cbor, ctx->testCredential); + + uint8_t cborSha256[EIC_SHA256_DIGEST_SIZE]; + eicCborFinal(&cbor, cborSha256); + if (!eicOpsEcDsa(ctx->credentialPrivateKey, cborSha256, signatureOfToBeSigned)) { + eicDebug("Error signing proofOfDeletion"); + return false; + } + + return true; +} diff --git a/identity/aidl/default/libeic/EicPresentation.h b/identity/aidl/default/libeic/EicPresentation.h new file mode 100644 index 0000000000..d79896212e --- /dev/null +++ b/identity/aidl/default/libeic/EicPresentation.h @@ -0,0 +1,229 @@ +/* + * Copyright 2020, The Android Open Source Project + * + * 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. + */ + +#if !defined(EIC_INSIDE_LIBEIC_H) && !defined(EIC_COMPILATION) +#error "Never include this file directly, include libeic.h instead." +#endif + +#ifndef ANDROID_HARDWARE_IDENTITY_EIC_PRESENTATION_H +#define ANDROID_HARDWARE_IDENTITY_EIC_PRESENTATION_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "EicCbor.h" + +// The maximum size we support for public keys in reader certificates. +#define EIC_PRESENTATION_MAX_READER_PUBLIC_KEY_SIZE 65 + +typedef struct { + uint8_t storageKey[EIC_AES_128_KEY_SIZE]; + uint8_t credentialPrivateKey[EIC_P256_PRIV_KEY_SIZE]; + + uint8_t ephemeralPrivateKey[EIC_P256_PRIV_KEY_SIZE]; + + // The challenge generated with eicPresentationCreateAuthChallenge() + uint64_t authChallenge; + + // Set by eicPresentationSetAuthToken() and contains the fields + // from the passed in authToken and verificationToken. + // + uint64_t authTokenChallenge; + uint64_t authTokenSecureUserId; + uint64_t authTokenTimestamp; + uint64_t verificationTokenTimestamp; + + // The public key for the reader. + // + // (During the process of pushing reader certificates, this is also used to store + // the public key of the previously pushed certificate.) + // + uint8_t readerPublicKey[EIC_PRESENTATION_MAX_READER_PUBLIC_KEY_SIZE]; + size_t readerPublicKeySize; + + // This is set to true only if eicPresentationValidateRequestMessage() successfully + // validated the requestMessage. + // + // Why even record this? Because there's no requirement the HAL actually calls that + // function and we validate ACPs before it's called... so it's possible that a + // compromised HAL could trick us into marking ACPs as authorized while they in fact + // aren't. + bool requestMessageValidated; + bool buildCbor; + + // Set to true initialized as a test credential. + bool testCredential; + + // These are bitmasks indicating which of the possible 32 access control profiles are + // authorized. They are built up by eicPresentationValidateAccessControlProfile(). + // + uint32_t accessControlProfileMaskValidated; // True if the profile was validated. + uint32_t accessControlProfileMaskUsesReaderAuth; // True if the ACP is using reader auth + uint32_t accessControlProfileMaskFailedReaderAuth; // True if failed reader auth + uint32_t accessControlProfileMaskFailedUserAuth; // True if failed user auth + + // SHA-256 for AdditionalData, updated for each entry. + uint8_t additionalDataSha256[EIC_SHA256_DIGEST_SIZE]; + + size_t expectedCborSizeAtEnd; + EicCbor cbor; +} EicPresentation; + +bool eicPresentationInit(EicPresentation* ctx, bool testCredential, const char* docType, + const uint8_t encryptedCredentialKeys[80]); + +bool eicPresentationGenerateSigningKeyPair(EicPresentation* ctx, const char* docType, time_t now, + uint8_t* publicKeyCert, size_t* publicKeyCertSize, + uint8_t signingKeyBlob[60]); + +// Create an ephemeral key-pair. +// +// The private key is stored in |ctx->ephemeralPrivateKey| and also returned in +// |ephemeralPrivateKey|. +// +bool eicPresentationCreateEphemeralKeyPair(EicPresentation* ctx, + uint8_t ephemeralPrivateKey[EIC_P256_PRIV_KEY_SIZE]); + +// Returns a non-zero challenge in |authChallenge|. +bool eicPresentationCreateAuthChallenge(EicPresentation* ctx, uint64_t* authChallenge); + +// Starts retrieveing entries. +// +bool eicPresentationStartRetrieveEntries(EicPresentation* ctx); + +// Sets the auth-token. +bool eicPresentationSetAuthToken(EicPresentation* ctx, uint64_t challenge, uint64_t secureUserId, + uint64_t authenticatorId, int hardwareAuthenticatorType, + uint64_t timeStamp, const uint8_t* mac, size_t macSize, + uint64_t verificationTokenChallenge, + uint64_t verificationTokenTimeStamp, + int verificationTokenSecurityLevel, + const uint8_t* verificationTokenMac, + size_t verificationTokenMacSize); + +// Function to push certificates in the reader certificate chain. +// +// This should start with the root certificate (e.g. the last in the chain) and +// continue up the chain, ending with the certificate for the reader. +// +// Calls to this function should be interleaved with calls to the +// eicPresentationValidateAccessControlProfile() function, see below. +// +bool eicPresentationPushReaderCert(EicPresentation* ctx, const uint8_t* certX509, + size_t certX509Size); + +// Checks an access control profile. +// +// Returns false if an error occurred while checking the profile (e.g. MAC doesn't check out). +// +// Returns in |accessGranted| whether access is granted. +// +// If |readerCertificate| is non-empty and the public key of one of those +// certificates appear in the chain presented by the reader, this function must +// be called after pushing that certificate using +// eicPresentationPushReaderCert(). +// +bool eicPresentationValidateAccessControlProfile(EicPresentation* ctx, int id, + const uint8_t* readerCertificate, + size_t readerCertificateSize, + bool userAuthenticationRequired, int timeoutMillis, + uint64_t secureUserId, const uint8_t mac[28], + bool* accessGranted); + +// Validates that the given requestMessage is signed by the public key in the +// certificate last set with eicPresentationPushReaderCert(). +// +// The format of the signature is the same encoding as the 'signature' field of +// COSE_Sign1 - that is, it's the R and S integers both with the same length as +// the key-size. +// +// Must be called after eicPresentationPushReaderCert() have been used to push +// the final certificate. Which is the certificate of the reader itself. +// +bool eicPresentationValidateRequestMessage(EicPresentation* ctx, const uint8_t* sessionTranscript, + size_t sessionTranscriptSize, + const uint8_t* requestMessage, size_t requestMessageSize, + int coseSignAlg, + const uint8_t* readerSignatureOfToBeSigned, + size_t readerSignatureOfToBeSignedSize); + +typedef enum { + // Returned if access is granted. + EIC_ACCESS_CHECK_RESULT_OK, + + // Returned if an error occurred checking for access. + EIC_ACCESS_CHECK_RESULT_FAILED, + + // Returned if access was denied because item is configured without any + // access control profiles. + EIC_ACCESS_CHECK_RESULT_NO_ACCESS_CONTROL_PROFILES, + + // Returned if access was denied because of user authentication. + EIC_ACCESS_CHECK_RESULT_USER_AUTHENTICATION_FAILED, + + // Returned if access was denied because of reader authentication. + EIC_ACCESS_CHECK_RESULT_READER_AUTHENTICATION_FAILED, +} EicAccessCheckResult; + +// Passes enough information to calculate the MACing key +// +bool eicPresentationCalcMacKey(EicPresentation* ctx, const uint8_t* sessionTranscript, + size_t sessionTranscriptSize, + const uint8_t readerEphemeralPublicKey[EIC_P256_PUB_KEY_SIZE], + const uint8_t signingKeyBlob[60], const char* docType, + unsigned int numNamespacesWithValues, + size_t expectedDeviceNamespacesSize); + +// The scratchSpace should be set to a buffer at least 512 bytes (ideally 1024 +// bytes, the bigger the better). It's done this way to avoid allocating stack +// space. +// +EicAccessCheckResult eicPresentationStartRetrieveEntryValue( + EicPresentation* ctx, const char* nameSpace, const char* name, + unsigned int newNamespaceNumEntries, int32_t entrySize, const int* accessControlProfileIds, + size_t numAccessControlProfileIds, uint8_t* scratchSpace, size_t scratchSpaceSize); + +// Note: |content| must be big enough to hold |encryptedContentSize| - 28 bytes. +// +// The scratchSpace should be set to a buffer at least 512 bytes. It's done this way to +// avoid allocating stack space. +// +bool eicPresentationRetrieveEntryValue(EicPresentation* ctx, const uint8_t* encryptedContent, + size_t encryptedContentSize, uint8_t* content, + const char* nameSpace, const char* name, + const int* accessControlProfileIds, + size_t numAccessControlProfileIds, uint8_t* scratchSpace, + size_t scratchSpaceSize); + +// Returns the HMAC-SHA256 of |ToBeMaced| as per RFC 8051 "6.3. How to Compute +// and Verify a MAC". +bool eicPresentationFinishRetrieval(EicPresentation* ctx, uint8_t* digestToBeMaced, + size_t* digestToBeMacedSize); + +// The data returned in |signatureOfToBeSigned| contains the ECDSA signature of +// the ToBeSigned CBOR from RFC 8051 "4.4. Signing and Verification Process" +// where content is set to the ProofOfDeletion CBOR. +// +bool eicPresentationDeleteCredential(EicPresentation* ctx, const char* docType, + size_t proofOfDeletionCborSize, + uint8_t signatureOfToBeSigned[EIC_ECDSA_P256_SIGNATURE_SIZE]); + +#ifdef __cplusplus +} +#endif + +#endif // ANDROID_HARDWARE_IDENTITY_EIC_PRESENTATION_H diff --git a/identity/aidl/default/libeic/EicProvisioning.c b/identity/aidl/default/libeic/EicProvisioning.c new file mode 100644 index 0000000000..f16605cfad --- /dev/null +++ b/identity/aidl/default/libeic/EicProvisioning.c @@ -0,0 +1,290 @@ +/* + * Copyright 2020, The Android Open Source Project + * + * 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 "EicProvisioning.h" + +bool eicProvisioningInit(EicProvisioning* ctx, bool testCredential) { + eicMemSet(ctx, '\0', sizeof(EicProvisioning)); + ctx->testCredential = testCredential; + if (!eicOpsRandom(ctx->storageKey, EIC_AES_128_KEY_SIZE)) { + return false; + } + + return true; +} + +bool eicProvisioningCreateCredentialKey(EicProvisioning* ctx, const uint8_t* challenge, + size_t challengeSize, const uint8_t* applicationId, + size_t applicationIdSize, uint8_t* publicKeyCert, + size_t* publicKeyCertSize) { + if (!eicOpsCreateCredentialKey(ctx->credentialPrivateKey, challenge, challengeSize, + applicationId, applicationIdSize, ctx->testCredential, + publicKeyCert, publicKeyCertSize)) { + return false; + } + return true; +} + +bool eicProvisioningStartPersonalization(EicProvisioning* ctx, int accessControlProfileCount, + const int* entryCounts, size_t numEntryCounts, + const char* docType, + size_t expectedProofOfProvisioningSize) { + if (numEntryCounts >= EIC_MAX_NUM_NAMESPACES) { + return false; + } + if (accessControlProfileCount >= EIC_MAX_NUM_ACCESS_CONTROL_PROFILE_IDS) { + return false; + } + + ctx->numEntryCounts = numEntryCounts; + if (numEntryCounts > EIC_MAX_NUM_NAMESPACES) { + return false; + } + for (size_t n = 0; n < numEntryCounts; n++) { + if (entryCounts[n] >= 256) { + return false; + } + ctx->entryCounts[n] = entryCounts[n]; + } + ctx->curNamespace = -1; + ctx->curNamespaceNumProcessed = 0; + + eicCborInit(&ctx->cbor, NULL, 0); + + // What we're going to sign is the COSE ToBeSigned structure which + // looks like the following: + // + // Sig_structure = [ + // context : "Signature" / "Signature1" / "CounterSignature", + // body_protected : empty_or_serialized_map, + // ? sign_protected : empty_or_serialized_map, + // external_aad : bstr, + // payload : bstr + // ] + // + eicCborAppendArray(&ctx->cbor, 4); + eicCborAppendString(&ctx->cbor, "Signature1"); + + // The COSE Encoded protected headers is just a single field with + // COSE_LABEL_ALG (1) -> COSE_ALG_ECSDA_256 (-7). For simplicitly we just + // hard-code the CBOR encoding: + static const uint8_t coseEncodedProtectedHeaders[] = {0xa1, 0x01, 0x26}; + eicCborAppendByteString(&ctx->cbor, coseEncodedProtectedHeaders, + sizeof(coseEncodedProtectedHeaders)); + + // We currently don't support Externally Supplied Data (RFC 8152 section 4.3) + // so external_aad is the empty bstr + static const uint8_t externalAad[0] = {}; + eicCborAppendByteString(&ctx->cbor, externalAad, sizeof(externalAad)); + + // For the payload, the _encoded_ form follows here. We handle this by simply + // opening a bstr, and then writing the CBOR. This requires us to know the + // size of said bstr, ahead of time. + eicCborBegin(&ctx->cbor, EIC_CBOR_MAJOR_TYPE_BYTE_STRING, expectedProofOfProvisioningSize); + ctx->expectedCborSizeAtEnd = expectedProofOfProvisioningSize + ctx->cbor.size; + + eicCborAppendArray(&ctx->cbor, 5); + eicCborAppendString(&ctx->cbor, "ProofOfProvisioning"); + eicCborAppendString(&ctx->cbor, docType); + + eicCborAppendArray(&ctx->cbor, accessControlProfileCount); + + return true; +} + +bool eicProvisioningAddAccessControlProfile(EicProvisioning* ctx, int id, + const uint8_t* readerCertificate, + size_t readerCertificateSize, + bool userAuthenticationRequired, uint64_t timeoutMillis, + uint64_t secureUserId, uint8_t outMac[28]) { + uint8_t cborBuffer[EIC_MAX_CBOR_SIZE_FOR_ACCESS_CONTROL_PROFILE]; + EicCbor cborBuilder; + + eicCborInit(&cborBuilder, cborBuffer, EIC_MAX_CBOR_SIZE_FOR_ACCESS_CONTROL_PROFILE); + + if (!eicCborCalcAccessControl(&cborBuilder, id, readerCertificate, readerCertificateSize, + userAuthenticationRequired, timeoutMillis, secureUserId)) { + return false; + } + + // Calculate and return MAC + uint8_t nonce[12]; + if (!eicOpsRandom(nonce, 12)) { + return false; + } + if (!eicOpsEncryptAes128Gcm(ctx->storageKey, nonce, NULL, 0, cborBuilder.buffer, + cborBuilder.size, outMac)) { + return false; + } + + // The ACP CBOR in the provisioning receipt doesn't include secureUserId so build + // it again. + eicCborInit(&cborBuilder, cborBuffer, EIC_MAX_CBOR_SIZE_FOR_ACCESS_CONTROL_PROFILE); + if (!eicCborCalcAccessControl(&cborBuilder, id, readerCertificate, readerCertificateSize, + userAuthenticationRequired, timeoutMillis, + 0 /* secureUserId */)) { + return false; + } + + // Append the CBOR from the local builder to the digester. + eicCborAppend(&ctx->cbor, cborBuilder.buffer, cborBuilder.size); + + return true; +} + +bool eicProvisioningBeginAddEntry(EicProvisioning* ctx, const int* accessControlProfileIds, + size_t numAccessControlProfileIds, const char* nameSpace, + const char* name, uint64_t entrySize, uint8_t* scratchSpace, + size_t scratchSpaceSize) { + uint8_t* additionalDataCbor = scratchSpace; + const size_t additionalDataCborBufSize = scratchSpaceSize; + size_t additionalDataCborSize; + + // We'll need to calc and store a digest of additionalData to check that it's the same + // additionalData being passed in for every eicProvisioningAddEntryValue() call... + if (!eicCborCalcEntryAdditionalData(accessControlProfileIds, numAccessControlProfileIds, + nameSpace, name, additionalDataCbor, + additionalDataCborBufSize, &additionalDataCborSize, + ctx->additionalDataSha256)) { + return false; + } + + if (ctx->curNamespace == -1) { + ctx->curNamespace = 0; + ctx->curNamespaceNumProcessed = 0; + // Opens the main map: { * Namespace => [ + Entry ] } + eicCborAppendMap(&ctx->cbor, ctx->numEntryCounts); + eicCborAppendString(&ctx->cbor, nameSpace); + // Opens the per-namespace array: [ + Entry ] + eicCborAppendArray(&ctx->cbor, ctx->entryCounts[ctx->curNamespace]); + } + + if (ctx->curNamespaceNumProcessed == ctx->entryCounts[ctx->curNamespace]) { + ctx->curNamespace += 1; + ctx->curNamespaceNumProcessed = 0; + eicCborAppendString(&ctx->cbor, nameSpace); + // Opens the per-namespace array: [ + Entry ] + eicCborAppendArray(&ctx->cbor, ctx->entryCounts[ctx->curNamespace]); + } + + eicCborAppendMap(&ctx->cbor, 3); + eicCborAppendString(&ctx->cbor, "name"); + eicCborAppendString(&ctx->cbor, name); + + ctx->curEntrySize = entrySize; + ctx->curEntryNumBytesReceived = 0; + + eicCborAppendString(&ctx->cbor, "value"); + + ctx->curNamespaceNumProcessed += 1; + return true; +} + +bool eicProvisioningAddEntryValue(EicProvisioning* ctx, const int* accessControlProfileIds, + size_t numAccessControlProfileIds, const char* nameSpace, + const char* name, const uint8_t* content, size_t contentSize, + uint8_t* outEncryptedContent, uint8_t* scratchSpace, + size_t scratchSpaceSize) { + uint8_t* additionalDataCbor = scratchSpace; + const size_t additionalDataCborBufSize = scratchSpaceSize; + size_t additionalDataCborSize; + + uint8_t calculatedSha256[EIC_SHA256_DIGEST_SIZE]; + if (!eicCborCalcEntryAdditionalData(accessControlProfileIds, numAccessControlProfileIds, + nameSpace, name, additionalDataCbor, + additionalDataCborBufSize, &additionalDataCborSize, + calculatedSha256)) { + return false; + } + if (eicCryptoMemCmp(calculatedSha256, ctx->additionalDataSha256, EIC_SHA256_DIGEST_SIZE) != 0) { + eicDebug("SHA-256 mismatch of additionalData"); + return false; + } + + eicCborAppend(&ctx->cbor, content, contentSize); + + uint8_t nonce[12]; + if (!eicOpsRandom(nonce, 12)) { + return false; + } + if (!eicOpsEncryptAes128Gcm(ctx->storageKey, nonce, content, contentSize, additionalDataCbor, + additionalDataCborSize, outEncryptedContent)) { + return false; + } + + // If done with this entry, close the map + ctx->curEntryNumBytesReceived += contentSize; + if (ctx->curEntryNumBytesReceived == ctx->curEntrySize) { + eicCborAppendString(&ctx->cbor, "accessControlProfiles"); + eicCborAppendArray(&ctx->cbor, numAccessControlProfileIds); + for (size_t n = 0; n < numAccessControlProfileIds; n++) { + eicCborAppendNumber(&ctx->cbor, accessControlProfileIds[n]); + } + } + return true; +} + +bool eicProvisioningFinishAddingEntries( + EicProvisioning* ctx, uint8_t signatureOfToBeSigned[EIC_ECDSA_P256_SIGNATURE_SIZE]) { + uint8_t cborSha256[EIC_SHA256_DIGEST_SIZE]; + + eicCborAppendBool(&ctx->cbor, ctx->testCredential); + eicCborFinal(&ctx->cbor, cborSha256); + + // This verifies that the correct expectedProofOfProvisioningSize value was + // passed in at eicStartPersonalization() time. + if (ctx->cbor.size != ctx->expectedCborSizeAtEnd) { + eicDebug("CBOR size is %zd, was expecting %zd", ctx->cbor.size, ctx->expectedCborSizeAtEnd); + return false; + } + + if (!eicOpsEcDsa(ctx->credentialPrivateKey, cborSha256, signatureOfToBeSigned)) { + eicDebug("Error signing proofOfProvisioning"); + return false; + } + + return true; +} + +bool eicProvisioningFinishGetCredentialData(EicProvisioning* ctx, const char* docType, + uint8_t encryptedCredentialKeys[80]) { + EicCbor cbor; + uint8_t cborBuf[52]; + + eicCborInit(&cbor, cborBuf, sizeof(cborBuf)); + eicCborAppendArray(&cbor, 2); + eicCborAppendByteString(&cbor, ctx->storageKey, EIC_AES_128_KEY_SIZE); + eicCborAppendByteString(&cbor, ctx->credentialPrivateKey, EIC_P256_PRIV_KEY_SIZE); + if (cbor.size > sizeof(cborBuf)) { + eicDebug("Exceeded buffer size"); + return false; + } + + uint8_t nonce[12]; + if (!eicOpsRandom(nonce, 12)) { + eicDebug("Error getting random"); + return false; + } + if (!eicOpsEncryptAes128Gcm( + eicOpsGetHardwareBoundKey(ctx->testCredential), nonce, cborBuf, cbor.size, + // DocType is the additionalAuthenticatedData + (const uint8_t*)docType, eicStrLen(docType), encryptedCredentialKeys)) { + eicDebug("Error encrypting CredentialKeys"); + return false; + } + + return true; +} diff --git a/identity/aidl/default/libeic/EicProvisioning.h b/identity/aidl/default/libeic/EicProvisioning.h new file mode 100644 index 0000000000..836d16e444 --- /dev/null +++ b/identity/aidl/default/libeic/EicProvisioning.h @@ -0,0 +1,123 @@ +/* + * Copyright 2020, The Android Open Source Project + * + * 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. + */ + +#if !defined(EIC_INSIDE_LIBEIC_H) && !defined(EIC_COMPILATION) +#error "Never include this file directly, include libeic.h instead." +#endif + +#ifndef ANDROID_HARDWARE_IDENTITY_EIC_PROVISIONING_H +#define ANDROID_HARDWARE_IDENTITY_EIC_PROVISIONING_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "EicCbor.h" + +#define EIC_MAX_NUM_NAMESPACES 32 +#define EIC_MAX_NUM_ACCESS_CONTROL_PROFILE_IDS 32 + +typedef struct { + // Set by eicCreateCredentialKey. + uint8_t credentialPrivateKey[EIC_P256_PRIV_KEY_SIZE]; + + int numEntryCounts; + uint8_t entryCounts[EIC_MAX_NUM_NAMESPACES]; + + int curNamespace; + int curNamespaceNumProcessed; + + size_t curEntrySize; + size_t curEntryNumBytesReceived; + + uint8_t storageKey[EIC_AES_128_KEY_SIZE]; + + size_t expectedCborSizeAtEnd; + + // SHA-256 for AdditionalData, updated for each entry. + uint8_t additionalDataSha256[EIC_SHA256_DIGEST_SIZE]; + + EicCbor cbor; + + bool testCredential; +} EicProvisioning; + +bool eicProvisioningInit(EicProvisioning* ctx, bool testCredential); + +bool eicProvisioningCreateCredentialKey(EicProvisioning* ctx, const uint8_t* challenge, + size_t challengeSize, const uint8_t* applicationId, + size_t applicationIdSize, uint8_t* publicKeyCert, + size_t* publicKeyCertSize); + +bool eicProvisioningStartPersonalization(EicProvisioning* ctx, int accessControlProfileCount, + const int* entryCounts, size_t numEntryCounts, + const char* docType, + size_t expectedProofOfProvisioningingSize); + +bool eicProvisioningAddAccessControlProfile(EicProvisioning* ctx, int id, + const uint8_t* readerCertificate, + size_t readerCertificateSize, + bool userAuthenticationRequired, uint64_t timeoutMillis, + uint64_t secureUserId, uint8_t outMac[28]); + +// The scratchSpace should be set to a buffer at least 512 bytes. It's done this way to +// avoid allocating stack space. +// +bool eicProvisioningBeginAddEntry(EicProvisioning* ctx, const int* accessControlProfileIds, + size_t numAccessControlProfileIds, const char* nameSpace, + const char* name, uint64_t entrySize, uint8_t* scratchSpace, + size_t scratchSpaceSize); + +// The outEncryptedContent array must be contentSize + 28 bytes long. +// +// The scratchSpace should be set to a buffer at least 512 bytes. It's done this way to +// avoid allocating stack space. +// +bool eicProvisioningAddEntryValue(EicProvisioning* ctx, const int* accessControlProfileIds, + size_t numAccessControlProfileIds, const char* nameSpace, + const char* name, const uint8_t* content, size_t contentSize, + uint8_t* outEncryptedContent, uint8_t* scratchSpace, + size_t scratchSpaceSize); + +// The data returned in |signatureOfToBeSigned| contains the ECDSA signature of +// the ToBeSigned CBOR from RFC 8051 "4.4. Signing and Verification Process" +// where content is set to the ProofOfProvisioninging CBOR. +// +bool eicProvisioningFinishAddingEntries( + EicProvisioning* ctx, uint8_t signatureOfToBeSigned[EIC_ECDSA_P256_SIGNATURE_SIZE]); + +// +// +// The |encryptedCredentialKeys| array is set to AES-GCM-ENC(HBK, R, CredentialKeys, docType) +// where +// +// CredentialKeys = [ +// bstr, ; storageKey, a 128-bit AES key +// bstr ; credentialPrivKey, the private key for credentialKey +// ] +// +// Since |storageKey| is 16 bytes and |credentialPrivKey| is 32 bytes, the +// encoded CBOR for CredentialKeys is 52 bytes and consequently +// |encryptedCredentialKeys| will be 52 + 28 = 80 bytes. +// +bool eicProvisioningFinishGetCredentialData(EicProvisioning* ctx, const char* docType, + uint8_t encryptedCredentialKeys[80]); + +#ifdef __cplusplus +} +#endif + +#endif // ANDROID_HARDWARE_IDENTITY_EIC_PROVISIONING_H diff --git a/identity/aidl/default/libeic/libeic.h b/identity/aidl/default/libeic/libeic.h new file mode 100644 index 0000000000..88abef808f --- /dev/null +++ b/identity/aidl/default/libeic/libeic.h @@ -0,0 +1,39 @@ +/* + * Copyright 2020, The Android Open Source Project + * + * 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 ANDROID_HARDWARE_IDENTITY_LIBEIC_H +#define ANDROID_HARDWARE_IDENTITY_LIBEIC_H + +#ifdef __cplusplus +extern "C" { +#endif + +/* The EIC_INSIDE_LIBEIC_H preprocessor symbol is used to enforce + * library users to include only this file. All public interfaces, and + * only public interfaces, must be included here. + */ +#define EIC_INSIDE_LIBEIC_H +#include "EicCbor.h" +#include "EicOps.h" +#include "EicPresentation.h" +#include "EicProvisioning.h" +#undef EIC_INSIDE_LIBEIC_H + +#ifdef __cplusplus +} +#endif + +#endif // ANDROID_HARDWARE_IDENTITY_LIBEIC_H diff --git a/identity/aidl/default/service.cpp b/identity/aidl/default/service.cpp index bf95df523a..c290c0827e 100644 --- a/identity/aidl/default/service.cpp +++ b/identity/aidl/default/service.cpp @@ -22,20 +22,26 @@ #include "IdentityCredentialStore.h" +#include "FakeSecureHardwareProxy.h" + +using ::android::sp; using ::android::base::InitLogging; using ::android::base::StderrLogger; -using aidl::android::hardware::identity::IdentityCredentialStore; +using ::aidl::android::hardware::identity::IdentityCredentialStore; +using ::android::hardware::identity::FakeSecureHardwareProxyFactory; +using ::android::hardware::identity::SecureHardwareProxyFactory; int main(int /*argc*/, char* argv[]) { InitLogging(argv, StderrLogger); + sp<SecureHardwareProxyFactory> hwProxyFactory = new FakeSecureHardwareProxyFactory(); + ABinderProcess_setThreadPoolMaxThreadCount(0); std::shared_ptr<IdentityCredentialStore> store = - ndk::SharedRefBase::make<IdentityCredentialStore>(); + ndk::SharedRefBase::make<IdentityCredentialStore>(hwProxyFactory); const std::string instance = std::string() + IdentityCredentialStore::descriptor + "/default"; - LOG(INFO) << "instance: " << instance; binder_status_t status = AServiceManager_addService(store->asBinder().get(), instance.c_str()); CHECK(status == STATUS_OK); diff --git a/identity/aidl/vts/VtsIWritableIdentityCredentialTests.cpp b/identity/aidl/vts/VtsIWritableIdentityCredentialTests.cpp index 56e17bac77..1629a0c952 100644 --- a/identity/aidl/vts/VtsIWritableIdentityCredentialTests.cpp +++ b/identity/aidl/vts/VtsIWritableIdentityCredentialTests.cpp @@ -182,7 +182,7 @@ TEST_P(IdentityCredentialTests, verifyStartPersonalizationLarge) { false /* testCredential */)); // Verify set a large number of profile count and entry count is ok - const vector<int32_t> entryCounts = {3000}; + const vector<int32_t> entryCounts = {255}; writableCredential->setExpectedProofOfProvisioningSize(123456); result = writableCredential->startPersonalization(25, entryCounts); EXPECT_TRUE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage() diff --git a/keymaster/4.1/default/OWNERS b/keymaster/4.1/default/OWNERS index 335660da3b..2b2ad2a08e 100644 --- a/keymaster/4.1/default/OWNERS +++ b/keymaster/4.1/default/OWNERS @@ -1,2 +1,4 @@ +jbires@google.com jdanis@google.com swillden@google.com +zeuthen@google.com
\ No newline at end of file diff --git a/keymaster/4.1/support/OWNERS b/keymaster/4.1/support/OWNERS index a9efe66419..2b2ad2a08e 100644 --- a/keymaster/4.1/support/OWNERS +++ b/keymaster/4.1/support/OWNERS @@ -1,3 +1,4 @@ +jbires@google.com jdanis@google.com swillden@google.com -jbires@google.com +zeuthen@google.com
\ No newline at end of file diff --git a/keymaster/4.1/vts/OWNERS b/keymaster/4.1/vts/OWNERS index 335660da3b..2b2ad2a08e 100644 --- a/keymaster/4.1/vts/OWNERS +++ b/keymaster/4.1/vts/OWNERS @@ -1,2 +1,4 @@ +jbires@google.com jdanis@google.com swillden@google.com +zeuthen@google.com
\ No newline at end of file diff --git a/neuralnetworks/1.0/utils/Android.bp b/neuralnetworks/1.0/utils/Android.bp index 4d61fc0a94..d03361724c 100644 --- a/neuralnetworks/1.0/utils/Android.bp +++ b/neuralnetworks/1.0/utils/Android.bp @@ -32,3 +32,29 @@ cc_library_static { "neuralnetworks_utils_hal_common", ], } + +cc_test { + name: "neuralnetworks_utils_hal_1_0_test", + srcs: ["test/*.cpp"], + static_libs: [ + "android.hardware.neuralnetworks@1.0", + "libgmock", + "libneuralnetworks_common", + "neuralnetworks_types", + "neuralnetworks_utils_hal_common", + "neuralnetworks_utils_hal_1_0", + ], + shared_libs: [ + "android.hidl.allocator@1.0", + "android.hidl.memory@1.0", + "libbase", + "libcutils", + "libfmq", + "libhidlbase", + "libhidlmemory", + "liblog", + "libnativewindow", + "libutils", + ], + test_suites: ["general-tests"], +} diff --git a/neuralnetworks/1.0/utils/include/nnapi/hal/1.0/Burst.h b/neuralnetworks/1.0/utils/include/nnapi/hal/1.0/Burst.h new file mode 100644 index 0000000000..f2cbe93c7c --- /dev/null +++ b/neuralnetworks/1.0/utils/include/nnapi/hal/1.0/Burst.h @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * 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 ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_0_UTILS_BURST_H +#define ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_0_UTILS_BURST_H + +#include <nnapi/IBurst.h> +#include <nnapi/IPreparedModel.h> +#include <nnapi/Result.h> +#include <nnapi/Types.h> + +#include <memory> +#include <optional> +#include <utility> + +// See hardware/interfaces/neuralnetworks/utils/README.md for more information on HIDL interface +// lifetimes across processes and for protecting asynchronous calls across HIDL. + +namespace android::hardware::neuralnetworks::V1_0::utils { + +// Class that adapts nn::IPreparedModel to nn::IBurst. +class Burst final : public nn::IBurst { + struct PrivateConstructorTag {}; + + public: + static nn::GeneralResult<std::shared_ptr<const Burst>> create( + nn::SharedPreparedModel preparedModel); + + Burst(PrivateConstructorTag tag, nn::SharedPreparedModel preparedModel); + + OptionalCacheHold cacheMemory(const nn::Memory& memory) const override; + + nn::ExecutionResult<std::pair<std::vector<nn::OutputShape>, nn::Timing>> execute( + const nn::Request& request, nn::MeasureTiming measure) const override; + + private: + const nn::SharedPreparedModel kPreparedModel; +}; + +} // namespace android::hardware::neuralnetworks::V1_0::utils + +#endif // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_0_UTILS_BURST_H diff --git a/neuralnetworks/1.0/utils/include/nnapi/hal/1.0/Device.h b/neuralnetworks/1.0/utils/include/nnapi/hal/1.0/Device.h index db3b2ad44f..4681b9e47f 100644 --- a/neuralnetworks/1.0/utils/include/nnapi/hal/1.0/Device.h +++ b/neuralnetworks/1.0/utils/include/nnapi/hal/1.0/Device.h @@ -52,6 +52,7 @@ class Device final : public nn::IDevice { const std::string& getVersionString() const override; nn::Version getFeatureLevel() const override; nn::DeviceType getType() const override; + bool isUpdatable() const override; const std::vector<nn::Extension>& getSupportedExtensions() const override; const nn::Capabilities& getCapabilities() const override; std::pair<uint32_t, uint32_t> getNumberOfCacheFilesNeeded() const override; diff --git a/neuralnetworks/1.0/utils/include/nnapi/hal/1.0/PreparedModel.h b/neuralnetworks/1.0/utils/include/nnapi/hal/1.0/PreparedModel.h index 2de182871d..8853eea048 100644 --- a/neuralnetworks/1.0/utils/include/nnapi/hal/1.0/PreparedModel.h +++ b/neuralnetworks/1.0/utils/include/nnapi/hal/1.0/PreparedModel.h @@ -35,7 +35,8 @@ namespace android::hardware::neuralnetworks::V1_0::utils { // Class that adapts V1_0::IPreparedModel to nn::IPreparedModel. -class PreparedModel final : public nn::IPreparedModel { +class PreparedModel final : public nn::IPreparedModel, + public std::enable_shared_from_this<PreparedModel> { struct PrivateConstructorTag {}; public: @@ -56,6 +57,8 @@ class PreparedModel final : public nn::IPreparedModel { const nn::OptionalDuration& loopTimeoutDuration, const nn::OptionalDuration& timeoutDurationAfterFence) const override; + nn::GeneralResult<nn::SharedBurst> configureExecutionBurst() const override; + std::any getUnderlyingResource() const override; private: diff --git a/neuralnetworks/1.0/utils/src/Burst.cpp b/neuralnetworks/1.0/utils/src/Burst.cpp new file mode 100644 index 0000000000..384bd9b699 --- /dev/null +++ b/neuralnetworks/1.0/utils/src/Burst.cpp @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * 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 "Burst.h" + +#include <android-base/logging.h> +#include <nnapi/IBurst.h> +#include <nnapi/IPreparedModel.h> +#include <nnapi/Result.h> +#include <nnapi/Types.h> + +#include <memory> +#include <optional> +#include <utility> + +namespace android::hardware::neuralnetworks::V1_0::utils { + +nn::GeneralResult<std::shared_ptr<const Burst>> Burst::create( + nn::SharedPreparedModel preparedModel) { + if (preparedModel == nullptr) { + return NN_ERROR(nn::ErrorStatus::GENERAL_FAILURE) + << "V1_0::utils::Burst::create must have non-null preparedModel"; + } + + return std::make_shared<const Burst>(PrivateConstructorTag{}, std::move(preparedModel)); +} + +Burst::Burst(PrivateConstructorTag /*tag*/, nn::SharedPreparedModel preparedModel) + : kPreparedModel(std::move(preparedModel)) { + CHECK(kPreparedModel != nullptr); +} + +Burst::OptionalCacheHold Burst::cacheMemory(const nn::Memory& /*memory*/) const { + return nullptr; +} + +nn::ExecutionResult<std::pair<std::vector<nn::OutputShape>, nn::Timing>> Burst::execute( + const nn::Request& request, nn::MeasureTiming measure) const { + return kPreparedModel->execute(request, measure, {}, {}); +} + +} // namespace android::hardware::neuralnetworks::V1_0::utils diff --git a/neuralnetworks/1.0/utils/src/Device.cpp b/neuralnetworks/1.0/utils/src/Device.cpp index 93bd81a19c..bb31a266e0 100644 --- a/neuralnetworks/1.0/utils/src/Device.cpp +++ b/neuralnetworks/1.0/utils/src/Device.cpp @@ -106,6 +106,10 @@ nn::DeviceType Device::getType() const { return nn::DeviceType::OTHER; } +bool Device::isUpdatable() const { + return false; +} + const std::vector<nn::Extension>& Device::getSupportedExtensions() const { return kExtensions; } diff --git a/neuralnetworks/1.0/utils/src/PreparedModel.cpp b/neuralnetworks/1.0/utils/src/PreparedModel.cpp index c0c22fbd6a..858571d401 100644 --- a/neuralnetworks/1.0/utils/src/PreparedModel.cpp +++ b/neuralnetworks/1.0/utils/src/PreparedModel.cpp @@ -16,6 +16,7 @@ #include "PreparedModel.h" +#include "Burst.h" #include "Callbacks.h" #include "Conversions.h" #include "Utils.h" @@ -90,6 +91,10 @@ PreparedModel::executeFenced(const nn::Request& /*request*/, << "IPreparedModel::executeFenced is not supported on 1.0 HAL service"; } +nn::GeneralResult<nn::SharedBurst> PreparedModel::configureExecutionBurst() const { + return Burst::create(shared_from_this()); +} + std::any PreparedModel::getUnderlyingResource() const { sp<V1_0::IPreparedModel> resource = kPreparedModel; return resource; diff --git a/neuralnetworks/1.0/utils/test/DeviceTest.cpp b/neuralnetworks/1.0/utils/test/DeviceTest.cpp new file mode 100644 index 0000000000..e881da2c85 --- /dev/null +++ b/neuralnetworks/1.0/utils/test/DeviceTest.cpp @@ -0,0 +1,524 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * 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 "MockDevice.h" +#include "MockPreparedModel.h" + +#include <android/hardware/neuralnetworks/1.0/IDevice.h> +#include <gmock/gmock.h> +#include <gtest/gtest.h> +#include <nnapi/IDevice.h> +#include <nnapi/TypeUtils.h> +#include <nnapi/Types.h> +#include <nnapi/hal/1.0/Device.h> + +#include <functional> +#include <memory> +#include <string> + +namespace android::hardware::neuralnetworks::V1_0::utils { +namespace { + +using ::testing::_; +using ::testing::Invoke; +using ::testing::InvokeWithoutArgs; + +const nn::Model kSimpleModel = { + .main = {.operands = {{.type = nn::OperandType::TENSOR_FLOAT32, + .dimensions = {1}, + .lifetime = nn::Operand::LifeTime::SUBGRAPH_INPUT}, + {.type = nn::OperandType::TENSOR_FLOAT32, + .dimensions = {1}, + .lifetime = nn::Operand::LifeTime::SUBGRAPH_OUTPUT}}, + .operations = {{.type = nn::OperationType::RELU, .inputs = {0}, .outputs = {1}}}, + .inputIndexes = {0}, + .outputIndexes = {1}}}; + +const std::string kName = "Google-MockV1"; +const std::string kInvalidName = ""; +const sp<V1_0::IDevice> kInvalidDevice; +constexpr V1_0::PerformanceInfo kNoPerformanceInfo = { + .execTime = std::numeric_limits<float>::max(), + .powerUsage = std::numeric_limits<float>::max()}; + +template <typename... Args> +auto makeCallbackReturn(Args&&... args) { + return [argPack = std::make_tuple(std::forward<Args>(args)...)](const auto& cb) { + std::apply(cb, argPack); + return Void(); + }; +} + +sp<MockDevice> createMockDevice() { + const auto mockDevice = MockDevice::create(); + + // Setup default actions for each relevant call. + const auto getCapabilities_ret = makeCallbackReturn( + V1_0::ErrorStatus::NONE, V1_0::Capabilities{ + .float32Performance = kNoPerformanceInfo, + .quantized8Performance = kNoPerformanceInfo, + }); + + // Setup default actions for each relevant call. + ON_CALL(*mockDevice, getCapabilities(_)).WillByDefault(Invoke(getCapabilities_ret)); + + // These EXPECT_CALL(...).Times(testing::AnyNumber()) calls are to suppress warnings on the + // uninteresting methods calls. + EXPECT_CALL(*mockDevice, getCapabilities(_)).Times(testing::AnyNumber()); + + return mockDevice; +} + +auto makePreparedModelReturn(V1_0::ErrorStatus launchStatus, V1_0::ErrorStatus returnStatus, + const sp<V1_0::utils::MockPreparedModel>& preparedModel) { + return [launchStatus, returnStatus, preparedModel](const V1_0::Model& /*model*/, + const sp<V1_0::IPreparedModelCallback>& cb) + -> hardware::Return<V1_0::ErrorStatus> { + cb->notify(returnStatus, preparedModel).isOk(); + return launchStatus; + }; +} + +std::function<hardware::Status()> makeTransportFailure(status_t status) { + return [status] { return hardware::Status::fromStatusT(status); }; +} + +const auto makeGeneralTransportFailure = makeTransportFailure(NO_MEMORY); +const auto makeDeadObjectFailure = makeTransportFailure(DEAD_OBJECT); + +} // namespace + +TEST(DeviceTest, invalidName) { + // run test + const auto device = MockDevice::create(); + const auto result = Device::create(kInvalidName, device); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::INVALID_ARGUMENT); +} + +TEST(DeviceTest, invalidDevice) { + // run test + const auto result = Device::create(kName, kInvalidDevice); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::INVALID_ARGUMENT); +} + +TEST(DeviceTest, getCapabilitiesError) { + // setup call + const auto mockDevice = createMockDevice(); + const auto ret = makeCallbackReturn(V1_0::ErrorStatus::GENERAL_FAILURE, + V1_0::Capabilities{ + .float32Performance = kNoPerformanceInfo, + .quantized8Performance = kNoPerformanceInfo, + }); + EXPECT_CALL(*mockDevice, getCapabilities(_)).Times(1).WillOnce(Invoke(ret)); + + // run test + const auto result = Device::create(kName, mockDevice); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); +} + +TEST(DeviceTest, getCapabilitiesTransportFailure) { + // setup call + const auto mockDevice = createMockDevice(); + EXPECT_CALL(*mockDevice, getCapabilities(_)) + .Times(1) + .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure)); + + // run test + const auto result = Device::create(kName, mockDevice); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); +} + +TEST(DeviceTest, getCapabilitiesDeadObject) { + // setup call + const auto mockDevice = createMockDevice(); + EXPECT_CALL(*mockDevice, getCapabilities(_)) + .Times(1) + .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure)); + + // run test + const auto result = Device::create(kName, mockDevice); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT); +} + +TEST(DeviceTest, linkToDeathError) { + // setup call + const auto mockDevice = createMockDevice(); + const auto ret = []() -> Return<bool> { return false; }; + EXPECT_CALL(*mockDevice, linkToDeathRet()).Times(1).WillOnce(InvokeWithoutArgs(ret)); + + // run test + const auto result = Device::create(kName, mockDevice); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); +} + +TEST(DeviceTest, linkToDeathTransportFailure) { + // setup call + const auto mockDevice = createMockDevice(); + EXPECT_CALL(*mockDevice, linkToDeathRet()) + .Times(1) + .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure)); + + // run test + const auto result = Device::create(kName, mockDevice); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); +} + +TEST(DeviceTest, linkToDeathDeadObject) { + // setup call + const auto mockDevice = createMockDevice(); + EXPECT_CALL(*mockDevice, linkToDeathRet()) + .Times(1) + .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure)); + + // run test + const auto result = Device::create(kName, mockDevice); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT); +} + +TEST(DeviceTest, getName) { + // setup call + const auto mockDevice = createMockDevice(); + const auto device = Device::create(kName, mockDevice).value(); + + // run test + const auto& name = device->getName(); + + // verify result + EXPECT_EQ(name, kName); +} + +TEST(DeviceTest, getFeatureLevel) { + // setup call + const auto mockDevice = createMockDevice(); + const auto device = Device::create(kName, mockDevice).value(); + + // run test + const auto featureLevel = device->getFeatureLevel(); + + // verify result + EXPECT_EQ(featureLevel, nn::Version::ANDROID_OC_MR1); +} + +TEST(DeviceTest, getCachedData) { + // setup call + const auto mockDevice = createMockDevice(); + const auto result = Device::create(kName, mockDevice); + ASSERT_TRUE(result.has_value()) + << "Failed with " << result.error().code << ": " << result.error().message; + const auto& device = result.value(); + + // run test and verify results + EXPECT_EQ(device->getVersionString(), device->getVersionString()); + EXPECT_EQ(device->getType(), device->getType()); + EXPECT_EQ(device->getSupportedExtensions(), device->getSupportedExtensions()); + EXPECT_EQ(device->getNumberOfCacheFilesNeeded(), device->getNumberOfCacheFilesNeeded()); + EXPECT_EQ(device->getCapabilities(), device->getCapabilities()); +} + +TEST(DeviceTest, wait) { + // setup call + const auto mockDevice = createMockDevice(); + const auto ret = []() -> Return<void> { return {}; }; + EXPECT_CALL(*mockDevice, ping()).Times(1).WillOnce(InvokeWithoutArgs(ret)); + const auto device = Device::create(kName, mockDevice).value(); + + // run test + const auto result = device->wait(); + + // verify result + ASSERT_TRUE(result.has_value()) + << "Failed with " << result.error().code << ": " << result.error().message; +} + +TEST(DeviceTest, waitTransportFailure) { + // setup call + const auto mockDevice = createMockDevice(); + EXPECT_CALL(*mockDevice, ping()) + .Times(1) + .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure)); + const auto device = Device::create(kName, mockDevice).value(); + + // run test + const auto result = device->wait(); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); +} + +TEST(DeviceTest, waitDeadObject) { + // setup call + const auto mockDevice = createMockDevice(); + EXPECT_CALL(*mockDevice, ping()).Times(1).WillOnce(InvokeWithoutArgs(makeDeadObjectFailure)); + const auto device = Device::create(kName, mockDevice).value(); + + // run test + const auto result = device->wait(); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT); +} + +TEST(DeviceTest, getSupportedOperations) { + // setup call + const auto mockDevice = createMockDevice(); + const auto device = Device::create(kName, mockDevice).value(); + const auto ret = [](const auto& model, const auto& cb) { + cb(V1_0::ErrorStatus::NONE, std::vector<bool>(model.operations.size(), true)); + return hardware::Void(); + }; + EXPECT_CALL(*mockDevice, getSupportedOperations(_, _)).Times(1).WillOnce(Invoke(ret)); + + // run test + const auto result = device->getSupportedOperations(kSimpleModel); + + // verify result + ASSERT_TRUE(result.has_value()) + << "Failed with " << result.error().code << ": " << result.error().message; + const auto& supportedOperations = result.value(); + EXPECT_EQ(supportedOperations.size(), kSimpleModel.main.operations.size()); + EXPECT_THAT(supportedOperations, Each(testing::IsTrue())); +} + +TEST(DeviceTest, getSupportedOperationsError) { + // setup call + const auto mockDevice = createMockDevice(); + const auto device = Device::create(kName, mockDevice).value(); + const auto ret = [](const auto& /*model*/, const auto& cb) { + cb(V1_0::ErrorStatus::GENERAL_FAILURE, {}); + return hardware::Void(); + }; + EXPECT_CALL(*mockDevice, getSupportedOperations(_, _)).Times(1).WillOnce(Invoke(ret)); + + // run test + const auto result = device->getSupportedOperations(kSimpleModel); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); +} + +TEST(DeviceTest, getSupportedOperationsTransportFailure) { + // setup call + const auto mockDevice = createMockDevice(); + const auto device = Device::create(kName, mockDevice).value(); + EXPECT_CALL(*mockDevice, getSupportedOperations(_, _)) + .Times(1) + .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure)); + + // run test + const auto result = device->getSupportedOperations(kSimpleModel); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); +} + +TEST(DeviceTest, getSupportedOperationsDeadObject) { + // setup call + const auto mockDevice = createMockDevice(); + const auto device = Device::create(kName, mockDevice).value(); + EXPECT_CALL(*mockDevice, getSupportedOperations(_, _)) + .Times(1) + .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure)); + + // run test + const auto result = device->getSupportedOperations(kSimpleModel); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT); +} + +TEST(DeviceTest, prepareModel) { + // setup call + const auto mockDevice = createMockDevice(); + const auto device = Device::create(kName, mockDevice).value(); + const auto mockPreparedModel = V1_0::utils::MockPreparedModel::create(); + EXPECT_CALL(*mockDevice, prepareModel(_, _)) + .Times(1) + .WillOnce(Invoke(makePreparedModelReturn(V1_0::ErrorStatus::NONE, + V1_0::ErrorStatus::NONE, mockPreparedModel))); + + // run test + const auto result = device->prepareModel(kSimpleModel, nn::ExecutionPreference::DEFAULT, + nn::Priority::DEFAULT, {}, {}, {}, {}); + + // verify result + ASSERT_TRUE(result.has_value()) + << "Failed with " << result.error().code << ": " << result.error().message; + EXPECT_NE(result.value(), nullptr); +} + +TEST(DeviceTest, prepareModelLaunchError) { + // setup call + const auto mockDevice = createMockDevice(); + const auto device = Device::create(kName, mockDevice).value(); + EXPECT_CALL(*mockDevice, prepareModel(_, _)) + .Times(1) + .WillOnce(Invoke(makePreparedModelReturn(V1_0::ErrorStatus::GENERAL_FAILURE, + V1_0::ErrorStatus::GENERAL_FAILURE, nullptr))); + + // run test + const auto result = device->prepareModel(kSimpleModel, nn::ExecutionPreference::DEFAULT, + nn::Priority::DEFAULT, {}, {}, {}, {}); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); +} + +TEST(DeviceTest, prepareModelReturnError) { + // setup call + const auto mockDevice = createMockDevice(); + const auto device = Device::create(kName, mockDevice).value(); + EXPECT_CALL(*mockDevice, prepareModel(_, _)) + .Times(1) + .WillOnce(Invoke(makePreparedModelReturn(V1_0::ErrorStatus::NONE, + V1_0::ErrorStatus::GENERAL_FAILURE, nullptr))); + + // run test + const auto result = device->prepareModel(kSimpleModel, nn::ExecutionPreference::DEFAULT, + nn::Priority::DEFAULT, {}, {}, {}, {}); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); +} + +TEST(DeviceTest, prepareModelNullptrError) { + // setup call + const auto mockDevice = createMockDevice(); + const auto device = Device::create(kName, mockDevice).value(); + EXPECT_CALL(*mockDevice, prepareModel(_, _)) + .Times(1) + .WillOnce(Invoke(makePreparedModelReturn(V1_0::ErrorStatus::NONE, + V1_0::ErrorStatus::NONE, nullptr))); + + // run test + const auto result = device->prepareModel(kSimpleModel, nn::ExecutionPreference::DEFAULT, + nn::Priority::DEFAULT, {}, {}, {}, {}); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); +} + +TEST(DeviceTest, prepareModelTransportFailure) { + // setup call + const auto mockDevice = createMockDevice(); + const auto device = Device::create(kName, mockDevice).value(); + EXPECT_CALL(*mockDevice, prepareModel(_, _)) + .Times(1) + .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure)); + + // run test + const auto result = device->prepareModel(kSimpleModel, nn::ExecutionPreference::DEFAULT, + nn::Priority::DEFAULT, {}, {}, {}, {}); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); +} + +TEST(DeviceTest, prepareModelDeadObject) { + // setup call + const auto mockDevice = createMockDevice(); + const auto device = Device::create(kName, mockDevice).value(); + EXPECT_CALL(*mockDevice, prepareModel(_, _)) + .Times(1) + .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure)); + + // run test + const auto result = device->prepareModel(kSimpleModel, nn::ExecutionPreference::DEFAULT, + nn::Priority::DEFAULT, {}, {}, {}, {}); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT); +} + +TEST(DeviceTest, prepareModelAsyncCrash) { + // setup test + const auto mockDevice = createMockDevice(); + const auto device = Device::create(kName, mockDevice).value(); + const auto ret = [&mockDevice]() -> hardware::Return<V1_0::ErrorStatus> { + mockDevice->simulateCrash(); + return V1_0::ErrorStatus::NONE; + }; + EXPECT_CALL(*mockDevice, prepareModel(_, _)).Times(1).WillOnce(InvokeWithoutArgs(ret)); + + // run test + const auto result = device->prepareModel(kSimpleModel, nn::ExecutionPreference::DEFAULT, + nn::Priority::DEFAULT, {}, {}, {}, {}); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT); +} + +TEST(DeviceTest, prepareModelFromCacheNotSupported) { + // setup call + const auto mockDevice = createMockDevice(); + const auto device = Device::create(kName, mockDevice).value(); + + // run test + const auto result = device->prepareModelFromCache({}, {}, {}, {}); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); +} + +TEST(DeviceTest, allocateNotSupported) { + // setup call + const auto mockDevice = createMockDevice(); + const auto device = Device::create(kName, mockDevice).value(); + + // run test + const auto result = device->allocate({}, {}, {}, {}); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); +} + +} // namespace android::hardware::neuralnetworks::V1_0::utils diff --git a/neuralnetworks/1.0/utils/test/MockDevice.h b/neuralnetworks/1.0/utils/test/MockDevice.h new file mode 100644 index 0000000000..0fb59e3a63 --- /dev/null +++ b/neuralnetworks/1.0/utils/test/MockDevice.h @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * 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 ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_0_UTILS_TEST_MOCK_DEVICE +#define ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_0_UTILS_TEST_MOCK_DEVICE + +#include <android/hardware/neuralnetworks/1.0/IDevice.h> +#include <gmock/gmock.h> +#include <gtest/gtest.h> +#include <hidl/Status.h> + +namespace android::hardware::neuralnetworks::V1_0::utils { + +class MockDevice final : public IDevice { + public: + static sp<MockDevice> create(); + + // IBase methods below. + MOCK_METHOD(Return<void>, ping, (), (override)); + MOCK_METHOD(Return<bool>, linkToDeathRet, ()); + Return<bool> linkToDeath(const sp<hidl_death_recipient>& recipient, uint64_t /*cookie*/); + + // V1_0 methods below. + MOCK_METHOD(Return<void>, getCapabilities, (getCapabilities_cb cb), (override)); + MOCK_METHOD(Return<void>, getSupportedOperations, + (const V1_0::Model& model, getSupportedOperations_cb cb), (override)); + MOCK_METHOD(Return<V1_0::ErrorStatus>, prepareModel, + (const V1_0::Model& model, const sp<V1_0::IPreparedModelCallback>& callback), + (override)); + MOCK_METHOD(Return<V1_0::DeviceStatus>, getStatus, (), (override)); + + // Helper methods. + void simulateCrash(); + + private: + sp<hidl_death_recipient> mDeathRecipient; +}; + +inline sp<MockDevice> MockDevice::create() { + auto mockDevice = sp<MockDevice>::make(); + + // Setup default actions for each relevant call. + const auto ret = []() -> Return<bool> { return true; }; + + // Setup default actions for each relevant call. + ON_CALL(*mockDevice, linkToDeathRet()).WillByDefault(testing::Invoke(ret)); + + // These EXPECT_CALL(...).Times(testing::AnyNumber()) calls are to suppress warnings on the + // uninteresting methods calls. + EXPECT_CALL(*mockDevice, linkToDeathRet()).Times(testing::AnyNumber()); + + return mockDevice; +} + +inline Return<bool> MockDevice::linkToDeath(const sp<hidl_death_recipient>& recipient, + uint64_t /*cookie*/) { + mDeathRecipient = recipient; + return linkToDeathRet(); +} + +inline void MockDevice::simulateCrash() { + ASSERT_NE(nullptr, mDeathRecipient.get()); + + // Currently, the utils::Device will not use the `cookie` or `who` arguments, so we pass in 0 + // and nullptr for these arguments instead. Normally, they are used by the hidl_death_recipient + // to determine which object is dead. However, the utils::Device code only pairs a single death + // recipient with a single HIDL interface object, so these arguments are redundant. + mDeathRecipient->serviceDied(0, nullptr); +} + +} // namespace android::hardware::neuralnetworks::V1_0::utils + +#endif // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_0_UTILS_TEST_MOCK_DEVICE diff --git a/neuralnetworks/1.0/utils/test/MockPreparedModel.h b/neuralnetworks/1.0/utils/test/MockPreparedModel.h new file mode 100644 index 0000000000..7a48a834ac --- /dev/null +++ b/neuralnetworks/1.0/utils/test/MockPreparedModel.h @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * 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 ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_0_UTILS_TEST_MOCK_PREPARED_MODEL +#define ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_0_UTILS_TEST_MOCK_PREPARED_MODEL + +#include <android/hardware/neuralnetworks/1.0/IPreparedModel.h> +#include <gmock/gmock.h> +#include <gtest/gtest.h> +#include <hidl/HidlSupport.h> +#include <hidl/Status.h> + +namespace android::hardware::neuralnetworks::V1_0::utils { + +class MockPreparedModel final : public IPreparedModel { + public: + static sp<MockPreparedModel> create(); + + // IBase methods below. + MOCK_METHOD(Return<void>, ping, (), (override)); + MOCK_METHOD(Return<bool>, linkToDeathRet, ()); + Return<bool> linkToDeath(const sp<hidl_death_recipient>& recipient, + uint64_t /*cookie*/) override; + + // V1_0 methods below. + MOCK_METHOD(Return<V1_0::ErrorStatus>, execute, + (const V1_0::Request& request, const sp<V1_0::IExecutionCallback>& callback), + (override)); + + // Helper methods. + void simulateCrash(); + + private: + sp<hidl_death_recipient> mDeathRecipient; +}; + +inline sp<MockPreparedModel> MockPreparedModel::create() { + auto mockPreparedModel = sp<MockPreparedModel>::make(); + + // Setup default actions for each relevant call. + const auto ret = []() -> Return<bool> { return true; }; + + // Setup default actions for each relevant call. + ON_CALL(*mockPreparedModel, linkToDeathRet()).WillByDefault(testing::Invoke(ret)); + + // These EXPECT_CALL(...).Times(testing::AnyNumber()) calls are to suppress warnings on the + // uninteresting methods calls. + EXPECT_CALL(*mockPreparedModel, linkToDeathRet()).Times(testing::AnyNumber()); + + return mockPreparedModel; +} + +inline Return<bool> MockPreparedModel::linkToDeath(const sp<hidl_death_recipient>& recipient, + uint64_t /*cookie*/) { + mDeathRecipient = recipient; + return linkToDeathRet(); +} + +inline void MockPreparedModel::simulateCrash() { + ASSERT_NE(nullptr, mDeathRecipient.get()); + + // Currently, the utils::PreparedModel will not use the `cookie` or `who` arguments, so we pass + // in 0 and nullptr for these arguments instead. Normally, they are used by the + // hidl_death_recipient to determine which object is dead. However, the utils::PreparedModel + // code only pairs a single death recipient with a single HIDL interface object, so these + // arguments are redundant. + mDeathRecipient->serviceDied(0, nullptr); +} + +} // namespace android::hardware::neuralnetworks::V1_0::utils + +#endif // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_0_UTILS_TEST_MOCK_PREPARED_MODEL diff --git a/neuralnetworks/1.0/utils/test/PreparedModelTest.cpp b/neuralnetworks/1.0/utils/test/PreparedModelTest.cpp new file mode 100644 index 0000000000..a5cbc72a71 --- /dev/null +++ b/neuralnetworks/1.0/utils/test/PreparedModelTest.cpp @@ -0,0 +1,243 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * 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 "MockPreparedModel.h" + +#include <android/hardware/neuralnetworks/1.0/IExecutionCallback.h> +#include <gmock/gmock.h> +#include <gtest/gtest.h> +#include <nnapi/IPreparedModel.h> +#include <nnapi/TypeUtils.h> +#include <nnapi/Types.h> +#include <nnapi/hal/1.0/PreparedModel.h> + +#include <functional> +#include <memory> + +namespace android::hardware::neuralnetworks::V1_0::utils { +namespace { + +using ::testing::_; +using ::testing::Invoke; +using ::testing::InvokeWithoutArgs; + +const sp<V1_0::IPreparedModel> kInvalidPreparedModel; + +sp<MockPreparedModel> createMockPreparedModel() { + return MockPreparedModel::create(); +} + +auto makeExecute(V1_0::ErrorStatus launchStatus, V1_0::ErrorStatus returnStatus) { + return [launchStatus, returnStatus]( + const V1_0::Request& /*request*/, + const sp<V1_0::IExecutionCallback>& cb) -> Return<V1_0::ErrorStatus> { + cb->notify(returnStatus); + return launchStatus; + }; +} + +std::function<hardware::Status()> makeTransportFailure(status_t status) { + return [status] { return hardware::Status::fromStatusT(status); }; +} + +const auto makeGeneralTransportFailure = makeTransportFailure(NO_MEMORY); +const auto makeDeadObjectFailure = makeTransportFailure(DEAD_OBJECT); + +} // namespace + +TEST(PreparedModelTest, invalidPreparedModel) { + // run test + const auto result = PreparedModel::create(kInvalidPreparedModel); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); +} + +TEST(PreparedModelTest, linkToDeathError) { + // setup call + const auto mockPreparedModel = createMockPreparedModel(); + const auto ret = []() -> Return<bool> { return false; }; + EXPECT_CALL(*mockPreparedModel, linkToDeathRet()).Times(1).WillOnce(InvokeWithoutArgs(ret)); + + // run test + const auto result = PreparedModel::create(mockPreparedModel); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); +} + +TEST(PreparedModelTest, linkToDeathTransportFailure) { + // setup call + const auto mockPreparedModel = createMockPreparedModel(); + EXPECT_CALL(*mockPreparedModel, linkToDeathRet()) + .Times(1) + .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure)); + + // run test + const auto result = PreparedModel::create(mockPreparedModel); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); +} + +TEST(PreparedModelTest, linkToDeathDeadObject) { + // setup call + const auto mockPreparedModel = createMockPreparedModel(); + EXPECT_CALL(*mockPreparedModel, linkToDeathRet()) + .Times(1) + .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure)); + + // run test + const auto result = PreparedModel::create(mockPreparedModel); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT); +} + +TEST(PreparedModelTest, execute) { + // setup call + const auto mockPreparedModel = createMockPreparedModel(); + const auto preparedModel = PreparedModel::create(mockPreparedModel).value(); + EXPECT_CALL(*mockPreparedModel, execute(_, _)) + .Times(1) + .WillOnce(Invoke(makeExecute(V1_0::ErrorStatus::NONE, V1_0::ErrorStatus::NONE))); + + // run test + const auto result = preparedModel->execute({}, {}, {}, {}); + + // verify result + EXPECT_TRUE(result.has_value()) + << "Failed with " << result.error().code << ": " << result.error().message; +} + +TEST(PreparedModelTest, executeLaunchError) { + // setup test + const auto mockPreparedModel = createMockPreparedModel(); + const auto preparedModel = PreparedModel::create(mockPreparedModel).value(); + EXPECT_CALL(*mockPreparedModel, execute(_, _)) + .Times(1) + .WillOnce(Invoke(makeExecute(V1_0::ErrorStatus::GENERAL_FAILURE, + V1_0::ErrorStatus::GENERAL_FAILURE))); + + // run test + const auto result = preparedModel->execute({}, {}, {}, {}); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); +} + +TEST(PreparedModelTest, executeReturnError) { + // setup test + const auto mockPreparedModel = createMockPreparedModel(); + const auto preparedModel = PreparedModel::create(mockPreparedModel).value(); + EXPECT_CALL(*mockPreparedModel, execute(_, _)) + .Times(1) + .WillOnce(Invoke( + makeExecute(V1_0::ErrorStatus::NONE, V1_0::ErrorStatus::GENERAL_FAILURE))); + + // run test + const auto result = preparedModel->execute({}, {}, {}, {}); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); +} + +TEST(PreparedModelTest, executeTransportFailure) { + // setup test + const auto mockPreparedModel = createMockPreparedModel(); + const auto preparedModel = PreparedModel::create(mockPreparedModel).value(); + EXPECT_CALL(*mockPreparedModel, execute(_, _)) + .Times(1) + .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure)); + + // run test + const auto result = preparedModel->execute({}, {}, {}, {}); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); +} + +TEST(PreparedModelTest, executeDeadObject) { + // setup test + const auto mockPreparedModel = createMockPreparedModel(); + const auto preparedModel = PreparedModel::create(mockPreparedModel).value(); + EXPECT_CALL(*mockPreparedModel, execute(_, _)) + .Times(1) + .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure)); + + // run test + const auto result = preparedModel->execute({}, {}, {}, {}); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT); +} + +TEST(PreparedModelTest, executeCrash) { + // setup test + const auto mockPreparedModel = createMockPreparedModel(); + const auto preparedModel = PreparedModel::create(mockPreparedModel).value(); + const auto ret = [&mockPreparedModel]() -> hardware::Return<V1_0::ErrorStatus> { + mockPreparedModel->simulateCrash(); + return V1_0::ErrorStatus::NONE; + }; + EXPECT_CALL(*mockPreparedModel, execute(_, _)).Times(1).WillOnce(InvokeWithoutArgs(ret)); + + // run test + const auto result = preparedModel->execute({}, {}, {}, {}); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT); +} + +TEST(PreparedModelTest, executeFencedNotSupported) { + // setup test + const auto mockPreparedModel = createMockPreparedModel(); + const auto preparedModel = PreparedModel::create(mockPreparedModel).value(); + + // run test + const auto result = preparedModel->executeFenced({}, {}, {}, {}, {}, {}); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); +} + +// TODO: test burst execution if/when it is added to nn::IPreparedModel. + +TEST(PreparedModelTest, getUnderlyingResource) { + // setup test + const auto mockPreparedModel = createMockPreparedModel(); + const auto preparedModel = PreparedModel::create(mockPreparedModel).value(); + + // run test + const auto resource = preparedModel->getUnderlyingResource(); + + // verify resource + const sp<V1_0::IPreparedModel>* maybeMock = std::any_cast<sp<V1_0::IPreparedModel>>(&resource); + ASSERT_NE(maybeMock, nullptr); + EXPECT_EQ(maybeMock->get(), mockPreparedModel.get()); +} + +} // namespace android::hardware::neuralnetworks::V1_0::utils diff --git a/neuralnetworks/1.1/utils/Android.bp b/neuralnetworks/1.1/utils/Android.bp index 909575b634..fe0c80ab45 100644 --- a/neuralnetworks/1.1/utils/Android.bp +++ b/neuralnetworks/1.1/utils/Android.bp @@ -34,3 +34,31 @@ cc_library_static { "neuralnetworks_utils_hal_common", ], } + +cc_test { + name: "neuralnetworks_utils_hal_1_1_test", + srcs: ["test/*.cpp"], + static_libs: [ + "android.hardware.neuralnetworks@1.0", + "android.hardware.neuralnetworks@1.1", + "libgmock", + "libneuralnetworks_common", + "neuralnetworks_types", + "neuralnetworks_utils_hal_common", + "neuralnetworks_utils_hal_1_0", + "neuralnetworks_utils_hal_1_1", + ], + shared_libs: [ + "android.hidl.allocator@1.0", + "android.hidl.memory@1.0", + "libbase", + "libcutils", + "libfmq", + "libhidlbase", + "libhidlmemory", + "liblog", + "libnativewindow", + "libutils", + ], + test_suites: ["general-tests"], +} diff --git a/neuralnetworks/1.1/utils/include/nnapi/hal/1.1/Device.h b/neuralnetworks/1.1/utils/include/nnapi/hal/1.1/Device.h index 5e224b5018..3aec8ee497 100644 --- a/neuralnetworks/1.1/utils/include/nnapi/hal/1.1/Device.h +++ b/neuralnetworks/1.1/utils/include/nnapi/hal/1.1/Device.h @@ -52,6 +52,7 @@ class Device final : public nn::IDevice { const std::string& getVersionString() const override; nn::Version getFeatureLevel() const override; nn::DeviceType getType() const override; + bool isUpdatable() const override; const std::vector<nn::Extension>& getSupportedExtensions() const override; const nn::Capabilities& getCapabilities() const override; std::pair<uint32_t, uint32_t> getNumberOfCacheFilesNeeded() const override; diff --git a/neuralnetworks/1.1/utils/src/Device.cpp b/neuralnetworks/1.1/utils/src/Device.cpp index 3197ef4ac3..d2ef57f18e 100644 --- a/neuralnetworks/1.1/utils/src/Device.cpp +++ b/neuralnetworks/1.1/utils/src/Device.cpp @@ -106,6 +106,10 @@ nn::DeviceType Device::getType() const { return nn::DeviceType::UNKNOWN; } +bool Device::isUpdatable() const { + return false; +} + const std::vector<nn::Extension>& Device::getSupportedExtensions() const { return kExtensions; } diff --git a/neuralnetworks/1.1/utils/test/DeviceTest.cpp b/neuralnetworks/1.1/utils/test/DeviceTest.cpp new file mode 100644 index 0000000000..41e0e3050d --- /dev/null +++ b/neuralnetworks/1.1/utils/test/DeviceTest.cpp @@ -0,0 +1,534 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * 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 "MockDevice.h" +#include "MockPreparedModel.h" + +#include <android/hardware/neuralnetworks/1.1/IDevice.h> +#include <gmock/gmock.h> +#include <gtest/gtest.h> +#include <nnapi/IDevice.h> +#include <nnapi/TypeUtils.h> +#include <nnapi/Types.h> +#include <nnapi/hal/1.1/Device.h> + +#include <functional> +#include <memory> +#include <string> + +namespace android::hardware::neuralnetworks::V1_1::utils { +namespace { + +using ::testing::_; +using ::testing::Invoke; +using ::testing::InvokeWithoutArgs; + +const nn::Model kSimpleModel = { + .main = {.operands = {{.type = nn::OperandType::TENSOR_FLOAT32, + .dimensions = {1}, + .lifetime = nn::Operand::LifeTime::SUBGRAPH_INPUT}, + {.type = nn::OperandType::TENSOR_FLOAT32, + .dimensions = {1}, + .lifetime = nn::Operand::LifeTime::SUBGRAPH_OUTPUT}}, + .operations = {{.type = nn::OperationType::RELU, .inputs = {0}, .outputs = {1}}}, + .inputIndexes = {0}, + .outputIndexes = {1}}}; + +const std::string kName = "Google-MockV1"; +const std::string kInvalidName = ""; +const sp<V1_1::IDevice> kInvalidDevice; +constexpr V1_0::PerformanceInfo kNoPerformanceInfo = { + .execTime = std::numeric_limits<float>::max(), + .powerUsage = std::numeric_limits<float>::max()}; + +template <typename... Args> +auto makeCallbackReturn(Args&&... args) { + return [argPack = std::make_tuple(std::forward<Args>(args)...)](const auto& cb) { + std::apply(cb, argPack); + return Void(); + }; +} + +sp<MockDevice> createMockDevice() { + const auto mockDevice = MockDevice::create(); + + // Setup default actions for each relevant call. + const auto getCapabilities_ret = + makeCallbackReturn(V1_0::ErrorStatus::NONE, + V1_1::Capabilities{ + .float32Performance = kNoPerformanceInfo, + .quantized8Performance = kNoPerformanceInfo, + .relaxedFloat32toFloat16Performance = kNoPerformanceInfo, + }); + + // Setup default actions for each relevant call. + ON_CALL(*mockDevice, getCapabilities_1_1(_)).WillByDefault(Invoke(getCapabilities_ret)); + + // Ensure that older calls are not used. + EXPECT_CALL(*mockDevice, getCapabilities(_)).Times(0); + EXPECT_CALL(*mockDevice, getSupportedOperations(_, _)).Times(0); + EXPECT_CALL(*mockDevice, prepareModel(_, _)).Times(0); + + // These EXPECT_CALL(...).Times(testing::AnyNumber()) calls are to suppress warnings on the + // uninteresting methods calls. + EXPECT_CALL(*mockDevice, getCapabilities_1_1(_)).Times(testing::AnyNumber()); + + return mockDevice; +} + +auto makePreparedModelReturn(V1_0::ErrorStatus launchStatus, V1_0::ErrorStatus returnStatus, + const sp<V1_0::utils::MockPreparedModel>& preparedModel) { + return [launchStatus, returnStatus, preparedModel](const V1_1::Model& /*model*/, + V1_1::ExecutionPreference /*preference*/, + const sp<V1_0::IPreparedModelCallback>& cb) + -> hardware::Return<V1_0::ErrorStatus> { + cb->notify(returnStatus, preparedModel).isOk(); + return launchStatus; + }; +} + +std::function<hardware::Status()> makeTransportFailure(status_t status) { + return [status] { return hardware::Status::fromStatusT(status); }; +} + +const auto makeGeneralTransportFailure = makeTransportFailure(NO_MEMORY); +const auto makeDeadObjectFailure = makeTransportFailure(DEAD_OBJECT); + +} // namespace + +TEST(DeviceTest, invalidName) { + // run test + const auto device = MockDevice::create(); + const auto result = Device::create(kInvalidName, device); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::INVALID_ARGUMENT); +} + +TEST(DeviceTest, invalidDevice) { + // run test + const auto result = Device::create(kName, kInvalidDevice); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::INVALID_ARGUMENT); +} + +TEST(DeviceTest, getCapabilitiesError) { + // setup call + const auto mockDevice = createMockDevice(); + const auto ret = + makeCallbackReturn(V1_0::ErrorStatus::GENERAL_FAILURE, + V1_1::Capabilities{ + .float32Performance = kNoPerformanceInfo, + .quantized8Performance = kNoPerformanceInfo, + .relaxedFloat32toFloat16Performance = kNoPerformanceInfo, + }); + EXPECT_CALL(*mockDevice, getCapabilities_1_1(_)).Times(1).WillOnce(Invoke(ret)); + + // run test + const auto result = Device::create(kName, mockDevice); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); +} + +TEST(DeviceTest, getCapabilitiesTransportFailure) { + // setup call + const auto mockDevice = createMockDevice(); + EXPECT_CALL(*mockDevice, getCapabilities_1_1(_)) + .Times(1) + .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure)); + + // run test + const auto result = Device::create(kName, mockDevice); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); +} + +TEST(DeviceTest, getCapabilitiesDeadObject) { + // setup call + const auto mockDevice = createMockDevice(); + EXPECT_CALL(*mockDevice, getCapabilities_1_1(_)) + .Times(1) + .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure)); + + // run test + const auto result = Device::create(kName, mockDevice); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT); +} + +TEST(DeviceTest, linkToDeathError) { + // setup call + const auto mockDevice = createMockDevice(); + const auto ret = []() -> Return<bool> { return false; }; + EXPECT_CALL(*mockDevice, linkToDeathRet()).Times(1).WillOnce(InvokeWithoutArgs(ret)); + + // run test + const auto result = Device::create(kName, mockDevice); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); +} + +TEST(DeviceTest, linkToDeathTransportFailure) { + // setup call + const auto mockDevice = createMockDevice(); + EXPECT_CALL(*mockDevice, linkToDeathRet()) + .Times(1) + .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure)); + + // run test + const auto result = Device::create(kName, mockDevice); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); +} + +TEST(DeviceTest, linkToDeathDeadObject) { + // setup call + const auto mockDevice = createMockDevice(); + EXPECT_CALL(*mockDevice, linkToDeathRet()) + .Times(1) + .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure)); + + // run test + const auto result = Device::create(kName, mockDevice); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT); +} + +TEST(DeviceTest, getName) { + // setup call + const auto mockDevice = createMockDevice(); + const auto device = Device::create(kName, mockDevice).value(); + + // run test + const auto& name = device->getName(); + + // verify result + EXPECT_EQ(name, kName); +} + +TEST(DeviceTest, getFeatureLevel) { + // setup call + const auto mockDevice = createMockDevice(); + const auto device = Device::create(kName, mockDevice).value(); + + // run test + const auto featureLevel = device->getFeatureLevel(); + + // verify result + EXPECT_EQ(featureLevel, nn::Version::ANDROID_P); +} + +TEST(DeviceTest, getCachedData) { + // setup call + const auto mockDevice = createMockDevice(); + const auto result = Device::create(kName, mockDevice); + ASSERT_TRUE(result.has_value()) + << "Failed with " << result.error().code << ": " << result.error().message; + const auto& device = result.value(); + + // run test and verify results + EXPECT_EQ(device->getVersionString(), device->getVersionString()); + EXPECT_EQ(device->getType(), device->getType()); + EXPECT_EQ(device->getSupportedExtensions(), device->getSupportedExtensions()); + EXPECT_EQ(device->getNumberOfCacheFilesNeeded(), device->getNumberOfCacheFilesNeeded()); + EXPECT_EQ(device->getCapabilities(), device->getCapabilities()); +} + +TEST(DeviceTest, wait) { + // setup call + const auto mockDevice = createMockDevice(); + const auto ret = []() -> Return<void> { return {}; }; + EXPECT_CALL(*mockDevice, ping()).Times(1).WillOnce(InvokeWithoutArgs(ret)); + const auto device = Device::create(kName, mockDevice).value(); + + // run test + const auto result = device->wait(); + + // verify result + ASSERT_TRUE(result.has_value()) + << "Failed with " << result.error().code << ": " << result.error().message; +} + +TEST(DeviceTest, waitTransportFailure) { + // setup call + const auto mockDevice = createMockDevice(); + EXPECT_CALL(*mockDevice, ping()) + .Times(1) + .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure)); + const auto device = Device::create(kName, mockDevice).value(); + + // run test + const auto result = device->wait(); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); +} + +TEST(DeviceTest, waitDeadObject) { + // setup call + const auto mockDevice = createMockDevice(); + EXPECT_CALL(*mockDevice, ping()).Times(1).WillOnce(InvokeWithoutArgs(makeDeadObjectFailure)); + const auto device = Device::create(kName, mockDevice).value(); + + // run test + const auto result = device->wait(); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT); +} + +TEST(DeviceTest, getSupportedOperations) { + // setup call + const auto mockDevice = createMockDevice(); + const auto device = Device::create(kName, mockDevice).value(); + const auto ret = [](const auto& model, const auto& cb) { + cb(V1_0::ErrorStatus::NONE, std::vector<bool>(model.operations.size(), true)); + return hardware::Void(); + }; + EXPECT_CALL(*mockDevice, getSupportedOperations_1_1(_, _)).Times(1).WillOnce(Invoke(ret)); + + // run test + const auto result = device->getSupportedOperations(kSimpleModel); + + // verify result + ASSERT_TRUE(result.has_value()) + << "Failed with " << result.error().code << ": " << result.error().message; + const auto& supportedOperations = result.value(); + EXPECT_EQ(supportedOperations.size(), kSimpleModel.main.operations.size()); + EXPECT_THAT(supportedOperations, Each(testing::IsTrue())); +} + +TEST(DeviceTest, getSupportedOperationsError) { + // setup call + const auto mockDevice = createMockDevice(); + const auto device = Device::create(kName, mockDevice).value(); + const auto ret = [](const auto& /*model*/, const auto& cb) { + cb(V1_0::ErrorStatus::GENERAL_FAILURE, {}); + return hardware::Void(); + }; + EXPECT_CALL(*mockDevice, getSupportedOperations_1_1(_, _)).Times(1).WillOnce(Invoke(ret)); + + // run test + const auto result = device->getSupportedOperations(kSimpleModel); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); +} + +TEST(DeviceTest, getSupportedOperationsTransportFailure) { + // setup call + const auto mockDevice = createMockDevice(); + const auto device = Device::create(kName, mockDevice).value(); + EXPECT_CALL(*mockDevice, getSupportedOperations_1_1(_, _)) + .Times(1) + .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure)); + + // run test + const auto result = device->getSupportedOperations(kSimpleModel); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); +} + +TEST(DeviceTest, getSupportedOperationsDeadObject) { + // setup call + const auto mockDevice = createMockDevice(); + const auto device = Device::create(kName, mockDevice).value(); + EXPECT_CALL(*mockDevice, getSupportedOperations_1_1(_, _)) + .Times(1) + .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure)); + + // run test + const auto result = device->getSupportedOperations(kSimpleModel); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT); +} + +TEST(DeviceTest, prepareModel) { + // setup call + const auto mockDevice = createMockDevice(); + const auto device = Device::create(kName, mockDevice).value(); + const auto mockPreparedModel = V1_0::utils::MockPreparedModel::create(); + EXPECT_CALL(*mockDevice, prepareModel_1_1(_, _, _)) + .Times(1) + .WillOnce(Invoke(makePreparedModelReturn(V1_0::ErrorStatus::NONE, + V1_0::ErrorStatus::NONE, mockPreparedModel))); + + // run test + const auto result = device->prepareModel(kSimpleModel, nn::ExecutionPreference::DEFAULT, + nn::Priority::DEFAULT, {}, {}, {}, {}); + + // verify result + ASSERT_TRUE(result.has_value()) + << "Failed with " << result.error().code << ": " << result.error().message; + EXPECT_NE(result.value(), nullptr); +} + +TEST(DeviceTest, prepareModelLaunchError) { + // setup call + const auto mockDevice = createMockDevice(); + const auto device = Device::create(kName, mockDevice).value(); + EXPECT_CALL(*mockDevice, prepareModel_1_1(_, _, _)) + .Times(1) + .WillOnce(Invoke(makePreparedModelReturn(V1_0::ErrorStatus::GENERAL_FAILURE, + V1_0::ErrorStatus::GENERAL_FAILURE, nullptr))); + + // run test + const auto result = device->prepareModel(kSimpleModel, nn::ExecutionPreference::DEFAULT, + nn::Priority::DEFAULT, {}, {}, {}, {}); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); +} + +TEST(DeviceTest, prepareModelReturnError) { + // setup call + const auto mockDevice = createMockDevice(); + const auto device = Device::create(kName, mockDevice).value(); + EXPECT_CALL(*mockDevice, prepareModel_1_1(_, _, _)) + .Times(1) + .WillOnce(Invoke(makePreparedModelReturn(V1_0::ErrorStatus::NONE, + V1_0::ErrorStatus::GENERAL_FAILURE, nullptr))); + + // run test + const auto result = device->prepareModel(kSimpleModel, nn::ExecutionPreference::DEFAULT, + nn::Priority::DEFAULT, {}, {}, {}, {}); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); +} + +TEST(DeviceTest, prepareModelNullptrError) { + // setup call + const auto mockDevice = createMockDevice(); + const auto device = Device::create(kName, mockDevice).value(); + EXPECT_CALL(*mockDevice, prepareModel_1_1(_, _, _)) + .Times(1) + .WillOnce(Invoke(makePreparedModelReturn(V1_0::ErrorStatus::NONE, + V1_0::ErrorStatus::NONE, nullptr))); + + // run test + const auto result = device->prepareModel(kSimpleModel, nn::ExecutionPreference::DEFAULT, + nn::Priority::DEFAULT, {}, {}, {}, {}); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); +} + +TEST(DeviceTest, prepareModelTransportFailure) { + // setup call + const auto mockDevice = createMockDevice(); + const auto device = Device::create(kName, mockDevice).value(); + EXPECT_CALL(*mockDevice, prepareModel_1_1(_, _, _)) + .Times(1) + .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure)); + + // run test + const auto result = device->prepareModel(kSimpleModel, nn::ExecutionPreference::DEFAULT, + nn::Priority::DEFAULT, {}, {}, {}, {}); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); +} + +TEST(DeviceTest, prepareModelDeadObject) { + // setup call + const auto mockDevice = createMockDevice(); + const auto device = Device::create(kName, mockDevice).value(); + EXPECT_CALL(*mockDevice, prepareModel_1_1(_, _, _)) + .Times(1) + .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure)); + + // run test + const auto result = device->prepareModel(kSimpleModel, nn::ExecutionPreference::DEFAULT, + nn::Priority::DEFAULT, {}, {}, {}, {}); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT); +} + +TEST(DeviceTest, prepareModelAsyncCrash) { + // setup test + const auto mockDevice = createMockDevice(); + const auto device = Device::create(kName, mockDevice).value(); + const auto ret = [&mockDevice]() -> hardware::Return<V1_0::ErrorStatus> { + mockDevice->simulateCrash(); + return V1_0::ErrorStatus::NONE; + }; + EXPECT_CALL(*mockDevice, prepareModel_1_1(_, _, _)).Times(1).WillOnce(InvokeWithoutArgs(ret)); + + // run test + const auto result = device->prepareModel(kSimpleModel, nn::ExecutionPreference::DEFAULT, + nn::Priority::DEFAULT, {}, {}, {}, {}); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT); +} + +TEST(DeviceTest, prepareModelFromCacheNotSupported) { + // setup call + const auto mockDevice = createMockDevice(); + const auto device = Device::create(kName, mockDevice).value(); + + // run test + const auto result = device->prepareModelFromCache({}, {}, {}, {}); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); +} + +TEST(DeviceTest, allocateNotSupported) { + // setup call + const auto mockDevice = createMockDevice(); + const auto device = Device::create(kName, mockDevice).value(); + + // run test + const auto result = device->allocate({}, {}, {}, {}); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); +} + +} // namespace android::hardware::neuralnetworks::V1_1::utils diff --git a/neuralnetworks/1.1/utils/test/MockDevice.h b/neuralnetworks/1.1/utils/test/MockDevice.h new file mode 100644 index 0000000000..3b92e58102 --- /dev/null +++ b/neuralnetworks/1.1/utils/test/MockDevice.h @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * 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 ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_1_UTILS_TEST_MOCK_DEVICE +#define ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_1_UTILS_TEST_MOCK_DEVICE + +#include <android/hardware/neuralnetworks/1.1/IDevice.h> +#include <gmock/gmock.h> +#include <gtest/gtest.h> +#include <hidl/Status.h> + +namespace android::hardware::neuralnetworks::V1_1::utils { + +class MockDevice final : public IDevice { + public: + static sp<MockDevice> create(); + + // IBase methods below. + MOCK_METHOD(Return<void>, ping, (), (override)); + MOCK_METHOD(Return<bool>, linkToDeathRet, ()); + Return<bool> linkToDeath(const sp<hidl_death_recipient>& recipient, uint64_t /*cookie*/); + + // V1_0 methods below. + MOCK_METHOD(Return<void>, getCapabilities, (getCapabilities_cb cb), (override)); + MOCK_METHOD(Return<void>, getSupportedOperations, + (const V1_0::Model& model, getSupportedOperations_cb cb), (override)); + MOCK_METHOD(Return<V1_0::ErrorStatus>, prepareModel, + (const V1_0::Model& model, const sp<V1_0::IPreparedModelCallback>& callback), + (override)); + MOCK_METHOD(Return<V1_0::DeviceStatus>, getStatus, (), (override)); + + // V1_1 methods below. + MOCK_METHOD(Return<void>, getCapabilities_1_1, (getCapabilities_1_1_cb cb), (override)); + MOCK_METHOD(Return<void>, getSupportedOperations_1_1, + (const V1_1::Model& model, getSupportedOperations_1_1_cb cb), (override)); + MOCK_METHOD(Return<V1_0::ErrorStatus>, prepareModel_1_1, + (const V1_1::Model& model, V1_1::ExecutionPreference preference, + const sp<V1_0::IPreparedModelCallback>& callback), + (override)); + + // Helper methods. + void simulateCrash(); + + private: + sp<hidl_death_recipient> mDeathRecipient; +}; + +inline sp<MockDevice> MockDevice::create() { + auto mockDevice = sp<MockDevice>::make(); + + // Setup default actions for each relevant call. + const auto ret = []() -> Return<bool> { return true; }; + + // Setup default actions for each relevant call. + ON_CALL(*mockDevice, linkToDeathRet()).WillByDefault(testing::Invoke(ret)); + + // These EXPECT_CALL(...).Times(testing::AnyNumber()) calls are to suppress warnings on the + // uninteresting methods calls. + EXPECT_CALL(*mockDevice, linkToDeathRet()).Times(testing::AnyNumber()); + + return mockDevice; +} + +inline Return<bool> MockDevice::linkToDeath(const sp<hidl_death_recipient>& recipient, + uint64_t /*cookie*/) { + mDeathRecipient = recipient; + return linkToDeathRet(); +} + +inline void MockDevice::simulateCrash() { + ASSERT_NE(nullptr, mDeathRecipient.get()); + + // Currently, the utils::Device will not use the `cookie` or `who` arguments, so we pass in 0 + // and nullptr for these arguments instead. Normally, they are used by the hidl_death_recipient + // to determine which object is dead. However, the utils::Device code only pairs a single death + // recipient with a single HIDL interface object, so these arguments are redundant. + mDeathRecipient->serviceDied(0, nullptr); +} + +} // namespace android::hardware::neuralnetworks::V1_1::utils + +#endif // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_1_UTILS_TEST_MOCK_DEVICE diff --git a/neuralnetworks/1.1/utils/test/MockPreparedModel.h b/neuralnetworks/1.1/utils/test/MockPreparedModel.h new file mode 100644 index 0000000000..aba731efa1 --- /dev/null +++ b/neuralnetworks/1.1/utils/test/MockPreparedModel.h @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * 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 ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_1_UTILS_TEST_MOCK_PREPARED_MODEL +#define ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_1_UTILS_TEST_MOCK_PREPARED_MODEL + +#include <android/hardware/neuralnetworks/1.0/IPreparedModel.h> +#include <gmock/gmock.h> +#include <gtest/gtest.h> +#include <hidl/HidlSupport.h> +#include <hidl/Status.h> + +namespace android::hardware::neuralnetworks::V1_0::utils { + +class MockPreparedModel final : public IPreparedModel { + public: + static sp<MockPreparedModel> create(); + + // V1_0 methods below. + MOCK_METHOD(Return<V1_0::ErrorStatus>, execute, + (const V1_0::Request& request, const sp<V1_0::IExecutionCallback>& callback), + (override)); +}; + +inline sp<MockPreparedModel> MockPreparedModel::create() { + return sp<MockPreparedModel>::make(); +} + +} // namespace android::hardware::neuralnetworks::V1_0::utils + +#endif // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_1_UTILS_TEST_MOCK_PREPARED_MODEL diff --git a/neuralnetworks/1.2/utils/Android.bp b/neuralnetworks/1.2/utils/Android.bp index 22e8659557..695905690e 100644 --- a/neuralnetworks/1.2/utils/Android.bp +++ b/neuralnetworks/1.2/utils/Android.bp @@ -18,6 +18,7 @@ cc_library_static { name: "neuralnetworks_utils_hal_1_2", defaults: ["neuralnetworks_utils_defaults"], srcs: ["src/*"], + exclude_srcs: ["src/ExecutionBurst*"], local_include_dirs: ["include/nnapi/hal/1.2/"], export_include_dirs: ["include"], cflags: ["-Wthread-safety"], @@ -36,3 +37,33 @@ cc_library_static { "neuralnetworks_utils_hal_common", ], } + +cc_test { + name: "neuralnetworks_utils_hal_1_2_test", + srcs: ["test/*.cpp"], + static_libs: [ + "android.hardware.neuralnetworks@1.0", + "android.hardware.neuralnetworks@1.1", + "android.hardware.neuralnetworks@1.2", + "libgmock", + "libneuralnetworks_common", + "neuralnetworks_types", + "neuralnetworks_utils_hal_common", + "neuralnetworks_utils_hal_1_0", + "neuralnetworks_utils_hal_1_1", + "neuralnetworks_utils_hal_1_2", + ], + shared_libs: [ + "android.hidl.allocator@1.0", + "android.hidl.memory@1.0", + "libbase", + "libcutils", + "libfmq", + "libhidlbase", + "libhidlmemory", + "liblog", + "libnativewindow", + "libutils", + ], + test_suites: ["general-tests"], +} diff --git a/neuralnetworks/1.2/utils/include/nnapi/hal/1.2/Device.h b/neuralnetworks/1.2/utils/include/nnapi/hal/1.2/Device.h index b4bef5ee0a..489f85749d 100644 --- a/neuralnetworks/1.2/utils/include/nnapi/hal/1.2/Device.h +++ b/neuralnetworks/1.2/utils/include/nnapi/hal/1.2/Device.h @@ -71,6 +71,7 @@ class Device final : public nn::IDevice { const std::string& getVersionString() const override; nn::Version getFeatureLevel() const override; nn::DeviceType getType() const override; + bool isUpdatable() const override; const std::vector<nn::Extension>& getSupportedExtensions() const override; const nn::Capabilities& getCapabilities() const override; std::pair<uint32_t, uint32_t> getNumberOfCacheFilesNeeded() const override; diff --git a/neuralnetworks/1.2/utils/include/nnapi/hal/1.2/ExecutionBurstController.h b/neuralnetworks/1.2/utils/include/nnapi/hal/1.2/ExecutionBurstController.h new file mode 100644 index 0000000000..5356a912bd --- /dev/null +++ b/neuralnetworks/1.2/utils/include/nnapi/hal/1.2/ExecutionBurstController.h @@ -0,0 +1,185 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * 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 ANDROID_FRAMEWORKS_ML_NN_COMMON_EXECUTION_BURST_CONTROLLER_H +#define ANDROID_FRAMEWORKS_ML_NN_COMMON_EXECUTION_BURST_CONTROLLER_H + +#include "ExecutionBurstUtils.h" + +#include <android-base/macros.h> +#include <android/hardware/neuralnetworks/1.0/types.h> +#include <android/hardware/neuralnetworks/1.1/types.h> +#include <android/hardware/neuralnetworks/1.2/IBurstCallback.h> +#include <android/hardware/neuralnetworks/1.2/IBurstContext.h> +#include <android/hardware/neuralnetworks/1.2/IPreparedModel.h> +#include <android/hardware/neuralnetworks/1.2/types.h> +#include <fmq/MessageQueue.h> +#include <hidl/MQDescriptor.h> + +#include <atomic> +#include <chrono> +#include <map> +#include <memory> +#include <mutex> +#include <stack> +#include <tuple> +#include <utility> +#include <vector> + +namespace android::nn { + +/** + * The ExecutionBurstController class manages both the serialization and + * deserialization of data across FMQ, making it appear to the runtime as a + * regular synchronous inference. Additionally, this class manages the burst's + * memory cache. + */ +class ExecutionBurstController { + DISALLOW_IMPLICIT_CONSTRUCTORS(ExecutionBurstController); + + public: + /** + * NN runtime burst callback object and memory cache. + * + * ExecutionBurstCallback associates a hidl_memory object with a slot number + * to be passed across FMQ. The ExecutionBurstServer can use this callback + * to retrieve this hidl_memory corresponding to the slot via HIDL. + * + * Whenever a hidl_memory object is copied, it will duplicate the underlying + * file descriptor. Because the NN runtime currently copies the hidl_memory + * on each execution, it is difficult to associate hidl_memory objects with + * previously cached hidl_memory objects. For this reason, callers of this + * class must pair each hidl_memory object with an associated key. For + * efficiency, if two hidl_memory objects represent the same underlying + * buffer, they must use the same key. + */ + class ExecutionBurstCallback : public hardware::neuralnetworks::V1_2::IBurstCallback { + DISALLOW_COPY_AND_ASSIGN(ExecutionBurstCallback); + + public: + ExecutionBurstCallback() = default; + + hardware::Return<void> getMemories(const hardware::hidl_vec<int32_t>& slots, + getMemories_cb cb) override; + + /** + * This function performs one of two different actions: + * 1) If a key corresponding to a memory resource is unrecognized by the + * ExecutionBurstCallback object, the ExecutionBurstCallback object + * will allocate a slot, bind the memory to the slot, and return the + * slot identifier. + * 2) If a key corresponding to a memory resource is recognized by the + * ExecutionBurstCallback object, the ExecutionBurstCallback object + * will return the existing slot identifier. + * + * @param memories Memory resources used in an inference. + * @param keys Unique identifiers where each element corresponds to a + * memory resource element in "memories". + * @return Unique slot identifiers where each returned slot element + * corresponds to a memory resource element in "memories". + */ + std::vector<int32_t> getSlots(const hardware::hidl_vec<hardware::hidl_memory>& memories, + const std::vector<intptr_t>& keys); + + /* + * This function performs two different actions: + * 1) Removes an entry from the cache (if present), including the local + * storage of the hidl_memory object. Note that this call does not + * free any corresponding hidl_memory object in ExecutionBurstServer, + * which is separately freed via IBurstContext::freeMemory. + * 2) Return whether a cache entry was removed and which slot was removed if + * found. If the key did not to correspond to any entry in the cache, a + * slot number of 0 is returned. The slot number and whether the entry + * existed is useful so the same slot can be freed in the + * ExecutionBurstServer's cache via IBurstContext::freeMemory. + */ + std::pair<bool, int32_t> freeMemory(intptr_t key); + + private: + int32_t getSlotLocked(const hardware::hidl_memory& memory, intptr_t key); + int32_t allocateSlotLocked(); + + std::mutex mMutex; + std::stack<int32_t, std::vector<int32_t>> mFreeSlots; + std::map<intptr_t, int32_t> mMemoryIdToSlot; + std::vector<hardware::hidl_memory> mMemoryCache; + }; + + /** + * Creates a burst controller on a prepared model. + * + * Prefer this over ExecutionBurstController's constructor. + * + * @param preparedModel Model prepared for execution to execute on. + * @param pollingTimeWindow How much time (in microseconds) the + * ExecutionBurstController is allowed to poll the FMQ before waiting on + * the blocking futex. Polling may result in lower latencies at the + * potential cost of more power usage. + * @return ExecutionBurstController Execution burst controller object. + */ + static std::unique_ptr<ExecutionBurstController> create( + const sp<hardware::neuralnetworks::V1_2::IPreparedModel>& preparedModel, + std::chrono::microseconds pollingTimeWindow); + + // prefer calling ExecutionBurstController::create + ExecutionBurstController(const std::shared_ptr<RequestChannelSender>& requestChannelSender, + const std::shared_ptr<ResultChannelReceiver>& resultChannelReceiver, + const sp<hardware::neuralnetworks::V1_2::IBurstContext>& burstContext, + const sp<ExecutionBurstCallback>& callback, + const sp<hardware::hidl_death_recipient>& deathHandler = nullptr); + + // explicit destructor to unregister the death recipient + ~ExecutionBurstController(); + + /** + * Execute a request on a model. + * + * @param request Arguments to be executed on a model. + * @param measure Whether to collect timing measurements, either YES or NO + * @param memoryIds Identifiers corresponding to each memory object in the + * request's pools. + * @return A tuple of: + * - result code of the execution + * - dynamic output shapes from the execution + * - any execution time measurements of the execution + * - whether or not a failed burst execution should be re-run using a + * different path (e.g., IPreparedModel::executeSynchronously) + */ + std::tuple<int, std::vector<hardware::neuralnetworks::V1_2::OutputShape>, + hardware::neuralnetworks::V1_2::Timing, bool> + compute(const hardware::neuralnetworks::V1_0::Request& request, + hardware::neuralnetworks::V1_2::MeasureTiming measure, + const std::vector<intptr_t>& memoryIds); + + /** + * Propagate a user's freeing of memory to the service. + * + * @param key Key corresponding to the memory object. + */ + void freeMemory(intptr_t key); + + private: + std::mutex mMutex; + const std::shared_ptr<RequestChannelSender> mRequestChannelSender; + const std::shared_ptr<ResultChannelReceiver> mResultChannelReceiver; + const sp<hardware::neuralnetworks::V1_2::IBurstContext> mBurstContext; + const sp<ExecutionBurstCallback> mMemoryCache; + const sp<hardware::hidl_death_recipient> mDeathHandler; +}; + +} // namespace android::nn + +#endif // ANDROID_FRAMEWORKS_ML_NN_COMMON_EXECUTION_BURST_CONTROLLER_H diff --git a/neuralnetworks/1.2/utils/include/nnapi/hal/1.2/ExecutionBurstServer.h b/neuralnetworks/1.2/utils/include/nnapi/hal/1.2/ExecutionBurstServer.h new file mode 100644 index 0000000000..2e109b2de7 --- /dev/null +++ b/neuralnetworks/1.2/utils/include/nnapi/hal/1.2/ExecutionBurstServer.h @@ -0,0 +1,208 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * 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 ANDROID_FRAMEWORKS_ML_NN_COMMON_EXECUTION_BURST_SERVER_H +#define ANDROID_FRAMEWORKS_ML_NN_COMMON_EXECUTION_BURST_SERVER_H + +#include "ExecutionBurstUtils.h" + +#include <android-base/macros.h> +#include <android/hardware/neuralnetworks/1.0/types.h> +#include <android/hardware/neuralnetworks/1.1/types.h> +#include <android/hardware/neuralnetworks/1.2/IBurstCallback.h> +#include <android/hardware/neuralnetworks/1.2/IPreparedModel.h> +#include <android/hardware/neuralnetworks/1.2/types.h> +#include <fmq/MessageQueue.h> +#include <hidl/MQDescriptor.h> + +#include <atomic> +#include <chrono> +#include <memory> +#include <optional> +#include <thread> +#include <tuple> +#include <vector> + +namespace android::nn { + +/** + * The ExecutionBurstServer class is responsible for waiting for and + * deserializing a request object from a FMQ, performing the inference, and + * serializing the result back across another FMQ. + */ +class ExecutionBurstServer : public hardware::neuralnetworks::V1_2::IBurstContext { + DISALLOW_IMPLICIT_CONSTRUCTORS(ExecutionBurstServer); + + public: + /** + * IBurstExecutorWithCache is a callback object passed to + * ExecutionBurstServer's factory function that is used to perform an + * execution. Because some memory resources are needed across multiple + * executions, this object also contains a local cache that can directly be + * used in the execution. + * + * ExecutionBurstServer will never access its IBurstExecutorWithCache object + * with concurrent calls. + */ + class IBurstExecutorWithCache { + DISALLOW_COPY_AND_ASSIGN(IBurstExecutorWithCache); + + public: + IBurstExecutorWithCache() = default; + virtual ~IBurstExecutorWithCache() = default; + + /** + * Checks if a cache entry specified by a slot is present in the cache. + * + * @param slot Identifier of the cache entry. + * @return 'true' if the cache entry is present in the cache, 'false' + * otherwise. + */ + virtual bool isCacheEntryPresent(int32_t slot) const = 0; + + /** + * Adds an entry specified by a slot to the cache. + * + * The caller of this function must ensure that the cache entry that is + * being added is not already present in the cache. This can be checked + * via isCacheEntryPresent. + * + * @param memory Memory resource to be cached. + * @param slot Slot identifier corresponding to the memory resource. + */ + virtual void addCacheEntry(const hardware::hidl_memory& memory, int32_t slot) = 0; + + /** + * Removes an entry specified by a slot from the cache. + * + * If the cache entry corresponding to the slot number does not exist, + * the call does nothing. + * + * @param slot Slot identifier corresponding to the memory resource. + */ + virtual void removeCacheEntry(int32_t slot) = 0; + + /** + * Perform an execution. + * + * @param request Request object with inputs and outputs specified. + * Request::pools is empty, and DataLocation::poolIndex instead + * refers to the 'slots' argument as if it were Request::pools. + * @param slots Slots corresponding to the cached memory entries to be + * used. + * @param measure Whether timing information is requested for the + * execution. + * @return Result of the execution, including the status of the + * execution, dynamic output shapes, and any timing information. + */ + virtual std::tuple<hardware::neuralnetworks::V1_0::ErrorStatus, + hardware::hidl_vec<hardware::neuralnetworks::V1_2::OutputShape>, + hardware::neuralnetworks::V1_2::Timing> + execute(const hardware::neuralnetworks::V1_0::Request& request, + const std::vector<int32_t>& slots, + hardware::neuralnetworks::V1_2::MeasureTiming measure) = 0; + }; + + /** + * Create automated context to manage FMQ-based executions. + * + * This function is intended to be used by a service to automatically: + * 1) Receive data from a provided FMQ + * 2) Execute a model with the given information + * 3) Send the result to the created FMQ + * + * @param callback Callback used to retrieve memories corresponding to + * unrecognized slots. + * @param requestChannel Input FMQ channel through which the client passes the + * request to the service. + * @param resultChannel Output FMQ channel from which the client can retrieve + * the result of the execution. + * @param executorWithCache Object which maintains a local cache of the + * memory pools and executes using the cached memory pools. + * @param pollingTimeWindow How much time (in microseconds) the + * ExecutionBurstServer is allowed to poll the FMQ before waiting on + * the blocking futex. Polling may result in lower latencies at the + * potential cost of more power usage. + * @result IBurstContext Handle to the burst context. + */ + static sp<ExecutionBurstServer> create( + const sp<hardware::neuralnetworks::V1_2::IBurstCallback>& callback, + const FmqRequestDescriptor& requestChannel, const FmqResultDescriptor& resultChannel, + std::shared_ptr<IBurstExecutorWithCache> executorWithCache, + std::chrono::microseconds pollingTimeWindow = std::chrono::microseconds{0}); + + /** + * Create automated context to manage FMQ-based executions. + * + * This function is intended to be used by a service to automatically: + * 1) Receive data from a provided FMQ + * 2) Execute a model with the given information + * 3) Send the result to the created FMQ + * + * @param callback Callback used to retrieve memories corresponding to + * unrecognized slots. + * @param requestChannel Input FMQ channel through which the client passes the + * request to the service. + * @param resultChannel Output FMQ channel from which the client can retrieve + * the result of the execution. + * @param preparedModel PreparedModel that the burst object was created from. + * IPreparedModel::executeSynchronously will be used to perform the + * execution. + * @param pollingTimeWindow How much time (in microseconds) the + * ExecutionBurstServer is allowed to poll the FMQ before waiting on + * the blocking futex. Polling may result in lower latencies at the + * potential cost of more power usage. + * @result IBurstContext Handle to the burst context. + */ + static sp<ExecutionBurstServer> create( + const sp<hardware::neuralnetworks::V1_2::IBurstCallback>& callback, + const FmqRequestDescriptor& requestChannel, const FmqResultDescriptor& resultChannel, + hardware::neuralnetworks::V1_2::IPreparedModel* preparedModel, + std::chrono::microseconds pollingTimeWindow = std::chrono::microseconds{0}); + + ExecutionBurstServer(const sp<hardware::neuralnetworks::V1_2::IBurstCallback>& callback, + std::unique_ptr<RequestChannelReceiver> requestChannel, + std::unique_ptr<ResultChannelSender> resultChannel, + std::shared_ptr<IBurstExecutorWithCache> cachedExecutor); + ~ExecutionBurstServer(); + + // Used by the NN runtime to preemptively remove any stored memory. + hardware::Return<void> freeMemory(int32_t slot) override; + + private: + // Ensures all cache entries contained in mExecutorWithCache are present in + // the cache. If they are not present, they are retrieved (via + // IBurstCallback::getMemories) and added to mExecutorWithCache. + // + // This method is locked via mMutex when it is called. + void ensureCacheEntriesArePresentLocked(const std::vector<int32_t>& slots); + + // Work loop that will continue processing execution requests until the + // ExecutionBurstServer object is freed. + void task(); + + std::thread mWorker; + std::mutex mMutex; + std::atomic<bool> mTeardown{false}; + const sp<hardware::neuralnetworks::V1_2::IBurstCallback> mCallback; + const std::unique_ptr<RequestChannelReceiver> mRequestChannelReceiver; + const std::unique_ptr<ResultChannelSender> mResultChannelSender; + const std::shared_ptr<IBurstExecutorWithCache> mExecutorWithCache; +}; + +} // namespace android::nn + +#endif // ANDROID_FRAMEWORKS_ML_NN_COMMON_EXECUTION_BURST_SERVER_H diff --git a/neuralnetworks/1.2/utils/include/nnapi/hal/1.2/ExecutionBurstUtils.h b/neuralnetworks/1.2/utils/include/nnapi/hal/1.2/ExecutionBurstUtils.h new file mode 100644 index 0000000000..8a4159122e --- /dev/null +++ b/neuralnetworks/1.2/utils/include/nnapi/hal/1.2/ExecutionBurstUtils.h @@ -0,0 +1,335 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * 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 ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_2_UTILS_EXECUTION_BURST_UTILS_H +#define ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_2_UTILS_EXECUTION_BURST_UTILS_H + +#include <android/hardware/neuralnetworks/1.0/types.h> +#include <android/hardware/neuralnetworks/1.1/types.h> +#include <android/hardware/neuralnetworks/1.2/types.h> +#include <fmq/MessageQueue.h> +#include <hidl/MQDescriptor.h> + +#include <atomic> +#include <chrono> +#include <memory> +#include <optional> +#include <tuple> +#include <utility> +#include <vector> + +namespace android::hardware::neuralnetworks::V1_2::utils { + +/** + * Number of elements in the FMQ. + */ +constexpr const size_t kExecutionBurstChannelLength = 1024; + +using FmqRequestDescriptor = MQDescriptorSync<FmqRequestDatum>; +using FmqResultDescriptor = MQDescriptorSync<FmqResultDatum>; + +/** + * Function to serialize a request. + * + * Prefer calling RequestChannelSender::send. + * + * @param request Request object without the pool information. + * @param measure Whether to collect timing information for the execution. + * @param memoryIds Slot identifiers corresponding to memory resources for the + * request. + * @return Serialized FMQ request data. + */ +std::vector<hardware::neuralnetworks::V1_2::FmqRequestDatum> serialize( + const hardware::neuralnetworks::V1_0::Request& request, + hardware::neuralnetworks::V1_2::MeasureTiming measure, const std::vector<int32_t>& slots); + +/** + * Deserialize the FMQ request data. + * + * The three resulting fields are the Request object (where Request::pools is + * empty), slot identifiers (which are stand-ins for Request::pools), and + * whether timing information must be collected for the run. + * + * @param data Serialized FMQ request data. + * @return Request object if successfully deserialized, std::nullopt otherwise. + */ +std::optional<std::tuple<hardware::neuralnetworks::V1_0::Request, std::vector<int32_t>, + hardware::neuralnetworks::V1_2::MeasureTiming>> +deserialize(const std::vector<hardware::neuralnetworks::V1_2::FmqRequestDatum>& data); + +/** + * Function to serialize results. + * + * Prefer calling ResultChannelSender::send. + * + * @param errorStatus Status of the execution. + * @param outputShapes Dynamic shapes of the output tensors. + * @param timing Timing information of the execution. + * @return Serialized FMQ result data. + */ +std::vector<hardware::neuralnetworks::V1_2::FmqResultDatum> serialize( + hardware::neuralnetworks::V1_0::ErrorStatus errorStatus, + const std::vector<hardware::neuralnetworks::V1_2::OutputShape>& outputShapes, + hardware::neuralnetworks::V1_2::Timing timing); + +/** + * Deserialize the FMQ result data. + * + * The three resulting fields are the status of the execution, the dynamic + * shapes of the output tensors, and the timing information of the execution. + * + * @param data Serialized FMQ result data. + * @return Result object if successfully deserialized, std::nullopt otherwise. + */ +std::optional<std::tuple<hardware::neuralnetworks::V1_0::ErrorStatus, + std::vector<hardware::neuralnetworks::V1_2::OutputShape>, + hardware::neuralnetworks::V1_2::Timing>> +deserialize(const std::vector<hardware::neuralnetworks::V1_2::FmqResultDatum>& data); + +/** + * Convert result code to error status. + * + * @param resultCode Result code to be converted. + * @return ErrorStatus Resultant error status. + */ +hardware::neuralnetworks::V1_0::ErrorStatus legacyConvertResultCodeToErrorStatus(int resultCode); + +/** + * RequestChannelSender is responsible for serializing the result packet of + * information, sending it on the result channel, and signaling that the data is + * available. + */ +class RequestChannelSender { + using FmqRequestDescriptor = + hardware::MQDescriptorSync<hardware::neuralnetworks::V1_2::FmqRequestDatum>; + using FmqRequestChannel = + hardware::MessageQueue<hardware::neuralnetworks::V1_2::FmqRequestDatum, + hardware::kSynchronizedReadWrite>; + + public: + /** + * Create the sending end of a request channel. + * + * Prefer this call over the constructor. + * + * @param channelLength Number of elements in the FMQ. + * @return A pair of ResultChannelReceiver and the FMQ descriptor on + * successful creation, both nullptr otherwise. + */ + static std::pair<std::unique_ptr<RequestChannelSender>, const FmqRequestDescriptor*> create( + size_t channelLength); + + /** + * Send the request to the channel. + * + * @param request Request object without the pool information. + * @param measure Whether to collect timing information for the execution. + * @param memoryIds Slot identifiers corresponding to memory resources for + * the request. + * @return 'true' on successful send, 'false' otherwise. + */ + bool send(const hardware::neuralnetworks::V1_0::Request& request, + hardware::neuralnetworks::V1_2::MeasureTiming measure, + const std::vector<int32_t>& slots); + + /** + * Method to mark the channel as invalid, causing all future calls to + * RequestChannelSender::send to immediately return false without attempting + * to send a message across the FMQ. + */ + void invalidate(); + + // prefer calling RequestChannelSender::send + bool sendPacket(const std::vector<hardware::neuralnetworks::V1_2::FmqRequestDatum>& packet); + + RequestChannelSender(std::unique_ptr<FmqRequestChannel> fmqRequestChannel); + + private: + const std::unique_ptr<FmqRequestChannel> mFmqRequestChannel; + std::atomic<bool> mValid{true}; +}; + +/** + * RequestChannelReceiver is responsible for waiting on the channel until the + * packet is available, extracting the packet from the channel, and + * deserializing the packet. + * + * Because the receiver can wait on a packet that may never come (e.g., because + * the sending side of the packet has been closed), this object can be + * invalidated, unblocking the receiver. + */ +class RequestChannelReceiver { + using FmqRequestChannel = + hardware::MessageQueue<hardware::neuralnetworks::V1_2::FmqRequestDatum, + hardware::kSynchronizedReadWrite>; + + public: + /** + * Create the receiving end of a request channel. + * + * Prefer this call over the constructor. + * + * @param requestChannel Descriptor for the request channel. + * @param pollingTimeWindow How much time (in microseconds) the + * RequestChannelReceiver is allowed to poll the FMQ before waiting on + * the blocking futex. Polling may result in lower latencies at the + * potential cost of more power usage. + * @return RequestChannelReceiver on successful creation, nullptr otherwise. + */ + static std::unique_ptr<RequestChannelReceiver> create( + const FmqRequestDescriptor& requestChannel, + std::chrono::microseconds pollingTimeWindow); + + /** + * Get the request from the channel. + * + * This method will block until either: + * 1) The packet has been retrieved, or + * 2) The receiver has been invalidated + * + * @return Request object if successfully received, std::nullopt if error or + * if the receiver object was invalidated. + */ + std::optional<std::tuple<hardware::neuralnetworks::V1_0::Request, std::vector<int32_t>, + hardware::neuralnetworks::V1_2::MeasureTiming>> + getBlocking(); + + /** + * Method to mark the channel as invalid, unblocking any current or future + * calls to RequestChannelReceiver::getBlocking. + */ + void invalidate(); + + RequestChannelReceiver(std::unique_ptr<FmqRequestChannel> fmqRequestChannel, + std::chrono::microseconds pollingTimeWindow); + + private: + std::optional<std::vector<hardware::neuralnetworks::V1_2::FmqRequestDatum>> getPacketBlocking(); + + const std::unique_ptr<FmqRequestChannel> mFmqRequestChannel; + std::atomic<bool> mTeardown{false}; + const std::chrono::microseconds kPollingTimeWindow; +}; + +/** + * ResultChannelSender is responsible for serializing the result packet of + * information, sending it on the result channel, and signaling that the data is + * available. + */ +class ResultChannelSender { + using FmqResultChannel = hardware::MessageQueue<hardware::neuralnetworks::V1_2::FmqResultDatum, + hardware::kSynchronizedReadWrite>; + + public: + /** + * Create the sending end of a result channel. + * + * Prefer this call over the constructor. + * + * @param resultChannel Descriptor for the result channel. + * @return ResultChannelSender on successful creation, nullptr otherwise. + */ + static std::unique_ptr<ResultChannelSender> create(const FmqResultDescriptor& resultChannel); + + /** + * Send the result to the channel. + * + * @param errorStatus Status of the execution. + * @param outputShapes Dynamic shapes of the output tensors. + * @param timing Timing information of the execution. + * @return 'true' on successful send, 'false' otherwise. + */ + bool send(hardware::neuralnetworks::V1_0::ErrorStatus errorStatus, + const std::vector<hardware::neuralnetworks::V1_2::OutputShape>& outputShapes, + hardware::neuralnetworks::V1_2::Timing timing); + + // prefer calling ResultChannelSender::send + bool sendPacket(const std::vector<hardware::neuralnetworks::V1_2::FmqResultDatum>& packet); + + ResultChannelSender(std::unique_ptr<FmqResultChannel> fmqResultChannel); + + private: + const std::unique_ptr<FmqResultChannel> mFmqResultChannel; +}; + +/** + * ResultChannelReceiver is responsible for waiting on the channel until the + * packet is available, extracting the packet from the channel, and + * deserializing the packet. + * + * Because the receiver can wait on a packet that may never come (e.g., because + * the sending side of the packet has been closed), this object can be + * invalidated, unblocking the receiver. + */ +class ResultChannelReceiver { + using FmqResultDescriptor = + hardware::MQDescriptorSync<hardware::neuralnetworks::V1_2::FmqResultDatum>; + using FmqResultChannel = hardware::MessageQueue<hardware::neuralnetworks::V1_2::FmqResultDatum, + hardware::kSynchronizedReadWrite>; + + public: + /** + * Create the receiving end of a result channel. + * + * Prefer this call over the constructor. + * + * @param channelLength Number of elements in the FMQ. + * @param pollingTimeWindow How much time (in microseconds) the + * ResultChannelReceiver is allowed to poll the FMQ before waiting on + * the blocking futex. Polling may result in lower latencies at the + * potential cost of more power usage. + * @return A pair of ResultChannelReceiver and the FMQ descriptor on + * successful creation, both nullptr otherwise. + */ + static std::pair<std::unique_ptr<ResultChannelReceiver>, const FmqResultDescriptor*> create( + size_t channelLength, std::chrono::microseconds pollingTimeWindow); + + /** + * Get the result from the channel. + * + * This method will block until either: + * 1) The packet has been retrieved, or + * 2) The receiver has been invalidated + * + * @return Result object if successfully received, std::nullopt if error or + * if the receiver object was invalidated. + */ + std::optional<std::tuple<hardware::neuralnetworks::V1_0::ErrorStatus, + std::vector<hardware::neuralnetworks::V1_2::OutputShape>, + hardware::neuralnetworks::V1_2::Timing>> + getBlocking(); + + /** + * Method to mark the channel as invalid, unblocking any current or future + * calls to ResultChannelReceiver::getBlocking. + */ + void invalidate(); + + // prefer calling ResultChannelReceiver::getBlocking + std::optional<std::vector<hardware::neuralnetworks::V1_2::FmqResultDatum>> getPacketBlocking(); + + ResultChannelReceiver(std::unique_ptr<FmqResultChannel> fmqResultChannel, + std::chrono::microseconds pollingTimeWindow); + + private: + const std::unique_ptr<FmqResultChannel> mFmqResultChannel; + std::atomic<bool> mValid{true}; + const std::chrono::microseconds kPollingTimeWindow; +}; + +} // namespace android::hardware::neuralnetworks::V1_2::utils + +#endif // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_2_UTILS_EXECUTION_BURST_UTILS_H diff --git a/neuralnetworks/1.2/utils/include/nnapi/hal/1.2/PreparedModel.h b/neuralnetworks/1.2/utils/include/nnapi/hal/1.2/PreparedModel.h index 6a56a82f99..fb1113051c 100644 --- a/neuralnetworks/1.2/utils/include/nnapi/hal/1.2/PreparedModel.h +++ b/neuralnetworks/1.2/utils/include/nnapi/hal/1.2/PreparedModel.h @@ -36,7 +36,8 @@ namespace android::hardware::neuralnetworks::V1_2::utils { // Class that adapts V1_2::IPreparedModel to nn::IPreparedModel. -class PreparedModel final : public nn::IPreparedModel { +class PreparedModel final : public nn::IPreparedModel, + public std::enable_shared_from_this<PreparedModel> { struct PrivateConstructorTag {}; public: @@ -57,6 +58,8 @@ class PreparedModel final : public nn::IPreparedModel { const nn::OptionalDuration& loopTimeoutDuration, const nn::OptionalDuration& timeoutDurationAfterFence) const override; + nn::GeneralResult<nn::SharedBurst> configureExecutionBurst() const override; + std::any getUnderlyingResource() const override; private: diff --git a/neuralnetworks/1.2/utils/src/Device.cpp b/neuralnetworks/1.2/utils/src/Device.cpp index 9fe0de25b3..1954dfa5bf 100644 --- a/neuralnetworks/1.2/utils/src/Device.cpp +++ b/neuralnetworks/1.2/utils/src/Device.cpp @@ -199,6 +199,10 @@ nn::DeviceType Device::getType() const { return kDeviceType; } +bool Device::isUpdatable() const { + return false; +} + const std::vector<nn::Extension>& Device::getSupportedExtensions() const { return kExtensions; } diff --git a/neuralnetworks/1.2/utils/src/ExecutionBurstController.cpp b/neuralnetworks/1.2/utils/src/ExecutionBurstController.cpp new file mode 100644 index 0000000000..2265861b41 --- /dev/null +++ b/neuralnetworks/1.2/utils/src/ExecutionBurstController.cpp @@ -0,0 +1,299 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * 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. + */ + +#define LOG_TAG "ExecutionBurstController" + +#include "ExecutionBurstController.h" + +#include <android-base/logging.h> + +#include <algorithm> +#include <cstring> +#include <limits> +#include <memory> +#include <string> +#include <tuple> +#include <utility> +#include <vector> + +#include "ExecutionBurstUtils.h" +#include "HalInterfaces.h" +#include "Tracing.h" +#include "Utils.h" + +namespace android::nn { +namespace { + +class BurstContextDeathHandler : public hardware::hidl_death_recipient { + public: + using Callback = std::function<void()>; + + BurstContextDeathHandler(const Callback& onDeathCallback) : mOnDeathCallback(onDeathCallback) { + CHECK(onDeathCallback != nullptr); + } + + void serviceDied(uint64_t /*cookie*/, const wp<hidl::base::V1_0::IBase>& /*who*/) override { + LOG(ERROR) << "BurstContextDeathHandler::serviceDied -- service unexpectedly died!"; + mOnDeathCallback(); + } + + private: + const Callback mOnDeathCallback; +}; + +} // anonymous namespace + +hardware::Return<void> ExecutionBurstController::ExecutionBurstCallback::getMemories( + const hardware::hidl_vec<int32_t>& slots, getMemories_cb cb) { + std::lock_guard<std::mutex> guard(mMutex); + + // get all memories + hardware::hidl_vec<hardware::hidl_memory> memories(slots.size()); + std::transform(slots.begin(), slots.end(), memories.begin(), [this](int32_t slot) { + return slot < mMemoryCache.size() ? mMemoryCache[slot] : hardware::hidl_memory{}; + }); + + // ensure all memories are valid + if (!std::all_of(memories.begin(), memories.end(), + [](const hardware::hidl_memory& memory) { return memory.valid(); })) { + cb(V1_0::ErrorStatus::INVALID_ARGUMENT, {}); + return hardware::Void(); + } + + // return successful + cb(V1_0::ErrorStatus::NONE, std::move(memories)); + return hardware::Void(); +} + +std::vector<int32_t> ExecutionBurstController::ExecutionBurstCallback::getSlots( + const hardware::hidl_vec<hardware::hidl_memory>& memories, + const std::vector<intptr_t>& keys) { + std::lock_guard<std::mutex> guard(mMutex); + + // retrieve (or bind) all slots corresponding to memories + std::vector<int32_t> slots; + slots.reserve(memories.size()); + for (size_t i = 0; i < memories.size(); ++i) { + slots.push_back(getSlotLocked(memories[i], keys[i])); + } + return slots; +} + +std::pair<bool, int32_t> ExecutionBurstController::ExecutionBurstCallback::freeMemory( + intptr_t key) { + std::lock_guard<std::mutex> guard(mMutex); + + auto iter = mMemoryIdToSlot.find(key); + if (iter == mMemoryIdToSlot.end()) { + return {false, 0}; + } + const int32_t slot = iter->second; + mMemoryIdToSlot.erase(key); + mMemoryCache[slot] = {}; + mFreeSlots.push(slot); + return {true, slot}; +} + +int32_t ExecutionBurstController::ExecutionBurstCallback::getSlotLocked( + const hardware::hidl_memory& memory, intptr_t key) { + auto iter = mMemoryIdToSlot.find(key); + if (iter == mMemoryIdToSlot.end()) { + const int32_t slot = allocateSlotLocked(); + mMemoryIdToSlot[key] = slot; + mMemoryCache[slot] = memory; + return slot; + } else { + const int32_t slot = iter->second; + return slot; + } +} + +int32_t ExecutionBurstController::ExecutionBurstCallback::allocateSlotLocked() { + constexpr size_t kMaxNumberOfSlots = std::numeric_limits<int32_t>::max(); + + // if there is a free slot, use it + if (mFreeSlots.size() > 0) { + const int32_t slot = mFreeSlots.top(); + mFreeSlots.pop(); + return slot; + } + + // otherwise use a slot for the first time + CHECK(mMemoryCache.size() < kMaxNumberOfSlots) << "Exceeded maximum number of slots!"; + const int32_t slot = static_cast<int32_t>(mMemoryCache.size()); + mMemoryCache.emplace_back(); + + return slot; +} + +std::unique_ptr<ExecutionBurstController> ExecutionBurstController::create( + const sp<V1_2::IPreparedModel>& preparedModel, + std::chrono::microseconds pollingTimeWindow) { + // check inputs + if (preparedModel == nullptr) { + LOG(ERROR) << "ExecutionBurstController::create passed a nullptr"; + return nullptr; + } + + // create callback object + sp<ExecutionBurstCallback> callback = new ExecutionBurstCallback(); + + // create FMQ objects + auto [requestChannelSenderTemp, requestChannelDescriptor] = + RequestChannelSender::create(kExecutionBurstChannelLength); + auto [resultChannelReceiverTemp, resultChannelDescriptor] = + ResultChannelReceiver::create(kExecutionBurstChannelLength, pollingTimeWindow); + std::shared_ptr<RequestChannelSender> requestChannelSender = + std::move(requestChannelSenderTemp); + std::shared_ptr<ResultChannelReceiver> resultChannelReceiver = + std::move(resultChannelReceiverTemp); + + // check FMQ objects + if (!requestChannelSender || !resultChannelReceiver || !requestChannelDescriptor || + !resultChannelDescriptor) { + LOG(ERROR) << "ExecutionBurstController::create failed to create FastMessageQueue"; + return nullptr; + } + + // configure burst + V1_0::ErrorStatus errorStatus; + sp<IBurstContext> burstContext; + const hardware::Return<void> ret = preparedModel->configureExecutionBurst( + callback, *requestChannelDescriptor, *resultChannelDescriptor, + [&errorStatus, &burstContext](V1_0::ErrorStatus status, + const sp<IBurstContext>& context) { + errorStatus = status; + burstContext = context; + }); + + // check burst + if (!ret.isOk()) { + LOG(ERROR) << "IPreparedModel::configureExecutionBurst failed with description " + << ret.description(); + return nullptr; + } + if (errorStatus != V1_0::ErrorStatus::NONE) { + LOG(ERROR) << "IPreparedModel::configureExecutionBurst failed with status " + << toString(errorStatus); + return nullptr; + } + if (burstContext == nullptr) { + LOG(ERROR) << "IPreparedModel::configureExecutionBurst returned nullptr for burst"; + return nullptr; + } + + // create death handler object + BurstContextDeathHandler::Callback onDeathCallback = [requestChannelSender, + resultChannelReceiver] { + requestChannelSender->invalidate(); + resultChannelReceiver->invalidate(); + }; + const sp<BurstContextDeathHandler> deathHandler = new BurstContextDeathHandler(onDeathCallback); + + // linkToDeath registers a callback that will be invoked on service death to + // proactively handle service crashes. If the linkToDeath call fails, + // asynchronous calls are susceptible to hangs if the service crashes before + // providing the response. + const hardware::Return<bool> deathHandlerRet = burstContext->linkToDeath(deathHandler, 0); + if (!deathHandlerRet.isOk() || deathHandlerRet != true) { + LOG(ERROR) << "ExecutionBurstController::create -- Failed to register a death recipient " + "for the IBurstContext object."; + return nullptr; + } + + // make and return controller + return std::make_unique<ExecutionBurstController>(requestChannelSender, resultChannelReceiver, + burstContext, callback, deathHandler); +} + +ExecutionBurstController::ExecutionBurstController( + const std::shared_ptr<RequestChannelSender>& requestChannelSender, + const std::shared_ptr<ResultChannelReceiver>& resultChannelReceiver, + const sp<IBurstContext>& burstContext, const sp<ExecutionBurstCallback>& callback, + const sp<hardware::hidl_death_recipient>& deathHandler) + : mRequestChannelSender(requestChannelSender), + mResultChannelReceiver(resultChannelReceiver), + mBurstContext(burstContext), + mMemoryCache(callback), + mDeathHandler(deathHandler) {} + +ExecutionBurstController::~ExecutionBurstController() { + // It is safe to ignore any errors resulting from this unlinkToDeath call + // because the ExecutionBurstController object is already being destroyed + // and its underlying IBurstContext object is no longer being used by the NN + // runtime. + if (mDeathHandler) { + mBurstContext->unlinkToDeath(mDeathHandler).isOk(); + } +} + +static std::tuple<int, std::vector<V1_2::OutputShape>, V1_2::Timing, bool> getExecutionResult( + V1_0::ErrorStatus status, std::vector<V1_2::OutputShape> outputShapes, V1_2::Timing timing, + bool fallback) { + auto [n, checkedOutputShapes, checkedTiming] = + getExecutionResult(convertToV1_3(status), std::move(outputShapes), timing); + return {n, convertToV1_2(checkedOutputShapes), convertToV1_2(checkedTiming), fallback}; +} + +std::tuple<int, std::vector<V1_2::OutputShape>, V1_2::Timing, bool> +ExecutionBurstController::compute(const V1_0::Request& request, V1_2::MeasureTiming measure, + const std::vector<intptr_t>& memoryIds) { + // This is the first point when we know an execution is occurring, so begin + // to collect systraces. Note that the first point we can begin collecting + // systraces in ExecutionBurstServer is when the RequestChannelReceiver + // realizes there is data in the FMQ, so ExecutionBurstServer collects + // systraces at different points in the code. + NNTRACE_FULL(NNTRACE_LAYER_IPC, NNTRACE_PHASE_EXECUTION, "ExecutionBurstController::compute"); + + std::lock_guard<std::mutex> guard(mMutex); + + // send request packet + const std::vector<int32_t> slots = mMemoryCache->getSlots(request.pools, memoryIds); + const bool success = mRequestChannelSender->send(request, measure, slots); + if (!success) { + LOG(ERROR) << "Error sending FMQ packet"; + // only use fallback execution path if the packet could not be sent + return getExecutionResult(V1_0::ErrorStatus::GENERAL_FAILURE, {}, kNoTiming12, + /*fallback=*/true); + } + + // get result packet + const auto result = mResultChannelReceiver->getBlocking(); + if (!result) { + LOG(ERROR) << "Error retrieving FMQ packet"; + // only use fallback execution path if the packet could not be sent + return getExecutionResult(V1_0::ErrorStatus::GENERAL_FAILURE, {}, kNoTiming12, + /*fallback=*/false); + } + + // unpack results and return (only use fallback execution path if the + // packet could not be sent) + auto [status, outputShapes, timing] = std::move(*result); + return getExecutionResult(status, std::move(outputShapes), timing, /*fallback=*/false); +} + +void ExecutionBurstController::freeMemory(intptr_t key) { + std::lock_guard<std::mutex> guard(mMutex); + + bool valid; + int32_t slot; + std::tie(valid, slot) = mMemoryCache->freeMemory(key); + if (valid) { + mBurstContext->freeMemory(slot).isOk(); + } +} + +} // namespace android::nn diff --git a/neuralnetworks/1.2/utils/src/ExecutionBurstServer.cpp b/neuralnetworks/1.2/utils/src/ExecutionBurstServer.cpp new file mode 100644 index 0000000000..022548dcd4 --- /dev/null +++ b/neuralnetworks/1.2/utils/src/ExecutionBurstServer.cpp @@ -0,0 +1,260 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * 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. + */ + +#define LOG_TAG "ExecutionBurstServer" + +#include "ExecutionBurstServer.h" + +#include <android-base/logging.h> + +#include <algorithm> +#include <cstring> +#include <limits> +#include <map> +#include <memory> +#include <tuple> +#include <utility> +#include <vector> + +#include "ExecutionBurstUtils.h" +#include "HalInterfaces.h" +#include "Tracing.h" + +namespace android::nn { +namespace { + +// DefaultBurstExecutorWithCache adapts an IPreparedModel so that it can be +// used as an IBurstExecutorWithCache. Specifically, the cache simply stores the +// hidl_memory object, and the execution forwards calls to the provided +// IPreparedModel's "executeSynchronously" method. With this class, hidl_memory +// must be mapped and unmapped for each execution. +class DefaultBurstExecutorWithCache : public ExecutionBurstServer::IBurstExecutorWithCache { + public: + DefaultBurstExecutorWithCache(V1_2::IPreparedModel* preparedModel) + : mpPreparedModel(preparedModel) {} + + bool isCacheEntryPresent(int32_t slot) const override { + const auto it = mMemoryCache.find(slot); + return (it != mMemoryCache.end()) && it->second.valid(); + } + + void addCacheEntry(const hardware::hidl_memory& memory, int32_t slot) override { + mMemoryCache[slot] = memory; + } + + void removeCacheEntry(int32_t slot) override { mMemoryCache.erase(slot); } + + std::tuple<V1_0::ErrorStatus, hardware::hidl_vec<V1_2::OutputShape>, V1_2::Timing> execute( + const V1_0::Request& request, const std::vector<int32_t>& slots, + V1_2::MeasureTiming measure) override { + // convert slots to pools + hardware::hidl_vec<hardware::hidl_memory> pools(slots.size()); + std::transform(slots.begin(), slots.end(), pools.begin(), + [this](int32_t slot) { return mMemoryCache[slot]; }); + + // create full request + V1_0::Request fullRequest = request; + fullRequest.pools = std::move(pools); + + // setup execution + V1_0::ErrorStatus returnedStatus = V1_0::ErrorStatus::GENERAL_FAILURE; + hardware::hidl_vec<V1_2::OutputShape> returnedOutputShapes; + V1_2::Timing returnedTiming; + auto cb = [&returnedStatus, &returnedOutputShapes, &returnedTiming]( + V1_0::ErrorStatus status, + const hardware::hidl_vec<V1_2::OutputShape>& outputShapes, + const V1_2::Timing& timing) { + returnedStatus = status; + returnedOutputShapes = outputShapes; + returnedTiming = timing; + }; + + // execute + const hardware::Return<void> ret = + mpPreparedModel->executeSynchronously(fullRequest, measure, cb); + if (!ret.isOk() || returnedStatus != V1_0::ErrorStatus::NONE) { + LOG(ERROR) << "IPreparedModelAdapter::execute -- Error executing"; + return {returnedStatus, std::move(returnedOutputShapes), kNoTiming}; + } + + return std::make_tuple(returnedStatus, std::move(returnedOutputShapes), returnedTiming); + } + + private: + V1_2::IPreparedModel* const mpPreparedModel; + std::map<int32_t, hardware::hidl_memory> mMemoryCache; +}; + +} // anonymous namespace + +// ExecutionBurstServer methods + +sp<ExecutionBurstServer> ExecutionBurstServer::create( + const sp<IBurstCallback>& callback, const MQDescriptorSync<FmqRequestDatum>& requestChannel, + const MQDescriptorSync<FmqResultDatum>& resultChannel, + std::shared_ptr<IBurstExecutorWithCache> executorWithCache, + std::chrono::microseconds pollingTimeWindow) { + // check inputs + if (callback == nullptr || executorWithCache == nullptr) { + LOG(ERROR) << "ExecutionBurstServer::create passed a nullptr"; + return nullptr; + } + + // create FMQ objects + std::unique_ptr<RequestChannelReceiver> requestChannelReceiver = + RequestChannelReceiver::create(requestChannel, pollingTimeWindow); + std::unique_ptr<ResultChannelSender> resultChannelSender = + ResultChannelSender::create(resultChannel); + + // check FMQ objects + if (!requestChannelReceiver || !resultChannelSender) { + LOG(ERROR) << "ExecutionBurstServer::create failed to create FastMessageQueue"; + return nullptr; + } + + // make and return context + return new ExecutionBurstServer(callback, std::move(requestChannelReceiver), + std::move(resultChannelSender), std::move(executorWithCache)); +} + +sp<ExecutionBurstServer> ExecutionBurstServer::create( + const sp<IBurstCallback>& callback, const MQDescriptorSync<FmqRequestDatum>& requestChannel, + const MQDescriptorSync<FmqResultDatum>& resultChannel, V1_2::IPreparedModel* preparedModel, + std::chrono::microseconds pollingTimeWindow) { + // check relevant input + if (preparedModel == nullptr) { + LOG(ERROR) << "ExecutionBurstServer::create passed a nullptr"; + return nullptr; + } + + // adapt IPreparedModel to have caching + const std::shared_ptr<DefaultBurstExecutorWithCache> preparedModelAdapter = + std::make_shared<DefaultBurstExecutorWithCache>(preparedModel); + + // make and return context + return ExecutionBurstServer::create(callback, requestChannel, resultChannel, + preparedModelAdapter, pollingTimeWindow); +} + +ExecutionBurstServer::ExecutionBurstServer( + const sp<IBurstCallback>& callback, std::unique_ptr<RequestChannelReceiver> requestChannel, + std::unique_ptr<ResultChannelSender> resultChannel, + std::shared_ptr<IBurstExecutorWithCache> executorWithCache) + : mCallback(callback), + mRequestChannelReceiver(std::move(requestChannel)), + mResultChannelSender(std::move(resultChannel)), + mExecutorWithCache(std::move(executorWithCache)) { + // TODO: highly document the threading behavior of this class + mWorker = std::thread([this] { task(); }); +} + +ExecutionBurstServer::~ExecutionBurstServer() { + // set teardown flag + mTeardown = true; + mRequestChannelReceiver->invalidate(); + + // wait for task thread to end + mWorker.join(); +} + +hardware::Return<void> ExecutionBurstServer::freeMemory(int32_t slot) { + std::lock_guard<std::mutex> hold(mMutex); + mExecutorWithCache->removeCacheEntry(slot); + return hardware::Void(); +} + +void ExecutionBurstServer::ensureCacheEntriesArePresentLocked(const std::vector<int32_t>& slots) { + const auto slotIsKnown = [this](int32_t slot) { + return mExecutorWithCache->isCacheEntryPresent(slot); + }; + + // find unique unknown slots + std::vector<int32_t> unknownSlots = slots; + auto unknownSlotsEnd = unknownSlots.end(); + std::sort(unknownSlots.begin(), unknownSlotsEnd); + unknownSlotsEnd = std::unique(unknownSlots.begin(), unknownSlotsEnd); + unknownSlotsEnd = std::remove_if(unknownSlots.begin(), unknownSlotsEnd, slotIsKnown); + unknownSlots.erase(unknownSlotsEnd, unknownSlots.end()); + + // quick-exit if all slots are known + if (unknownSlots.empty()) { + return; + } + + V1_0::ErrorStatus errorStatus = V1_0::ErrorStatus::GENERAL_FAILURE; + std::vector<hardware::hidl_memory> returnedMemories; + auto cb = [&errorStatus, &returnedMemories]( + V1_0::ErrorStatus status, + const hardware::hidl_vec<hardware::hidl_memory>& memories) { + errorStatus = status; + returnedMemories = memories; + }; + + const hardware::Return<void> ret = mCallback->getMemories(unknownSlots, cb); + + if (!ret.isOk() || errorStatus != V1_0::ErrorStatus::NONE || + returnedMemories.size() != unknownSlots.size()) { + LOG(ERROR) << "Error retrieving memories"; + return; + } + + // add memories to unknown slots + for (size_t i = 0; i < unknownSlots.size(); ++i) { + mExecutorWithCache->addCacheEntry(returnedMemories[i], unknownSlots[i]); + } +} + +void ExecutionBurstServer::task() { + // loop until the burst object is being destroyed + while (!mTeardown) { + // receive request + auto arguments = mRequestChannelReceiver->getBlocking(); + + // if the request packet was not properly received, return a generic + // error and skip the execution + // + // if the burst is being torn down, skip the execution so the "task" + // function can end + if (!arguments) { + if (!mTeardown) { + mResultChannelSender->send(V1_0::ErrorStatus::GENERAL_FAILURE, {}, kNoTiming); + } + continue; + } + + // otherwise begin tracing execution + NNTRACE_FULL(NNTRACE_LAYER_IPC, NNTRACE_PHASE_EXECUTION, + "ExecutionBurstServer getting memory, executing, and returning results"); + + // unpack the arguments; types are Request, std::vector<int32_t>, and + // MeasureTiming, respectively + const auto [requestWithoutPools, slotsOfPools, measure] = std::move(*arguments); + + // ensure executor with cache has required memory + std::lock_guard<std::mutex> hold(mMutex); + ensureCacheEntriesArePresentLocked(slotsOfPools); + + // perform computation; types are ErrorStatus, hidl_vec<OutputShape>, + // and Timing, respectively + const auto [errorStatus, outputShapes, returnedTiming] = + mExecutorWithCache->execute(requestWithoutPools, slotsOfPools, measure); + + // return result + mResultChannelSender->send(errorStatus, outputShapes, returnedTiming); + } +} + +} // namespace android::nn diff --git a/neuralnetworks/1.2/utils/src/ExecutionBurstUtils.cpp b/neuralnetworks/1.2/utils/src/ExecutionBurstUtils.cpp new file mode 100644 index 0000000000..f0275f933a --- /dev/null +++ b/neuralnetworks/1.2/utils/src/ExecutionBurstUtils.cpp @@ -0,0 +1,749 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * 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. + */ + +#define LOG_TAG "ExecutionBurstUtils" + +#include "ExecutionBurstUtils.h" + +#include <android-base/logging.h> +#include <android/hardware/neuralnetworks/1.0/types.h> +#include <android/hardware/neuralnetworks/1.1/types.h> +#include <android/hardware/neuralnetworks/1.2/types.h> +#include <fmq/MessageQueue.h> +#include <hidl/MQDescriptor.h> + +#include <atomic> +#include <chrono> +#include <memory> +#include <thread> +#include <tuple> +#include <utility> +#include <vector> + +namespace android::hardware::neuralnetworks::V1_2::utils { +namespace { + +constexpr V1_2::Timing kNoTiming = {std::numeric_limits<uint64_t>::max(), + std::numeric_limits<uint64_t>::max()}; + +} + +// serialize a request into a packet +std::vector<FmqRequestDatum> serialize(const V1_0::Request& request, V1_2::MeasureTiming measure, + const std::vector<int32_t>& slots) { + // count how many elements need to be sent for a request + size_t count = 2 + request.inputs.size() + request.outputs.size() + request.pools.size(); + for (const auto& input : request.inputs) { + count += input.dimensions.size(); + } + for (const auto& output : request.outputs) { + count += output.dimensions.size(); + } + + // create buffer to temporarily store elements + std::vector<FmqRequestDatum> data; + data.reserve(count); + + // package packetInfo + { + FmqRequestDatum datum; + datum.packetInformation( + {/*.packetSize=*/static_cast<uint32_t>(count), + /*.numberOfInputOperands=*/static_cast<uint32_t>(request.inputs.size()), + /*.numberOfOutputOperands=*/static_cast<uint32_t>(request.outputs.size()), + /*.numberOfPools=*/static_cast<uint32_t>(request.pools.size())}); + data.push_back(datum); + } + + // package input data + for (const auto& input : request.inputs) { + // package operand information + FmqRequestDatum datum; + datum.inputOperandInformation( + {/*.hasNoValue=*/input.hasNoValue, + /*.location=*/input.location, + /*.numberOfDimensions=*/static_cast<uint32_t>(input.dimensions.size())}); + data.push_back(datum); + + // package operand dimensions + for (uint32_t dimension : input.dimensions) { + FmqRequestDatum datum; + datum.inputOperandDimensionValue(dimension); + data.push_back(datum); + } + } + + // package output data + for (const auto& output : request.outputs) { + // package operand information + FmqRequestDatum datum; + datum.outputOperandInformation( + {/*.hasNoValue=*/output.hasNoValue, + /*.location=*/output.location, + /*.numberOfDimensions=*/static_cast<uint32_t>(output.dimensions.size())}); + data.push_back(datum); + + // package operand dimensions + for (uint32_t dimension : output.dimensions) { + FmqRequestDatum datum; + datum.outputOperandDimensionValue(dimension); + data.push_back(datum); + } + } + + // package pool identifier + for (int32_t slot : slots) { + FmqRequestDatum datum; + datum.poolIdentifier(slot); + data.push_back(datum); + } + + // package measureTiming + { + FmqRequestDatum datum; + datum.measureTiming(measure); + data.push_back(datum); + } + + // return packet + return data; +} + +// serialize result +std::vector<FmqResultDatum> serialize(V1_0::ErrorStatus errorStatus, + const std::vector<V1_2::OutputShape>& outputShapes, + V1_2::Timing timing) { + // count how many elements need to be sent for a request + size_t count = 2 + outputShapes.size(); + for (const auto& outputShape : outputShapes) { + count += outputShape.dimensions.size(); + } + + // create buffer to temporarily store elements + std::vector<FmqResultDatum> data; + data.reserve(count); + + // package packetInfo + { + FmqResultDatum datum; + datum.packetInformation({/*.packetSize=*/static_cast<uint32_t>(count), + /*.errorStatus=*/errorStatus, + /*.numberOfOperands=*/static_cast<uint32_t>(outputShapes.size())}); + data.push_back(datum); + } + + // package output shape data + for (const auto& operand : outputShapes) { + // package operand information + FmqResultDatum::OperandInformation info{}; + info.isSufficient = operand.isSufficient; + info.numberOfDimensions = static_cast<uint32_t>(operand.dimensions.size()); + + FmqResultDatum datum; + datum.operandInformation(info); + data.push_back(datum); + + // package operand dimensions + for (uint32_t dimension : operand.dimensions) { + FmqResultDatum datum; + datum.operandDimensionValue(dimension); + data.push_back(datum); + } + } + + // package executionTiming + { + FmqResultDatum datum; + datum.executionTiming(timing); + data.push_back(datum); + } + + // return result + return data; +} + +// deserialize request +std::optional<std::tuple<V1_0::Request, std::vector<int32_t>, V1_2::MeasureTiming>> deserialize( + const std::vector<FmqRequestDatum>& data) { + using discriminator = FmqRequestDatum::hidl_discriminator; + + size_t index = 0; + + // validate packet information + if (data.size() == 0 || data[index].getDiscriminator() != discriminator::packetInformation) { + LOG(ERROR) << "FMQ Request packet ill-formed"; + return std::nullopt; + } + + // unpackage packet information + const FmqRequestDatum::PacketInformation& packetInfo = data[index].packetInformation(); + index++; + const uint32_t packetSize = packetInfo.packetSize; + const uint32_t numberOfInputOperands = packetInfo.numberOfInputOperands; + const uint32_t numberOfOutputOperands = packetInfo.numberOfOutputOperands; + const uint32_t numberOfPools = packetInfo.numberOfPools; + + // verify packet size + if (data.size() != packetSize) { + LOG(ERROR) << "FMQ Request packet ill-formed"; + return std::nullopt; + } + + // unpackage input operands + std::vector<V1_0::RequestArgument> inputs; + inputs.reserve(numberOfInputOperands); + for (size_t operand = 0; operand < numberOfInputOperands; ++operand) { + // validate input operand information + if (data[index].getDiscriminator() != discriminator::inputOperandInformation) { + LOG(ERROR) << "FMQ Request packet ill-formed"; + return std::nullopt; + } + + // unpackage operand information + const FmqRequestDatum::OperandInformation& operandInfo = + data[index].inputOperandInformation(); + index++; + const bool hasNoValue = operandInfo.hasNoValue; + const V1_0::DataLocation location = operandInfo.location; + const uint32_t numberOfDimensions = operandInfo.numberOfDimensions; + + // unpackage operand dimensions + std::vector<uint32_t> dimensions; + dimensions.reserve(numberOfDimensions); + for (size_t i = 0; i < numberOfDimensions; ++i) { + // validate dimension + if (data[index].getDiscriminator() != discriminator::inputOperandDimensionValue) { + LOG(ERROR) << "FMQ Request packet ill-formed"; + return std::nullopt; + } + + // unpackage dimension + const uint32_t dimension = data[index].inputOperandDimensionValue(); + index++; + + // store result + dimensions.push_back(dimension); + } + + // store result + inputs.push_back( + {/*.hasNoValue=*/hasNoValue, /*.location=*/location, /*.dimensions=*/dimensions}); + } + + // unpackage output operands + std::vector<V1_0::RequestArgument> outputs; + outputs.reserve(numberOfOutputOperands); + for (size_t operand = 0; operand < numberOfOutputOperands; ++operand) { + // validate output operand information + if (data[index].getDiscriminator() != discriminator::outputOperandInformation) { + LOG(ERROR) << "FMQ Request packet ill-formed"; + return std::nullopt; + } + + // unpackage operand information + const FmqRequestDatum::OperandInformation& operandInfo = + data[index].outputOperandInformation(); + index++; + const bool hasNoValue = operandInfo.hasNoValue; + const V1_0::DataLocation location = operandInfo.location; + const uint32_t numberOfDimensions = operandInfo.numberOfDimensions; + + // unpackage operand dimensions + std::vector<uint32_t> dimensions; + dimensions.reserve(numberOfDimensions); + for (size_t i = 0; i < numberOfDimensions; ++i) { + // validate dimension + if (data[index].getDiscriminator() != discriminator::outputOperandDimensionValue) { + LOG(ERROR) << "FMQ Request packet ill-formed"; + return std::nullopt; + } + + // unpackage dimension + const uint32_t dimension = data[index].outputOperandDimensionValue(); + index++; + + // store result + dimensions.push_back(dimension); + } + + // store result + outputs.push_back( + {/*.hasNoValue=*/hasNoValue, /*.location=*/location, /*.dimensions=*/dimensions}); + } + + // unpackage pools + std::vector<int32_t> slots; + slots.reserve(numberOfPools); + for (size_t pool = 0; pool < numberOfPools; ++pool) { + // validate input operand information + if (data[index].getDiscriminator() != discriminator::poolIdentifier) { + LOG(ERROR) << "FMQ Request packet ill-formed"; + return std::nullopt; + } + + // unpackage operand information + const int32_t poolId = data[index].poolIdentifier(); + index++; + + // store result + slots.push_back(poolId); + } + + // validate measureTiming + if (data[index].getDiscriminator() != discriminator::measureTiming) { + LOG(ERROR) << "FMQ Request packet ill-formed"; + return std::nullopt; + } + + // unpackage measureTiming + const V1_2::MeasureTiming measure = data[index].measureTiming(); + index++; + + // validate packet information + if (index != packetSize) { + LOG(ERROR) << "FMQ Result packet ill-formed"; + return std::nullopt; + } + + // return request + V1_0::Request request = {/*.inputs=*/inputs, /*.outputs=*/outputs, /*.pools=*/{}}; + return std::make_tuple(std::move(request), std::move(slots), measure); +} + +// deserialize a packet into the result +std::optional<std::tuple<V1_0::ErrorStatus, std::vector<V1_2::OutputShape>, V1_2::Timing>> +deserialize(const std::vector<FmqResultDatum>& data) { + using discriminator = FmqResultDatum::hidl_discriminator; + + std::vector<V1_2::OutputShape> outputShapes; + size_t index = 0; + + // validate packet information + if (data.size() == 0 || data[index].getDiscriminator() != discriminator::packetInformation) { + LOG(ERROR) << "FMQ Result packet ill-formed"; + return std::nullopt; + } + + // unpackage packet information + const FmqResultDatum::PacketInformation& packetInfo = data[index].packetInformation(); + index++; + const uint32_t packetSize = packetInfo.packetSize; + const V1_0::ErrorStatus errorStatus = packetInfo.errorStatus; + const uint32_t numberOfOperands = packetInfo.numberOfOperands; + + // verify packet size + if (data.size() != packetSize) { + LOG(ERROR) << "FMQ Result packet ill-formed"; + return std::nullopt; + } + + // unpackage operands + for (size_t operand = 0; operand < numberOfOperands; ++operand) { + // validate operand information + if (data[index].getDiscriminator() != discriminator::operandInformation) { + LOG(ERROR) << "FMQ Result packet ill-formed"; + return std::nullopt; + } + + // unpackage operand information + const FmqResultDatum::OperandInformation& operandInfo = data[index].operandInformation(); + index++; + const bool isSufficient = operandInfo.isSufficient; + const uint32_t numberOfDimensions = operandInfo.numberOfDimensions; + + // unpackage operand dimensions + std::vector<uint32_t> dimensions; + dimensions.reserve(numberOfDimensions); + for (size_t i = 0; i < numberOfDimensions; ++i) { + // validate dimension + if (data[index].getDiscriminator() != discriminator::operandDimensionValue) { + LOG(ERROR) << "FMQ Result packet ill-formed"; + return std::nullopt; + } + + // unpackage dimension + const uint32_t dimension = data[index].operandDimensionValue(); + index++; + + // store result + dimensions.push_back(dimension); + } + + // store result + outputShapes.push_back({/*.dimensions=*/dimensions, /*.isSufficient=*/isSufficient}); + } + + // validate execution timing + if (data[index].getDiscriminator() != discriminator::executionTiming) { + LOG(ERROR) << "FMQ Result packet ill-formed"; + return std::nullopt; + } + + // unpackage execution timing + const V1_2::Timing timing = data[index].executionTiming(); + index++; + + // validate packet information + if (index != packetSize) { + LOG(ERROR) << "FMQ Result packet ill-formed"; + return std::nullopt; + } + + // return result + return std::make_tuple(errorStatus, std::move(outputShapes), timing); +} + +V1_0::ErrorStatus legacyConvertResultCodeToErrorStatus(int resultCode) { + return convertToV1_0(convertResultCodeToErrorStatus(resultCode)); +} + +// RequestChannelSender methods + +std::pair<std::unique_ptr<RequestChannelSender>, const FmqRequestDescriptor*> +RequestChannelSender::create(size_t channelLength) { + std::unique_ptr<FmqRequestChannel> fmqRequestChannel = + std::make_unique<FmqRequestChannel>(channelLength, /*confEventFlag=*/true); + if (!fmqRequestChannel->isValid()) { + LOG(ERROR) << "Unable to create RequestChannelSender"; + return {nullptr, nullptr}; + } + + const FmqRequestDescriptor* descriptor = fmqRequestChannel->getDesc(); + return std::make_pair(std::make_unique<RequestChannelSender>(std::move(fmqRequestChannel)), + descriptor); +} + +RequestChannelSender::RequestChannelSender(std::unique_ptr<FmqRequestChannel> fmqRequestChannel) + : mFmqRequestChannel(std::move(fmqRequestChannel)) {} + +bool RequestChannelSender::send(const V1_0::Request& request, V1_2::MeasureTiming measure, + const std::vector<int32_t>& slots) { + const std::vector<FmqRequestDatum> serialized = serialize(request, measure, slots); + return sendPacket(serialized); +} + +bool RequestChannelSender::sendPacket(const std::vector<FmqRequestDatum>& packet) { + if (!mValid) { + return false; + } + + if (packet.size() > mFmqRequestChannel->availableToWrite()) { + LOG(ERROR) + << "RequestChannelSender::sendPacket -- packet size exceeds size available in FMQ"; + return false; + } + + // Always send the packet with "blocking" because this signals the futex and + // unblocks the consumer if it is waiting on the futex. + return mFmqRequestChannel->writeBlocking(packet.data(), packet.size()); +} + +void RequestChannelSender::invalidate() { + mValid = false; +} + +// RequestChannelReceiver methods + +std::unique_ptr<RequestChannelReceiver> RequestChannelReceiver::create( + const FmqRequestDescriptor& requestChannel, std::chrono::microseconds pollingTimeWindow) { + std::unique_ptr<FmqRequestChannel> fmqRequestChannel = + std::make_unique<FmqRequestChannel>(requestChannel); + + if (!fmqRequestChannel->isValid()) { + LOG(ERROR) << "Unable to create RequestChannelReceiver"; + return nullptr; + } + if (fmqRequestChannel->getEventFlagWord() == nullptr) { + LOG(ERROR) + << "RequestChannelReceiver::create was passed an MQDescriptor without an EventFlag"; + return nullptr; + } + + return std::make_unique<RequestChannelReceiver>(std::move(fmqRequestChannel), + pollingTimeWindow); +} + +RequestChannelReceiver::RequestChannelReceiver(std::unique_ptr<FmqRequestChannel> fmqRequestChannel, + std::chrono::microseconds pollingTimeWindow) + : mFmqRequestChannel(std::move(fmqRequestChannel)), kPollingTimeWindow(pollingTimeWindow) {} + +std::optional<std::tuple<V1_0::Request, std::vector<int32_t>, V1_2::MeasureTiming>> +RequestChannelReceiver::getBlocking() { + const auto packet = getPacketBlocking(); + if (!packet) { + return std::nullopt; + } + + return deserialize(*packet); +} + +void RequestChannelReceiver::invalidate() { + mTeardown = true; + + // force unblock + // ExecutionBurstServer is by default waiting on a request packet. If the + // client process destroys its burst object, the server may still be waiting + // on the futex. This force unblock wakes up any thread waiting on the + // futex. + // TODO: look for a different/better way to signal/notify the futex to wake + // up any thread waiting on it + FmqRequestDatum datum; + datum.packetInformation({/*.packetSize=*/0, /*.numberOfInputOperands=*/0, + /*.numberOfOutputOperands=*/0, /*.numberOfPools=*/0}); + mFmqRequestChannel->writeBlocking(&datum, 1); +} + +std::optional<std::vector<FmqRequestDatum>> RequestChannelReceiver::getPacketBlocking() { + if (mTeardown) { + return std::nullopt; + } + + // First spend time polling if results are available in FMQ instead of + // waiting on the futex. Polling is more responsive (yielding lower + // latencies), but can take up more power, so only poll for a limited period + // of time. + + auto& getCurrentTime = std::chrono::high_resolution_clock::now; + const auto timeToStopPolling = getCurrentTime() + kPollingTimeWindow; + + while (getCurrentTime() < timeToStopPolling) { + // if class is being torn down, immediately return + if (mTeardown.load(std::memory_order_relaxed)) { + return std::nullopt; + } + + // Check if data is available. If it is, immediately retrieve it and + // return. + const size_t available = mFmqRequestChannel->availableToRead(); + if (available > 0) { + // This is the first point when we know an execution is occurring, + // so begin to collect systraces. Note that a similar systrace does + // not exist at the corresponding point in + // ResultChannelReceiver::getPacketBlocking because the execution is + // already in flight. + NNTRACE_FULL(NNTRACE_LAYER_IPC, NNTRACE_PHASE_EXECUTION, + "ExecutionBurstServer getting packet"); + std::vector<FmqRequestDatum> packet(available); + const bool success = mFmqRequestChannel->read(packet.data(), available); + if (!success) { + LOG(ERROR) << "Error receiving packet"; + return std::nullopt; + } + return std::make_optional(std::move(packet)); + } + } + + // If we get to this point, we either stopped polling because it was taking + // too long or polling was not allowed. Instead, perform a blocking call + // which uses a futex to save power. + + // wait for request packet and read first element of request packet + FmqRequestDatum datum; + bool success = mFmqRequestChannel->readBlocking(&datum, 1); + + // This is the first point when we know an execution is occurring, so begin + // to collect systraces. Note that a similar systrace does not exist at the + // corresponding point in ResultChannelReceiver::getPacketBlocking because + // the execution is already in flight. + NNTRACE_FULL(NNTRACE_LAYER_IPC, NNTRACE_PHASE_EXECUTION, "ExecutionBurstServer getting packet"); + + // retrieve remaining elements + // NOTE: all of the data is already available at this point, so there's no + // need to do a blocking wait to wait for more data. This is known because + // in FMQ, all writes are published (made available) atomically. Currently, + // the producer always publishes the entire packet in one function call, so + // if the first element of the packet is available, the remaining elements + // are also available. + const size_t count = mFmqRequestChannel->availableToRead(); + std::vector<FmqRequestDatum> packet(count + 1); + std::memcpy(&packet.front(), &datum, sizeof(datum)); + success &= mFmqRequestChannel->read(packet.data() + 1, count); + + // terminate loop + if (mTeardown) { + return std::nullopt; + } + + // ensure packet was successfully received + if (!success) { + LOG(ERROR) << "Error receiving packet"; + return std::nullopt; + } + + return std::make_optional(std::move(packet)); +} + +// ResultChannelSender methods + +std::unique_ptr<ResultChannelSender> ResultChannelSender::create( + const FmqResultDescriptor& resultChannel) { + std::unique_ptr<FmqResultChannel> fmqResultChannel = + std::make_unique<FmqResultChannel>(resultChannel); + + if (!fmqResultChannel->isValid()) { + LOG(ERROR) << "Unable to create RequestChannelSender"; + return nullptr; + } + if (fmqResultChannel->getEventFlagWord() == nullptr) { + LOG(ERROR) << "ResultChannelSender::create was passed an MQDescriptor without an EventFlag"; + return nullptr; + } + + return std::make_unique<ResultChannelSender>(std::move(fmqResultChannel)); +} + +ResultChannelSender::ResultChannelSender(std::unique_ptr<FmqResultChannel> fmqResultChannel) + : mFmqResultChannel(std::move(fmqResultChannel)) {} + +bool ResultChannelSender::send(V1_0::ErrorStatus errorStatus, + const std::vector<V1_2::OutputShape>& outputShapes, + V1_2::Timing timing) { + const std::vector<FmqResultDatum> serialized = serialize(errorStatus, outputShapes, timing); + return sendPacket(serialized); +} + +bool ResultChannelSender::sendPacket(const std::vector<FmqResultDatum>& packet) { + if (packet.size() > mFmqResultChannel->availableToWrite()) { + LOG(ERROR) + << "ResultChannelSender::sendPacket -- packet size exceeds size available in FMQ"; + const std::vector<FmqResultDatum> errorPacket = + serialize(V1_0::ErrorStatus::GENERAL_FAILURE, {}, kNoTiming); + + // Always send the packet with "blocking" because this signals the futex + // and unblocks the consumer if it is waiting on the futex. + return mFmqResultChannel->writeBlocking(errorPacket.data(), errorPacket.size()); + } + + // Always send the packet with "blocking" because this signals the futex and + // unblocks the consumer if it is waiting on the futex. + return mFmqResultChannel->writeBlocking(packet.data(), packet.size()); +} + +// ResultChannelReceiver methods + +std::pair<std::unique_ptr<ResultChannelReceiver>, const FmqResultDescriptor*> +ResultChannelReceiver::create(size_t channelLength, std::chrono::microseconds pollingTimeWindow) { + std::unique_ptr<FmqResultChannel> fmqResultChannel = + std::make_unique<FmqResultChannel>(channelLength, /*confEventFlag=*/true); + if (!fmqResultChannel->isValid()) { + LOG(ERROR) << "Unable to create ResultChannelReceiver"; + return {nullptr, nullptr}; + } + + const FmqResultDescriptor* descriptor = fmqResultChannel->getDesc(); + return std::make_pair( + std::make_unique<ResultChannelReceiver>(std::move(fmqResultChannel), pollingTimeWindow), + descriptor); +} + +ResultChannelReceiver::ResultChannelReceiver(std::unique_ptr<FmqResultChannel> fmqResultChannel, + std::chrono::microseconds pollingTimeWindow) + : mFmqResultChannel(std::move(fmqResultChannel)), kPollingTimeWindow(pollingTimeWindow) {} + +std::optional<std::tuple<V1_0::ErrorStatus, std::vector<V1_2::OutputShape>, V1_2::Timing>> +ResultChannelReceiver::getBlocking() { + const auto packet = getPacketBlocking(); + if (!packet) { + return std::nullopt; + } + + return deserialize(*packet); +} + +void ResultChannelReceiver::invalidate() { + mValid = false; + + // force unblock + // ExecutionBurstController waits on a result packet after sending a + // request. If the driver containing ExecutionBurstServer crashes, the + // controller may be waiting on the futex. This force unblock wakes up any + // thread waiting on the futex. + // TODO: look for a different/better way to signal/notify the futex to + // wake up any thread waiting on it + FmqResultDatum datum; + datum.packetInformation({/*.packetSize=*/0, + /*.errorStatus=*/V1_0::ErrorStatus::GENERAL_FAILURE, + /*.numberOfOperands=*/0}); + mFmqResultChannel->writeBlocking(&datum, 1); +} + +std::optional<std::vector<FmqResultDatum>> ResultChannelReceiver::getPacketBlocking() { + if (!mValid) { + return std::nullopt; + } + + // First spend time polling if results are available in FMQ instead of + // waiting on the futex. Polling is more responsive (yielding lower + // latencies), but can take up more power, so only poll for a limited period + // of time. + + auto& getCurrentTime = std::chrono::high_resolution_clock::now; + const auto timeToStopPolling = getCurrentTime() + kPollingTimeWindow; + + while (getCurrentTime() < timeToStopPolling) { + // if class is being torn down, immediately return + if (!mValid.load(std::memory_order_relaxed)) { + return std::nullopt; + } + + // Check if data is available. If it is, immediately retrieve it and + // return. + const size_t available = mFmqResultChannel->availableToRead(); + if (available > 0) { + std::vector<FmqResultDatum> packet(available); + const bool success = mFmqResultChannel->read(packet.data(), available); + if (!success) { + LOG(ERROR) << "Error receiving packet"; + return std::nullopt; + } + return std::make_optional(std::move(packet)); + } + } + + // If we get to this point, we either stopped polling because it was taking + // too long or polling was not allowed. Instead, perform a blocking call + // which uses a futex to save power. + + // wait for result packet and read first element of result packet + FmqResultDatum datum; + bool success = mFmqResultChannel->readBlocking(&datum, 1); + + // retrieve remaining elements + // NOTE: all of the data is already available at this point, so there's no + // need to do a blocking wait to wait for more data. This is known because + // in FMQ, all writes are published (made available) atomically. Currently, + // the producer always publishes the entire packet in one function call, so + // if the first element of the packet is available, the remaining elements + // are also available. + const size_t count = mFmqResultChannel->availableToRead(); + std::vector<FmqResultDatum> packet(count + 1); + std::memcpy(&packet.front(), &datum, sizeof(datum)); + success &= mFmqResultChannel->read(packet.data() + 1, count); + + if (!mValid) { + return std::nullopt; + } + + // ensure packet was successfully received + if (!success) { + LOG(ERROR) << "Error receiving packet"; + return std::nullopt; + } + + return std::make_optional(std::move(packet)); +} + +} // namespace android::hardware::neuralnetworks::V1_2::utils diff --git a/neuralnetworks/1.2/utils/src/PreparedModel.cpp b/neuralnetworks/1.2/utils/src/PreparedModel.cpp index 6d00082a5f..6841c5e007 100644 --- a/neuralnetworks/1.2/utils/src/PreparedModel.cpp +++ b/neuralnetworks/1.2/utils/src/PreparedModel.cpp @@ -27,6 +27,7 @@ #include <nnapi/IPreparedModel.h> #include <nnapi/Result.h> #include <nnapi/Types.h> +#include <nnapi/hal/1.0/Burst.h> #include <nnapi/hal/1.0/Conversions.h> #include <nnapi/hal/CommonUtils.h> #include <nnapi/hal/HandleError.h> @@ -117,6 +118,10 @@ PreparedModel::executeFenced(const nn::Request& /*request*/, << "IPreparedModel::executeFenced is not supported on 1.2 HAL service"; } +nn::GeneralResult<nn::SharedBurst> PreparedModel::configureExecutionBurst() const { + return V1_0::utils::Burst::create(shared_from_this()); +} + std::any PreparedModel::getUnderlyingResource() const { sp<V1_2::IPreparedModel> resource = kPreparedModel; return resource; diff --git a/neuralnetworks/1.2/utils/test/DeviceTest.cpp b/neuralnetworks/1.2/utils/test/DeviceTest.cpp new file mode 100644 index 0000000000..9c8addef1f --- /dev/null +++ b/neuralnetworks/1.2/utils/test/DeviceTest.cpp @@ -0,0 +1,875 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * 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 "MockDevice.h" +#include "MockPreparedModel.h" + +#include <android/hardware/neuralnetworks/1.2/IDevice.h> +#include <gmock/gmock.h> +#include <gtest/gtest.h> +#include <nnapi/IDevice.h> +#include <nnapi/TypeUtils.h> +#include <nnapi/Types.h> +#include <nnapi/hal/1.2/Device.h> + +#include <functional> +#include <memory> +#include <string> + +namespace android::hardware::neuralnetworks::V1_2::utils { +namespace { + +using ::testing::_; +using ::testing::Invoke; +using ::testing::InvokeWithoutArgs; + +const nn::Model kSimpleModel = { + .main = {.operands = {{.type = nn::OperandType::TENSOR_FLOAT32, + .dimensions = {1}, + .lifetime = nn::Operand::LifeTime::SUBGRAPH_INPUT}, + {.type = nn::OperandType::TENSOR_FLOAT32, + .dimensions = {1}, + .lifetime = nn::Operand::LifeTime::SUBGRAPH_OUTPUT}}, + .operations = {{.type = nn::OperationType::RELU, .inputs = {0}, .outputs = {1}}}, + .inputIndexes = {0}, + .outputIndexes = {1}}}; + +const std::string kName = "Google-MockV1"; +const std::string kInvalidName = ""; +const sp<V1_2::IDevice> kInvalidDevice; +constexpr V1_0::PerformanceInfo kNoPerformanceInfo = { + .execTime = std::numeric_limits<float>::max(), + .powerUsage = std::numeric_limits<float>::max()}; + +template <typename... Args> +auto makeCallbackReturn(Args&&... args) { + return [argPack = std::make_tuple(std::forward<Args>(args)...)](const auto& cb) { + std::apply(cb, argPack); + return Void(); + }; +} + +sp<MockDevice> createMockDevice() { + const auto mockDevice = MockDevice::create(); + + // Setup default actions for each relevant call. + const auto getVersionString_ret = makeCallbackReturn(V1_0::ErrorStatus::NONE, kName); + const auto getType_ret = makeCallbackReturn(V1_0::ErrorStatus::NONE, V1_2::DeviceType::OTHER); + const auto getSupportedExtensions_ret = + makeCallbackReturn(V1_0::ErrorStatus::NONE, hidl_vec<V1_2::Extension>{}); + const auto getNumberOfCacheFilesNeeded_ret = makeCallbackReturn( + V1_0::ErrorStatus::NONE, nn::kMaxNumberOfCacheFiles, nn::kMaxNumberOfCacheFiles); + const auto getCapabilities_ret = makeCallbackReturn( + V1_0::ErrorStatus::NONE, + V1_2::Capabilities{ + .relaxedFloat32toFloat16PerformanceScalar = kNoPerformanceInfo, + .relaxedFloat32toFloat16PerformanceTensor = kNoPerformanceInfo, + }); + + // Setup default actions for each relevant call. + ON_CALL(*mockDevice, getVersionString(_)).WillByDefault(Invoke(getVersionString_ret)); + ON_CALL(*mockDevice, getType(_)).WillByDefault(Invoke(getType_ret)); + ON_CALL(*mockDevice, getSupportedExtensions(_)) + .WillByDefault(Invoke(getSupportedExtensions_ret)); + ON_CALL(*mockDevice, getNumberOfCacheFilesNeeded(_)) + .WillByDefault(Invoke(getNumberOfCacheFilesNeeded_ret)); + ON_CALL(*mockDevice, getCapabilities_1_2(_)).WillByDefault(Invoke(getCapabilities_ret)); + + // Ensure that older calls are not used. + EXPECT_CALL(*mockDevice, getCapabilities(_)).Times(0); + EXPECT_CALL(*mockDevice, getCapabilities_1_1(_)).Times(0); + EXPECT_CALL(*mockDevice, getSupportedOperations(_, _)).Times(0); + EXPECT_CALL(*mockDevice, getSupportedOperations_1_1(_, _)).Times(0); + EXPECT_CALL(*mockDevice, prepareModel(_, _)).Times(0); + EXPECT_CALL(*mockDevice, prepareModel_1_1(_, _, _)).Times(0); + + // These EXPECT_CALL(...).Times(testing::AnyNumber()) calls are to suppress warnings on the + // uninteresting methods calls. + EXPECT_CALL(*mockDevice, getVersionString(_)).Times(testing::AnyNumber()); + EXPECT_CALL(*mockDevice, getType(_)).Times(testing::AnyNumber()); + EXPECT_CALL(*mockDevice, getSupportedExtensions(_)).Times(testing::AnyNumber()); + EXPECT_CALL(*mockDevice, getNumberOfCacheFilesNeeded(_)).Times(testing::AnyNumber()); + EXPECT_CALL(*mockDevice, getCapabilities_1_2(_)).Times(testing::AnyNumber()); + + return mockDevice; +} + +auto makePreparedModelReturn(V1_0::ErrorStatus launchStatus, V1_0::ErrorStatus returnStatus, + const sp<MockPreparedModel>& preparedModel) { + return [launchStatus, returnStatus, preparedModel]( + const V1_2::Model& /*model*/, V1_1::ExecutionPreference /*preference*/, + const hardware::hidl_vec<hardware::hidl_handle>& /*modelCache*/, + const hardware::hidl_vec<hardware::hidl_handle>& /*dataCache*/, + const CacheToken& /*token*/, const sp<V1_2::IPreparedModelCallback>& cb) + -> hardware::Return<V1_0::ErrorStatus> { + cb->notify_1_2(returnStatus, preparedModel).isOk(); + return launchStatus; + }; +} +auto makePreparedModelFromCacheReturn(V1_0::ErrorStatus launchStatus, + V1_0::ErrorStatus returnStatus, + const sp<MockPreparedModel>& preparedModel) { + return [launchStatus, returnStatus, preparedModel]( + const hardware::hidl_vec<hardware::hidl_handle>& /*modelCache*/, + const hardware::hidl_vec<hardware::hidl_handle>& /*dataCache*/, + const CacheToken& /*token*/, const sp<V1_2::IPreparedModelCallback>& cb) + -> hardware::Return<V1_0::ErrorStatus> { + cb->notify_1_2(returnStatus, preparedModel).isOk(); + return launchStatus; + }; +} + +std::function<hardware::Status()> makeTransportFailure(status_t status) { + return [status] { return hardware::Status::fromStatusT(status); }; +} + +const auto makeGeneralTransportFailure = makeTransportFailure(NO_MEMORY); +const auto makeDeadObjectFailure = makeTransportFailure(DEAD_OBJECT); + +} // namespace + +TEST(DeviceTest, invalidName) { + // run test + const auto device = MockDevice::create(); + const auto result = Device::create(kInvalidName, device); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::INVALID_ARGUMENT); +} + +TEST(DeviceTest, invalidDevice) { + // run test + const auto result = Device::create(kName, kInvalidDevice); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::INVALID_ARGUMENT); +} + +TEST(DeviceTest, getVersionStringError) { + // setup call + const auto mockDevice = createMockDevice(); + const auto ret = makeCallbackReturn(V1_0::ErrorStatus::GENERAL_FAILURE, ""); + EXPECT_CALL(*mockDevice, getVersionString(_)).Times(1).WillOnce(Invoke(ret)); + + // run test + const auto result = Device::create(kName, mockDevice); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); +} + +TEST(DeviceTest, getVersionStringTransportFailure) { + // setup call + const auto mockDevice = createMockDevice(); + EXPECT_CALL(*mockDevice, getVersionString(_)) + .Times(1) + .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure)); + + // run test + const auto result = Device::create(kName, mockDevice); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); +} + +TEST(DeviceTest, getVersionStringDeadObject) { + // setup call + const auto mockDevice = createMockDevice(); + EXPECT_CALL(*mockDevice, getVersionString(_)) + .Times(1) + .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure)); + + // run test + const auto result = Device::create(kName, mockDevice); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT); +} + +TEST(DeviceTest, getTypeError) { + // setup call + const auto mockDevice = createMockDevice(); + const auto ret = + makeCallbackReturn(V1_0::ErrorStatus::GENERAL_FAILURE, V1_2::DeviceType::OTHER); + EXPECT_CALL(*mockDevice, getType(_)).Times(1).WillOnce(Invoke(ret)); + + // run test + const auto result = Device::create(kName, mockDevice); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); +} + +TEST(DeviceTest, getTypeTransportFailure) { + // setup call + const auto mockDevice = createMockDevice(); + EXPECT_CALL(*mockDevice, getType(_)) + .Times(1) + .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure)); + + // run test + const auto result = Device::create(kName, mockDevice); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); +} + +TEST(DeviceTest, getTypeDeadObject) { + // setup call + const auto mockDevice = createMockDevice(); + EXPECT_CALL(*mockDevice, getType(_)) + .Times(1) + .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure)); + + // run test + const auto result = Device::create(kName, mockDevice); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT); +} + +TEST(DeviceTest, getSupportedExtensionsError) { + // setup call + const auto mockDevice = createMockDevice(); + const auto ret = + makeCallbackReturn(V1_0::ErrorStatus::GENERAL_FAILURE, hidl_vec<V1_2::Extension>{}); + EXPECT_CALL(*mockDevice, getSupportedExtensions(_)).Times(1).WillOnce(Invoke(ret)); + + // run test + const auto result = Device::create(kName, mockDevice); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); +} + +TEST(DeviceTest, getSupportedExtensionsTransportFailure) { + // setup call + const auto mockDevice = createMockDevice(); + EXPECT_CALL(*mockDevice, getSupportedExtensions(_)) + .Times(1) + .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure)); + + // run test + const auto result = Device::create(kName, mockDevice); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); +} + +TEST(DeviceTest, getSupportedExtensionsDeadObject) { + // setup call + const auto mockDevice = createMockDevice(); + EXPECT_CALL(*mockDevice, getSupportedExtensions(_)) + .Times(1) + .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure)); + + // run test + const auto result = Device::create(kName, mockDevice); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT); +} + +TEST(DeviceTest, getNumberOfCacheFilesNeededError) { + // setup call + const auto mockDevice = createMockDevice(); + const auto ret = makeCallbackReturn(V1_0::ErrorStatus::GENERAL_FAILURE, + nn::kMaxNumberOfCacheFiles, nn::kMaxNumberOfCacheFiles); + EXPECT_CALL(*mockDevice, getNumberOfCacheFilesNeeded(_)).Times(1).WillOnce(Invoke(ret)); + + // run test + const auto result = Device::create(kName, mockDevice); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); +} + +TEST(DeviceTest, dataCacheFilesExceedsSpecifiedMax) { + // setup test + const auto mockDevice = createMockDevice(); + const auto ret = makeCallbackReturn(V1_0::ErrorStatus::NONE, nn::kMaxNumberOfCacheFiles + 1, + nn::kMaxNumberOfCacheFiles); + EXPECT_CALL(*mockDevice, getNumberOfCacheFilesNeeded(_)).Times(1).WillOnce(Invoke(ret)); + + // run test + const auto result = Device::create(kName, mockDevice); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); +} + +TEST(DeviceTest, modelCacheFilesExceedsSpecifiedMax) { + // setup test + const auto mockDevice = createMockDevice(); + const auto ret = makeCallbackReturn(V1_0::ErrorStatus::NONE, nn::kMaxNumberOfCacheFiles, + nn::kMaxNumberOfCacheFiles + 1); + EXPECT_CALL(*mockDevice, getNumberOfCacheFilesNeeded(_)).Times(1).WillOnce(Invoke(ret)); + + // run test + const auto result = Device::create(kName, mockDevice); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); +} + +TEST(DeviceTest, getNumberOfCacheFilesNeededTransportFailure) { + // setup call + const auto mockDevice = createMockDevice(); + EXPECT_CALL(*mockDevice, getNumberOfCacheFilesNeeded(_)) + .Times(1) + .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure)); + + // run test + const auto result = Device::create(kName, mockDevice); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); +} + +TEST(DeviceTest, getNumberOfCacheFilesNeededDeadObject) { + // setup call + const auto mockDevice = createMockDevice(); + EXPECT_CALL(*mockDevice, getNumberOfCacheFilesNeeded(_)) + .Times(1) + .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure)); + + // run test + const auto result = Device::create(kName, mockDevice); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT); +} + +TEST(DeviceTest, getCapabilitiesError) { + // setup call + const auto mockDevice = createMockDevice(); + const auto ret = makeCallbackReturn( + V1_0::ErrorStatus::GENERAL_FAILURE, + V1_2::Capabilities{ + .relaxedFloat32toFloat16PerformanceScalar = kNoPerformanceInfo, + .relaxedFloat32toFloat16PerformanceTensor = kNoPerformanceInfo, + }); + EXPECT_CALL(*mockDevice, getCapabilities_1_2(_)).Times(1).WillOnce(Invoke(ret)); + + // run test + const auto result = Device::create(kName, mockDevice); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); +} + +TEST(DeviceTest, getCapabilitiesTransportFailure) { + // setup call + const auto mockDevice = createMockDevice(); + EXPECT_CALL(*mockDevice, getCapabilities_1_2(_)) + .Times(1) + .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure)); + + // run test + const auto result = Device::create(kName, mockDevice); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); +} + +TEST(DeviceTest, getCapabilitiesDeadObject) { + // setup call + const auto mockDevice = createMockDevice(); + EXPECT_CALL(*mockDevice, getCapabilities_1_2(_)) + .Times(1) + .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure)); + + // run test + const auto result = Device::create(kName, mockDevice); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT); +} + +TEST(DeviceTest, linkToDeathError) { + // setup call + const auto mockDevice = createMockDevice(); + const auto ret = []() -> Return<bool> { return false; }; + EXPECT_CALL(*mockDevice, linkToDeathRet()).Times(1).WillOnce(InvokeWithoutArgs(ret)); + + // run test + const auto result = Device::create(kName, mockDevice); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); +} + +TEST(DeviceTest, linkToDeathTransportFailure) { + // setup call + const auto mockDevice = createMockDevice(); + EXPECT_CALL(*mockDevice, linkToDeathRet()) + .Times(1) + .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure)); + + // run test + const auto result = Device::create(kName, mockDevice); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); +} + +TEST(DeviceTest, linkToDeathDeadObject) { + // setup call + const auto mockDevice = createMockDevice(); + EXPECT_CALL(*mockDevice, linkToDeathRet()) + .Times(1) + .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure)); + + // run test + const auto result = Device::create(kName, mockDevice); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT); +} + +TEST(DeviceTest, getName) { + // setup call + const auto mockDevice = createMockDevice(); + const auto device = Device::create(kName, mockDevice).value(); + + // run test + const auto& name = device->getName(); + + // verify result + EXPECT_EQ(name, kName); +} + +TEST(DeviceTest, getFeatureLevel) { + // setup call + const auto mockDevice = createMockDevice(); + const auto device = Device::create(kName, mockDevice).value(); + + // run test + const auto featureLevel = device->getFeatureLevel(); + + // verify result + EXPECT_EQ(featureLevel, nn::Version::ANDROID_Q); +} + +TEST(DeviceTest, getCachedData) { + // setup call + const auto mockDevice = createMockDevice(); + EXPECT_CALL(*mockDevice, getVersionString(_)).Times(1); + EXPECT_CALL(*mockDevice, getType(_)).Times(1); + EXPECT_CALL(*mockDevice, getSupportedExtensions(_)).Times(1); + EXPECT_CALL(*mockDevice, getNumberOfCacheFilesNeeded(_)).Times(1); + EXPECT_CALL(*mockDevice, getCapabilities_1_2(_)).Times(1); + + const auto result = Device::create(kName, mockDevice); + ASSERT_TRUE(result.has_value()) + << "Failed with " << result.error().code << ": " << result.error().message; + const auto& device = result.value(); + + // run test and verify results + EXPECT_EQ(device->getVersionString(), device->getVersionString()); + EXPECT_EQ(device->getType(), device->getType()); + EXPECT_EQ(device->getSupportedExtensions(), device->getSupportedExtensions()); + EXPECT_EQ(device->getNumberOfCacheFilesNeeded(), device->getNumberOfCacheFilesNeeded()); + EXPECT_EQ(device->getCapabilities(), device->getCapabilities()); +} + +TEST(DeviceTest, wait) { + // setup call + const auto mockDevice = createMockDevice(); + const auto ret = []() -> Return<void> { return {}; }; + EXPECT_CALL(*mockDevice, ping()).Times(1).WillOnce(InvokeWithoutArgs(ret)); + const auto device = Device::create(kName, mockDevice).value(); + + // run test + const auto result = device->wait(); + + // verify result + ASSERT_TRUE(result.has_value()) + << "Failed with " << result.error().code << ": " << result.error().message; +} + +TEST(DeviceTest, waitTransportFailure) { + // setup call + const auto mockDevice = createMockDevice(); + EXPECT_CALL(*mockDevice, ping()) + .Times(1) + .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure)); + const auto device = Device::create(kName, mockDevice).value(); + + // run test + const auto result = device->wait(); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); +} + +TEST(DeviceTest, waitDeadObject) { + // setup call + const auto mockDevice = createMockDevice(); + EXPECT_CALL(*mockDevice, ping()).Times(1).WillOnce(InvokeWithoutArgs(makeDeadObjectFailure)); + const auto device = Device::create(kName, mockDevice).value(); + + // run test + const auto result = device->wait(); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT); +} + +TEST(DeviceTest, getSupportedOperations) { + // setup call + const auto mockDevice = createMockDevice(); + const auto device = Device::create(kName, mockDevice).value(); + const auto ret = [](const auto& model, const auto& cb) { + cb(V1_0::ErrorStatus::NONE, std::vector<bool>(model.operations.size(), true)); + return hardware::Void(); + }; + EXPECT_CALL(*mockDevice, getSupportedOperations_1_2(_, _)).Times(1).WillOnce(Invoke(ret)); + + // run test + const auto result = device->getSupportedOperations(kSimpleModel); + + // verify result + ASSERT_TRUE(result.has_value()) + << "Failed with " << result.error().code << ": " << result.error().message; + const auto& supportedOperations = result.value(); + EXPECT_EQ(supportedOperations.size(), kSimpleModel.main.operations.size()); + EXPECT_THAT(supportedOperations, Each(testing::IsTrue())); +} + +TEST(DeviceTest, getSupportedOperationsError) { + // setup call + const auto mockDevice = createMockDevice(); + const auto device = Device::create(kName, mockDevice).value(); + const auto ret = [](const auto& /*model*/, const auto& cb) { + cb(V1_0::ErrorStatus::GENERAL_FAILURE, {}); + return hardware::Void(); + }; + EXPECT_CALL(*mockDevice, getSupportedOperations_1_2(_, _)).Times(1).WillOnce(Invoke(ret)); + + // run test + const auto result = device->getSupportedOperations(kSimpleModel); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); +} + +TEST(DeviceTest, getSupportedOperationsTransportFailure) { + // setup call + const auto mockDevice = createMockDevice(); + const auto device = Device::create(kName, mockDevice).value(); + EXPECT_CALL(*mockDevice, getSupportedOperations_1_2(_, _)) + .Times(1) + .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure)); + + // run test + const auto result = device->getSupportedOperations(kSimpleModel); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); +} + +TEST(DeviceTest, getSupportedOperationsDeadObject) { + // setup call + const auto mockDevice = createMockDevice(); + const auto device = Device::create(kName, mockDevice).value(); + EXPECT_CALL(*mockDevice, getSupportedOperations_1_2(_, _)) + .Times(1) + .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure)); + + // run test + const auto result = device->getSupportedOperations(kSimpleModel); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT); +} + +TEST(DeviceTest, prepareModel) { + // setup call + const auto mockDevice = createMockDevice(); + const auto device = Device::create(kName, mockDevice).value(); + const auto mockPreparedModel = MockPreparedModel::create(); + EXPECT_CALL(*mockDevice, prepareModel_1_2(_, _, _, _, _, _)) + .Times(1) + .WillOnce(Invoke(makePreparedModelReturn(V1_0::ErrorStatus::NONE, + V1_0::ErrorStatus::NONE, mockPreparedModel))); + + // run test + const auto result = device->prepareModel(kSimpleModel, nn::ExecutionPreference::DEFAULT, + nn::Priority::DEFAULT, {}, {}, {}, {}); + + // verify result + ASSERT_TRUE(result.has_value()) + << "Failed with " << result.error().code << ": " << result.error().message; + EXPECT_NE(result.value(), nullptr); +} + +TEST(DeviceTest, prepareModelLaunchError) { + // setup call + const auto mockDevice = createMockDevice(); + const auto device = Device::create(kName, mockDevice).value(); + EXPECT_CALL(*mockDevice, prepareModel_1_2(_, _, _, _, _, _)) + .Times(1) + .WillOnce(Invoke(makePreparedModelReturn(V1_0::ErrorStatus::GENERAL_FAILURE, + V1_0::ErrorStatus::GENERAL_FAILURE, nullptr))); + + // run test + const auto result = device->prepareModel(kSimpleModel, nn::ExecutionPreference::DEFAULT, + nn::Priority::DEFAULT, {}, {}, {}, {}); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); +} + +TEST(DeviceTest, prepareModelReturnError) { + // setup call + const auto mockDevice = createMockDevice(); + const auto device = Device::create(kName, mockDevice).value(); + EXPECT_CALL(*mockDevice, prepareModel_1_2(_, _, _, _, _, _)) + .Times(1) + .WillOnce(Invoke(makePreparedModelReturn(V1_0::ErrorStatus::NONE, + V1_0::ErrorStatus::GENERAL_FAILURE, nullptr))); + + // run test + const auto result = device->prepareModel(kSimpleModel, nn::ExecutionPreference::DEFAULT, + nn::Priority::DEFAULT, {}, {}, {}, {}); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); +} + +TEST(DeviceTest, prepareModelNullptrError) { + // setup call + const auto mockDevice = createMockDevice(); + const auto device = Device::create(kName, mockDevice).value(); + EXPECT_CALL(*mockDevice, prepareModel_1_2(_, _, _, _, _, _)) + .Times(1) + .WillOnce(Invoke(makePreparedModelReturn(V1_0::ErrorStatus::NONE, + V1_0::ErrorStatus::NONE, nullptr))); + + // run test + const auto result = device->prepareModel(kSimpleModel, nn::ExecutionPreference::DEFAULT, + nn::Priority::DEFAULT, {}, {}, {}, {}); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); +} + +TEST(DeviceTest, prepareModelTransportFailure) { + // setup call + const auto mockDevice = createMockDevice(); + const auto device = Device::create(kName, mockDevice).value(); + EXPECT_CALL(*mockDevice, prepareModel_1_2(_, _, _, _, _, _)) + .Times(1) + .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure)); + + // run test + const auto result = device->prepareModel(kSimpleModel, nn::ExecutionPreference::DEFAULT, + nn::Priority::DEFAULT, {}, {}, {}, {}); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); +} + +TEST(DeviceTest, prepareModelDeadObject) { + // setup call + const auto mockDevice = createMockDevice(); + const auto device = Device::create(kName, mockDevice).value(); + EXPECT_CALL(*mockDevice, prepareModel_1_2(_, _, _, _, _, _)) + .Times(1) + .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure)); + + // run test + const auto result = device->prepareModel(kSimpleModel, nn::ExecutionPreference::DEFAULT, + nn::Priority::DEFAULT, {}, {}, {}, {}); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT); +} + +TEST(DeviceTest, prepareModelAsyncCrash) { + // setup test + const auto mockDevice = createMockDevice(); + const auto device = Device::create(kName, mockDevice).value(); + const auto ret = [&mockDevice]() -> hardware::Return<V1_0::ErrorStatus> { + mockDevice->simulateCrash(); + return V1_0::ErrorStatus::NONE; + }; + EXPECT_CALL(*mockDevice, prepareModel_1_2(_, _, _, _, _, _)) + .Times(1) + .WillOnce(InvokeWithoutArgs(ret)); + + // run test + const auto result = device->prepareModel(kSimpleModel, nn::ExecutionPreference::DEFAULT, + nn::Priority::DEFAULT, {}, {}, {}, {}); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT); +} + +TEST(DeviceTest, prepareModelFromCache) { + // setup call + const auto mockDevice = createMockDevice(); + const auto device = Device::create(kName, mockDevice).value(); + const auto mockPreparedModel = MockPreparedModel::create(); + EXPECT_CALL(*mockDevice, prepareModelFromCache(_, _, _, _)) + .Times(1) + .WillOnce(Invoke(makePreparedModelFromCacheReturn( + V1_0::ErrorStatus::NONE, V1_0::ErrorStatus::NONE, mockPreparedModel))); + + // run test + const auto result = device->prepareModelFromCache({}, {}, {}, {}); + + // verify result + ASSERT_TRUE(result.has_value()) + << "Failed with " << result.error().code << ": " << result.error().message; + EXPECT_NE(result.value(), nullptr); +} + +TEST(DeviceTest, prepareModelFromCacheError) { + // setup call + const auto mockDevice = createMockDevice(); + const auto device = Device::create(kName, mockDevice).value(); + EXPECT_CALL(*mockDevice, prepareModelFromCache(_, _, _, _)) + .Times(1) + .WillOnce(Invoke(makePreparedModelFromCacheReturn(V1_0::ErrorStatus::GENERAL_FAILURE, + V1_0::ErrorStatus::GENERAL_FAILURE, + nullptr))); + + // run test + const auto result = device->prepareModelFromCache({}, {}, {}, {}); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); +} + +TEST(DeviceTest, prepareModelFromCacheNullptrError) { + // setup call + const auto mockDevice = createMockDevice(); + const auto device = Device::create(kName, mockDevice).value(); + EXPECT_CALL(*mockDevice, prepareModelFromCache(_, _, _, _)) + .Times(1) + .WillOnce(Invoke(makePreparedModelFromCacheReturn(V1_0::ErrorStatus::NONE, + V1_0::ErrorStatus::NONE, nullptr))); + + // run test + const auto result = device->prepareModelFromCache({}, {}, {}, {}); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); +} + +TEST(DeviceTest, prepareModelFromCacheTransportFailure) { + // setup call + const auto mockDevice = createMockDevice(); + const auto device = Device::create(kName, mockDevice).value(); + EXPECT_CALL(*mockDevice, prepareModelFromCache(_, _, _, _)) + .Times(1) + .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure)); + + // run test + const auto result = device->prepareModelFromCache({}, {}, {}, {}); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); +} + +TEST(DeviceTest, prepareModelFromCacheDeadObject) { + // setup call + const auto mockDevice = createMockDevice(); + const auto device = Device::create(kName, mockDevice).value(); + EXPECT_CALL(*mockDevice, prepareModelFromCache(_, _, _, _)) + .Times(1) + .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure)); + + // run test + const auto result = device->prepareModelFromCache({}, {}, {}, {}); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT); +} + +TEST(DeviceTest, prepareModelFromCacheAsyncCrash) { + // setup test + const auto mockDevice = createMockDevice(); + const auto device = Device::create(kName, mockDevice).value(); + const auto ret = [&mockDevice]() -> hardware::Return<V1_0::ErrorStatus> { + mockDevice->simulateCrash(); + return V1_0::ErrorStatus::NONE; + }; + EXPECT_CALL(*mockDevice, prepareModelFromCache(_, _, _, _)) + .Times(1) + .WillOnce(InvokeWithoutArgs(ret)); + + // run test + const auto result = device->prepareModelFromCache({}, {}, {}, {}); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT); +} + +TEST(DeviceTest, allocateNotSupported) { + // setup call + const auto mockDevice = createMockDevice(); + const auto device = Device::create(kName, mockDevice).value(); + + // run test + const auto result = device->allocate({}, {}, {}, {}); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); +} + +} // namespace android::hardware::neuralnetworks::V1_2::utils diff --git a/neuralnetworks/1.2/utils/test/MockDevice.h b/neuralnetworks/1.2/utils/test/MockDevice.h new file mode 100644 index 0000000000..b4599430a2 --- /dev/null +++ b/neuralnetworks/1.2/utils/test/MockDevice.h @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * 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 ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_2_UTILS_TEST_MOCK_DEVICE +#define ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_2_UTILS_TEST_MOCK_DEVICE + +#include <android/hardware/neuralnetworks/1.2/IDevice.h> +#include <gmock/gmock.h> +#include <gtest/gtest.h> +#include <hidl/Status.h> + +namespace android::hardware::neuralnetworks::V1_2::utils { + +using CacheToken = + hidl_array<uint8_t, static_cast<uint32_t>(V1_2::Constant::BYTE_SIZE_OF_CACHE_TOKEN)>; + +class MockDevice final : public IDevice { + public: + static sp<MockDevice> create(); + + // IBase methods below. + MOCK_METHOD(Return<void>, ping, (), (override)); + MOCK_METHOD(Return<bool>, linkToDeathRet, ()); + Return<bool> linkToDeath(const sp<hidl_death_recipient>& recipient, uint64_t /*cookie*/); + + // V1_0 methods below. + MOCK_METHOD(Return<void>, getCapabilities, (getCapabilities_cb cb), (override)); + MOCK_METHOD(Return<void>, getSupportedOperations, + (const V1_0::Model& model, getSupportedOperations_cb cb), (override)); + MOCK_METHOD(Return<V1_0::ErrorStatus>, prepareModel, + (const V1_0::Model& model, const sp<V1_0::IPreparedModelCallback>& callback), + (override)); + MOCK_METHOD(Return<V1_0::DeviceStatus>, getStatus, (), (override)); + + // V1_1 methods below. + MOCK_METHOD(Return<void>, getCapabilities_1_1, (getCapabilities_1_1_cb cb), (override)); + MOCK_METHOD(Return<void>, getSupportedOperations_1_1, + (const V1_1::Model& model, getSupportedOperations_1_1_cb cb), (override)); + MOCK_METHOD(Return<V1_0::ErrorStatus>, prepareModel_1_1, + (const V1_1::Model& model, V1_1::ExecutionPreference preference, + const sp<V1_0::IPreparedModelCallback>& callback), + (override)); + + // V1_2 methods below. + MOCK_METHOD(Return<void>, getVersionString, (getVersionString_cb cb), (override)); + MOCK_METHOD(Return<void>, getType, (getType_cb cb), (override)); + MOCK_METHOD(Return<void>, getCapabilities_1_2, (getCapabilities_1_2_cb cb), (override)); + MOCK_METHOD(Return<void>, getSupportedExtensions, (getSupportedExtensions_cb cb), (override)); + MOCK_METHOD(Return<void>, getSupportedOperations_1_2, + (const V1_2::Model& model, getSupportedOperations_1_2_cb cb), (override)); + MOCK_METHOD(Return<void>, getNumberOfCacheFilesNeeded, (getNumberOfCacheFilesNeeded_cb cb), + (override)); + MOCK_METHOD(Return<V1_0::ErrorStatus>, prepareModel_1_2, + (const V1_2::Model& model, V1_1::ExecutionPreference preference, + const hidl_vec<hidl_handle>& modelCache, const hidl_vec<hidl_handle>& dataCache, + const CacheToken& token, const sp<V1_2::IPreparedModelCallback>& callback), + (override)); + MOCK_METHOD(Return<V1_0::ErrorStatus>, prepareModelFromCache, + (const hidl_vec<hidl_handle>& modelCache, const hidl_vec<hidl_handle>& dataCache, + const CacheToken& token, const sp<V1_2::IPreparedModelCallback>& callback), + (override)); + + // Helper methods. + void simulateCrash(); + + private: + sp<hidl_death_recipient> mDeathRecipient; +}; + +inline sp<MockDevice> MockDevice::create() { + auto mockDevice = sp<MockDevice>::make(); + + // Setup default actions for each relevant call. + const auto ret = []() -> Return<bool> { return true; }; + + // Setup default actions for each relevant call. + ON_CALL(*mockDevice, linkToDeathRet()).WillByDefault(testing::Invoke(ret)); + + // These EXPECT_CALL(...).Times(testing::AnyNumber()) calls are to suppress warnings on the + // uninteresting methods calls. + EXPECT_CALL(*mockDevice, linkToDeathRet()).Times(testing::AnyNumber()); + + return mockDevice; +} + +inline Return<bool> MockDevice::linkToDeath(const sp<hidl_death_recipient>& recipient, + uint64_t /*cookie*/) { + mDeathRecipient = recipient; + return linkToDeathRet(); +} + +inline void MockDevice::simulateCrash() { + ASSERT_NE(nullptr, mDeathRecipient.get()); + + // Currently, the utils::Device will not use the `cookie` or `who` arguments, so we pass in 0 + // and nullptr for these arguments instead. Normally, they are used by the hidl_death_recipient + // to determine which object is dead. However, the utils::Device code only pairs a single death + // recipient with a single HIDL interface object, so these arguments are redundant. + mDeathRecipient->serviceDied(0, nullptr); +} + +} // namespace android::hardware::neuralnetworks::V1_2::utils + +#endif // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_2_UTILS_TEST_MOCK_DEVICE diff --git a/neuralnetworks/1.2/utils/test/MockPreparedModel.h b/neuralnetworks/1.2/utils/test/MockPreparedModel.h new file mode 100644 index 0000000000..f5fd1f3204 --- /dev/null +++ b/neuralnetworks/1.2/utils/test/MockPreparedModel.h @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * 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 ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_2_UTILS_TEST_MOCK_PREPARED_MODEL +#define ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_2_UTILS_TEST_MOCK_PREPARED_MODEL + +#include <android/hardware/neuralnetworks/1.2/IPreparedModel.h> +#include <gmock/gmock.h> +#include <gtest/gtest.h> +#include <hidl/HidlSupport.h> +#include <hidl/Status.h> + +namespace android::hardware::neuralnetworks::V1_2::utils { + +class MockPreparedModel final : public IPreparedModel { + public: + static sp<MockPreparedModel> create(); + + // IBase methods below. + MOCK_METHOD(Return<void>, ping, (), (override)); + MOCK_METHOD(Return<bool>, linkToDeathRet, ()); + Return<bool> linkToDeath(const sp<hidl_death_recipient>& recipient, + uint64_t /*cookie*/) override; + + // V1_0 methods below. + MOCK_METHOD(Return<V1_0::ErrorStatus>, execute, + (const V1_0::Request& request, const sp<V1_0::IExecutionCallback>& callback), + (override)); + + // V1_2 methods below. + MOCK_METHOD(Return<V1_0::ErrorStatus>, execute_1_2, + (const V1_0::Request& request, V1_2::MeasureTiming measure, + const sp<V1_2::IExecutionCallback>& callback), + (override)); + MOCK_METHOD(Return<void>, executeSynchronously, + (const V1_0::Request& request, V1_2::MeasureTiming measure, + executeSynchronously_cb cb), + (override)); + MOCK_METHOD(Return<void>, configureExecutionBurst, + (const sp<V1_2::IBurstCallback>& callback, + const MQDescriptorSync<V1_2::FmqRequestDatum>& requestChannel, + const MQDescriptorSync<V1_2::FmqResultDatum>& resultChannel, + configureExecutionBurst_cb cb), + (override)); + + // Helper methods. + void simulateCrash(); + + private: + sp<hidl_death_recipient> mDeathRecipient; +}; + +inline sp<MockPreparedModel> MockPreparedModel::create() { + auto mockPreparedModel = sp<MockPreparedModel>::make(); + + // Setup default actions for each relevant call. + const auto ret = []() -> Return<bool> { return true; }; + + // Setup default actions for each relevant call. + ON_CALL(*mockPreparedModel, linkToDeathRet()).WillByDefault(testing::Invoke(ret)); + + // These EXPECT_CALL(...).Times(testing::AnyNumber()) calls are to suppress warnings on the + // uninteresting methods calls. + EXPECT_CALL(*mockPreparedModel, linkToDeathRet()).Times(testing::AnyNumber()); + + return mockPreparedModel; +} + +inline Return<bool> MockPreparedModel::linkToDeath(const sp<hidl_death_recipient>& recipient, + uint64_t /*cookie*/) { + mDeathRecipient = recipient; + return linkToDeathRet(); +} + +inline void MockPreparedModel::simulateCrash() { + ASSERT_NE(nullptr, mDeathRecipient.get()); + + // Currently, the utils::PreparedModel will not use the `cookie` or `who` arguments, so we pass + // in 0 and nullptr for these arguments instead. Normally, they are used by the + // hidl_death_recipient to determine which object is dead. However, the utils::PreparedModel + // code only pairs a single death recipient with a single HIDL interface object, so these + // arguments are redundant. + mDeathRecipient->serviceDied(0, nullptr); +} + +} // namespace android::hardware::neuralnetworks::V1_2::utils + +#endif // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_2_UTILS_TEST_MOCK_PREPARED_MODEL diff --git a/neuralnetworks/1.2/utils/test/PreparedModelTest.cpp b/neuralnetworks/1.2/utils/test/PreparedModelTest.cpp new file mode 100644 index 0000000000..5062ac9a97 --- /dev/null +++ b/neuralnetworks/1.2/utils/test/PreparedModelTest.cpp @@ -0,0 +1,341 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * 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 "MockPreparedModel.h" + +#include <android/hardware/neuralnetworks/1.2/IExecutionCallback.h> +#include <gmock/gmock.h> +#include <gtest/gtest.h> +#include <nnapi/IPreparedModel.h> +#include <nnapi/TypeUtils.h> +#include <nnapi/Types.h> +#include <nnapi/hal/1.2/PreparedModel.h> + +#include <functional> +#include <memory> + +namespace android::hardware::neuralnetworks::V1_2::utils { +namespace { + +using ::testing::_; +using ::testing::Invoke; +using ::testing::InvokeWithoutArgs; + +const sp<V1_2::IPreparedModel> kInvalidPreparedModel; +constexpr auto kNoTiming = V1_2::Timing{.timeOnDevice = std::numeric_limits<uint64_t>::max(), + .timeInDriver = std::numeric_limits<uint64_t>::max()}; + +sp<MockPreparedModel> createMockPreparedModel() { + const auto mockPreparedModel = MockPreparedModel::create(); + + // Ensure that older calls are not used. + EXPECT_CALL(*mockPreparedModel, execute(_, _)).Times(0); + + return mockPreparedModel; +} + +auto makeExecuteSynchronously(V1_0::ErrorStatus status, + const std::vector<V1_2::OutputShape>& outputShapes, + const V1_2::Timing& timing) { + return [status, outputShapes, timing](const V1_0::Request& /*request*/, + V1_2::MeasureTiming /*measureTiming*/, + const V1_2::IPreparedModel::executeSynchronously_cb& cb) { + cb(status, outputShapes, timing); + return hardware::Void(); + }; +} +auto makeExecuteAsynchronously(V1_0::ErrorStatus launchStatus, V1_0::ErrorStatus returnStatus, + const std::vector<V1_2::OutputShape>& outputShapes, + const V1_2::Timing& timing) { + return [launchStatus, returnStatus, outputShapes, timing]( + const V1_0::Request& /*request*/, V1_2::MeasureTiming /*measureTiming*/, + const sp<V1_2::IExecutionCallback>& cb) -> Return<V1_0::ErrorStatus> { + cb->notify_1_2(returnStatus, outputShapes, timing); + return launchStatus; + }; +} + +std::function<hardware::Status()> makeTransportFailure(status_t status) { + return [status] { return hardware::Status::fromStatusT(status); }; +} + +const auto makeGeneralTransportFailure = makeTransportFailure(NO_MEMORY); +const auto makeDeadObjectFailure = makeTransportFailure(DEAD_OBJECT); + +} // namespace + +TEST(PreparedModelTest, invalidPreparedModel) { + // run test + const auto result = PreparedModel::create(kInvalidPreparedModel, /*executeSynchronously=*/true); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); +} + +TEST(PreparedModelTest, linkToDeathError) { + // setup call + const auto mockPreparedModel = createMockPreparedModel(); + const auto ret = []() -> Return<bool> { return false; }; + EXPECT_CALL(*mockPreparedModel, linkToDeathRet()).Times(1).WillOnce(InvokeWithoutArgs(ret)); + + // run test + const auto result = PreparedModel::create(mockPreparedModel, /*executeSynchronously=*/true); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); +} + +TEST(PreparedModelTest, linkToDeathTransportFailure) { + // setup call + const auto mockPreparedModel = createMockPreparedModel(); + EXPECT_CALL(*mockPreparedModel, linkToDeathRet()) + .Times(1) + .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure)); + + // run test + const auto result = PreparedModel::create(mockPreparedModel, /*executeSynchronously=*/true); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); +} + +TEST(PreparedModelTest, linkToDeathDeadObject) { + // setup call + const auto mockPreparedModel = createMockPreparedModel(); + EXPECT_CALL(*mockPreparedModel, linkToDeathRet()) + .Times(1) + .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure)); + + // run test + const auto result = PreparedModel::create(mockPreparedModel, /*executeSynchronously=*/true); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT); +} + +TEST(PreparedModelTest, executeSync) { + // setup call + const auto mockPreparedModel = createMockPreparedModel(); + const auto preparedModel = + PreparedModel::create(mockPreparedModel, /*executeSynchronously=*/true).value(); + EXPECT_CALL(*mockPreparedModel, executeSynchronously(_, _, _)) + .Times(1) + .WillOnce(Invoke(makeExecuteSynchronously(V1_0::ErrorStatus::NONE, {}, kNoTiming))); + + // run test + const auto result = preparedModel->execute({}, {}, {}, {}); + + // verify result + EXPECT_TRUE(result.has_value()) + << "Failed with " << result.error().code << ": " << result.error().message; +} + +TEST(PreparedModelTest, executeSyncError) { + // setup test + const auto mockPreparedModel = createMockPreparedModel(); + const auto preparedModel = + PreparedModel::create(mockPreparedModel, /*executeSynchronously=*/true).value(); + EXPECT_CALL(*mockPreparedModel, executeSynchronously(_, _, _)) + .Times(1) + .WillOnce(Invoke( + makeExecuteSynchronously(V1_0::ErrorStatus::GENERAL_FAILURE, {}, kNoTiming))); + + // run test + const auto result = preparedModel->execute({}, {}, {}, {}); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); +} + +TEST(PreparedModelTest, executeSyncTransportFailure) { + // setup test + const auto mockPreparedModel = createMockPreparedModel(); + const auto preparedModel = + PreparedModel::create(mockPreparedModel, /*executeSynchronously=*/true).value(); + EXPECT_CALL(*mockPreparedModel, executeSynchronously(_, _, _)) + .Times(1) + .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure)); + + // run test + const auto result = preparedModel->execute({}, {}, {}, {}); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); +} + +TEST(PreparedModelTest, executeSyncDeadObject) { + // setup test + const auto mockPreparedModel = createMockPreparedModel(); + const auto preparedModel = + PreparedModel::create(mockPreparedModel, /*executeSynchronously=*/true).value(); + EXPECT_CALL(*mockPreparedModel, executeSynchronously(_, _, _)) + .Times(1) + .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure)); + + // run test + const auto result = preparedModel->execute({}, {}, {}, {}); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT); +} + +TEST(PreparedModelTest, executeAsync) { + // setup call + const auto mockPreparedModel = createMockPreparedModel(); + const auto preparedModel = + PreparedModel::create(mockPreparedModel, /*executeSynchronously=*/false).value(); + EXPECT_CALL(*mockPreparedModel, execute_1_2(_, _, _)) + .Times(1) + .WillOnce(Invoke(makeExecuteAsynchronously(V1_0::ErrorStatus::NONE, + V1_0::ErrorStatus::NONE, {}, kNoTiming))); + + // run test + const auto result = preparedModel->execute({}, {}, {}, {}); + + // verify result + EXPECT_TRUE(result.has_value()) + << "Failed with " << result.error().code << ": " << result.error().message; +} + +TEST(PreparedModelTest, executeAsyncLaunchError) { + // setup test + const auto mockPreparedModel = createMockPreparedModel(); + const auto preparedModel = + PreparedModel::create(mockPreparedModel, /*executeSynchronously=*/false).value(); + EXPECT_CALL(*mockPreparedModel, execute_1_2(_, _, _)) + .Times(1) + .WillOnce(Invoke(makeExecuteAsynchronously(V1_0::ErrorStatus::GENERAL_FAILURE, + V1_0::ErrorStatus::GENERAL_FAILURE, {}, + kNoTiming))); + + // run test + const auto result = preparedModel->execute({}, {}, {}, {}); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); +} + +TEST(PreparedModelTest, executeAsyncReturnError) { + // setup test + const auto mockPreparedModel = createMockPreparedModel(); + const auto preparedModel = + PreparedModel::create(mockPreparedModel, /*executeSynchronously=*/false).value(); + EXPECT_CALL(*mockPreparedModel, execute_1_2(_, _, _)) + .Times(1) + .WillOnce(Invoke(makeExecuteAsynchronously( + V1_0::ErrorStatus::NONE, V1_0::ErrorStatus::GENERAL_FAILURE, {}, kNoTiming))); + + // run test + const auto result = preparedModel->execute({}, {}, {}, {}); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); +} + +TEST(PreparedModelTest, executeAsyncTransportFailure) { + // setup test + const auto mockPreparedModel = createMockPreparedModel(); + const auto preparedModel = + PreparedModel::create(mockPreparedModel, /*executeSynchronously=*/false).value(); + EXPECT_CALL(*mockPreparedModel, execute_1_2(_, _, _)) + .Times(1) + .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure)); + + // run test + const auto result = preparedModel->execute({}, {}, {}, {}); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); +} + +TEST(PreparedModelTest, executeAsyncDeadObject) { + // setup test + const auto mockPreparedModel = createMockPreparedModel(); + const auto preparedModel = + PreparedModel::create(mockPreparedModel, /*executeSynchronously=*/false).value(); + EXPECT_CALL(*mockPreparedModel, execute_1_2(_, _, _)) + .Times(1) + .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure)); + + // run test + const auto result = preparedModel->execute({}, {}, {}, {}); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT); +} + +TEST(PreparedModelTest, executeAsyncCrash) { + // setup test + const auto mockPreparedModel = createMockPreparedModel(); + const auto preparedModel = + PreparedModel::create(mockPreparedModel, /*executeSynchronously=*/false).value(); + const auto ret = [&mockPreparedModel]() -> hardware::Return<V1_0::ErrorStatus> { + mockPreparedModel->simulateCrash(); + return V1_0::ErrorStatus::NONE; + }; + EXPECT_CALL(*mockPreparedModel, execute_1_2(_, _, _)).Times(1).WillOnce(InvokeWithoutArgs(ret)); + + // run test + const auto result = preparedModel->execute({}, {}, {}, {}); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT); +} + +TEST(PreparedModelTest, executeFencedNotSupported) { + // setup test + const auto mockPreparedModel = createMockPreparedModel(); + const auto preparedModel = + PreparedModel::create(mockPreparedModel, /*executeSynchronously=*/true).value(); + + // run test + const auto result = preparedModel->executeFenced({}, {}, {}, {}, {}, {}); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); +} + +// TODO: test burst execution if/when it is added to nn::IPreparedModel. + +TEST(PreparedModelTest, getUnderlyingResource) { + // setup test + const auto mockPreparedModel = createMockPreparedModel(); + const auto preparedModel = + PreparedModel::create(mockPreparedModel, /*executeSynchronously=*/true).value(); + + // run test + const auto resource = preparedModel->getUnderlyingResource(); + + // verify resource + const sp<V1_2::IPreparedModel>* maybeMock = std::any_cast<sp<V1_2::IPreparedModel>>(&resource); + ASSERT_NE(maybeMock, nullptr); + EXPECT_EQ(maybeMock->get(), mockPreparedModel.get()); +} + +} // namespace android::hardware::neuralnetworks::V1_2::utils diff --git a/neuralnetworks/1.3/utils/Android.bp b/neuralnetworks/1.3/utils/Android.bp index d5d897d470..41d9521173 100644 --- a/neuralnetworks/1.3/utils/Android.bp +++ b/neuralnetworks/1.3/utils/Android.bp @@ -38,3 +38,35 @@ cc_library_static { "neuralnetworks_utils_hal_common", ], } + +cc_test { + name: "neuralnetworks_utils_hal_1_3_test", + srcs: ["test/*.cpp"], + static_libs: [ + "android.hardware.neuralnetworks@1.0", + "android.hardware.neuralnetworks@1.1", + "android.hardware.neuralnetworks@1.2", + "android.hardware.neuralnetworks@1.3", + "libgmock", + "libneuralnetworks_common", + "neuralnetworks_types", + "neuralnetworks_utils_hal_common", + "neuralnetworks_utils_hal_1_0", + "neuralnetworks_utils_hal_1_1", + "neuralnetworks_utils_hal_1_2", + "neuralnetworks_utils_hal_1_3", + ], + shared_libs: [ + "android.hidl.allocator@1.0", + "android.hidl.memory@1.0", + "libbase", + "libcutils", + "libfmq", + "libhidlbase", + "libhidlmemory", + "liblog", + "libnativewindow", + "libutils", + ], + test_suites: ["general-tests"], +} diff --git a/neuralnetworks/1.3/utils/include/nnapi/hal/1.3/Device.h b/neuralnetworks/1.3/utils/include/nnapi/hal/1.3/Device.h index 84f606a357..f36b6c0642 100644 --- a/neuralnetworks/1.3/utils/include/nnapi/hal/1.3/Device.h +++ b/neuralnetworks/1.3/utils/include/nnapi/hal/1.3/Device.h @@ -54,6 +54,7 @@ class Device final : public nn::IDevice { const std::string& getVersionString() const override; nn::Version getFeatureLevel() const override; nn::DeviceType getType() const override; + bool isUpdatable() const override; const std::vector<nn::Extension>& getSupportedExtensions() const override; const nn::Capabilities& getCapabilities() const override; std::pair<uint32_t, uint32_t> getNumberOfCacheFilesNeeded() const override; diff --git a/neuralnetworks/1.3/utils/include/nnapi/hal/1.3/PreparedModel.h b/neuralnetworks/1.3/utils/include/nnapi/hal/1.3/PreparedModel.h index 664d87a7c2..690fecccfb 100644 --- a/neuralnetworks/1.3/utils/include/nnapi/hal/1.3/PreparedModel.h +++ b/neuralnetworks/1.3/utils/include/nnapi/hal/1.3/PreparedModel.h @@ -35,7 +35,8 @@ namespace android::hardware::neuralnetworks::V1_3::utils { // Class that adapts V1_3::IPreparedModel to nn::IPreparedModel. -class PreparedModel final : public nn::IPreparedModel { +class PreparedModel final : public nn::IPreparedModel, + public std::enable_shared_from_this<PreparedModel> { struct PrivateConstructorTag {}; public: @@ -56,6 +57,8 @@ class PreparedModel final : public nn::IPreparedModel { const nn::OptionalDuration& loopTimeoutDuration, const nn::OptionalDuration& timeoutDurationAfterFence) const override; + nn::GeneralResult<nn::SharedBurst> configureExecutionBurst() const override; + std::any getUnderlyingResource() const override; private: diff --git a/neuralnetworks/1.3/utils/src/Device.cpp b/neuralnetworks/1.3/utils/src/Device.cpp index d710b85070..87c9f32e4d 100644 --- a/neuralnetworks/1.3/utils/src/Device.cpp +++ b/neuralnetworks/1.3/utils/src/Device.cpp @@ -150,6 +150,10 @@ nn::DeviceType Device::getType() const { return kDeviceType; } +bool Device::isUpdatable() const { + return false; +} + const std::vector<nn::Extension>& Device::getSupportedExtensions() const { return kExtensions; } diff --git a/neuralnetworks/1.3/utils/src/PreparedModel.cpp b/neuralnetworks/1.3/utils/src/PreparedModel.cpp index 7b4b7bac3b..725e4f546a 100644 --- a/neuralnetworks/1.3/utils/src/PreparedModel.cpp +++ b/neuralnetworks/1.3/utils/src/PreparedModel.cpp @@ -29,6 +29,7 @@ #include <nnapi/Result.h> #include <nnapi/TypeUtils.h> #include <nnapi/Types.h> +#include <nnapi/hal/1.0/Burst.h> #include <nnapi/hal/1.2/Conversions.h> #include <nnapi/hal/CommonUtils.h> #include <nnapi/hal/HandleError.h> @@ -197,6 +198,10 @@ PreparedModel::executeFenced(const nn::Request& request, const std::vector<nn::S return std::make_pair(std::move(syncFence), std::move(callback)); } +nn::GeneralResult<nn::SharedBurst> PreparedModel::configureExecutionBurst() const { + return V1_0::utils::Burst::create(shared_from_this()); +} + std::any PreparedModel::getUnderlyingResource() const { sp<V1_3::IPreparedModel> resource = kPreparedModel; return resource; diff --git a/neuralnetworks/1.3/utils/test/BufferTest.cpp b/neuralnetworks/1.3/utils/test/BufferTest.cpp new file mode 100644 index 0000000000..d892b8787e --- /dev/null +++ b/neuralnetworks/1.3/utils/test/BufferTest.cpp @@ -0,0 +1,208 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * 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 "MockBuffer.h" + +#include <android/hardware/neuralnetworks/1.3/IBuffer.h> +#include <gmock/gmock.h> +#include <gtest/gtest.h> +#include <nnapi/IBuffer.h> +#include <nnapi/SharedMemory.h> +#include <nnapi/TypeUtils.h> +#include <nnapi/Types.h> +#include <nnapi/hal/1.3/Buffer.h> + +#include <functional> +#include <memory> + +namespace android::hardware::neuralnetworks::V1_3::utils { +namespace { + +using ::testing::_; +using ::testing::Invoke; +using ::testing::InvokeWithoutArgs; +using ::testing::Return; + +const auto kMemory = nn::createSharedMemory(4).value(); +const sp<V1_3::IBuffer> kInvalidBuffer; +constexpr auto kInvalidToken = nn::Request::MemoryDomainToken{0}; +constexpr auto kToken = nn::Request::MemoryDomainToken{1}; + +std::function<hardware::Return<V1_3::ErrorStatus>()> makeFunctionReturn(V1_3::ErrorStatus status) { + return [status]() -> hardware::Return<V1_3::ErrorStatus> { return status; }; +} + +std::function<hardware::Status()> makeTransportFailure(status_t status) { + return [status] { return hardware::Status::fromStatusT(status); }; +} + +const auto makeSuccessful = makeFunctionReturn(V1_3::ErrorStatus::NONE); +const auto makeGeneralError = makeFunctionReturn(V1_3::ErrorStatus::GENERAL_FAILURE); +const auto makeGeneralTransportFailure = makeTransportFailure(NO_MEMORY); +const auto makeDeadObjectFailure = makeTransportFailure(DEAD_OBJECT); + +} // namespace + +TEST(BufferTest, invalidBuffer) { + // run test + const auto result = Buffer::create(kInvalidBuffer, kToken); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); +} + +TEST(BufferTest, invalidToken) { + // setup call + const auto mockBuffer = MockBuffer::create(); + + // run test + const auto result = Buffer::create(mockBuffer, kInvalidToken); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); +} + +TEST(BufferTest, create) { + // setup call + const auto mockBuffer = MockBuffer::create(); + const auto buffer = Buffer::create(mockBuffer, kToken).value(); + + // run test + const auto token = buffer->getToken(); + + // verify result + EXPECT_EQ(token, kToken); +} + +TEST(BufferTest, copyTo) { + // setup call + const auto mockBuffer = MockBuffer::create(); + const auto buffer = Buffer::create(mockBuffer, kToken).value(); + EXPECT_CALL(*mockBuffer, copyTo(_)).Times(1).WillOnce(InvokeWithoutArgs(makeSuccessful)); + + // run test + const auto result = buffer->copyTo(kMemory); + + // verify result + EXPECT_TRUE(result.has_value()) << result.error().message; +} + +TEST(BufferTest, copyToError) { + // setup test + const auto mockBuffer = MockBuffer::create(); + const auto buffer = Buffer::create(mockBuffer, kToken).value(); + EXPECT_CALL(*mockBuffer, copyTo(_)).Times(1).WillOnce(InvokeWithoutArgs(makeGeneralError)); + + // run test + const auto result = buffer->copyTo(kMemory); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); +} + +TEST(BufferTest, copyToTransportFailure) { + // setup test + const auto mockBuffer = MockBuffer::create(); + const auto buffer = Buffer::create(mockBuffer, kToken).value(); + EXPECT_CALL(*mockBuffer, copyTo(_)) + .Times(1) + .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure)); + + // run test + const auto result = buffer->copyTo(kMemory); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); +} + +TEST(BufferTest, copyToDeadObject) { + // setup test + const auto mockBuffer = MockBuffer::create(); + const auto buffer = Buffer::create(mockBuffer, kToken).value(); + EXPECT_CALL(*mockBuffer, copyTo(_)).Times(1).WillOnce(InvokeWithoutArgs(makeDeadObjectFailure)); + + // run test + const auto result = buffer->copyTo(kMemory); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT); +} + +TEST(BufferTest, copyFrom) { + // setup call + const auto mockBuffer = MockBuffer::create(); + const auto buffer = Buffer::create(mockBuffer, kToken).value(); + EXPECT_CALL(*mockBuffer, copyFrom(_, _)).Times(1).WillOnce(InvokeWithoutArgs(makeSuccessful)); + + // run test + const auto result = buffer->copyFrom(kMemory, {}); + + // verify result + EXPECT_TRUE(result.has_value()); +} + +TEST(BufferTest, copyFromError) { + // setup test + const auto mockBuffer = MockBuffer::create(); + const auto buffer = Buffer::create(mockBuffer, kToken).value(); + EXPECT_CALL(*mockBuffer, copyFrom(_, _)).Times(1).WillOnce(InvokeWithoutArgs(makeGeneralError)); + + // run test + const auto result = buffer->copyFrom(kMemory, {}); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); +} + +TEST(BufferTest, copyFromTransportFailure) { + // setup test + const auto mockBuffer = MockBuffer::create(); + const auto buffer = Buffer::create(mockBuffer, kToken).value(); + EXPECT_CALL(*mockBuffer, copyFrom(_, _)) + .Times(1) + .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure)); + + // run test + const auto result = buffer->copyFrom(kMemory, {}); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); +} + +TEST(BufferTest, copyFromDeadObject) { + // setup test + const auto mockBuffer = MockBuffer::create(); + const auto buffer = Buffer::create(mockBuffer, kToken).value(); + EXPECT_CALL(*mockBuffer, copyFrom(_, _)) + .Times(1) + .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure)); + + // run test + const auto result = buffer->copyFrom(kMemory, {}); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT); +} + +} // namespace android::hardware::neuralnetworks::V1_3::utils diff --git a/neuralnetworks/1.3/utils/test/DeviceTest.cpp b/neuralnetworks/1.3/utils/test/DeviceTest.cpp new file mode 100644 index 0000000000..f260990471 --- /dev/null +++ b/neuralnetworks/1.3/utils/test/DeviceTest.cpp @@ -0,0 +1,951 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * 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 "MockBuffer.h" +#include "MockDevice.h" +#include "MockPreparedModel.h" + +#include <android/hardware/neuralnetworks/1.3/IDevice.h> +#include <gmock/gmock.h> +#include <gtest/gtest.h> +#include <nnapi/IDevice.h> +#include <nnapi/TypeUtils.h> +#include <nnapi/Types.h> +#include <nnapi/hal/1.3/Device.h> + +#include <functional> +#include <memory> +#include <string> + +namespace android::hardware::neuralnetworks::V1_3::utils { +namespace { + +using ::testing::_; +using ::testing::Invoke; +using ::testing::InvokeWithoutArgs; + +const nn::Model kSimpleModel = { + .main = {.operands = {{.type = nn::OperandType::TENSOR_FLOAT32, + .dimensions = {1}, + .lifetime = nn::Operand::LifeTime::SUBGRAPH_INPUT}, + {.type = nn::OperandType::TENSOR_FLOAT32, + .dimensions = {1}, + .lifetime = nn::Operand::LifeTime::SUBGRAPH_OUTPUT}}, + .operations = {{.type = nn::OperationType::RELU, .inputs = {0}, .outputs = {1}}}, + .inputIndexes = {0}, + .outputIndexes = {1}}}; + +const std::string kName = "Google-MockV1"; +const std::string kInvalidName = ""; +const sp<V1_3::IDevice> kInvalidDevice; +constexpr V1_0::PerformanceInfo kNoPerformanceInfo = { + .execTime = std::numeric_limits<float>::max(), + .powerUsage = std::numeric_limits<float>::max()}; + +template <typename... Args> +auto makeCallbackReturn(Args&&... args) { + return [argPack = std::make_tuple(std::forward<Args>(args)...)](const auto& cb) { + std::apply(cb, argPack); + return Void(); + }; +} + +sp<MockDevice> createMockDevice() { + const auto mockDevice = MockDevice::create(); + + // Setup default actions for each relevant call. + const auto getVersionString_ret = makeCallbackReturn(V1_0::ErrorStatus::NONE, kName); + const auto getType_ret = makeCallbackReturn(V1_0::ErrorStatus::NONE, V1_2::DeviceType::OTHER); + const auto getSupportedExtensions_ret = + makeCallbackReturn(V1_0::ErrorStatus::NONE, hidl_vec<V1_2::Extension>{}); + const auto getNumberOfCacheFilesNeeded_ret = makeCallbackReturn( + V1_0::ErrorStatus::NONE, nn::kMaxNumberOfCacheFiles, nn::kMaxNumberOfCacheFiles); + const auto getCapabilities_ret = makeCallbackReturn( + V1_3::ErrorStatus::NONE, + V1_3::Capabilities{ + .relaxedFloat32toFloat16PerformanceScalar = kNoPerformanceInfo, + .relaxedFloat32toFloat16PerformanceTensor = kNoPerformanceInfo, + .ifPerformance = kNoPerformanceInfo, + .whilePerformance = kNoPerformanceInfo, + }); + + // Setup default actions for each relevant call. + ON_CALL(*mockDevice, getVersionString(_)).WillByDefault(Invoke(getVersionString_ret)); + ON_CALL(*mockDevice, getType(_)).WillByDefault(Invoke(getType_ret)); + ON_CALL(*mockDevice, getSupportedExtensions(_)) + .WillByDefault(Invoke(getSupportedExtensions_ret)); + ON_CALL(*mockDevice, getNumberOfCacheFilesNeeded(_)) + .WillByDefault(Invoke(getNumberOfCacheFilesNeeded_ret)); + ON_CALL(*mockDevice, getCapabilities_1_3(_)).WillByDefault(Invoke(getCapabilities_ret)); + + // Ensure that older calls are not used. + EXPECT_CALL(*mockDevice, getCapabilities(_)).Times(0); + EXPECT_CALL(*mockDevice, getCapabilities_1_1(_)).Times(0); + EXPECT_CALL(*mockDevice, getCapabilities_1_2(_)).Times(0); + EXPECT_CALL(*mockDevice, getSupportedOperations(_, _)).Times(0); + EXPECT_CALL(*mockDevice, getSupportedOperations_1_1(_, _)).Times(0); + EXPECT_CALL(*mockDevice, prepareModel(_, _)).Times(0); + EXPECT_CALL(*mockDevice, prepareModel_1_1(_, _, _)).Times(0); + EXPECT_CALL(*mockDevice, getSupportedOperations_1_2(_, _)).Times(0); + EXPECT_CALL(*mockDevice, prepareModel_1_2(_, _, _, _, _, _)).Times(0); + EXPECT_CALL(*mockDevice, prepareModelFromCache(_, _, _, _)).Times(0); + + // These EXPECT_CALL(...).Times(testing::AnyNumber()) calls are to suppress warnings on the + // uninteresting methods calls. + EXPECT_CALL(*mockDevice, getVersionString(_)).Times(testing::AnyNumber()); + EXPECT_CALL(*mockDevice, getType(_)).Times(testing::AnyNumber()); + EXPECT_CALL(*mockDevice, getSupportedExtensions(_)).Times(testing::AnyNumber()); + EXPECT_CALL(*mockDevice, getNumberOfCacheFilesNeeded(_)).Times(testing::AnyNumber()); + EXPECT_CALL(*mockDevice, getCapabilities_1_3(_)).Times(testing::AnyNumber()); + + return mockDevice; +} + +auto makePreparedModelReturn(V1_3::ErrorStatus launchStatus, V1_3::ErrorStatus returnStatus, + const sp<MockPreparedModel>& preparedModel) { + return [launchStatus, returnStatus, preparedModel]( + const V1_3::Model& /*model*/, V1_1::ExecutionPreference /*preference*/, + V1_3::Priority /*priority*/, const V1_3::OptionalTimePoint& /*deadline*/, + const hardware::hidl_vec<hardware::hidl_handle>& /*modelCache*/, + const hardware::hidl_vec<hardware::hidl_handle>& /*dataCache*/, + const CacheToken& /*token*/, const sp<V1_3::IPreparedModelCallback>& cb) + -> hardware::Return<V1_3::ErrorStatus> { + cb->notify_1_3(returnStatus, preparedModel).isOk(); + return launchStatus; + }; +} +auto makePreparedModelFromCacheReturn(V1_3::ErrorStatus launchStatus, + V1_3::ErrorStatus returnStatus, + const sp<MockPreparedModel>& preparedModel) { + return [launchStatus, returnStatus, preparedModel]( + const V1_3::OptionalTimePoint& /*deadline*/, + const hardware::hidl_vec<hardware::hidl_handle>& /*modelCache*/, + const hardware::hidl_vec<hardware::hidl_handle>& /*dataCache*/, + const CacheToken& /*token*/, const sp<V1_3::IPreparedModelCallback>& cb) + -> hardware::Return<V1_3::ErrorStatus> { + cb->notify_1_3(returnStatus, preparedModel).isOk(); + return launchStatus; + }; +} +auto makeAllocateReturn(ErrorStatus status, const sp<MockBuffer>& buffer, uint32_t token) { + return [status, buffer, token]( + const V1_3::BufferDesc& /*desc*/, + const hardware::hidl_vec<sp<V1_3::IPreparedModel>>& /*preparedModels*/, + const hardware::hidl_vec<V1_3::BufferRole>& /*inputRoles*/, + const hardware::hidl_vec<V1_3::BufferRole>& /*outputRoles*/, + const V1_3::IDevice::allocate_cb& cb) -> hardware::Return<void> { + cb(status, buffer, token); + return hardware::Void(); + }; +} + +std::function<hardware::Status()> makeTransportFailure(status_t status) { + return [status] { return hardware::Status::fromStatusT(status); }; +} + +const auto makeGeneralTransportFailure = makeTransportFailure(NO_MEMORY); +const auto makeDeadObjectFailure = makeTransportFailure(DEAD_OBJECT); + +} // namespace + +TEST(DeviceTest, invalidName) { + // run test + const auto device = MockDevice::create(); + const auto result = Device::create(kInvalidName, device); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::INVALID_ARGUMENT); +} + +TEST(DeviceTest, invalidDevice) { + // run test + const auto result = Device::create(kName, kInvalidDevice); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::INVALID_ARGUMENT); +} + +TEST(DeviceTest, getVersionStringError) { + // setup call + const auto mockDevice = createMockDevice(); + const auto ret = makeCallbackReturn(V1_0::ErrorStatus::GENERAL_FAILURE, ""); + EXPECT_CALL(*mockDevice, getVersionString(_)).Times(1).WillOnce(Invoke(ret)); + + // run test + const auto result = Device::create(kName, mockDevice); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); +} + +TEST(DeviceTest, getVersionStringTransportFailure) { + // setup call + const auto mockDevice = createMockDevice(); + EXPECT_CALL(*mockDevice, getVersionString(_)) + .Times(1) + .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure)); + + // run test + const auto result = Device::create(kName, mockDevice); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); +} + +TEST(DeviceTest, getVersionStringDeadObject) { + // setup call + const auto mockDevice = createMockDevice(); + EXPECT_CALL(*mockDevice, getVersionString(_)) + .Times(1) + .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure)); + + // run test + const auto result = Device::create(kName, mockDevice); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT); +} + +TEST(DeviceTest, getTypeError) { + // setup call + const auto mockDevice = createMockDevice(); + const auto ret = + makeCallbackReturn(V1_0::ErrorStatus::GENERAL_FAILURE, V1_2::DeviceType::OTHER); + EXPECT_CALL(*mockDevice, getType(_)).Times(1).WillOnce(Invoke(ret)); + + // run test + const auto result = Device::create(kName, mockDevice); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); +} + +TEST(DeviceTest, getTypeTransportFailure) { + // setup call + const auto mockDevice = createMockDevice(); + EXPECT_CALL(*mockDevice, getType(_)) + .Times(1) + .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure)); + + // run test + const auto result = Device::create(kName, mockDevice); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); +} + +TEST(DeviceTest, getTypeDeadObject) { + // setup call + const auto mockDevice = createMockDevice(); + EXPECT_CALL(*mockDevice, getType(_)) + .Times(1) + .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure)); + + // run test + const auto result = Device::create(kName, mockDevice); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT); +} + +TEST(DeviceTest, getSupportedExtensionsError) { + // setup call + const auto mockDevice = createMockDevice(); + const auto ret = + makeCallbackReturn(V1_0::ErrorStatus::GENERAL_FAILURE, hidl_vec<V1_2::Extension>{}); + EXPECT_CALL(*mockDevice, getSupportedExtensions(_)).Times(1).WillOnce(Invoke(ret)); + + // run test + const auto result = Device::create(kName, mockDevice); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); +} + +TEST(DeviceTest, getSupportedExtensionsTransportFailure) { + // setup call + const auto mockDevice = createMockDevice(); + EXPECT_CALL(*mockDevice, getSupportedExtensions(_)) + .Times(1) + .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure)); + + // run test + const auto result = Device::create(kName, mockDevice); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); +} + +TEST(DeviceTest, getSupportedExtensionsDeadObject) { + // setup call + const auto mockDevice = createMockDevice(); + EXPECT_CALL(*mockDevice, getSupportedExtensions(_)) + .Times(1) + .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure)); + + // run test + const auto result = Device::create(kName, mockDevice); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT); +} + +TEST(DeviceTest, getNumberOfCacheFilesNeededError) { + // setup call + const auto mockDevice = createMockDevice(); + const auto ret = makeCallbackReturn(V1_0::ErrorStatus::GENERAL_FAILURE, + nn::kMaxNumberOfCacheFiles, nn::kMaxNumberOfCacheFiles); + EXPECT_CALL(*mockDevice, getNumberOfCacheFilesNeeded(_)).Times(1).WillOnce(Invoke(ret)); + + // run test + const auto result = Device::create(kName, mockDevice); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); +} + +TEST(DeviceTest, dataCacheFilesExceedsSpecifiedMax) { + // setup test + const auto mockDevice = createMockDevice(); + const auto ret = makeCallbackReturn(V1_0::ErrorStatus::NONE, nn::kMaxNumberOfCacheFiles + 1, + nn::kMaxNumberOfCacheFiles); + EXPECT_CALL(*mockDevice, getNumberOfCacheFilesNeeded(_)).Times(1).WillOnce(Invoke(ret)); + + // run test + const auto result = Device::create(kName, mockDevice); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); +} + +TEST(DeviceTest, modelCacheFilesExceedsSpecifiedMax) { + // setup test + const auto mockDevice = createMockDevice(); + const auto ret = makeCallbackReturn(V1_0::ErrorStatus::NONE, nn::kMaxNumberOfCacheFiles, + nn::kMaxNumberOfCacheFiles + 1); + EXPECT_CALL(*mockDevice, getNumberOfCacheFilesNeeded(_)).Times(1).WillOnce(Invoke(ret)); + + // run test + const auto result = Device::create(kName, mockDevice); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); +} + +TEST(DeviceTest, getNumberOfCacheFilesNeededTransportFailure) { + // setup call + const auto mockDevice = createMockDevice(); + EXPECT_CALL(*mockDevice, getNumberOfCacheFilesNeeded(_)) + .Times(1) + .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure)); + + // run test + const auto result = Device::create(kName, mockDevice); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); +} + +TEST(DeviceTest, getNumberOfCacheFilesNeededDeadObject) { + // setup call + const auto mockDevice = createMockDevice(); + EXPECT_CALL(*mockDevice, getNumberOfCacheFilesNeeded(_)) + .Times(1) + .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure)); + + // run test + const auto result = Device::create(kName, mockDevice); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT); +} + +TEST(DeviceTest, getCapabilitiesError) { + // setup call + const auto mockDevice = createMockDevice(); + const auto ret = makeCallbackReturn( + V1_3::ErrorStatus::GENERAL_FAILURE, + V1_3::Capabilities{ + .relaxedFloat32toFloat16PerformanceScalar = kNoPerformanceInfo, + .relaxedFloat32toFloat16PerformanceTensor = kNoPerformanceInfo, + .ifPerformance = kNoPerformanceInfo, + .whilePerformance = kNoPerformanceInfo, + }); + EXPECT_CALL(*mockDevice, getCapabilities_1_3(_)).Times(1).WillOnce(Invoke(ret)); + + // run test + const auto result = Device::create(kName, mockDevice); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); +} + +TEST(DeviceTest, getCapabilitiesTransportFailure) { + // setup call + const auto mockDevice = createMockDevice(); + EXPECT_CALL(*mockDevice, getCapabilities_1_3(_)) + .Times(1) + .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure)); + + // run test + const auto result = Device::create(kName, mockDevice); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); +} + +TEST(DeviceTest, getCapabilitiesDeadObject) { + // setup call + const auto mockDevice = createMockDevice(); + EXPECT_CALL(*mockDevice, getCapabilities_1_3(_)) + .Times(1) + .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure)); + + // run test + const auto result = Device::create(kName, mockDevice); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT); +} + +TEST(DeviceTest, linkToDeathError) { + // setup call + const auto mockDevice = createMockDevice(); + const auto ret = []() -> Return<bool> { return false; }; + EXPECT_CALL(*mockDevice, linkToDeathRet()).Times(1).WillOnce(InvokeWithoutArgs(ret)); + + // run test + const auto result = Device::create(kName, mockDevice); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); +} + +TEST(DeviceTest, linkToDeathTransportFailure) { + // setup call + const auto mockDevice = createMockDevice(); + EXPECT_CALL(*mockDevice, linkToDeathRet()) + .Times(1) + .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure)); + + // run test + const auto result = Device::create(kName, mockDevice); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); +} + +TEST(DeviceTest, linkToDeathDeadObject) { + // setup call + const auto mockDevice = createMockDevice(); + EXPECT_CALL(*mockDevice, linkToDeathRet()) + .Times(1) + .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure)); + + // run test + const auto result = Device::create(kName, mockDevice); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT); +} + +TEST(DeviceTest, getName) { + // setup call + const auto mockDevice = createMockDevice(); + const auto device = Device::create(kName, mockDevice).value(); + + // run test + const auto& name = device->getName(); + + // verify result + EXPECT_EQ(name, kName); +} + +TEST(DeviceTest, getFeatureLevel) { + // setup call + const auto mockDevice = createMockDevice(); + const auto device = Device::create(kName, mockDevice).value(); + + // run test + const auto featureLevel = device->getFeatureLevel(); + + // verify result + EXPECT_EQ(featureLevel, nn::Version::ANDROID_R); +} + +TEST(DeviceTest, getCachedData) { + // setup call + const auto mockDevice = createMockDevice(); + EXPECT_CALL(*mockDevice, getVersionString(_)).Times(1); + EXPECT_CALL(*mockDevice, getType(_)).Times(1); + EXPECT_CALL(*mockDevice, getSupportedExtensions(_)).Times(1); + EXPECT_CALL(*mockDevice, getNumberOfCacheFilesNeeded(_)).Times(1); + EXPECT_CALL(*mockDevice, getCapabilities_1_3(_)).Times(1); + + const auto result = Device::create(kName, mockDevice); + ASSERT_TRUE(result.has_value()) + << "Failed with " << result.error().code << ": " << result.error().message; + const auto& device = result.value(); + + // run test and verify results + EXPECT_EQ(device->getVersionString(), device->getVersionString()); + EXPECT_EQ(device->getType(), device->getType()); + EXPECT_EQ(device->getSupportedExtensions(), device->getSupportedExtensions()); + EXPECT_EQ(device->getNumberOfCacheFilesNeeded(), device->getNumberOfCacheFilesNeeded()); + EXPECT_EQ(device->getCapabilities(), device->getCapabilities()); +} + +TEST(DeviceTest, wait) { + // setup call + const auto mockDevice = createMockDevice(); + const auto ret = []() -> Return<void> { return {}; }; + EXPECT_CALL(*mockDevice, ping()).Times(1).WillOnce(InvokeWithoutArgs(ret)); + const auto device = Device::create(kName, mockDevice).value(); + + // run test + const auto result = device->wait(); + + // verify result + ASSERT_TRUE(result.has_value()) + << "Failed with " << result.error().code << ": " << result.error().message; +} + +TEST(DeviceTest, waitTransportFailure) { + // setup call + const auto mockDevice = createMockDevice(); + EXPECT_CALL(*mockDevice, ping()) + .Times(1) + .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure)); + const auto device = Device::create(kName, mockDevice).value(); + + // run test + const auto result = device->wait(); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); +} + +TEST(DeviceTest, waitDeadObject) { + // setup call + const auto mockDevice = createMockDevice(); + EXPECT_CALL(*mockDevice, ping()).Times(1).WillOnce(InvokeWithoutArgs(makeDeadObjectFailure)); + const auto device = Device::create(kName, mockDevice).value(); + + // run test + const auto result = device->wait(); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT); +} + +TEST(DeviceTest, getSupportedOperations) { + // setup call + const auto mockDevice = createMockDevice(); + const auto device = Device::create(kName, mockDevice).value(); + const auto ret = [](const auto& model, const auto& cb) { + cb(V1_3::ErrorStatus::NONE, std::vector<bool>(model.main.operations.size(), true)); + return hardware::Void(); + }; + EXPECT_CALL(*mockDevice, getSupportedOperations_1_3(_, _)).Times(1).WillOnce(Invoke(ret)); + + // run test + const auto result = device->getSupportedOperations(kSimpleModel); + + // verify result + ASSERT_TRUE(result.has_value()) + << "Failed with " << result.error().code << ": " << result.error().message; + const auto& supportedOperations = result.value(); + EXPECT_EQ(supportedOperations.size(), kSimpleModel.main.operations.size()); + EXPECT_THAT(supportedOperations, Each(testing::IsTrue())); +} + +TEST(DeviceTest, getSupportedOperationsError) { + // setup call + const auto mockDevice = createMockDevice(); + const auto device = Device::create(kName, mockDevice).value(); + const auto ret = [](const auto& /*model*/, const auto& cb) { + cb(V1_3::ErrorStatus::GENERAL_FAILURE, {}); + return hardware::Void(); + }; + EXPECT_CALL(*mockDevice, getSupportedOperations_1_3(_, _)).Times(1).WillOnce(Invoke(ret)); + + // run test + const auto result = device->getSupportedOperations(kSimpleModel); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); +} + +TEST(DeviceTest, getSupportedOperationsTransportFailure) { + // setup call + const auto mockDevice = createMockDevice(); + const auto device = Device::create(kName, mockDevice).value(); + EXPECT_CALL(*mockDevice, getSupportedOperations_1_3(_, _)) + .Times(1) + .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure)); + + // run test + const auto result = device->getSupportedOperations(kSimpleModel); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); +} + +TEST(DeviceTest, getSupportedOperationsDeadObject) { + // setup call + const auto mockDevice = createMockDevice(); + const auto device = Device::create(kName, mockDevice).value(); + EXPECT_CALL(*mockDevice, getSupportedOperations_1_3(_, _)) + .Times(1) + .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure)); + + // run test + const auto result = device->getSupportedOperations(kSimpleModel); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT); +} + +TEST(DeviceTest, prepareModel) { + // setup call + const auto mockDevice = createMockDevice(); + const auto device = Device::create(kName, mockDevice).value(); + const auto mockPreparedModel = MockPreparedModel::create(); + EXPECT_CALL(*mockDevice, prepareModel_1_3(_, _, _, _, _, _, _, _)) + .Times(1) + .WillOnce(Invoke(makePreparedModelReturn(V1_3::ErrorStatus::NONE, + V1_3::ErrorStatus::NONE, mockPreparedModel))); + + // run test + const auto result = device->prepareModel(kSimpleModel, nn::ExecutionPreference::DEFAULT, + nn::Priority::DEFAULT, {}, {}, {}, {}); + + // verify result + ASSERT_TRUE(result.has_value()) + << "Failed with " << result.error().code << ": " << result.error().message; + EXPECT_NE(result.value(), nullptr); +} + +TEST(DeviceTest, prepareModelLaunchError) { + // setup call + const auto mockDevice = createMockDevice(); + const auto device = Device::create(kName, mockDevice).value(); + EXPECT_CALL(*mockDevice, prepareModel_1_3(_, _, _, _, _, _, _, _)) + .Times(1) + .WillOnce(Invoke(makePreparedModelReturn(V1_3::ErrorStatus::GENERAL_FAILURE, + V1_3::ErrorStatus::GENERAL_FAILURE, nullptr))); + + // run test + const auto result = device->prepareModel(kSimpleModel, nn::ExecutionPreference::DEFAULT, + nn::Priority::DEFAULT, {}, {}, {}, {}); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); +} + +TEST(DeviceTest, prepareModelReturnError) { + // setup call + const auto mockDevice = createMockDevice(); + const auto device = Device::create(kName, mockDevice).value(); + EXPECT_CALL(*mockDevice, prepareModel_1_3(_, _, _, _, _, _, _, _)) + .Times(1) + .WillOnce(Invoke(makePreparedModelReturn(V1_3::ErrorStatus::NONE, + V1_3::ErrorStatus::GENERAL_FAILURE, nullptr))); + + // run test + const auto result = device->prepareModel(kSimpleModel, nn::ExecutionPreference::DEFAULT, + nn::Priority::DEFAULT, {}, {}, {}, {}); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); +} + +TEST(DeviceTest, prepareModelNullptrError) { + // setup call + const auto mockDevice = createMockDevice(); + const auto device = Device::create(kName, mockDevice).value(); + EXPECT_CALL(*mockDevice, prepareModel_1_3(_, _, _, _, _, _, _, _)) + .Times(1) + .WillOnce(Invoke(makePreparedModelReturn(V1_3::ErrorStatus::NONE, + V1_3::ErrorStatus::NONE, nullptr))); + + // run test + const auto result = device->prepareModel(kSimpleModel, nn::ExecutionPreference::DEFAULT, + nn::Priority::DEFAULT, {}, {}, {}, {}); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); +} + +TEST(DeviceTest, prepareModelTransportFailure) { + // setup call + const auto mockDevice = createMockDevice(); + const auto device = Device::create(kName, mockDevice).value(); + EXPECT_CALL(*mockDevice, prepareModel_1_3(_, _, _, _, _, _, _, _)) + .Times(1) + .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure)); + + // run test + const auto result = device->prepareModel(kSimpleModel, nn::ExecutionPreference::DEFAULT, + nn::Priority::DEFAULT, {}, {}, {}, {}); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); +} + +TEST(DeviceTest, prepareModelDeadObject) { + // setup call + const auto mockDevice = createMockDevice(); + const auto device = Device::create(kName, mockDevice).value(); + EXPECT_CALL(*mockDevice, prepareModel_1_3(_, _, _, _, _, _, _, _)) + .Times(1) + .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure)); + + // run test + const auto result = device->prepareModel(kSimpleModel, nn::ExecutionPreference::DEFAULT, + nn::Priority::DEFAULT, {}, {}, {}, {}); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT); +} + +TEST(DeviceTest, prepareModelAsyncCrash) { + // setup test + const auto mockDevice = createMockDevice(); + const auto device = Device::create(kName, mockDevice).value(); + const auto ret = [&mockDevice]() -> hardware::Return<V1_3::ErrorStatus> { + mockDevice->simulateCrash(); + return V1_3::ErrorStatus::NONE; + }; + EXPECT_CALL(*mockDevice, prepareModel_1_3(_, _, _, _, _, _, _, _)) + .Times(1) + .WillOnce(InvokeWithoutArgs(ret)); + + // run test + const auto result = device->prepareModel(kSimpleModel, nn::ExecutionPreference::DEFAULT, + nn::Priority::DEFAULT, {}, {}, {}, {}); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT); +} + +TEST(DeviceTest, prepareModelFromCache) { + // setup call + const auto mockDevice = createMockDevice(); + const auto device = Device::create(kName, mockDevice).value(); + const auto mockPreparedModel = MockPreparedModel::create(); + EXPECT_CALL(*mockDevice, prepareModelFromCache_1_3(_, _, _, _, _)) + .Times(1) + .WillOnce(Invoke(makePreparedModelFromCacheReturn( + V1_3::ErrorStatus::NONE, V1_3::ErrorStatus::NONE, mockPreparedModel))); + + // run test + const auto result = device->prepareModelFromCache({}, {}, {}, {}); + + // verify result + ASSERT_TRUE(result.has_value()) + << "Failed with " << result.error().code << ": " << result.error().message; + EXPECT_NE(result.value(), nullptr); +} + +TEST(DeviceTest, prepareModelFromCacheError) { + // setup call + const auto mockDevice = createMockDevice(); + const auto device = Device::create(kName, mockDevice).value(); + EXPECT_CALL(*mockDevice, prepareModelFromCache_1_3(_, _, _, _, _)) + .Times(1) + .WillOnce(Invoke(makePreparedModelFromCacheReturn(V1_3::ErrorStatus::GENERAL_FAILURE, + V1_3::ErrorStatus::GENERAL_FAILURE, + nullptr))); + + // run test + const auto result = device->prepareModelFromCache({}, {}, {}, {}); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); +} + +TEST(DeviceTest, prepareModelFromCacheNullptrError) { + // setup call + const auto mockDevice = createMockDevice(); + const auto device = Device::create(kName, mockDevice).value(); + EXPECT_CALL(*mockDevice, prepareModelFromCache_1_3(_, _, _, _, _)) + .Times(1) + .WillOnce(Invoke(makePreparedModelFromCacheReturn(V1_3::ErrorStatus::NONE, + V1_3::ErrorStatus::NONE, nullptr))); + + // run test + const auto result = device->prepareModelFromCache({}, {}, {}, {}); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); +} + +TEST(DeviceTest, prepareModelFromCacheTransportFailure) { + // setup call + const auto mockDevice = createMockDevice(); + const auto device = Device::create(kName, mockDevice).value(); + EXPECT_CALL(*mockDevice, prepareModelFromCache_1_3(_, _, _, _, _)) + .Times(1) + .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure)); + + // run test + const auto result = device->prepareModelFromCache({}, {}, {}, {}); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); +} + +TEST(DeviceTest, prepareModelFromCacheDeadObject) { + // setup call + const auto mockDevice = createMockDevice(); + const auto device = Device::create(kName, mockDevice).value(); + EXPECT_CALL(*mockDevice, prepareModelFromCache_1_3(_, _, _, _, _)) + .Times(1) + .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure)); + + // run test + const auto result = device->prepareModelFromCache({}, {}, {}, {}); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT); +} + +TEST(DeviceTest, prepareModelFromCacheAsyncCrash) { + // setup test + const auto mockDevice = createMockDevice(); + const auto device = Device::create(kName, mockDevice).value(); + const auto ret = [&mockDevice]() -> hardware::Return<V1_3::ErrorStatus> { + mockDevice->simulateCrash(); + return V1_3::ErrorStatus::NONE; + }; + EXPECT_CALL(*mockDevice, prepareModelFromCache_1_3(_, _, _, _, _)) + .Times(1) + .WillOnce(InvokeWithoutArgs(ret)); + + // run test + const auto result = device->prepareModelFromCache({}, {}, {}, {}); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT); +} + +TEST(DeviceTest, allocate) { + // setup call + const auto mockDevice = createMockDevice(); + const auto device = Device::create(kName, mockDevice).value(); + const auto mockBuffer = MockBuffer::create(); + constexpr uint32_t token = 1; + EXPECT_CALL(*mockDevice, allocate(_, _, _, _, _)) + .Times(1) + .WillOnce(Invoke(makeAllocateReturn(ErrorStatus::NONE, mockBuffer, token))); + + // run test + const auto result = device->allocate({}, {}, {}, {}); + + // verify result + ASSERT_TRUE(result.has_value()) + << "Failed with " << result.error().code << ": " << result.error().message; + EXPECT_NE(result.value(), nullptr); +} + +TEST(DeviceTest, allocateError) { + // setup call + const auto mockDevice = createMockDevice(); + const auto device = Device::create(kName, mockDevice).value(); + EXPECT_CALL(*mockDevice, allocate(_, _, _, _, _)) + .Times(1) + .WillOnce(Invoke(makeAllocateReturn(ErrorStatus::GENERAL_FAILURE, nullptr, 0))); + + // run test + const auto result = device->allocate({}, {}, {}, {}); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); +} + +TEST(DeviceTest, allocateTransportFailure) { + // setup call + const auto mockDevice = createMockDevice(); + const auto device = Device::create(kName, mockDevice).value(); + EXPECT_CALL(*mockDevice, allocate(_, _, _, _, _)) + .Times(1) + .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure)); + + // run test + const auto result = device->allocate({}, {}, {}, {}); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); +} + +TEST(DeviceTest, allocateDeadObject) { + // setup call + const auto mockDevice = createMockDevice(); + const auto device = Device::create(kName, mockDevice).value(); + EXPECT_CALL(*mockDevice, allocate(_, _, _, _, _)) + .Times(1) + .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure)); + + // run test + const auto result = device->allocate({}, {}, {}, {}); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT); +} + +} // namespace android::hardware::neuralnetworks::V1_3::utils diff --git a/neuralnetworks/1.3/utils/test/MockBuffer.h b/neuralnetworks/1.3/utils/test/MockBuffer.h new file mode 100644 index 0000000000..fb31b51261 --- /dev/null +++ b/neuralnetworks/1.3/utils/test/MockBuffer.h @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * 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 ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_3_UTILS_TEST_MOCK_BUFFER +#define ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_3_UTILS_TEST_MOCK_BUFFER + +#include <android/hardware/neuralnetworks/1.3/IBuffer.h> +#include <gmock/gmock.h> +#include <gtest/gtest.h> +#include <hidl/Status.h> + +namespace android::hardware::neuralnetworks::V1_3::utils { + +class MockBuffer final : public IBuffer { + public: + static sp<MockBuffer> create(); + + // V1_3 methods below. + MOCK_METHOD(Return<V1_3::ErrorStatus>, copyTo, (const hidl_memory& dst), (override)); + MOCK_METHOD(Return<V1_3::ErrorStatus>, copyFrom, + (const hidl_memory& src, const hidl_vec<uint32_t>& dimensions), (override)); +}; + +inline sp<MockBuffer> MockBuffer::create() { + return sp<MockBuffer>::make(); +} + +} // namespace android::hardware::neuralnetworks::V1_3::utils + +#endif // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_3_UTILS_TEST_MOCK_BUFFER diff --git a/neuralnetworks/1.3/utils/test/MockDevice.h b/neuralnetworks/1.3/utils/test/MockDevice.h new file mode 100644 index 0000000000..85d3750d22 --- /dev/null +++ b/neuralnetworks/1.3/utils/test/MockDevice.h @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * 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 ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_3_UTILS_TEST_MOCK_DEVICE +#define ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_3_UTILS_TEST_MOCK_DEVICE + +#include <android/hardware/neuralnetworks/1.3/IDevice.h> +#include <gmock/gmock.h> +#include <gtest/gtest.h> +#include <hidl/Status.h> + +namespace android::hardware::neuralnetworks::V1_3::utils { + +using CacheToken = + hidl_array<uint8_t, static_cast<uint32_t>(V1_2::Constant::BYTE_SIZE_OF_CACHE_TOKEN)>; + +class MockDevice final : public IDevice { + public: + static sp<MockDevice> create(); + + // IBase methods below. + MOCK_METHOD(Return<void>, ping, (), (override)); + MOCK_METHOD(Return<bool>, linkToDeathRet, ()); + Return<bool> linkToDeath(const sp<hidl_death_recipient>& recipient, uint64_t /*cookie*/); + + // V1_0 methods below. + MOCK_METHOD(Return<void>, getCapabilities, (getCapabilities_cb cb), (override)); + MOCK_METHOD(Return<void>, getSupportedOperations, + (const V1_0::Model& model, getSupportedOperations_cb cb), (override)); + MOCK_METHOD(Return<V1_0::ErrorStatus>, prepareModel, + (const V1_0::Model& model, const sp<V1_0::IPreparedModelCallback>& callback), + (override)); + MOCK_METHOD(Return<V1_0::DeviceStatus>, getStatus, (), (override)); + + // V1_1 methods below. + MOCK_METHOD(Return<void>, getCapabilities_1_1, (getCapabilities_1_1_cb cb), (override)); + MOCK_METHOD(Return<void>, getSupportedOperations_1_1, + (const V1_1::Model& model, getSupportedOperations_1_1_cb cb), (override)); + MOCK_METHOD(Return<V1_0::ErrorStatus>, prepareModel_1_1, + (const V1_1::Model& model, V1_1::ExecutionPreference preference, + const sp<V1_0::IPreparedModelCallback>& callback), + (override)); + + // V1_2 methods below. + MOCK_METHOD(Return<void>, getVersionString, (getVersionString_cb cb), (override)); + MOCK_METHOD(Return<void>, getType, (getType_cb cb), (override)); + MOCK_METHOD(Return<void>, getCapabilities_1_2, (getCapabilities_1_2_cb cb), (override)); + MOCK_METHOD(Return<void>, getSupportedExtensions, (getSupportedExtensions_cb cb), (override)); + MOCK_METHOD(Return<void>, getSupportedOperations_1_2, + (const V1_2::Model& model, getSupportedOperations_1_2_cb cb), (override)); + MOCK_METHOD(Return<void>, getNumberOfCacheFilesNeeded, (getNumberOfCacheFilesNeeded_cb cb), + (override)); + MOCK_METHOD(Return<V1_0::ErrorStatus>, prepareModel_1_2, + (const V1_2::Model& model, V1_1::ExecutionPreference preference, + const hidl_vec<hidl_handle>& modelCache, const hidl_vec<hidl_handle>& dataCache, + const CacheToken& token, const sp<V1_2::IPreparedModelCallback>& callback), + (override)); + MOCK_METHOD(Return<V1_0::ErrorStatus>, prepareModelFromCache, + (const hidl_vec<hidl_handle>& modelCache, const hidl_vec<hidl_handle>& dataCache, + const CacheToken& token, const sp<V1_2::IPreparedModelCallback>& callback), + (override)); + + // V1_3 methods below. + MOCK_METHOD(Return<void>, getCapabilities_1_3, (getCapabilities_1_3_cb cb), (override)); + MOCK_METHOD(Return<void>, getSupportedOperations_1_3, + (const V1_3::Model& model, getSupportedOperations_1_3_cb cb), (override)); + MOCK_METHOD(Return<V1_3::ErrorStatus>, prepareModel_1_3, + (const V1_3::Model& model, V1_1::ExecutionPreference preference, + V1_3::Priority priority, const V1_3::OptionalTimePoint& deadline, + const hidl_vec<hidl_handle>& modelCache, const hidl_vec<hidl_handle>& dataCache, + const CacheToken& token, const sp<V1_3::IPreparedModelCallback>& callback), + (override)); + MOCK_METHOD(Return<V1_3::ErrorStatus>, prepareModelFromCache_1_3, + (const V1_3::OptionalTimePoint& deadline, const hidl_vec<hidl_handle>& modelCache, + const hidl_vec<hidl_handle>& dataCache, const CacheToken& token, + const sp<V1_3::IPreparedModelCallback>& callback), + (override)); + MOCK_METHOD(Return<void>, allocate, + (const V1_3::BufferDesc& desc, + const hidl_vec<sp<V1_3::IPreparedModel>>& preparedModels, + const hidl_vec<V1_3::BufferRole>& inputRoles, + const hidl_vec<V1_3::BufferRole>& outputRoles, allocate_cb cb), + (override)); + + // Helper methods. + void simulateCrash(); + + private: + sp<hidl_death_recipient> mDeathRecipient; +}; + +inline sp<MockDevice> MockDevice::create() { + auto mockDevice = sp<MockDevice>::make(); + + // Setup default actions for each relevant call. + const auto ret = []() -> Return<bool> { return true; }; + + // Setup default actions for each relevant call. + ON_CALL(*mockDevice, linkToDeathRet()).WillByDefault(testing::Invoke(ret)); + + // These EXPECT_CALL(...).Times(testing::AnyNumber()) calls are to suppress warnings on the + // uninteresting methods calls. + EXPECT_CALL(*mockDevice, linkToDeathRet()).Times(testing::AnyNumber()); + + return mockDevice; +} + +inline Return<bool> MockDevice::linkToDeath(const sp<hidl_death_recipient>& recipient, + uint64_t /*cookie*/) { + mDeathRecipient = recipient; + return linkToDeathRet(); +} + +inline void MockDevice::simulateCrash() { + ASSERT_NE(nullptr, mDeathRecipient.get()); + + // Currently, the utils::Device will not use the `cookie` or `who` arguments, so we pass in 0 + // and nullptr for these arguments instead. Normally, they are used by the hidl_death_recipient + // to determine which object is dead. However, the utils::Device code only pairs a single death + // recipient with a single HIDL interface object, so these arguments are redundant. + mDeathRecipient->serviceDied(0, nullptr); +} + +} // namespace android::hardware::neuralnetworks::V1_3::utils + +#endif // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_3_UTILS_TEST_MOCK_DEVICE diff --git a/neuralnetworks/1.3/utils/test/MockFencedExecutionCallback.h b/neuralnetworks/1.3/utils/test/MockFencedExecutionCallback.h new file mode 100644 index 0000000000..fc08a7fc70 --- /dev/null +++ b/neuralnetworks/1.3/utils/test/MockFencedExecutionCallback.h @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * 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 ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_3_UTILS_TEST_MOCK_FENCED_EXECUTION_CALLBACK +#define ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_3_UTILS_TEST_MOCK_FENCED_EXECUTION_CALLBACK + +#include <android/hardware/neuralnetworks/1.3/IFencedExecutionCallback.h> +#include <gmock/gmock.h> +#include <gtest/gtest.h> +#include <hidl/Status.h> + +namespace android::hardware::neuralnetworks::V1_3::utils { + +class MockFencedExecutionCallback final : public IFencedExecutionCallback { + public: + static sp<MockFencedExecutionCallback> create(); + + // V1_3 methods below. + MOCK_METHOD(Return<void>, getExecutionInfo, (IFencedExecutionCallback::getExecutionInfo_cb cb), + (override)); +}; + +inline sp<MockFencedExecutionCallback> MockFencedExecutionCallback::create() { + return sp<MockFencedExecutionCallback>::make(); +} + +} // namespace android::hardware::neuralnetworks::V1_3::utils + +#endif // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_3_UTILS_TEST_MOCK_FENCED_EXECUTION_CALLBACK diff --git a/neuralnetworks/1.3/utils/test/MockPreparedModel.h b/neuralnetworks/1.3/utils/test/MockPreparedModel.h new file mode 100644 index 0000000000..e44152426b --- /dev/null +++ b/neuralnetworks/1.3/utils/test/MockPreparedModel.h @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * 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 ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_3_UTILS_TEST_MOCK_PREPARED_MODEL +#define ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_3_UTILS_TEST_MOCK_PREPARED_MODEL + +#include <android/hardware/neuralnetworks/1.3/IPreparedModel.h> +#include <gmock/gmock.h> +#include <gtest/gtest.h> +#include <hidl/HidlSupport.h> +#include <hidl/Status.h> + +namespace android::hardware::neuralnetworks::V1_3::utils { + +class MockPreparedModel final : public IPreparedModel { + public: + static sp<MockPreparedModel> create(); + + // IBase methods below. + MOCK_METHOD(Return<void>, ping, (), (override)); + MOCK_METHOD(Return<bool>, linkToDeathRet, ()); + Return<bool> linkToDeath(const sp<hidl_death_recipient>& recipient, + uint64_t /*cookie*/) override; + + // V1_0 methods below. + MOCK_METHOD(Return<V1_0::ErrorStatus>, execute, + (const V1_0::Request& request, const sp<V1_0::IExecutionCallback>& callback), + (override)); + + // V1_2 methods below. + MOCK_METHOD(Return<V1_0::ErrorStatus>, execute_1_2, + (const V1_0::Request& request, V1_2::MeasureTiming measure, + const sp<V1_2::IExecutionCallback>& callback), + (override)); + MOCK_METHOD(Return<void>, executeSynchronously, + (const V1_0::Request& request, V1_2::MeasureTiming measure, + executeSynchronously_cb cb), + (override)); + MOCK_METHOD(Return<void>, configureExecutionBurst, + (const sp<V1_2::IBurstCallback>& callback, + const MQDescriptorSync<V1_2::FmqRequestDatum>& requestChannel, + const MQDescriptorSync<V1_2::FmqResultDatum>& resultChannel, + configureExecutionBurst_cb cb), + (override)); + + // V1_3 methods below. + MOCK_METHOD(Return<V1_3::ErrorStatus>, execute_1_3, + (const V1_3::Request& request, V1_2::MeasureTiming measure, + const V1_3::OptionalTimePoint& deadline, + const V1_3::OptionalTimeoutDuration& loopTimeoutDuration, + const sp<V1_3::IExecutionCallback>& callback), + (override)); + MOCK_METHOD(Return<void>, executeSynchronously_1_3, + (const V1_3::Request& request, V1_2::MeasureTiming measure, + const V1_3::OptionalTimePoint& deadline, + const V1_3::OptionalTimeoutDuration& loopTimeoutDuration, + executeSynchronously_1_3_cb cb), + (override)); + MOCK_METHOD(Return<void>, executeFenced, + (const V1_3::Request& request, const hidl_vec<hidl_handle>& waitFor, + V1_2::MeasureTiming measure, const V1_3::OptionalTimePoint& deadline, + const V1_3::OptionalTimeoutDuration& loopTimeoutDuration, + const V1_3::OptionalTimeoutDuration& duration, executeFenced_cb cb), + (override)); + + // Helper methods. + void simulateCrash(); + + private: + sp<hidl_death_recipient> mDeathRecipient; +}; + +inline sp<MockPreparedModel> MockPreparedModel::create() { + auto mockPreparedModel = sp<MockPreparedModel>::make(); + + // Setup default actions for each relevant call. + const auto ret = []() -> Return<bool> { return true; }; + + // Setup default actions for each relevant call. + ON_CALL(*mockPreparedModel, linkToDeathRet()).WillByDefault(testing::Invoke(ret)); + + // These EXPECT_CALL(...).Times(testing::AnyNumber()) calls are to suppress warnings on the + // uninteresting methods calls. + EXPECT_CALL(*mockPreparedModel, linkToDeathRet()).Times(testing::AnyNumber()); + + return mockPreparedModel; +} + +inline Return<bool> MockPreparedModel::linkToDeath(const sp<hidl_death_recipient>& recipient, + uint64_t /*cookie*/) { + mDeathRecipient = recipient; + return linkToDeathRet(); +} + +inline void MockPreparedModel::simulateCrash() { + ASSERT_NE(nullptr, mDeathRecipient.get()); + + // Currently, the utils::PreparedModel will not use the `cookie` or `who` arguments, so we pass + // in 0 and nullptr for these arguments instead. Normally, they are used by the + // hidl_death_recipient to determine which object is dead. However, the utils::PreparedModel + // code only pairs a single death recipient with a single HIDL interface object, so these + // arguments are redundant. + mDeathRecipient->serviceDied(0, nullptr); +} + +} // namespace android::hardware::neuralnetworks::V1_3::utils + +#endif // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_3_UTILS_TEST_MOCK_PREPARED_MODEL diff --git a/neuralnetworks/1.3/utils/test/PreparedModelTest.cpp b/neuralnetworks/1.3/utils/test/PreparedModelTest.cpp new file mode 100644 index 0000000000..11796ddc2d --- /dev/null +++ b/neuralnetworks/1.3/utils/test/PreparedModelTest.cpp @@ -0,0 +1,470 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * 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 "MockFencedExecutionCallback.h" +#include "MockPreparedModel.h" + +#include <android/hardware/neuralnetworks/1.3/IExecutionCallback.h> +#include <android/hardware/neuralnetworks/1.3/IFencedExecutionCallback.h> +#include <gmock/gmock.h> +#include <gtest/gtest.h> +#include <nnapi/IPreparedModel.h> +#include <nnapi/TypeUtils.h> +#include <nnapi/Types.h> +#include <nnapi/hal/1.3/PreparedModel.h> + +#include <functional> +#include <memory> + +namespace android::hardware::neuralnetworks::V1_3::utils { +namespace { + +using ::testing::_; +using ::testing::Invoke; +using ::testing::InvokeWithoutArgs; + +const sp<V1_3::IPreparedModel> kInvalidPreparedModel; +constexpr auto kNoTiming = V1_2::Timing{.timeOnDevice = std::numeric_limits<uint64_t>::max(), + .timeInDriver = std::numeric_limits<uint64_t>::max()}; + +sp<MockPreparedModel> createMockPreparedModel() { + const auto mockPreparedModel = MockPreparedModel::create(); + + // Ensure that older calls are not used. + EXPECT_CALL(*mockPreparedModel, execute(_, _)).Times(0); + EXPECT_CALL(*mockPreparedModel, execute_1_2(_, _, _)).Times(0); + EXPECT_CALL(*mockPreparedModel, executeSynchronously(_, _, _)).Times(0); + + return mockPreparedModel; +} + +auto makeExecuteSynchronously(V1_3::ErrorStatus status, + const std::vector<V1_2::OutputShape>& outputShapes, + const V1_2::Timing& timing) { + return [status, outputShapes, timing]( + const V1_3::Request& /*request*/, V1_2::MeasureTiming /*measureTiming*/, + const V1_3::OptionalTimePoint& /*deadline*/, + const V1_3::OptionalTimeoutDuration& /*loopTimeoutDuration*/, + const V1_3::IPreparedModel::executeSynchronously_1_3_cb& cb) { + cb(status, outputShapes, timing); + return hardware::Void(); + }; +} +auto makeExecuteAsynchronously(V1_3::ErrorStatus launchStatus, V1_3::ErrorStatus returnStatus, + const std::vector<V1_2::OutputShape>& outputShapes, + const V1_2::Timing& timing) { + return [launchStatus, returnStatus, outputShapes, timing]( + const V1_3::Request& /*request*/, V1_2::MeasureTiming /*measureTiming*/, + const V1_3::OptionalTimePoint& /*deadline*/, + const V1_3::OptionalTimeoutDuration& /*loopTimeoutDuration*/, + const sp<V1_3::IExecutionCallback>& cb) -> Return<V1_3::ErrorStatus> { + cb->notify_1_3(returnStatus, outputShapes, timing); + return launchStatus; + }; +} +auto makeExecuteFencedReturn(V1_3::ErrorStatus status, const hardware::hidl_handle& syncFence, + const sp<V1_3::IFencedExecutionCallback>& dispatchCallback) { + return [status, syncFence, dispatchCallback]( + const V1_3::Request& /*request*/, + const hardware::hidl_vec<hardware::hidl_handle>& /*waitFor*/, + V1_2::MeasureTiming /*measure*/, const V1_3::OptionalTimePoint& /*deadline*/, + const V1_3::OptionalTimeoutDuration& /*loopTimeoutDuration*/, + const V1_3::OptionalTimeoutDuration& /*duration*/, + const V1_3::IPreparedModel::executeFenced_cb& cb) { + cb(status, syncFence, dispatchCallback); + return hardware::Void(); + }; +} +auto makeExecuteFencedCallbackReturn(V1_3::ErrorStatus status, const V1_2::Timing& timingA, + const V1_2::Timing& timingB) { + return [status, timingA, + timingB](const V1_3::IFencedExecutionCallback::getExecutionInfo_cb& cb) { + cb(status, timingA, timingB); + return hardware::Void(); + }; +} + +std::function<hardware::Status()> makeTransportFailure(status_t status) { + return [status] { return hardware::Status::fromStatusT(status); }; +} + +const auto makeGeneralTransportFailure = makeTransportFailure(NO_MEMORY); +const auto makeDeadObjectFailure = makeTransportFailure(DEAD_OBJECT); + +} // namespace + +TEST(PreparedModelTest, invalidPreparedModel) { + // run test + const auto result = PreparedModel::create(kInvalidPreparedModel, /*executeSynchronously=*/true); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); +} + +TEST(PreparedModelTest, linkToDeathError) { + // setup call + const auto mockPreparedModel = createMockPreparedModel(); + const auto ret = []() -> Return<bool> { return false; }; + EXPECT_CALL(*mockPreparedModel, linkToDeathRet()).Times(1).WillOnce(InvokeWithoutArgs(ret)); + + // run test + const auto result = PreparedModel::create(mockPreparedModel, /*executeSynchronously=*/true); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); +} + +TEST(PreparedModelTest, linkToDeathTransportFailure) { + // setup call + const auto mockPreparedModel = createMockPreparedModel(); + EXPECT_CALL(*mockPreparedModel, linkToDeathRet()) + .Times(1) + .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure)); + + // run test + const auto result = PreparedModel::create(mockPreparedModel, /*executeSynchronously=*/true); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); +} + +TEST(PreparedModelTest, linkToDeathDeadObject) { + // setup call + const auto mockPreparedModel = createMockPreparedModel(); + EXPECT_CALL(*mockPreparedModel, linkToDeathRet()) + .Times(1) + .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure)); + + // run test + const auto result = PreparedModel::create(mockPreparedModel, /*executeSynchronously=*/true); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT); +} + +TEST(PreparedModelTest, executeSync) { + // setup call + const auto mockPreparedModel = createMockPreparedModel(); + const auto preparedModel = + PreparedModel::create(mockPreparedModel, /*executeSynchronously=*/true).value(); + EXPECT_CALL(*mockPreparedModel, executeSynchronously_1_3(_, _, _, _, _)) + .Times(1) + .WillOnce(Invoke(makeExecuteSynchronously(V1_3::ErrorStatus::NONE, {}, kNoTiming))); + + // run test + const auto result = preparedModel->execute({}, {}, {}, {}); + + // verify result + EXPECT_TRUE(result.has_value()) + << "Failed with " << result.error().code << ": " << result.error().message; +} + +TEST(PreparedModelTest, executeSyncError) { + // setup test + const auto mockPreparedModel = createMockPreparedModel(); + const auto preparedModel = + PreparedModel::create(mockPreparedModel, /*executeSynchronously=*/true).value(); + EXPECT_CALL(*mockPreparedModel, executeSynchronously_1_3(_, _, _, _, _)) + .Times(1) + .WillOnce(Invoke( + makeExecuteSynchronously(V1_3::ErrorStatus::GENERAL_FAILURE, {}, kNoTiming))); + + // run test + const auto result = preparedModel->execute({}, {}, {}, {}); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); +} + +TEST(PreparedModelTest, executeSyncTransportFailure) { + // setup test + const auto mockPreparedModel = createMockPreparedModel(); + const auto preparedModel = + PreparedModel::create(mockPreparedModel, /*executeSynchronously=*/true).value(); + EXPECT_CALL(*mockPreparedModel, executeSynchronously_1_3(_, _, _, _, _)) + .Times(1) + .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure)); + + // run test + const auto result = preparedModel->execute({}, {}, {}, {}); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); +} + +TEST(PreparedModelTest, executeSyncDeadObject) { + // setup test + const auto mockPreparedModel = createMockPreparedModel(); + const auto preparedModel = + PreparedModel::create(mockPreparedModel, /*executeSynchronously=*/true).value(); + EXPECT_CALL(*mockPreparedModel, executeSynchronously_1_3(_, _, _, _, _)) + .Times(1) + .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure)); + + // run test + const auto result = preparedModel->execute({}, {}, {}, {}); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT); +} + +TEST(PreparedModelTest, executeAsync) { + // setup call + const auto mockPreparedModel = createMockPreparedModel(); + const auto preparedModel = + PreparedModel::create(mockPreparedModel, /*executeSynchronously=*/false).value(); + EXPECT_CALL(*mockPreparedModel, execute_1_3(_, _, _, _, _)) + .Times(1) + .WillOnce(Invoke(makeExecuteAsynchronously(V1_3::ErrorStatus::NONE, + V1_3::ErrorStatus::NONE, {}, kNoTiming))); + + // run test + const auto result = preparedModel->execute({}, {}, {}, {}); + + // verify result + EXPECT_TRUE(result.has_value()) + << "Failed with " << result.error().code << ": " << result.error().message; +} + +TEST(PreparedModelTest, executeAsyncLaunchError) { + // setup test + const auto mockPreparedModel = createMockPreparedModel(); + const auto preparedModel = + PreparedModel::create(mockPreparedModel, /*executeSynchronously=*/false).value(); + EXPECT_CALL(*mockPreparedModel, execute_1_3(_, _, _, _, _)) + .Times(1) + .WillOnce(Invoke(makeExecuteAsynchronously(V1_3::ErrorStatus::GENERAL_FAILURE, + V1_3::ErrorStatus::GENERAL_FAILURE, {}, + kNoTiming))); + + // run test + const auto result = preparedModel->execute({}, {}, {}, {}); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); +} + +TEST(PreparedModelTest, executeAsyncReturnError) { + // setup test + const auto mockPreparedModel = createMockPreparedModel(); + const auto preparedModel = + PreparedModel::create(mockPreparedModel, /*executeSynchronously=*/false).value(); + EXPECT_CALL(*mockPreparedModel, execute_1_3(_, _, _, _, _)) + .Times(1) + .WillOnce(Invoke(makeExecuteAsynchronously( + V1_3::ErrorStatus::NONE, V1_3::ErrorStatus::GENERAL_FAILURE, {}, kNoTiming))); + + // run test + const auto result = preparedModel->execute({}, {}, {}, {}); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); +} + +TEST(PreparedModelTest, executeAsyncTransportFailure) { + // setup test + const auto mockPreparedModel = createMockPreparedModel(); + const auto preparedModel = + PreparedModel::create(mockPreparedModel, /*executeSynchronously=*/false).value(); + EXPECT_CALL(*mockPreparedModel, execute_1_3(_, _, _, _, _)) + .Times(1) + .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure)); + + // run test + const auto result = preparedModel->execute({}, {}, {}, {}); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); +} + +TEST(PreparedModelTest, executeAsyncDeadObject) { + // setup test + const auto mockPreparedModel = createMockPreparedModel(); + const auto preparedModel = + PreparedModel::create(mockPreparedModel, /*executeSynchronously=*/false).value(); + EXPECT_CALL(*mockPreparedModel, execute_1_3(_, _, _, _, _)) + .Times(1) + .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure)); + + // run test + const auto result = preparedModel->execute({}, {}, {}, {}); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT); +} + +TEST(PreparedModelTest, executeAsyncCrash) { + // setup test + const auto mockPreparedModel = createMockPreparedModel(); + const auto preparedModel = + PreparedModel::create(mockPreparedModel, /*executeSynchronously=*/false).value(); + const auto ret = [&mockPreparedModel]() -> hardware::Return<V1_3::ErrorStatus> { + mockPreparedModel->simulateCrash(); + return V1_3::ErrorStatus::NONE; + }; + EXPECT_CALL(*mockPreparedModel, execute_1_3(_, _, _, _, _)) + .Times(1) + .WillOnce(InvokeWithoutArgs(ret)); + + // run test + const auto result = preparedModel->execute({}, {}, {}, {}); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT); +} + +TEST(PreparedModelTest, executeFenced) { + // setup call + const auto mockPreparedModel = createMockPreparedModel(); + const auto preparedModel = + PreparedModel::create(mockPreparedModel, /*executeSynchronously=*/true).value(); + const auto mockCallback = MockFencedExecutionCallback::create(); + EXPECT_CALL(*mockCallback, getExecutionInfo(_)) + .Times(1) + .WillOnce(Invoke(makeExecuteFencedCallbackReturn(V1_3::ErrorStatus::NONE, kNoTiming, + kNoTiming))); + EXPECT_CALL(*mockPreparedModel, executeFenced(_, _, _, _, _, _, _)) + .Times(1) + .WillOnce(Invoke(makeExecuteFencedReturn(V1_3::ErrorStatus::NONE, {}, mockCallback))); + + // run test + const auto result = preparedModel->executeFenced({}, {}, {}, {}, {}, {}); + + // verify result + ASSERT_TRUE(result.has_value()) + << "Failed with " << result.error().code << ": " << result.error().message; + const auto& [syncFence, callback] = result.value(); + EXPECT_EQ(syncFence.syncWait({}), nn::SyncFence::FenceState::SIGNALED); + ASSERT_NE(callback, nullptr); + + // get results from callback + const auto callbackResult = callback(); + ASSERT_TRUE(callbackResult.has_value()) << "Failed with " << callbackResult.error().code << ": " + << callbackResult.error().message; +} + +TEST(PreparedModelTest, executeFencedCallbackError) { + // setup call + const auto mockPreparedModel = createMockPreparedModel(); + const auto preparedModel = + PreparedModel::create(mockPreparedModel, /*executeSynchronously=*/true).value(); + const auto mockCallback = MockFencedExecutionCallback::create(); + EXPECT_CALL(*mockCallback, getExecutionInfo(_)) + .Times(1) + .WillOnce(Invoke(makeExecuteFencedCallbackReturn(V1_3::ErrorStatus::GENERAL_FAILURE, + kNoTiming, kNoTiming))); + EXPECT_CALL(*mockPreparedModel, executeFenced(_, _, _, _, _, _, _)) + .Times(1) + .WillOnce(Invoke(makeExecuteFencedReturn(V1_3::ErrorStatus::NONE, {}, mockCallback))); + + // run test + const auto result = preparedModel->executeFenced({}, {}, {}, {}, {}, {}); + + // verify result + ASSERT_TRUE(result.has_value()) + << "Failed with " << result.error().code << ": " << result.error().message; + const auto& [syncFence, callback] = result.value(); + EXPECT_NE(syncFence.syncWait({}), nn::SyncFence::FenceState::ACTIVE); + ASSERT_NE(callback, nullptr); + + // verify callback failure + const auto callbackResult = callback(); + ASSERT_FALSE(callbackResult.has_value()); + EXPECT_EQ(callbackResult.error().code, nn::ErrorStatus::GENERAL_FAILURE); +} + +TEST(PreparedModelTest, executeFencedError) { + // setup test + const auto mockPreparedModel = createMockPreparedModel(); + const auto preparedModel = + PreparedModel::create(mockPreparedModel, /*executeSynchronously=*/true).value(); + EXPECT_CALL(*mockPreparedModel, executeFenced(_, _, _, _, _, _, _)) + .Times(1) + .WillOnce(Invoke( + makeExecuteFencedReturn(V1_3::ErrorStatus::GENERAL_FAILURE, {}, nullptr))); + + // run test + const auto result = preparedModel->executeFenced({}, {}, {}, {}, {}, {}); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); +} + +TEST(PreparedModelTest, executeFencedTransportFailure) { + // setup test + const auto mockPreparedModel = createMockPreparedModel(); + const auto preparedModel = + PreparedModel::create(mockPreparedModel, /*executeSynchronously=*/true).value(); + EXPECT_CALL(*mockPreparedModel, executeFenced(_, _, _, _, _, _, _)) + .Times(1) + .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure)); + + // run test + const auto result = preparedModel->executeFenced({}, {}, {}, {}, {}, {}); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); +} + +TEST(PreparedModelTest, executeFencedDeadObject) { + // setup test + const auto mockPreparedModel = createMockPreparedModel(); + const auto preparedModel = + PreparedModel::create(mockPreparedModel, /*executeSynchronously=*/true).value(); + EXPECT_CALL(*mockPreparedModel, executeFenced(_, _, _, _, _, _, _)) + .Times(1) + .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure)); + + // run test + const auto result = preparedModel->executeFenced({}, {}, {}, {}, {}, {}); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT); +} + +// TODO: test burst execution if/when it is added to nn::IPreparedModel. + +TEST(PreparedModelTest, getUnderlyingResource) { + // setup test + const auto mockPreparedModel = createMockPreparedModel(); + const auto preparedModel = + PreparedModel::create(mockPreparedModel, /*executeSynchronously=*/true).value(); + + // run test + const auto resource = preparedModel->getUnderlyingResource(); + + // verify resource + const sp<V1_3::IPreparedModel>* maybeMock = std::any_cast<sp<V1_3::IPreparedModel>>(&resource); + ASSERT_NE(maybeMock, nullptr); + EXPECT_EQ(maybeMock->get(), mockPreparedModel.get()); +} + +} // namespace android::hardware::neuralnetworks::V1_3::utils diff --git a/neuralnetworks/TEST_MAPPING b/neuralnetworks/TEST_MAPPING index ca5041db4b..de846244a1 100644 --- a/neuralnetworks/TEST_MAPPING +++ b/neuralnetworks/TEST_MAPPING @@ -1,6 +1,21 @@ { "presubmit": [ { + "name": "neuralnetworks_utils_hal_common_test" + }, + { + "name": "neuralnetworks_utils_hal_1_0_test" + }, + { + "name": "neuralnetworks_utils_hal_1_1_test" + }, + { + "name": "neuralnetworks_utils_hal_1_2_test" + }, + { + "name": "neuralnetworks_utils_hal_1_3_test" + }, + { "name": "VtsHalNeuralnetworksV1_0TargetTest", "options": [ { diff --git a/neuralnetworks/utils/common/Android.bp b/neuralnetworks/utils/common/Android.bp index 21562cffaf..6c491ae7ae 100644 --- a/neuralnetworks/utils/common/Android.bp +++ b/neuralnetworks/utils/common/Android.bp @@ -28,3 +28,28 @@ cc_library_static { "libhidlbase", ], } + +cc_test { + name: "neuralnetworks_utils_hal_common_test", + srcs: ["test/*.cpp"], + static_libs: [ + "android.hardware.neuralnetworks@1.0", + "libgmock", + "libneuralnetworks_common", + "neuralnetworks_types", + "neuralnetworks_utils_hal_common", + ], + shared_libs: [ + "android.hidl.allocator@1.0", + "android.hidl.memory@1.0", + "libbase", + "libcutils", + "libfmq", + "libhidlbase", + "libhidlmemory", + "liblog", + "libnativewindow", + "libutils", + ], + test_suites: ["general-tests"], +} diff --git a/neuralnetworks/utils/common/include/nnapi/hal/InvalidBurst.h b/neuralnetworks/utils/common/include/nnapi/hal/InvalidBurst.h new file mode 100644 index 0000000000..83e60b6a25 --- /dev/null +++ b/neuralnetworks/utils/common/include/nnapi/hal/InvalidBurst.h @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * 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 ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_UTILS_COMMON_INVALID_BURST_H +#define ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_UTILS_COMMON_INVALID_BURST_H + +#include <nnapi/IBurst.h> +#include <nnapi/Result.h> +#include <nnapi/Types.h> + +#include <memory> +#include <optional> +#include <utility> + +namespace android::hardware::neuralnetworks::utils { + +class InvalidBurst final : public nn::IBurst { + public: + OptionalCacheHold cacheMemory(const nn::Memory& memory) const override; + + nn::ExecutionResult<std::pair<std::vector<nn::OutputShape>, nn::Timing>> execute( + const nn::Request& request, nn::MeasureTiming measure) const override; +}; + +} // namespace android::hardware::neuralnetworks::utils + +#endif // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_UTILS_COMMON_INVALID_BURST_H diff --git a/neuralnetworks/utils/common/include/nnapi/hal/InvalidDevice.h b/neuralnetworks/utils/common/include/nnapi/hal/InvalidDevice.h index 5e62b9ae0b..d8435262cc 100644 --- a/neuralnetworks/utils/common/include/nnapi/hal/InvalidDevice.h +++ b/neuralnetworks/utils/common/include/nnapi/hal/InvalidDevice.h @@ -32,7 +32,7 @@ namespace android::hardware::neuralnetworks::utils { class InvalidDevice final : public nn::IDevice { public: InvalidDevice(std::string name, std::string versionString, nn::Version featureLevel, - nn::DeviceType type, std::vector<nn::Extension> extensions, + nn::DeviceType type, bool isUpdatable, std::vector<nn::Extension> extensions, nn::Capabilities capabilities, std::pair<uint32_t, uint32_t> numberOfCacheFilesNeeded); @@ -40,6 +40,7 @@ class InvalidDevice final : public nn::IDevice { const std::string& getVersionString() const override; nn::Version getFeatureLevel() const override; nn::DeviceType getType() const override; + bool isUpdatable() const override; const std::vector<nn::Extension>& getSupportedExtensions() const override; const nn::Capabilities& getCapabilities() const override; std::pair<uint32_t, uint32_t> getNumberOfCacheFilesNeeded() const override; @@ -70,6 +71,7 @@ class InvalidDevice final : public nn::IDevice { const std::string kVersionString; const nn::Version kFeatureLevel; const nn::DeviceType kType; + const bool kIsUpdatable; const std::vector<nn::Extension> kExtensions; const nn::Capabilities kCapabilities; const std::pair<uint32_t, uint32_t> kNumberOfCacheFilesNeeded; diff --git a/neuralnetworks/utils/common/include/nnapi/hal/InvalidPreparedModel.h b/neuralnetworks/utils/common/include/nnapi/hal/InvalidPreparedModel.h index 985cddb2c2..3e1dca7139 100644 --- a/neuralnetworks/utils/common/include/nnapi/hal/InvalidPreparedModel.h +++ b/neuralnetworks/utils/common/include/nnapi/hal/InvalidPreparedModel.h @@ -40,6 +40,8 @@ class InvalidPreparedModel final : public nn::IPreparedModel { const nn::OptionalDuration& loopTimeoutDuration, const nn::OptionalDuration& timeoutDurationAfterFence) const override; + nn::GeneralResult<nn::SharedBurst> configureExecutionBurst() const override; + std::any getUnderlyingResource() const override; }; diff --git a/neuralnetworks/utils/common/include/nnapi/hal/ResilientBuffer.h b/neuralnetworks/utils/common/include/nnapi/hal/ResilientBuffer.h index 9d5e3e6a05..d2c2469403 100644 --- a/neuralnetworks/utils/common/include/nnapi/hal/ResilientBuffer.h +++ b/neuralnetworks/utils/common/include/nnapi/hal/ResilientBuffer.h @@ -42,7 +42,7 @@ class ResilientBuffer final : public nn::IBuffer { nn::SharedBuffer buffer); nn::SharedBuffer getBuffer() const; - nn::SharedBuffer recover(const nn::IBuffer* failingBuffer, bool blocking) const; + nn::GeneralResult<nn::SharedBuffer> recover(const nn::IBuffer* failingBuffer) const; nn::Request::MemoryDomainToken getToken() const override; diff --git a/neuralnetworks/utils/common/include/nnapi/hal/ResilientBurst.h b/neuralnetworks/utils/common/include/nnapi/hal/ResilientBurst.h new file mode 100644 index 0000000000..0df287f2f8 --- /dev/null +++ b/neuralnetworks/utils/common/include/nnapi/hal/ResilientBurst.h @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * 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 ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_UTILS_COMMON_RESILIENT_BURST_H +#define ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_UTILS_COMMON_RESILIENT_BURST_H + +#include <android-base/thread_annotations.h> +#include <nnapi/IBurst.h> +#include <nnapi/Result.h> +#include <nnapi/Types.h> + +#include <functional> +#include <memory> +#include <mutex> +#include <optional> +#include <utility> + +namespace android::hardware::neuralnetworks::utils { + +class ResilientBurst final : public nn::IBurst, + public std::enable_shared_from_this<ResilientBurst> { + struct PrivateConstructorTag {}; + + public: + using Factory = std::function<nn::GeneralResult<nn::SharedBurst>()>; + + static nn::GeneralResult<std::shared_ptr<const ResilientBurst>> create(Factory makeBurst); + + ResilientBurst(PrivateConstructorTag tag, Factory makeBurst, nn::SharedBurst burst); + + nn::SharedBurst getBurst() const; + nn::GeneralResult<nn::SharedBurst> recover(const nn::IBurst* failingBurst) const; + + OptionalCacheHold cacheMemory(const nn::Memory& memory) const override; + + nn::ExecutionResult<std::pair<std::vector<nn::OutputShape>, nn::Timing>> execute( + const nn::Request& request, nn::MeasureTiming measure) const override; + + private: + const Factory kMakeBurst; + mutable std::mutex mMutex; + mutable nn::SharedBurst mBurst GUARDED_BY(mMutex); +}; + +} // namespace android::hardware::neuralnetworks::utils + +#endif // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_UTILS_COMMON_RESILIENT_BURST_H diff --git a/neuralnetworks/utils/common/include/nnapi/hal/ResilientDevice.h b/neuralnetworks/utils/common/include/nnapi/hal/ResilientDevice.h index 84ae799aad..8199c522e3 100644 --- a/neuralnetworks/utils/common/include/nnapi/hal/ResilientDevice.h +++ b/neuralnetworks/utils/common/include/nnapi/hal/ResilientDevice.h @@ -53,6 +53,7 @@ class ResilientDevice final : public nn::IDevice, const std::string& getVersionString() const override; nn::Version getFeatureLevel() const override; nn::DeviceType getType() const override; + bool isUpdatable() const override; const std::vector<nn::Extension>& getSupportedExtensions() const override; const nn::Capabilities& getCapabilities() const override; std::pair<uint32_t, uint32_t> getNumberOfCacheFilesNeeded() const override; diff --git a/neuralnetworks/utils/common/include/nnapi/hal/ResilientPreparedModel.h b/neuralnetworks/utils/common/include/nnapi/hal/ResilientPreparedModel.h index faae673ba7..a6c1b1911a 100644 --- a/neuralnetworks/utils/common/include/nnapi/hal/ResilientPreparedModel.h +++ b/neuralnetworks/utils/common/include/nnapi/hal/ResilientPreparedModel.h @@ -30,7 +30,8 @@ namespace android::hardware::neuralnetworks::utils { -class ResilientPreparedModel final : public nn::IPreparedModel { +class ResilientPreparedModel final : public nn::IPreparedModel, + public std::enable_shared_from_this<ResilientPreparedModel> { struct PrivateConstructorTag {}; public: @@ -43,8 +44,8 @@ class ResilientPreparedModel final : public nn::IPreparedModel { nn::SharedPreparedModel preparedModel); nn::SharedPreparedModel getPreparedModel() const; - nn::SharedPreparedModel recover(const nn::IPreparedModel* failingPreparedModel, - bool blocking) const; + nn::GeneralResult<nn::SharedPreparedModel> recover( + const nn::IPreparedModel* failingPreparedModel) const; nn::ExecutionResult<std::pair<std::vector<nn::OutputShape>, nn::Timing>> execute( const nn::Request& request, nn::MeasureTiming measure, @@ -57,9 +58,14 @@ class ResilientPreparedModel final : public nn::IPreparedModel { const nn::OptionalDuration& loopTimeoutDuration, const nn::OptionalDuration& timeoutDurationAfterFence) const override; + nn::GeneralResult<nn::SharedBurst> configureExecutionBurst() const override; + std::any getUnderlyingResource() const override; private: + bool isValidInternal() const EXCLUDES(mMutex); + nn::GeneralResult<nn::SharedBurst> configureExecutionBurstInternal() const; + const Factory kMakePreparedModel; mutable std::mutex mMutex; mutable nn::SharedPreparedModel mPreparedModel GUARDED_BY(mMutex); diff --git a/neuralnetworks/utils/common/src/InvalidBurst.cpp b/neuralnetworks/utils/common/src/InvalidBurst.cpp new file mode 100644 index 0000000000..4ca6603eb7 --- /dev/null +++ b/neuralnetworks/utils/common/src/InvalidBurst.cpp @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * 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 "InvalidBurst.h" + +#include <nnapi/IBurst.h> +#include <nnapi/Result.h> +#include <nnapi/Types.h> + +#include <memory> +#include <optional> +#include <utility> + +namespace android::hardware::neuralnetworks::utils { + +InvalidBurst::OptionalCacheHold InvalidBurst::cacheMemory(const nn::Memory& /*memory*/) const { + return nullptr; +} + +nn::ExecutionResult<std::pair<std::vector<nn::OutputShape>, nn::Timing>> InvalidBurst::execute( + const nn::Request& /*request*/, nn::MeasureTiming /*measure*/) const { + return NN_ERROR() << "InvalidBurst"; +} + +} // namespace android::hardware::neuralnetworks::utils diff --git a/neuralnetworks/utils/common/src/InvalidDevice.cpp b/neuralnetworks/utils/common/src/InvalidDevice.cpp index 535ccb41c7..81bca7fad0 100644 --- a/neuralnetworks/utils/common/src/InvalidDevice.cpp +++ b/neuralnetworks/utils/common/src/InvalidDevice.cpp @@ -32,13 +32,14 @@ namespace android::hardware::neuralnetworks::utils { InvalidDevice::InvalidDevice(std::string name, std::string versionString, nn::Version featureLevel, - nn::DeviceType type, std::vector<nn::Extension> extensions, - nn::Capabilities capabilities, + nn::DeviceType type, bool isUpdatable, + std::vector<nn::Extension> extensions, nn::Capabilities capabilities, std::pair<uint32_t, uint32_t> numberOfCacheFilesNeeded) : kName(std::move(name)), kVersionString(std::move(versionString)), kFeatureLevel(featureLevel), kType(type), + kIsUpdatable(isUpdatable), kExtensions(std::move(extensions)), kCapabilities(std::move(capabilities)), kNumberOfCacheFilesNeeded(numberOfCacheFilesNeeded) {} @@ -59,6 +60,10 @@ nn::DeviceType InvalidDevice::getType() const { return kType; } +bool InvalidDevice::isUpdatable() const { + return kIsUpdatable; +} + const std::vector<nn::Extension>& InvalidDevice::getSupportedExtensions() const { return kExtensions; } diff --git a/neuralnetworks/utils/common/src/InvalidPreparedModel.cpp b/neuralnetworks/utils/common/src/InvalidPreparedModel.cpp index a46f4ac574..9081e1fdd1 100644 --- a/neuralnetworks/utils/common/src/InvalidPreparedModel.cpp +++ b/neuralnetworks/utils/common/src/InvalidPreparedModel.cpp @@ -42,6 +42,10 @@ InvalidPreparedModel::executeFenced( return NN_ERROR() << "InvalidPreparedModel"; } +nn::GeneralResult<nn::SharedBurst> InvalidPreparedModel::configureExecutionBurst() const { + return NN_ERROR() << "InvalidPreparedModel"; +} + std::any InvalidPreparedModel::getUnderlyingResource() const { return {}; } diff --git a/neuralnetworks/utils/common/src/ResilientBuffer.cpp b/neuralnetworks/utils/common/src/ResilientBuffer.cpp index cf5496ac39..47abbe268f 100644 --- a/neuralnetworks/utils/common/src/ResilientBuffer.cpp +++ b/neuralnetworks/utils/common/src/ResilientBuffer.cpp @@ -20,6 +20,7 @@ #include <android-base/thread_annotations.h> #include <nnapi/IBuffer.h> #include <nnapi/Result.h> +#include <nnapi/TypeUtils.h> #include <nnapi/Types.h> #include <functional> @@ -29,6 +30,34 @@ #include <vector> namespace android::hardware::neuralnetworks::utils { +namespace { + +template <typename FnType> +auto protect(const ResilientBuffer& resilientBuffer, const FnType& fn) + -> decltype(fn(*resilientBuffer.getBuffer())) { + auto buffer = resilientBuffer.getBuffer(); + auto result = fn(*buffer); + + // Immediately return if device is not dead. + if (result.has_value() || result.error().code != nn::ErrorStatus::DEAD_OBJECT) { + return result; + } + + // Attempt recovery and return if it fails. + auto maybeBuffer = resilientBuffer.recover(buffer.get()); + if (!maybeBuffer.has_value()) { + const auto& [resultErrorMessage, resultErrorCode] = result.error(); + const auto& [recoveryErrorMessage, recoveryErrorCode] = maybeBuffer.error(); + return nn::error(resultErrorCode) + << resultErrorMessage << ", and failed to recover dead buffer with error " + << recoveryErrorCode << ": " << recoveryErrorMessage; + } + buffer = std::move(maybeBuffer).value(); + + return fn(*buffer); +} + +} // namespace nn::GeneralResult<std::shared_ptr<const ResilientBuffer>> ResilientBuffer::create( Factory makeBuffer) { @@ -53,9 +82,16 @@ nn::SharedBuffer ResilientBuffer::getBuffer() const { std::lock_guard guard(mMutex); return mBuffer; } -nn::SharedBuffer ResilientBuffer::recover(const nn::IBuffer* /*failingBuffer*/, - bool /*blocking*/) const { +nn::GeneralResult<nn::SharedBuffer> ResilientBuffer::recover( + const nn::IBuffer* failingBuffer) const { std::lock_guard guard(mMutex); + + // Another caller updated the failing prepared model. + if (mBuffer.get() != failingBuffer) { + return mBuffer; + } + + mBuffer = NN_TRY(kMakeBuffer()); return mBuffer; } @@ -64,12 +100,16 @@ nn::Request::MemoryDomainToken ResilientBuffer::getToken() const { } nn::GeneralResult<void> ResilientBuffer::copyTo(const nn::Memory& dst) const { - return getBuffer()->copyTo(dst); + const auto fn = [&dst](const nn::IBuffer& buffer) { return buffer.copyTo(dst); }; + return protect(*this, fn); } nn::GeneralResult<void> ResilientBuffer::copyFrom(const nn::Memory& src, const nn::Dimensions& dimensions) const { - return getBuffer()->copyFrom(src, dimensions); + const auto fn = [&src, &dimensions](const nn::IBuffer& buffer) { + return buffer.copyFrom(src, dimensions); + }; + return protect(*this, fn); } } // namespace android::hardware::neuralnetworks::utils diff --git a/neuralnetworks/utils/common/src/ResilientBurst.cpp b/neuralnetworks/utils/common/src/ResilientBurst.cpp new file mode 100644 index 0000000000..0d3cb33a98 --- /dev/null +++ b/neuralnetworks/utils/common/src/ResilientBurst.cpp @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * 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 "ResilientBurst.h" + +#include <android-base/logging.h> +#include <android-base/thread_annotations.h> +#include <nnapi/IBurst.h> +#include <nnapi/Result.h> +#include <nnapi/TypeUtils.h> +#include <nnapi/Types.h> + +#include <functional> +#include <memory> +#include <mutex> +#include <optional> +#include <utility> + +namespace android::hardware::neuralnetworks::utils { +namespace { + +template <typename FnType> +auto protect(const ResilientBurst& resilientBurst, const FnType& fn) + -> decltype(fn(*resilientBurst.getBurst())) { + auto burst = resilientBurst.getBurst(); + auto result = fn(*burst); + + // Immediately return if burst is not dead. + if (result.has_value() || result.error().code != nn::ErrorStatus::DEAD_OBJECT) { + return result; + } + + // Attempt recovery and return if it fails. + auto maybeBurst = resilientBurst.recover(burst.get()); + if (!maybeBurst.has_value()) { + auto [resultErrorMessage, resultErrorCode, resultOutputShapes] = std::move(result).error(); + const auto& [recoveryErrorMessage, recoveryErrorCode] = maybeBurst.error(); + return nn::error(resultErrorCode, std::move(resultOutputShapes)) + << resultErrorMessage << ", and failed to recover dead burst object with error " + << recoveryErrorCode << ": " << recoveryErrorMessage; + } + burst = std::move(maybeBurst).value(); + + return fn(*burst); +} + +} // namespace + +nn::GeneralResult<std::shared_ptr<const ResilientBurst>> ResilientBurst::create(Factory makeBurst) { + if (makeBurst == nullptr) { + return NN_ERROR(nn::ErrorStatus::INVALID_ARGUMENT) + << "utils::ResilientBurst::create must have non-empty makeBurst"; + } + auto burst = NN_TRY(makeBurst()); + CHECK(burst != nullptr); + return std::make_shared<ResilientBurst>(PrivateConstructorTag{}, std::move(makeBurst), + std::move(burst)); +} + +ResilientBurst::ResilientBurst(PrivateConstructorTag /*tag*/, Factory makeBurst, + nn::SharedBurst burst) + : kMakeBurst(std::move(makeBurst)), mBurst(std::move(burst)) { + CHECK(kMakeBurst != nullptr); + CHECK(mBurst != nullptr); +} + +nn::SharedBurst ResilientBurst::getBurst() const { + std::lock_guard guard(mMutex); + return mBurst; +} + +nn::GeneralResult<nn::SharedBurst> ResilientBurst::recover(const nn::IBurst* failingBurst) const { + std::lock_guard guard(mMutex); + + // Another caller updated the failing burst. + if (mBurst.get() != failingBurst) { + return mBurst; + } + + mBurst = NN_TRY(kMakeBurst()); + return mBurst; +} + +ResilientBurst::OptionalCacheHold ResilientBurst::cacheMemory(const nn::Memory& memory) const { + return getBurst()->cacheMemory(memory); +} + +nn::ExecutionResult<std::pair<std::vector<nn::OutputShape>, nn::Timing>> ResilientBurst::execute( + const nn::Request& request, nn::MeasureTiming measure) const { + const auto fn = [&request, measure](const nn::IBurst& burst) { + return burst.execute(request, measure); + }; + return protect(*this, fn); +} + +} // namespace android::hardware::neuralnetworks::utils diff --git a/neuralnetworks/utils/common/src/ResilientDevice.cpp b/neuralnetworks/utils/common/src/ResilientDevice.cpp index 6ad3fadee6..13965afd37 100644 --- a/neuralnetworks/utils/common/src/ResilientDevice.cpp +++ b/neuralnetworks/utils/common/src/ResilientDevice.cpp @@ -122,12 +122,14 @@ nn::GeneralResult<nn::SharedDevice> ResilientDevice::recover(const nn::IDevice* }; if (compare(&IDevice::getName) || compare(&IDevice::getVersionString) || compare(&IDevice::getFeatureLevel) || compare(&IDevice::getType) || - compare(&IDevice::getSupportedExtensions) || compare(&IDevice::getCapabilities)) { + compare(&IDevice::isUpdatable) || compare(&IDevice::getSupportedExtensions) || + compare(&IDevice::getCapabilities)) { LOG(ERROR) << "Recovered device has different metadata than what is cached. Marking " "IDevice object as invalid."; device = std::make_shared<const InvalidDevice>( - kName, kVersionString, mDevice->getFeatureLevel(), mDevice->getType(), kExtensions, - kCapabilities, mDevice->getNumberOfCacheFilesNeeded()); + kName, kVersionString, mDevice->getFeatureLevel(), mDevice->getType(), + mDevice->isUpdatable(), kExtensions, kCapabilities, + mDevice->getNumberOfCacheFilesNeeded()); mIsValid = false; } @@ -151,6 +153,10 @@ nn::DeviceType ResilientDevice::getType() const { return getDevice()->getType(); } +bool ResilientDevice::isUpdatable() const { + return getDevice()->isUpdatable(); +} + const std::vector<nn::Extension>& ResilientDevice::getSupportedExtensions() const { return kExtensions; } @@ -180,6 +186,7 @@ nn::GeneralResult<nn::SharedPreparedModel> ResilientDevice::prepareModel( const nn::Model& model, nn::ExecutionPreference preference, nn::Priority priority, nn::OptionalTimePoint deadline, const std::vector<nn::SharedHandle>& modelCache, const std::vector<nn::SharedHandle>& dataCache, const nn::CacheToken& token) const { +#if 0 auto self = shared_from_this(); ResilientPreparedModel::Factory makePreparedModel = [device = std::move(self), model, preference, priority, deadline, modelCache, @@ -188,29 +195,41 @@ nn::GeneralResult<nn::SharedPreparedModel> ResilientDevice::prepareModel( dataCache, token); }; return ResilientPreparedModel::create(std::move(makePreparedModel)); +#else + return prepareModelInternal(model, preference, priority, deadline, modelCache, dataCache, + token); +#endif } nn::GeneralResult<nn::SharedPreparedModel> ResilientDevice::prepareModelFromCache( nn::OptionalTimePoint deadline, const std::vector<nn::SharedHandle>& modelCache, const std::vector<nn::SharedHandle>& dataCache, const nn::CacheToken& token) const { +#if 0 auto self = shared_from_this(); ResilientPreparedModel::Factory makePreparedModel = [device = std::move(self), deadline, modelCache, dataCache, token] { return device->prepareModelFromCacheInternal(deadline, modelCache, dataCache, token); }; return ResilientPreparedModel::create(std::move(makePreparedModel)); +#else + return prepareModelFromCacheInternal(deadline, modelCache, dataCache, token); +#endif } nn::GeneralResult<nn::SharedBuffer> ResilientDevice::allocate( const nn::BufferDesc& desc, const std::vector<nn::SharedPreparedModel>& preparedModels, const std::vector<nn::BufferRole>& inputRoles, const std::vector<nn::BufferRole>& outputRoles) const { +#if 0 auto self = shared_from_this(); ResilientBuffer::Factory makeBuffer = [device = std::move(self), desc, preparedModels, inputRoles, outputRoles] { return device->allocateInternal(desc, preparedModels, inputRoles, outputRoles); }; return ResilientBuffer::create(std::move(makeBuffer)); +#else + return allocateInternal(desc, preparedModels, inputRoles, outputRoles); +#endif } bool ResilientDevice::isValidInternal() const { @@ -225,8 +244,8 @@ nn::GeneralResult<nn::SharedPreparedModel> ResilientDevice::prepareModelInternal if (!isValidInternal()) { return std::make_shared<const InvalidPreparedModel>(); } - const auto fn = [&model, preference, priority, deadline, &modelCache, &dataCache, - token](const nn::IDevice& device) { + const auto fn = [&model, preference, priority, &deadline, &modelCache, &dataCache, + &token](const nn::IDevice& device) { return device.prepareModel(model, preference, priority, deadline, modelCache, dataCache, token); }; @@ -239,7 +258,7 @@ nn::GeneralResult<nn::SharedPreparedModel> ResilientDevice::prepareModelFromCach if (!isValidInternal()) { return std::make_shared<const InvalidPreparedModel>(); } - const auto fn = [deadline, &modelCache, &dataCache, token](const nn::IDevice& device) { + const auto fn = [&deadline, &modelCache, &dataCache, &token](const nn::IDevice& device) { return device.prepareModelFromCache(deadline, modelCache, dataCache, token); }; return protect(*this, fn, /*blocking=*/false); diff --git a/neuralnetworks/utils/common/src/ResilientPreparedModel.cpp b/neuralnetworks/utils/common/src/ResilientPreparedModel.cpp index b8acee16c9..5dd5f99f5f 100644 --- a/neuralnetworks/utils/common/src/ResilientPreparedModel.cpp +++ b/neuralnetworks/utils/common/src/ResilientPreparedModel.cpp @@ -16,19 +16,52 @@ #include "ResilientPreparedModel.h" +#include "InvalidBurst.h" +#include "ResilientBurst.h" + #include <android-base/logging.h> #include <android-base/thread_annotations.h> #include <nnapi/IPreparedModel.h> #include <nnapi/Result.h> +#include <nnapi/TypeUtils.h> #include <nnapi/Types.h> #include <functional> #include <memory> #include <mutex> +#include <sstream> #include <utility> #include <vector> namespace android::hardware::neuralnetworks::utils { +namespace { + +template <typename FnType> +auto protect(const ResilientPreparedModel& resilientPreparedModel, const FnType& fn) + -> decltype(fn(*resilientPreparedModel.getPreparedModel())) { + auto preparedModel = resilientPreparedModel.getPreparedModel(); + auto result = fn(*preparedModel); + + // Immediately return if prepared model is not dead. + if (result.has_value() || result.error().code != nn::ErrorStatus::DEAD_OBJECT) { + return result; + } + + // Attempt recovery and return if it fails. + auto maybePreparedModel = resilientPreparedModel.recover(preparedModel.get()); + if (!maybePreparedModel.has_value()) { + const auto& [message, code] = maybePreparedModel.error(); + std::ostringstream oss; + oss << ", and failed to recover dead prepared model with error " << code << ": " << message; + result.error().message += oss.str(); + return result; + } + preparedModel = std::move(maybePreparedModel).value(); + + return fn(*preparedModel); +} + +} // namespace nn::GeneralResult<std::shared_ptr<const ResilientPreparedModel>> ResilientPreparedModel::create( Factory makePreparedModel) { @@ -55,9 +88,16 @@ nn::SharedPreparedModel ResilientPreparedModel::getPreparedModel() const { return mPreparedModel; } -nn::SharedPreparedModel ResilientPreparedModel::recover( - const nn::IPreparedModel* /*failingPreparedModel*/, bool /*blocking*/) const { +nn::GeneralResult<nn::SharedPreparedModel> ResilientPreparedModel::recover( + const nn::IPreparedModel* failingPreparedModel) const { std::lock_guard guard(mMutex); + + // Another caller updated the failing prepared model. + if (mPreparedModel.get() != failingPreparedModel) { + return mPreparedModel; + } + + mPreparedModel = NN_TRY(kMakePreparedModel()); return mPreparedModel; } @@ -65,7 +105,11 @@ nn::ExecutionResult<std::pair<std::vector<nn::OutputShape>, nn::Timing>> ResilientPreparedModel::execute(const nn::Request& request, nn::MeasureTiming measure, const nn::OptionalTimePoint& deadline, const nn::OptionalDuration& loopTimeoutDuration) const { - return getPreparedModel()->execute(request, measure, deadline, loopTimeoutDuration); + const auto fn = [&request, measure, &deadline, + &loopTimeoutDuration](const nn::IPreparedModel& preparedModel) { + return preparedModel.execute(request, measure, deadline, loopTimeoutDuration); + }; + return protect(*this, fn); } nn::GeneralResult<std::pair<nn::SyncFence, nn::ExecuteFencedInfoCallback>> @@ -75,12 +119,43 @@ ResilientPreparedModel::executeFenced(const nn::Request& request, const nn::OptionalTimePoint& deadline, const nn::OptionalDuration& loopTimeoutDuration, const nn::OptionalDuration& timeoutDurationAfterFence) const { - return getPreparedModel()->executeFenced(request, waitFor, measure, deadline, - loopTimeoutDuration, timeoutDurationAfterFence); + const auto fn = [&request, &waitFor, measure, &deadline, &loopTimeoutDuration, + &timeoutDurationAfterFence](const nn::IPreparedModel& preparedModel) { + return preparedModel.executeFenced(request, waitFor, measure, deadline, loopTimeoutDuration, + timeoutDurationAfterFence); + }; + return protect(*this, fn); +} + +nn::GeneralResult<nn::SharedBurst> ResilientPreparedModel::configureExecutionBurst() const { +#if 0 + auto self = shared_from_this(); + ResilientBurst::Factory makeBurst = + [preparedModel = std::move(self)]() -> nn::GeneralResult<nn::SharedBurst> { + return preparedModel->configureExecutionBurst(); + }; + return ResilientBurst::create(std::move(makeBurst)); +#else + return configureExecutionBurstInternal(); +#endif } std::any ResilientPreparedModel::getUnderlyingResource() const { return getPreparedModel()->getUnderlyingResource(); } +bool ResilientPreparedModel::isValidInternal() const { + return true; +} + +nn::GeneralResult<nn::SharedBurst> ResilientPreparedModel::configureExecutionBurstInternal() const { + if (!isValidInternal()) { + return std::make_shared<const InvalidBurst>(); + } + const auto fn = [](const nn::IPreparedModel& preparedModel) { + return preparedModel.configureExecutionBurst(); + }; + return protect(*this, fn); +} + } // namespace android::hardware::neuralnetworks::utils diff --git a/neuralnetworks/utils/common/test/MockBuffer.h b/neuralnetworks/utils/common/test/MockBuffer.h new file mode 100644 index 0000000000..c5405fb837 --- /dev/null +++ b/neuralnetworks/utils/common/test/MockBuffer.h @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * 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 ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_UTILS_COMMON_TEST_MOCK_BUFFER +#define ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_UTILS_COMMON_TEST_MOCK_BUFFER + +#include <gmock/gmock.h> +#include <gtest/gtest.h> +#include <nnapi/IBuffer.h> +#include <nnapi/Types.h> + +namespace android::nn { + +class MockBuffer final : public IBuffer { + public: + MOCK_METHOD(Request::MemoryDomainToken, getToken, (), (const, override)); + MOCK_METHOD(GeneralResult<void>, copyTo, (const Memory& dst), (const, override)); + MOCK_METHOD(GeneralResult<void>, copyFrom, (const Memory& src, const Dimensions& dimensions), + (const, override)); +}; + +} // namespace android::nn + +#endif // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_UTILS_COMMON_TEST_MOCK_BUFFER diff --git a/neuralnetworks/utils/common/test/MockDevice.h b/neuralnetworks/utils/common/test/MockDevice.h new file mode 100644 index 0000000000..5566968c68 --- /dev/null +++ b/neuralnetworks/utils/common/test/MockDevice.h @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * 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 ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_UTILS_COMMON_TEST_MOCK_DEVICE +#define ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_UTILS_COMMON_TEST_MOCK_DEVICE + +#include <gmock/gmock.h> +#include <gtest/gtest.h> +#include <nnapi/IDevice.h> + +namespace android::nn { + +class MockDevice final : public IDevice { + public: + MOCK_METHOD(const std::string&, getName, (), (const, override)); + MOCK_METHOD(const std::string&, getVersionString, (), (const, override)); + MOCK_METHOD(Version, getFeatureLevel, (), (const, override)); + MOCK_METHOD(DeviceType, getType, (), (const, override)); + MOCK_METHOD(bool, isUpdatable, (), (const, override)); + MOCK_METHOD(const std::vector<Extension>&, getSupportedExtensions, (), (const, override)); + MOCK_METHOD(const Capabilities&, getCapabilities, (), (const, override)); + MOCK_METHOD((std::pair<uint32_t, uint32_t>), getNumberOfCacheFilesNeeded, (), + (const, override)); + MOCK_METHOD(GeneralResult<void>, wait, (), (const, override)); + MOCK_METHOD(GeneralResult<std::vector<bool>>, getSupportedOperations, (const Model& model), + (const, override)); + MOCK_METHOD(GeneralResult<SharedPreparedModel>, prepareModel, + (const Model& model, ExecutionPreference preference, Priority priority, + OptionalTimePoint deadline, const std::vector<SharedHandle>& modelCache, + const std::vector<SharedHandle>& dataCache, const CacheToken& token), + (const, override)); + MOCK_METHOD(GeneralResult<SharedPreparedModel>, prepareModelFromCache, + (OptionalTimePoint deadline, const std::vector<SharedHandle>& modelCache, + const std::vector<SharedHandle>& dataCache, const CacheToken& token), + (const, override)); + MOCK_METHOD(GeneralResult<SharedBuffer>, allocate, + (const BufferDesc& desc, const std::vector<SharedPreparedModel>& preparedModels, + const std::vector<BufferRole>& inputRoles, + const std::vector<BufferRole>& outputRoles), + (const, override)); +}; + +} // namespace android::nn + +#endif // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_UTILS_COMMON_TEST_MOCK_DEVICE diff --git a/neuralnetworks/utils/common/test/MockPreparedModel.h b/neuralnetworks/utils/common/test/MockPreparedModel.h new file mode 100644 index 0000000000..418af61666 --- /dev/null +++ b/neuralnetworks/utils/common/test/MockPreparedModel.h @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * 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 ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_UTILS_COMMON_TEST_MOCK_PREPARED_MODEL +#define ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_UTILS_COMMON_TEST_MOCK_PREPARED_MODEL + +#include <gmock/gmock.h> +#include <gtest/gtest.h> +#include <nnapi/IPreparedModel.h> + +namespace android::nn { + +class MockPreparedModel final : public IPreparedModel { + public: + MOCK_METHOD((ExecutionResult<std::pair<std::vector<OutputShape>, Timing>>), execute, + (const Request& request, MeasureTiming measure, const OptionalTimePoint& deadline, + const OptionalDuration& loopTimeoutDuration), + (const, override)); + MOCK_METHOD((GeneralResult<std::pair<SyncFence, ExecuteFencedInfoCallback>>), executeFenced, + (const Request& request, const std::vector<SyncFence>& waitFor, + MeasureTiming measure, const OptionalTimePoint& deadline, + const OptionalDuration& loopTimeoutDuration, + const OptionalDuration& timeoutDurationAfterFence), + (const, override)); + MOCK_METHOD(GeneralResult<SharedBurst>, configureExecutionBurst, (), (const, override)); + MOCK_METHOD(std::any, getUnderlyingResource, (), (const, override)); +}; + +} // namespace android::nn + +#endif // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_UTILS_COMMON_TEST_MOCK_PREPARED_MODEL diff --git a/neuralnetworks/utils/common/test/ResilientBufferTest.cpp b/neuralnetworks/utils/common/test/ResilientBufferTest.cpp new file mode 100644 index 0000000000..deb9b7cf21 --- /dev/null +++ b/neuralnetworks/utils/common/test/ResilientBufferTest.cpp @@ -0,0 +1,266 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * 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 <gmock/gmock.h> +#include <nnapi/TypeUtils.h> +#include <nnapi/Types.h> +#include <nnapi/hal/ResilientBuffer.h> +#include <tuple> +#include <utility> +#include "MockBuffer.h" + +namespace android::hardware::neuralnetworks::utils { +namespace { + +using ::testing::_; +using ::testing::InvokeWithoutArgs; +using ::testing::Return; + +constexpr auto kToken = nn::Request::MemoryDomainToken{1}; + +using SharedMockBuffer = std::shared_ptr<const nn::MockBuffer>; +using MockBufferFactory = ::testing::MockFunction<nn::GeneralResult<nn::SharedBuffer>()>; + +SharedMockBuffer createConfiguredMockBuffer() { + return std::make_shared<const nn::MockBuffer>(); +} + +std::tuple<std::shared_ptr<const nn::MockBuffer>, std::unique_ptr<MockBufferFactory>, + std::shared_ptr<const ResilientBuffer>> +setup() { + auto mockBuffer = std::make_shared<const nn::MockBuffer>(); + + auto mockBufferFactory = std::make_unique<MockBufferFactory>(); + EXPECT_CALL(*mockBufferFactory, Call()).Times(1).WillOnce(Return(mockBuffer)); + + auto buffer = ResilientBuffer::create(mockBufferFactory->AsStdFunction()).value(); + return std::make_tuple(std::move(mockBuffer), std::move(mockBufferFactory), std::move(buffer)); +} + +constexpr auto makeError = [](nn::ErrorStatus status) { + return [status](const auto&... /*args*/) { return nn::error(status); }; +}; +const auto kReturnGeneralFailure = makeError(nn::ErrorStatus::GENERAL_FAILURE); +const auto kReturnDeadObject = makeError(nn::ErrorStatus::DEAD_OBJECT); + +const auto kNoError = nn::GeneralResult<void>{}; + +} // namespace + +TEST(ResilientBufferTest, invalidBufferFactory) { + // setup call + const auto invalidBufferFactory = ResilientBuffer::Factory{}; + + // run test + const auto result = ResilientBuffer::create(invalidBufferFactory); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::INVALID_ARGUMENT); +} + +TEST(ResilientBufferTest, bufferFactoryFailure) { + // setup call + const auto invalidBufferFactory = kReturnGeneralFailure; + + // run test + const auto result = ResilientBuffer::create(invalidBufferFactory); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); +} + +TEST(ResilientBufferTest, getBuffer) { + // setup call + const auto [mockBuffer, mockBufferFactory, buffer] = setup(); + + // run test + const auto result = buffer->getBuffer(); + + // verify result + EXPECT_TRUE(result == mockBuffer); +} + +TEST(ResilientBufferTest, getToken) { + // setup call + const auto [mockBuffer, mockBufferFactory, buffer] = setup(); + EXPECT_CALL(*mockBuffer, getToken()).Times(1).WillOnce(Return(kToken)); + + // run test + const auto token = buffer->getToken(); + + // verify result + EXPECT_EQ(token, kToken); +} + +TEST(ResilientBufferTest, copyTo) { + // setup call + const auto [mockBuffer, mockBufferFactory, buffer] = setup(); + EXPECT_CALL(*mockBuffer, copyTo(_)).Times(1).WillOnce(Return(kNoError)); + + // run test + const auto result = buffer->copyTo({}); + + // verify result + ASSERT_TRUE(result.has_value()) + << "Failed with " << result.error().code << ": " << result.error().message; +} + +TEST(ResilientBufferTest, copyToError) { + // setup call + const auto [mockBuffer, mockBufferFactory, buffer] = setup(); + EXPECT_CALL(*mockBuffer, copyTo(_)).Times(1).WillOnce(kReturnGeneralFailure); + + // run test + const auto result = buffer->copyTo({}); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); +} + +TEST(ResilientBufferTest, copyToDeadObjectFailedRecovery) { + // setup call + const auto [mockBuffer, mockBufferFactory, buffer] = setup(); + EXPECT_CALL(*mockBuffer, copyTo(_)).Times(1).WillOnce(kReturnDeadObject); + EXPECT_CALL(*mockBufferFactory, Call()).Times(1).WillOnce(kReturnGeneralFailure); + + // run test + const auto result = buffer->copyTo({}); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT); +} + +TEST(ResilientBufferTest, copyToDeadObjectSuccessfulRecovery) { + // setup call + const auto [mockBuffer, mockBufferFactory, buffer] = setup(); + EXPECT_CALL(*mockBuffer, copyTo(_)).Times(1).WillOnce(kReturnDeadObject); + const auto recoveredMockBuffer = createConfiguredMockBuffer(); + EXPECT_CALL(*recoveredMockBuffer, copyTo(_)).Times(1).WillOnce(Return(kNoError)); + EXPECT_CALL(*mockBufferFactory, Call()).Times(1).WillOnce(Return(recoveredMockBuffer)); + + // run test + const auto result = buffer->copyTo({}); + + // verify result + ASSERT_TRUE(result.has_value()) + << "Failed with " << result.error().code << ": " << result.error().message; +} + +TEST(ResilientBufferTest, copyFrom) { + // setup call + const auto [mockBuffer, mockBufferFactory, buffer] = setup(); + EXPECT_CALL(*mockBuffer, copyFrom(_, _)).Times(1).WillOnce(Return(kNoError)); + + // run test + const auto result = buffer->copyFrom({}, {}); + + // verify result + ASSERT_TRUE(result.has_value()) + << "Failed with " << result.error().code << ": " << result.error().message; +} + +TEST(ResilientBufferTest, copyFromError) { + // setup call + const auto [mockBuffer, mockBufferFactory, buffer] = setup(); + EXPECT_CALL(*mockBuffer, copyFrom(_, _)).Times(1).WillOnce(kReturnGeneralFailure); + + // run test + const auto result = buffer->copyFrom({}, {}); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); +} + +TEST(ResilientBufferTest, copyFromDeadObjectFailedRecovery) { + // setup call + const auto [mockBuffer, mockBufferFactory, buffer] = setup(); + EXPECT_CALL(*mockBuffer, copyFrom(_, _)).Times(1).WillOnce(kReturnDeadObject); + EXPECT_CALL(*mockBufferFactory, Call()).Times(1).WillOnce(kReturnGeneralFailure); + + // run test + const auto result = buffer->copyFrom({}, {}); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT); +} + +TEST(ResilientBufferTest, copyFromDeadObjectSuccessfulRecovery) { + // setup call + const auto [mockBuffer, mockBufferFactory, buffer] = setup(); + EXPECT_CALL(*mockBuffer, copyFrom(_, _)).Times(1).WillOnce(kReturnDeadObject); + const auto recoveredMockBuffer = createConfiguredMockBuffer(); + EXPECT_CALL(*recoveredMockBuffer, copyFrom(_, _)).Times(1).WillOnce(Return(kNoError)); + EXPECT_CALL(*mockBufferFactory, Call()).Times(1).WillOnce(Return(recoveredMockBuffer)); + + // run test + const auto result = buffer->copyFrom({}, {}); + + // verify result + ASSERT_TRUE(result.has_value()) + << "Failed with " << result.error().code << ": " << result.error().message; +} + +TEST(ResilientBufferTest, recover) { + // setup call + const auto [mockBuffer, mockBufferFactory, buffer] = setup(); + const auto recoveredMockBuffer = createConfiguredMockBuffer(); + EXPECT_CALL(*mockBufferFactory, Call()).Times(1).WillOnce(Return(recoveredMockBuffer)); + + // run test + const auto result = buffer->recover(mockBuffer.get()); + + // verify result + ASSERT_TRUE(result.has_value()) + << "Failed with " << result.error().code << ": " << result.error().message; + EXPECT_TRUE(result.value() == recoveredMockBuffer); +} + +TEST(ResilientBufferTest, recoverFailure) { + // setup call + const auto [mockBuffer, mockBufferFactory, buffer] = setup(); + const auto recoveredMockBuffer = createConfiguredMockBuffer(); + EXPECT_CALL(*mockBufferFactory, Call()).Times(1).WillOnce(kReturnGeneralFailure); + + // run test + const auto result = buffer->recover(mockBuffer.get()); + + // verify result + EXPECT_FALSE(result.has_value()); +} + +TEST(ResilientBufferTest, someoneElseRecovered) { + // setup call + const auto [mockBuffer, mockBufferFactory, buffer] = setup(); + const auto recoveredMockBuffer = createConfiguredMockBuffer(); + EXPECT_CALL(*mockBufferFactory, Call()).Times(1).WillOnce(Return(recoveredMockBuffer)); + buffer->recover(mockBuffer.get()); + + // run test + const auto result = buffer->recover(mockBuffer.get()); + + // verify result + ASSERT_TRUE(result.has_value()) + << "Failed with " << result.error().code << ": " << result.error().message; + EXPECT_TRUE(result.value() == recoveredMockBuffer); +} + +} // namespace android::hardware::neuralnetworks::utils diff --git a/neuralnetworks/utils/common/test/ResilientDeviceTest.cpp b/neuralnetworks/utils/common/test/ResilientDeviceTest.cpp new file mode 100644 index 0000000000..3abd724c8c --- /dev/null +++ b/neuralnetworks/utils/common/test/ResilientDeviceTest.cpp @@ -0,0 +1,725 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * 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 <gmock/gmock.h> +#include <nnapi/TypeUtils.h> +#include <nnapi/Types.h> +#include <nnapi/hal/ResilientDevice.h> +#include <tuple> +#include <utility> +#include "MockBuffer.h" +#include "MockDevice.h" +#include "MockPreparedModel.h" + +namespace android::hardware::neuralnetworks::utils { +namespace { + +using ::testing::_; +using ::testing::InvokeWithoutArgs; +using ::testing::Return; + +using SharedMockDevice = std::shared_ptr<const nn::MockDevice>; +using MockDeviceFactory = ::testing::MockFunction<nn::GeneralResult<nn::SharedDevice>(bool)>; + +const std::string kName = "Google-MockV1"; +const std::string kVersionString = "version1"; +const auto kExtensions = std::vector<nn::Extension>{}; +constexpr auto kNoInfo = std::numeric_limits<float>::max(); +constexpr auto kNoPerformanceInfo = + nn::Capabilities::PerformanceInfo{.execTime = kNoInfo, .powerUsage = kNoInfo}; +const auto kCapabilities = nn::Capabilities{ + .relaxedFloat32toFloat16PerformanceScalar = kNoPerformanceInfo, + .relaxedFloat32toFloat16PerformanceTensor = kNoPerformanceInfo, + .operandPerformance = nn::Capabilities::OperandPerformanceTable::create({}).value(), + .ifPerformance = kNoPerformanceInfo, + .whilePerformance = kNoPerformanceInfo}; +constexpr auto kNumberOfCacheFilesNeeded = std::pair<uint32_t, uint32_t>(5, 3); + +SharedMockDevice createConfiguredMockDevice() { + auto mockDevice = std::make_shared<const nn::MockDevice>(); + + // Setup default actions for each relevant call. + constexpr auto getName_ret = []() -> const std::string& { return kName; }; + constexpr auto getVersionString_ret = []() -> const std::string& { return kVersionString; }; + constexpr auto kFeatureLevel = nn::Version::ANDROID_OC_MR1; + constexpr auto kDeviceType = nn::DeviceType::ACCELERATOR; + constexpr auto getSupportedExtensions_ret = []() -> const std::vector<nn::Extension>& { + return kExtensions; + }; + constexpr auto getCapabilities_ret = []() -> const nn::Capabilities& { return kCapabilities; }; + + // Setup default actions for each relevant call. + ON_CALL(*mockDevice, getName()).WillByDefault(getName_ret); + ON_CALL(*mockDevice, getVersionString()).WillByDefault(getVersionString_ret); + ON_CALL(*mockDevice, getFeatureLevel()).WillByDefault(Return(kFeatureLevel)); + ON_CALL(*mockDevice, getType()).WillByDefault(Return(kDeviceType)); + ON_CALL(*mockDevice, getSupportedExtensions()).WillByDefault(getSupportedExtensions_ret); + ON_CALL(*mockDevice, getCapabilities()).WillByDefault(getCapabilities_ret); + ON_CALL(*mockDevice, getNumberOfCacheFilesNeeded()) + .WillByDefault(Return(kNumberOfCacheFilesNeeded)); + + // These EXPECT_CALL(...).Times(testing::AnyNumber()) calls are to suppress warnings on the + // uninteresting methods calls. + EXPECT_CALL(*mockDevice, getName()).Times(testing::AnyNumber()); + EXPECT_CALL(*mockDevice, getVersionString()).Times(testing::AnyNumber()); + EXPECT_CALL(*mockDevice, getFeatureLevel()).Times(testing::AnyNumber()); + EXPECT_CALL(*mockDevice, getType()).Times(testing::AnyNumber()); + EXPECT_CALL(*mockDevice, getSupportedExtensions()).Times(testing::AnyNumber()); + EXPECT_CALL(*mockDevice, getCapabilities()).Times(testing::AnyNumber()); + EXPECT_CALL(*mockDevice, getNumberOfCacheFilesNeeded()).Times(testing::AnyNumber()); + + return mockDevice; +} + +std::tuple<SharedMockDevice, std::unique_ptr<MockDeviceFactory>, + std::shared_ptr<const ResilientDevice>> +setup() { + auto mockDevice = createConfiguredMockDevice(); + + auto mockDeviceFactory = std::make_unique<MockDeviceFactory>(); + EXPECT_CALL(*mockDeviceFactory, Call(true)).Times(1).WillOnce(Return(mockDevice)); + + auto device = ResilientDevice::create(mockDeviceFactory->AsStdFunction()).value(); + return std::make_tuple(std::move(mockDevice), std::move(mockDeviceFactory), std::move(device)); +} + +constexpr auto makeError = [](nn::ErrorStatus status) { + return [status](const auto&... /*args*/) { return nn::error(status); }; +}; +const auto kReturnGeneralFailure = makeError(nn::ErrorStatus::GENERAL_FAILURE); +const auto kReturnDeadObject = makeError(nn::ErrorStatus::DEAD_OBJECT); + +} // namespace + +TEST(ResilientDeviceTest, invalidDeviceFactory) { + // setup call + const auto invalidDeviceFactory = ResilientDevice::Factory{}; + + // run test + const auto result = ResilientDevice::create(invalidDeviceFactory); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::INVALID_ARGUMENT); +} + +TEST(ResilientDeviceTest, preparedModelFactoryFailure) { + // setup call + const auto invalidDeviceFactory = kReturnGeneralFailure; + + // run test + const auto result = ResilientDevice::create(invalidDeviceFactory); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); +} + +TEST(ResilientDeviceTest, cachedData) { + // setup call + const auto [mockDevice, mockDeviceFactory, device] = setup(); + + // run test and verify results + EXPECT_EQ(device->getName(), kName); + EXPECT_EQ(device->getVersionString(), kVersionString); + EXPECT_EQ(device->getSupportedExtensions(), kExtensions); + EXPECT_EQ(device->getCapabilities(), kCapabilities); +} + +TEST(ResilientDeviceTest, getFeatureLevel) { + // setup call + const auto [mockDevice, mockDeviceFactory, device] = setup(); + constexpr auto kFeatureLevel = nn::Version::ANDROID_OC_MR1; + EXPECT_CALL(*mockDevice, getFeatureLevel()).Times(1).WillOnce(Return(kFeatureLevel)); + + // run test + const auto featureLevel = device->getFeatureLevel(); + + // verify results + EXPECT_EQ(featureLevel, kFeatureLevel); +} + +TEST(ResilientDeviceTest, getType) { + // setup call + const auto [mockDevice, mockDeviceFactory, device] = setup(); + constexpr auto kDeviceType = nn::DeviceType::ACCELERATOR; + EXPECT_CALL(*mockDevice, getType()).Times(1).WillOnce(Return(kDeviceType)); + + // run test + const auto type = device->getType(); + + // verify results + EXPECT_EQ(type, kDeviceType); +} + +TEST(ResilientDeviceTest, getNumberOfCacheFilesNeeded) { + // setup call + const auto [mockDevice, mockDeviceFactory, device] = setup(); + EXPECT_CALL(*mockDevice, getNumberOfCacheFilesNeeded()) + .Times(1) + .WillOnce(Return(kNumberOfCacheFilesNeeded)); + + // run test + const auto numberOfCacheFilesNeeded = device->getNumberOfCacheFilesNeeded(); + + // verify results + EXPECT_EQ(numberOfCacheFilesNeeded, kNumberOfCacheFilesNeeded); +} + +TEST(ResilientDeviceTest, getDevice) { + // setup call + const auto [mockDevice, mockDeviceFactory, device] = setup(); + + // run test + const auto result = device->getDevice(); + + // verify result + EXPECT_TRUE(result == mockDevice); +} + +TEST(ResilientDeviceTest, wait) { + // setup call + const auto [mockDevice, mockDeviceFactory, device] = setup(); + EXPECT_CALL(*mockDevice, wait()).Times(1).WillOnce(Return(nn::GeneralResult<void>{})); + + // run test + const auto result = device->wait(); + + // verify result + ASSERT_TRUE(result.has_value()) + << "Failed with " << result.error().code << ": " << result.error().message; +} + +TEST(ResilientDeviceTest, waitError) { + // setup call + const auto [mockDevice, mockDeviceFactory, device] = setup(); + EXPECT_CALL(*mockDevice, wait()).Times(1).WillOnce(kReturnGeneralFailure); + + // run test + const auto result = device->wait(); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); +} + +TEST(ResilientDeviceTest, waitDeadObjectFailedRecovery) { + // setup call + const auto [mockDevice, mockDeviceFactory, device] = setup(); + EXPECT_CALL(*mockDevice, wait()).Times(1).WillOnce(kReturnDeadObject); + EXPECT_CALL(*mockDeviceFactory, Call(true)).Times(1).WillOnce(kReturnGeneralFailure); + + // run test + const auto result = device->wait(); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT); +} + +TEST(ResilientDeviceTest, waitDeadObjectSuccessfulRecovery) { + // setup call + const auto [mockDevice, mockDeviceFactory, device] = setup(); + EXPECT_CALL(*mockDevice, wait()).Times(1).WillOnce(kReturnDeadObject); + const auto recoveredMockDevice = createConfiguredMockDevice(); + EXPECT_CALL(*recoveredMockDevice, wait()).Times(1).WillOnce(Return(nn::GeneralResult<void>{})); + EXPECT_CALL(*mockDeviceFactory, Call(true)).Times(1).WillOnce(Return(recoveredMockDevice)); + + // run test + const auto result = device->wait(); + + // verify result + ASSERT_TRUE(result.has_value()) + << "Failed with " << result.error().code << ": " << result.error().message; +} + +TEST(ResilientDeviceTest, getSupportedOperations) { + // setup call + const auto [mockDevice, mockDeviceFactory, device] = setup(); + EXPECT_CALL(*mockDevice, getSupportedOperations(_)) + .Times(1) + .WillOnce(Return(nn::GeneralResult<std::vector<bool>>{})); + + // run test + const auto result = device->getSupportedOperations({}); + + // verify result + ASSERT_TRUE(result.has_value()) + << "Failed with " << result.error().code << ": " << result.error().message; +} + +TEST(ResilientDeviceTest, getSupportedOperationsError) { + // setup call + const auto [mockDevice, mockDeviceFactory, device] = setup(); + EXPECT_CALL(*mockDevice, getSupportedOperations(_)).Times(1).WillOnce(kReturnGeneralFailure); + + // run test + const auto result = device->getSupportedOperations({}); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); +} + +TEST(ResilientDeviceTest, getSupportedOperationsDeadObjectFailedRecovery) { + // setup call + const auto [mockDevice, mockDeviceFactory, device] = setup(); + EXPECT_CALL(*mockDevice, getSupportedOperations(_)).Times(1).WillOnce(kReturnDeadObject); + EXPECT_CALL(*mockDeviceFactory, Call(false)).Times(1).WillOnce(kReturnGeneralFailure); + + // run test + const auto result = device->getSupportedOperations({}); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT); +} + +TEST(ResilientDeviceTest, getSupportedOperationsDeadObjectSuccessfulRecovery) { + // setup call + const auto [mockDevice, mockDeviceFactory, device] = setup(); + EXPECT_CALL(*mockDevice, getSupportedOperations(_)).Times(1).WillOnce(kReturnDeadObject); + const auto recoveredMockDevice = createConfiguredMockDevice(); + EXPECT_CALL(*recoveredMockDevice, getSupportedOperations(_)) + .Times(1) + .WillOnce(Return(nn::GeneralResult<std::vector<bool>>{})); + EXPECT_CALL(*mockDeviceFactory, Call(false)).Times(1).WillOnce(Return(recoveredMockDevice)); + + // run test + const auto result = device->getSupportedOperations({}); + + // verify result + ASSERT_TRUE(result.has_value()) + << "Failed with " << result.error().code << ": " << result.error().message; +} + +TEST(ResilientDeviceTest, prepareModel) { + // setup call + const auto [mockDevice, mockDeviceFactory, device] = setup(); + const auto mockPreparedModel = std::make_shared<const nn::MockPreparedModel>(); + EXPECT_CALL(*mockDevice, prepareModel(_, _, _, _, _, _, _)) + .Times(1) + .WillOnce(Return(mockPreparedModel)); + + // run test + const auto result = device->prepareModel({}, {}, {}, {}, {}, {}, {}); + + // verify result + ASSERT_TRUE(result.has_value()) + << "Failed with " << result.error().code << ": " << result.error().message; +} + +TEST(ResilientDeviceTest, prepareModelError) { + // setup call + const auto [mockDevice, mockDeviceFactory, device] = setup(); + EXPECT_CALL(*mockDevice, prepareModel(_, _, _, _, _, _, _)) + .Times(1) + .WillOnce(kReturnGeneralFailure); + + // run test + const auto result = device->prepareModel({}, {}, {}, {}, {}, {}, {}); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); +} + +TEST(ResilientDeviceTest, prepareModelDeadObjectFailedRecovery) { + // setup call + const auto [mockDevice, mockDeviceFactory, device] = setup(); + EXPECT_CALL(*mockDevice, prepareModel(_, _, _, _, _, _, _)) + .Times(1) + .WillOnce(kReturnDeadObject); + EXPECT_CALL(*mockDeviceFactory, Call(false)).Times(1).WillOnce(kReturnGeneralFailure); + + // run test + const auto result = device->prepareModel({}, {}, {}, {}, {}, {}, {}); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT); +} + +TEST(ResilientDeviceTest, prepareModelDeadObjectSuccessfulRecovery) { + // setup call + const auto [mockDevice, mockDeviceFactory, device] = setup(); + EXPECT_CALL(*mockDevice, prepareModel(_, _, _, _, _, _, _)) + .Times(1) + .WillOnce(kReturnDeadObject); + const auto recoveredMockDevice = createConfiguredMockDevice(); + const auto mockPreparedModel = std::make_shared<const nn::MockPreparedModel>(); + EXPECT_CALL(*recoveredMockDevice, prepareModel(_, _, _, _, _, _, _)) + .Times(1) + .WillOnce(Return(mockPreparedModel)); + EXPECT_CALL(*mockDeviceFactory, Call(false)).Times(1).WillOnce(Return(recoveredMockDevice)); + + // run test + const auto result = device->prepareModel({}, {}, {}, {}, {}, {}, {}); + + // verify result + ASSERT_TRUE(result.has_value()) + << "Failed with " << result.error().code << ": " << result.error().message; +} + +TEST(ResilientDeviceTest, prepareModelFromCache) { + // setup call + const auto [mockDevice, mockDeviceFactory, device] = setup(); + const auto mockPreparedModel = std::make_shared<const nn::MockPreparedModel>(); + EXPECT_CALL(*mockDevice, prepareModelFromCache(_, _, _, _)) + .Times(1) + .WillOnce(Return(mockPreparedModel)); + + // run test + const auto result = device->prepareModelFromCache({}, {}, {}, {}); + + // verify result + ASSERT_TRUE(result.has_value()) + << "Failed with " << result.error().code << ": " << result.error().message; +} + +TEST(ResilientDeviceTest, prepareModelFromCacheError) { + // setup call + const auto [mockDevice, mockDeviceFactory, device] = setup(); + EXPECT_CALL(*mockDevice, prepareModelFromCache(_, _, _, _)) + .Times(1) + .WillOnce(kReturnGeneralFailure); + + // run test + const auto result = device->prepareModelFromCache({}, {}, {}, {}); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); +} + +TEST(ResilientDeviceTest, prepareModelFromCacheDeadObjectFailedRecovery) { + // setup call + const auto [mockDevice, mockDeviceFactory, device] = setup(); + EXPECT_CALL(*mockDevice, prepareModelFromCache(_, _, _, _)) + .Times(1) + .WillOnce(kReturnDeadObject); + EXPECT_CALL(*mockDeviceFactory, Call(false)).Times(1).WillOnce(kReturnGeneralFailure); + + // run test + const auto result = device->prepareModelFromCache({}, {}, {}, {}); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT); +} + +TEST(ResilientDeviceTest, prepareModelFromCacheDeadObjectSuccessfulRecovery) { + // setup call + const auto [mockDevice, mockDeviceFactory, device] = setup(); + EXPECT_CALL(*mockDevice, prepareModelFromCache(_, _, _, _)) + .Times(1) + .WillOnce(kReturnDeadObject); + const auto recoveredMockDevice = createConfiguredMockDevice(); + const auto mockPreparedModel = std::make_shared<const nn::MockPreparedModel>(); + EXPECT_CALL(*recoveredMockDevice, prepareModelFromCache(_, _, _, _)) + .Times(1) + .WillOnce(Return(mockPreparedModel)); + EXPECT_CALL(*mockDeviceFactory, Call(false)).Times(1).WillOnce(Return(recoveredMockDevice)); + + // run test + const auto result = device->prepareModelFromCache({}, {}, {}, {}); + + // verify result + ASSERT_TRUE(result.has_value()) + << "Failed with " << result.error().code << ": " << result.error().message; +} + +TEST(ResilientDeviceTest, allocate) { + // setup call + const auto [mockDevice, mockDeviceFactory, device] = setup(); + const auto mockBuffer = std::make_shared<const nn::MockBuffer>(); + EXPECT_CALL(*mockDevice, allocate(_, _, _, _)).Times(1).WillOnce(Return(mockBuffer)); + + // run test + const auto result = device->allocate({}, {}, {}, {}); + + // verify result + ASSERT_TRUE(result.has_value()) + << "Failed with " << result.error().code << ": " << result.error().message; +} + +TEST(ResilientDeviceTest, allocateError) { + // setup call + const auto [mockDevice, mockDeviceFactory, device] = setup(); + EXPECT_CALL(*mockDevice, allocate(_, _, _, _)).Times(1).WillOnce(kReturnGeneralFailure); + + // run test + const auto result = device->allocate({}, {}, {}, {}); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); +} + +TEST(ResilientDeviceTest, allocateDeadObjectFailedRecovery) { + // setup call + const auto [mockDevice, mockDeviceFactory, device] = setup(); + EXPECT_CALL(*mockDevice, allocate(_, _, _, _)).Times(1).WillOnce(kReturnDeadObject); + EXPECT_CALL(*mockDeviceFactory, Call(false)).Times(1).WillOnce(kReturnGeneralFailure); + + // run test + const auto result = device->allocate({}, {}, {}, {}); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT); +} + +TEST(ResilientDeviceTest, allocateDeadObjectSuccessfulRecovery) { + // setup call + const auto [mockDevice, mockDeviceFactory, device] = setup(); + EXPECT_CALL(*mockDevice, allocate(_, _, _, _)).Times(1).WillOnce(kReturnDeadObject); + const auto recoveredMockDevice = createConfiguredMockDevice(); + const auto mockBuffer = std::make_shared<const nn::MockBuffer>(); + EXPECT_CALL(*recoveredMockDevice, allocate(_, _, _, _)).Times(1).WillOnce(Return(mockBuffer)); + EXPECT_CALL(*mockDeviceFactory, Call(false)).Times(1).WillOnce(Return(recoveredMockDevice)); + + // run test + const auto result = device->allocate({}, {}, {}, {}); + + // verify result + ASSERT_TRUE(result.has_value()) + << "Failed with " << result.error().code << ": " << result.error().message; +} + +TEST(ResilientDeviceTest, recover) { + // setup call + const auto [mockDevice, mockDeviceFactory, device] = setup(); + const auto recoveredMockDevice = createConfiguredMockDevice(); + EXPECT_CALL(*mockDeviceFactory, Call(false)).Times(1).WillOnce(Return(recoveredMockDevice)); + + // run test + const auto result = device->recover(mockDevice.get(), /*blocking=*/false); + + // verify result + ASSERT_TRUE(result.has_value()) + << "Failed with " << result.error().code << ": " << result.error().message; + EXPECT_TRUE(result.value() == recoveredMockDevice); +} + +TEST(ResilientDeviceTest, recoverFailure) { + // setup call + const auto [mockDevice, mockDeviceFactory, device] = setup(); + const auto recoveredMockDevice = createConfiguredMockDevice(); + EXPECT_CALL(*mockDeviceFactory, Call(_)).Times(1).WillOnce(kReturnGeneralFailure); + + // run test + const auto result = device->recover(mockDevice.get(), /*blocking=*/false); + + // verify result + EXPECT_FALSE(result.has_value()); +} + +TEST(ResilientDeviceTest, someoneElseRecovered) { + // setup call + const auto [mockDevice, mockDeviceFactory, device] = setup(); + const auto recoveredMockDevice = createConfiguredMockDevice(); + EXPECT_CALL(*mockDeviceFactory, Call(false)).Times(1).WillOnce(Return(recoveredMockDevice)); + device->recover(mockDevice.get(), /*blocking=*/false); + + // run test + const auto result = device->recover(mockDevice.get(), /*blocking=*/false); + + // verify result + ASSERT_TRUE(result.has_value()) + << "Failed with " << result.error().code << ": " << result.error().message; + EXPECT_TRUE(result.value() == recoveredMockDevice); +} + +TEST(ResilientDeviceTest, recoverCacheMismatchGetName) { + // setup call + const auto [mockDevice, mockDeviceFactory, device] = setup(); + const auto recoveredMockDevice = createConfiguredMockDevice(); + const std::string kDifferentName = "Google-DifferentName"; + const auto ret = [&kDifferentName]() -> const std::string& { return kDifferentName; }; + EXPECT_CALL(*recoveredMockDevice, getName()).Times(1).WillOnce(ret); + EXPECT_CALL(*mockDeviceFactory, Call(false)).Times(1).WillOnce(Return(recoveredMockDevice)); + + // run test + const auto result = device->recover(mockDevice.get(), /*blocking=*/false); + + // verify result + ASSERT_TRUE(result.has_value()) + << "Failed with " << result.error().code << ": " << result.error().message; + EXPECT_TRUE(result.value() != nullptr); + EXPECT_TRUE(result.value() != mockDevice); + EXPECT_TRUE(result.value() != recoveredMockDevice); +} + +TEST(ResilientDeviceTest, recoverCacheMismatchGetVersionString) { + // setup call + const auto [mockDevice, mockDeviceFactory, device] = setup(); + const auto recoveredMockDevice = createConfiguredMockDevice(); + const std::string kDifferentVersionString = "differentversion"; + const auto ret = [&kDifferentVersionString]() -> const std::string& { + return kDifferentVersionString; + }; + EXPECT_CALL(*recoveredMockDevice, getVersionString()).Times(1).WillOnce(ret); + EXPECT_CALL(*mockDeviceFactory, Call(false)).Times(1).WillOnce(Return(recoveredMockDevice)); + + // run test + const auto result = device->recover(mockDevice.get(), /*blocking=*/false); + + // verify result + ASSERT_TRUE(result.has_value()) + << "Failed with " << result.error().code << ": " << result.error().message; + EXPECT_TRUE(result.value() != nullptr); + EXPECT_TRUE(result.value() != mockDevice); + EXPECT_TRUE(result.value() != recoveredMockDevice); +} + +TEST(ResilientDeviceTest, recoverCacheMismatchGetFeatureLevel) { + // setup call + const auto [mockDevice, mockDeviceFactory, device] = setup(); + const auto recoveredMockDevice = createConfiguredMockDevice(); + EXPECT_CALL(*recoveredMockDevice, getFeatureLevel()) + .Times(1) + .WillOnce(Return(nn::Version::ANDROID_P)); + EXPECT_CALL(*mockDeviceFactory, Call(false)).Times(1).WillOnce(Return(recoveredMockDevice)); + + // run test + const auto result = device->recover(mockDevice.get(), /*blocking=*/false); + + // verify result + ASSERT_TRUE(result.has_value()) + << "Failed with " << result.error().code << ": " << result.error().message; + EXPECT_TRUE(result.value() != nullptr); + EXPECT_TRUE(result.value() != mockDevice); + EXPECT_TRUE(result.value() != recoveredMockDevice); +} + +TEST(ResilientDeviceTest, recoverCacheMismatchGetType) { + // setup call + const auto [mockDevice, mockDeviceFactory, device] = setup(); + const auto recoveredMockDevice = createConfiguredMockDevice(); + EXPECT_CALL(*recoveredMockDevice, getType()).Times(1).WillOnce(Return(nn::DeviceType::GPU)); + EXPECT_CALL(*mockDeviceFactory, Call(false)).Times(1).WillOnce(Return(recoveredMockDevice)); + + // run test + const auto result = device->recover(mockDevice.get(), /*blocking=*/false); + + // verify result + ASSERT_TRUE(result.has_value()) + << "Failed with " << result.error().code << ": " << result.error().message; + EXPECT_TRUE(result.value() != nullptr); + EXPECT_TRUE(result.value() != mockDevice); + EXPECT_TRUE(result.value() != recoveredMockDevice); +} + +TEST(ResilientDeviceTest, recoverCacheMismatchGetSupportedExtensions) { + // setup call + const auto [mockDevice, mockDeviceFactory, device] = setup(); + const auto recoveredMockDevice = createConfiguredMockDevice(); + const auto kDifferentExtensions = + std::vector<nn::Extension>{nn::Extension{.name = "", .operandTypes = {}}}; + const auto ret = [&kDifferentExtensions]() -> const std::vector<nn::Extension>& { + return kDifferentExtensions; + }; + EXPECT_CALL(*recoveredMockDevice, getSupportedExtensions()).Times(1).WillOnce(ret); + EXPECT_CALL(*mockDeviceFactory, Call(false)).Times(1).WillOnce(Return(recoveredMockDevice)); + + // run test + const auto result = device->recover(mockDevice.get(), /*blocking=*/false); + + // verify result + ASSERT_TRUE(result.has_value()) + << "Failed with " << result.error().code << ": " << result.error().message; + EXPECT_TRUE(result.value() != nullptr); + EXPECT_TRUE(result.value() != mockDevice); + EXPECT_TRUE(result.value() != recoveredMockDevice); +} + +TEST(ResilientDeviceTest, recoverCacheMismatchGetCapabilities) { + // setup call + const auto [mockDevice, mockDeviceFactory, device] = setup(); + const auto recoveredMockDevice = createConfiguredMockDevice(); + const auto kDifferentCapabilities = nn::Capabilities{ + .relaxedFloat32toFloat16PerformanceTensor = {.execTime = 0.5f, .powerUsage = 0.5f}, + .operandPerformance = nn::Capabilities::OperandPerformanceTable::create({}).value()}; + const auto ret = [&kDifferentCapabilities]() -> const nn::Capabilities& { + return kDifferentCapabilities; + }; + EXPECT_CALL(*recoveredMockDevice, getCapabilities()).Times(1).WillOnce(ret); + EXPECT_CALL(*mockDeviceFactory, Call(false)).Times(1).WillOnce(Return(recoveredMockDevice)); + + // run test + const auto result = device->recover(mockDevice.get(), /*blocking=*/false); + + // verify result + ASSERT_TRUE(result.has_value()) + << "Failed with " << result.error().code << ": " << result.error().message; + EXPECT_TRUE(result.value() != nullptr); + EXPECT_TRUE(result.value() != mockDevice); + EXPECT_TRUE(result.value() != recoveredMockDevice); +} + +TEST(ResilientDeviceTest, recoverCacheMismatchInvalidPrepareModel) { + // setup call + const auto [mockDevice, mockDeviceFactory, device] = setup(); + const auto recoveredMockDevice = createConfiguredMockDevice(); + EXPECT_CALL(*recoveredMockDevice, getType()).Times(1).WillOnce(Return(nn::DeviceType::GPU)); + EXPECT_CALL(*mockDeviceFactory, Call(false)).Times(1).WillOnce(Return(recoveredMockDevice)); + device->recover(mockDevice.get(), /*blocking=*/false); + + // run test + auto result = device->prepareModel({}, {}, {}, {}, {}, {}, {}); + + // verify result + ASSERT_TRUE(result.has_value()) + << "Failed with " << result.error().code << ": " << result.error().message; + EXPECT_TRUE(result.value() != nullptr); +} + +TEST(ResilientDeviceTest, recoverCacheMismatchInvalidPrepareModelFromCache) { + // setup call + const auto [mockDevice, mockDeviceFactory, device] = setup(); + const auto recoveredMockDevice = createConfiguredMockDevice(); + EXPECT_CALL(*recoveredMockDevice, getType()).Times(1).WillOnce(Return(nn::DeviceType::GPU)); + EXPECT_CALL(*mockDeviceFactory, Call(false)).Times(1).WillOnce(Return(recoveredMockDevice)); + device->recover(mockDevice.get(), /*blocking=*/false); + + // run test + auto result = device->prepareModelFromCache({}, {}, {}, {}); + + // verify result + ASSERT_TRUE(result.has_value()) + << "Failed with " << result.error().code << ": " << result.error().message; + EXPECT_TRUE(result.value() != nullptr); +} + +TEST(ResilientDeviceTest, recoverCacheMismatchInvalidAllocate) { + // setup call + const auto [mockDevice, mockDeviceFactory, device] = setup(); + const auto recoveredMockDevice = createConfiguredMockDevice(); + EXPECT_CALL(*recoveredMockDevice, getType()).Times(1).WillOnce(Return(nn::DeviceType::GPU)); + EXPECT_CALL(*mockDeviceFactory, Call(false)).Times(1).WillOnce(Return(recoveredMockDevice)); + device->recover(mockDevice.get(), /*blocking=*/false); + + // run test + auto result = device->allocate({}, {}, {}, {}); + + // verify result + ASSERT_TRUE(result.has_value()) + << "Failed with " << result.error().code << ": " << result.error().message; + EXPECT_TRUE(result.value() != nullptr); +} + +} // namespace android::hardware::neuralnetworks::utils diff --git a/neuralnetworks/utils/common/test/ResilientPreparedModelTest.cpp b/neuralnetworks/utils/common/test/ResilientPreparedModelTest.cpp new file mode 100644 index 0000000000..6d86e10df2 --- /dev/null +++ b/neuralnetworks/utils/common/test/ResilientPreparedModelTest.cpp @@ -0,0 +1,297 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * 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 <gmock/gmock.h> +#include <nnapi/TypeUtils.h> +#include <nnapi/Types.h> +#include <nnapi/hal/ResilientPreparedModel.h> +#include <utility> +#include "MockPreparedModel.h" + +namespace android::hardware::neuralnetworks::utils { +namespace { + +using ::testing::_; +using ::testing::InvokeWithoutArgs; +using ::testing::Return; + +using SharedMockPreparedModel = std::shared_ptr<const nn::MockPreparedModel>; +using MockPreparedModelFactory = + ::testing::MockFunction<nn::GeneralResult<nn::SharedPreparedModel>()>; + +SharedMockPreparedModel createConfiguredMockPreparedModel() { + return std::make_shared<const nn::MockPreparedModel>(); +} + +std::tuple<std::shared_ptr<const nn::MockPreparedModel>, std::unique_ptr<MockPreparedModelFactory>, + std::shared_ptr<const ResilientPreparedModel>> +setup() { + auto mockPreparedModel = std::make_shared<const nn::MockPreparedModel>(); + + auto mockPreparedModelFactory = std::make_unique<MockPreparedModelFactory>(); + EXPECT_CALL(*mockPreparedModelFactory, Call()).Times(1).WillOnce(Return(mockPreparedModel)); + + auto buffer = ResilientPreparedModel::create(mockPreparedModelFactory->AsStdFunction()).value(); + return std::make_tuple(std::move(mockPreparedModel), std::move(mockPreparedModelFactory), + std::move(buffer)); +} + +constexpr auto makeError = [](nn::ErrorStatus status) { + return [status](const auto&... /*args*/) { return nn::error(status); }; +}; +const auto kReturnGeneralFailure = makeError(nn::ErrorStatus::GENERAL_FAILURE); +const auto kReturnDeadObject = makeError(nn::ErrorStatus::DEAD_OBJECT); + +const auto kNoExecutionError = + nn::ExecutionResult<std::pair<std::vector<nn::OutputShape>, nn::Timing>>{}; +const auto kNoFencedExecutionError = + nn::GeneralResult<std::pair<nn::SyncFence, nn::ExecuteFencedInfoCallback>>( + std::make_pair(nn::SyncFence::createAsSignaled(), nullptr)); + +struct FakeResource {}; + +} // namespace + +TEST(ResilientPreparedModelTest, invalidPreparedModelFactory) { + // setup call + const auto invalidPreparedModelFactory = ResilientPreparedModel::Factory{}; + + // run test + const auto result = ResilientPreparedModel::create(invalidPreparedModelFactory); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::INVALID_ARGUMENT); +} + +TEST(ResilientPreparedModelTest, preparedModelFactoryFailure) { + // setup call + const auto invalidPreparedModelFactory = kReturnGeneralFailure; + + // run test + const auto result = ResilientPreparedModel::create(invalidPreparedModelFactory); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); +} + +TEST(ResilientPreparedModelTest, getPreparedModel) { + // setup call + const auto [mockPreparedModel, mockPreparedModelFactory, preparedModel] = setup(); + + // run test + const auto result = preparedModel->getPreparedModel(); + + // verify result + EXPECT_TRUE(result == mockPreparedModel); +} + +TEST(ResilientPreparedModelTest, execute) { + // setup call + const auto [mockPreparedModel, mockPreparedModelFactory, preparedModel] = setup(); + EXPECT_CALL(*mockPreparedModel, execute(_, _, _, _)) + .Times(1) + .WillOnce(Return(kNoExecutionError)); + + // run test + const auto result = preparedModel->execute({}, {}, {}, {}); + + // verify result + ASSERT_TRUE(result.has_value()) + << "Failed with " << result.error().code << ": " << result.error().message; +} + +TEST(ResilientPreparedModelTest, executeError) { + // setup call + const auto [mockPreparedModel, mockPreparedModelFactory, preparedModel] = setup(); + EXPECT_CALL(*mockPreparedModel, execute(_, _, _, _)).Times(1).WillOnce(kReturnGeneralFailure); + + // run test + const auto result = preparedModel->execute({}, {}, {}, {}); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); +} + +TEST(ResilientPreparedModelTest, executeDeadObjectFailedRecovery) { + // setup call + const auto [mockPreparedModel, mockPreparedModelFactory, preparedModel] = setup(); + EXPECT_CALL(*mockPreparedModel, execute(_, _, _, _)).Times(1).WillOnce(kReturnDeadObject); + constexpr auto ret = [] { return nn::error(nn::ErrorStatus::GENERAL_FAILURE); }; + EXPECT_CALL(*mockPreparedModelFactory, Call()).Times(1).WillOnce(ret); + + // run test + const auto result = preparedModel->execute({}, {}, {}, {}); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT); +} + +TEST(ResilientPreparedModelTest, executeDeadObjectSuccessfulRecovery) { + // setup call + const auto [mockPreparedModel, mockPreparedModelFactory, preparedModel] = setup(); + EXPECT_CALL(*mockPreparedModel, execute(_, _, _, _)).Times(1).WillOnce(kReturnDeadObject); + const auto recoveredMockPreparedModel = createConfiguredMockPreparedModel(); + EXPECT_CALL(*recoveredMockPreparedModel, execute(_, _, _, _)) + .Times(1) + .WillOnce(Return(kNoExecutionError)); + EXPECT_CALL(*mockPreparedModelFactory, Call()) + .Times(1) + .WillOnce(Return(recoveredMockPreparedModel)); + + // run test + const auto result = preparedModel->execute({}, {}, {}, {}); + + // verify result + ASSERT_TRUE(result.has_value()) + << "Failed with " << result.error().code << ": " << result.error().message; +} + +TEST(ResilientPreparedModelTest, executeFenced) { + // setup call + const auto [mockPreparedModel, mockPreparedModelFactory, preparedModel] = setup(); + EXPECT_CALL(*mockPreparedModel, executeFenced(_, _, _, _, _, _)) + .Times(1) + .WillOnce(Return(kNoFencedExecutionError)); + + // run test + const auto result = preparedModel->executeFenced({}, {}, {}, {}, {}, {}); + + // verify result + ASSERT_TRUE(result.has_value()) + << "Failed with " << result.error().code << ": " << result.error().message; +} + +TEST(ResilientPreparedModelTest, executeFencedError) { + // setup call + const auto [mockPreparedModel, mockPreparedModelFactory, preparedModel] = setup(); + EXPECT_CALL(*mockPreparedModel, executeFenced(_, _, _, _, _, _)) + .Times(1) + .WillOnce(kReturnGeneralFailure); + + // run test + const auto result = preparedModel->executeFenced({}, {}, {}, {}, {}, {}); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); +} + +TEST(ResilientPreparedModelTest, executeFencedDeadObjectFailedRecovery) { + // setup call + const auto [mockPreparedModel, mockPreparedModelFactory, preparedModel] = setup(); + EXPECT_CALL(*mockPreparedModel, executeFenced(_, _, _, _, _, _)) + .Times(1) + .WillOnce(kReturnDeadObject); + EXPECT_CALL(*mockPreparedModelFactory, Call()).Times(1).WillOnce(kReturnGeneralFailure); + + // run test + const auto result = preparedModel->executeFenced({}, {}, {}, {}, {}, {}); + + // verify result + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT); +} + +TEST(ResilientPreparedModelTest, executeFencedDeadObjectSuccessfulRecovery) { + // setup call + const auto [mockPreparedModel, mockPreparedModelFactory, preparedModel] = setup(); + EXPECT_CALL(*mockPreparedModel, executeFenced(_, _, _, _, _, _)) + .Times(1) + .WillOnce(kReturnDeadObject); + const auto recoveredMockPreparedModel = createConfiguredMockPreparedModel(); + EXPECT_CALL(*recoveredMockPreparedModel, executeFenced(_, _, _, _, _, _)) + .Times(1) + .WillOnce(Return(kNoFencedExecutionError)); + EXPECT_CALL(*mockPreparedModelFactory, Call()) + .Times(1) + .WillOnce(Return(recoveredMockPreparedModel)); + + // run test + const auto result = preparedModel->executeFenced({}, {}, {}, {}, {}, {}); + + // verify result + ASSERT_TRUE(result.has_value()) + << "Failed with " << result.error().code << ": " << result.error().message; +} + +TEST(ResilientPreparedModelTest, getUnderlyingResource) { + // setup call + const auto [mockPreparedModel, mockPreparedModelFactory, preparedModel] = setup(); + EXPECT_CALL(*mockPreparedModel, getUnderlyingResource()) + .Times(1) + .WillOnce(Return(FakeResource{})); + + // run test + const auto resource = preparedModel->getUnderlyingResource(); + + // verify resource + const FakeResource* maybeFakeResource = std::any_cast<FakeResource>(&resource); + EXPECT_NE(maybeFakeResource, nullptr); +} + +TEST(ResilientPreparedModelTest, recover) { + // setup call + const auto [mockPreparedModel, mockPreparedModelFactory, preparedModel] = setup(); + const auto recoveredMockPreparedModel = createConfiguredMockPreparedModel(); + EXPECT_CALL(*mockPreparedModelFactory, Call()) + .Times(1) + .WillOnce(Return(recoveredMockPreparedModel)); + + // run test + const auto result = preparedModel->recover(mockPreparedModel.get()); + + // verify result + ASSERT_TRUE(result.has_value()) + << "Failed with " << result.error().code << ": " << result.error().message; + EXPECT_TRUE(result.value() == recoveredMockPreparedModel); +} + +TEST(ResilientPreparedModelTest, recoverFailure) { + // setup call + const auto [mockPreparedModel, mockPreparedModelFactory, preparedModel] = setup(); + const auto recoveredMockPreparedModel = createConfiguredMockPreparedModel(); + EXPECT_CALL(*mockPreparedModelFactory, Call()).Times(1).WillOnce(kReturnGeneralFailure); + + // run test + const auto result = preparedModel->recover(mockPreparedModel.get()); + + // verify result + EXPECT_FALSE(result.has_value()); +} + +TEST(ResilientPreparedModelTest, someoneElseRecovered) { + // setup call + const auto [mockPreparedModel, mockPreparedModelFactory, preparedModel] = setup(); + const auto recoveredMockPreparedModel = createConfiguredMockPreparedModel(); + EXPECT_CALL(*mockPreparedModelFactory, Call()) + .Times(1) + .WillOnce(Return(recoveredMockPreparedModel)); + preparedModel->recover(mockPreparedModel.get()); + + // run test + const auto result = preparedModel->recover(mockPreparedModel.get()); + + // verify result + ASSERT_TRUE(result.has_value()) + << "Failed with " << result.error().code << ": " << result.error().message; + EXPECT_TRUE(result.value() == recoveredMockPreparedModel); +} + +} // namespace android::hardware::neuralnetworks::utils diff --git a/oemlock/aidl/Android.bp b/oemlock/aidl/Android.bp new file mode 100644 index 0000000000..bfc99e7b5f --- /dev/null +++ b/oemlock/aidl/Android.bp @@ -0,0 +1,16 @@ +aidl_interface { + name: "android.hardware.oemlock", + vendor_available: true, + srcs: ["android/hardware/oemlock/*.aidl"], + stability: "vintf", + backend: { + java: { + platform_apis: true, + }, + ndk: { + vndk: { + enabled: true, + }, + }, + }, +} diff --git a/oemlock/aidl/aidl_api/android.hardware.oemlock/current/android/hardware/oemlock/IOemLock.aidl b/oemlock/aidl/aidl_api/android.hardware.oemlock/current/android/hardware/oemlock/IOemLock.aidl new file mode 100644 index 0000000000..e3c974d757 --- /dev/null +++ b/oemlock/aidl/aidl_api/android.hardware.oemlock/current/android/hardware/oemlock/IOemLock.aidl @@ -0,0 +1,27 @@ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. +// +// You must not make a backward incompatible change to any AIDL file built +// with the aidl_interface module type with versions property set. The module +// type is used to build AIDL files in a way that they can be used across +// independently updatable components of the system. If a device is shipped +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.hardware.oemlock; +@VintfStability +interface IOemLock { + String getName(); + boolean isOemUnlockAllowedByCarrier(); + boolean isOemUnlockAllowedByDevice(); + android.hardware.oemlock.OemLockSecureStatus setOemUnlockAllowedByCarrier(in boolean allowed, in byte[] signature); + void setOemUnlockAllowedByDevice(in boolean allowed); +} diff --git a/oemlock/aidl/aidl_api/android.hardware.oemlock/current/android/hardware/oemlock/OemLockSecureStatus.aidl b/oemlock/aidl/aidl_api/android.hardware.oemlock/current/android/hardware/oemlock/OemLockSecureStatus.aidl new file mode 100644 index 0000000000..9d1327d434 --- /dev/null +++ b/oemlock/aidl/aidl_api/android.hardware.oemlock/current/android/hardware/oemlock/OemLockSecureStatus.aidl @@ -0,0 +1,25 @@ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. +// +// You must not make a backward incompatible change to any AIDL file built +// with the aidl_interface module type with versions property set. The module +// type is used to build AIDL files in a way that they can be used across +// independently updatable components of the system. If a device is shipped +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.hardware.oemlock; +@Backing(type="int") @VintfStability +enum OemLockSecureStatus { + OK = 0, + FAILED = 1, + INVALID_SIGNATURE = 2, +} diff --git a/oemlock/aidl/android/hardware/oemlock/IOemLock.aidl b/oemlock/aidl/android/hardware/oemlock/IOemLock.aidl new file mode 100644 index 0000000000..674ff85ca5 --- /dev/null +++ b/oemlock/aidl/android/hardware/oemlock/IOemLock.aidl @@ -0,0 +1,81 @@ +/* + * Copyright 2020 The Android Open Source Project + * + * 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. + */ + +package android.hardware.oemlock; + +import android.hardware.oemlock.OemLockSecureStatus; + +/* + * The OEM lock prevents the bootloader from allowing the device to be flashed. + * + * Both the carrier and the device itself have a say as to whether OEM unlock is + * allowed and both must agree that is allowed in order for unlock to be + * possible. + */ +@VintfStability +interface IOemLock { + /** + * Returns a vendor specific identifier of the HAL. + * + * The name returned must not be interpreted by the framework but must be + * passed to vendor code which may use it to identify the security protocol + * used by setOemUnlockAllowedByCarrier. This allows the vendor to identify + * the protocol without having to maintain a device-to-protocol mapping. + * + * @return name of the implementation and STATUS_OK if get name successfully + */ + String getName(); + + /** + * Returns whether OEM unlock is allowed by the carrier. + * + * @return the current state(allowed/not allowed) of the flag + * and STATUS_OK if the flag was successfully read. + */ + boolean isOemUnlockAllowedByCarrier(); + + /** + * Returns whether OEM unlock ia allowed by the device. + * + * @return the current state(allowed/not allowed) of the flag + * and STATUS_OK if the flag was successfully read. + */ + boolean isOemUnlockAllowedByDevice(); + + /** + * Updates whether OEM unlock is allowed by the carrier. + * + * The implementation may require a vendor defined signature to prove the + * validity of this request in order to harden its security. + * + * @param allowed is the new value of the flag. + * @param signature to prove validity of this request or empty if not + * required. + * @return OK if the flag was successfully updated, + * INVALID_SIGNATURE if a signature is required but the wrong one + * was provided + * FAILED if the update was otherwise unsuccessful. + */ + OemLockSecureStatus setOemUnlockAllowedByCarrier(in boolean allowed, in byte[] signature); + + /** + * Updates whether OEM unlock is allowed by the device. + * + * @param allowed the new value of the flag. + * @return STATUS_OK if the flag was successfully updated. + */ + void setOemUnlockAllowedByDevice(in boolean allowed); +} diff --git a/oemlock/aidl/android/hardware/oemlock/OemLockSecureStatus.aidl b/oemlock/aidl/android/hardware/oemlock/OemLockSecureStatus.aidl new file mode 100644 index 0000000000..3c113774cc --- /dev/null +++ b/oemlock/aidl/android/hardware/oemlock/OemLockSecureStatus.aidl @@ -0,0 +1,34 @@ +/* + * Copyright 2020 The Android Open Source Project + * + * 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. + */ + +package android.hardware.oemlock; + +@VintfStability +@Backing(type="int") +enum OemLockSecureStatus { + /** + * The operation completed successfully. + */ + OK, + /** + * The operation encountered a problem. + */ + FAILED, + /** + * An invalid signature was provided so the operation was not performed. + */ + INVALID_SIGNATURE, +} diff --git a/oemlock/aidl/default/Android.bp b/oemlock/aidl/default/Android.bp new file mode 100644 index 0000000000..b9872d7eda --- /dev/null +++ b/oemlock/aidl/default/Android.bp @@ -0,0 +1,32 @@ +// +// Copyright (C) 2020 The Android Open Source Project +// +// 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. +// + +cc_binary { + name: "android.hardware.oemlock-service.example", + relative_install_path: "hw", + init_rc: ["android.hardware.oemlock-service.example.rc"], + vintf_fragments: ["android.hardware.oemlock-service.example.xml"], + vendor: true, + srcs: [ + "service.cpp", + "OemLock.cpp", + ], + shared_libs: [ + "android.hardware.oemlock-ndk_platform", + "libbase", + "libbinder_ndk", + ], +} diff --git a/oemlock/aidl/default/OemLock.cpp b/oemlock/aidl/default/OemLock.cpp new file mode 100644 index 0000000000..646b5321ed --- /dev/null +++ b/oemlock/aidl/default/OemLock.cpp @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * 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 "OemLock.h" + +namespace aidl { +namespace android { +namespace hardware { +namespace oemlock { + +// Methods from ::android::hardware::oemlock::IOemLock follow. + +::ndk::ScopedAStatus OemLock::getName(std::string *out_name) { + (void)out_name; + return ::ndk::ScopedAStatus::ok(); +} + +::ndk::ScopedAStatus OemLock::setOemUnlockAllowedByCarrier(bool in_allowed, const std::vector<uint8_t> &in_signature, OemLockSecureStatus *_aidl_return) { + (void)in_allowed; + (void)in_signature; + (void)_aidl_return; + return ::ndk::ScopedAStatus::ok(); +} + +::ndk::ScopedAStatus OemLock::isOemUnlockAllowedByCarrier(bool *out_allowed) { + (void)out_allowed; + return ::ndk::ScopedAStatus::ok(); +} + +::ndk::ScopedAStatus OemLock::setOemUnlockAllowedByDevice(bool in_allowed) { + (void)in_allowed; + return ::ndk::ScopedAStatus::ok(); +} + +::ndk::ScopedAStatus OemLock::isOemUnlockAllowedByDevice(bool *out_allowed) { + (void)out_allowed; + return ::ndk::ScopedAStatus::ok(); +} + +} // namespace oemlock +} // namespace hardware +} // namespace android +} // aidl diff --git a/oemlock/aidl/default/OemLock.h b/oemlock/aidl/default/OemLock.h new file mode 100644 index 0000000000..b0df414254 --- /dev/null +++ b/oemlock/aidl/default/OemLock.h @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * 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. + */ + +#pragma once + +#include <aidl/android/hardware/oemlock/BnOemLock.h> + +namespace aidl { +namespace android { +namespace hardware { +namespace oemlock { + +using ::aidl::android::hardware::oemlock::IOemLock; +using ::aidl::android::hardware::oemlock::OemLockSecureStatus; + +struct OemLock : public BnOemLock { +public: + OemLock() = default; + + // Methods from ::android::hardware::oemlock::IOemLock follow. + ::ndk::ScopedAStatus getName(std::string* out_name) override; + ::ndk::ScopedAStatus isOemUnlockAllowedByCarrier(bool* out_allowed) override; + ::ndk::ScopedAStatus isOemUnlockAllowedByDevice(bool* out_allowed) override; + ::ndk::ScopedAStatus setOemUnlockAllowedByCarrier(bool in_allowed, const std::vector<uint8_t>& in_signature, OemLockSecureStatus* _aidl_return) override; + ::ndk::ScopedAStatus setOemUnlockAllowedByDevice(bool in_allowed) override; +}; + +} // namespace oemlock +} // namespace hardware +} // namespace android +} // aidl diff --git a/oemlock/aidl/default/android.hardware.oemlock-service.example.rc b/oemlock/aidl/default/android.hardware.oemlock-service.example.rc new file mode 100644 index 0000000000..57b79d3c9a --- /dev/null +++ b/oemlock/aidl/default/android.hardware.oemlock-service.example.rc @@ -0,0 +1,4 @@ +service vendor.oemlock_default /vendor/bin/hw/android.hardware.oemlock-service.example + class hal + user hsm + group hsm diff --git a/oemlock/aidl/default/android.hardware.oemlock-service.example.xml b/oemlock/aidl/default/android.hardware.oemlock-service.example.xml new file mode 100644 index 0000000000..b9f137fc27 --- /dev/null +++ b/oemlock/aidl/default/android.hardware.oemlock-service.example.xml @@ -0,0 +1,9 @@ +<manifest version="1.0" type="device"> + <hal format="aidl"> + <name>android.hardware.oemlock</name> + <interface> + <name>IOemLock</name> + <instance>default</instance> + </interface> + </hal> +</manifest> diff --git a/oemlock/aidl/default/service.cpp b/oemlock/aidl/default/service.cpp new file mode 100644 index 0000000000..af828a0d1d --- /dev/null +++ b/oemlock/aidl/default/service.cpp @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * 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 <android-base/logging.h> +#include <android/binder_manager.h> +#include <android/binder_process.h> + +#include "OemLock.h" + +using ::aidl::android::hardware::oemlock::OemLock; + +int main() { + ABinderProcess_setThreadPoolMaxThreadCount(0); + std::shared_ptr<OemLock> oemlock = ndk::SharedRefBase::make<OemLock>(); + + const std::string instance = std::string() + OemLock::descriptor + "/default"; + binder_status_t status = AServiceManager_addService(oemlock->asBinder().get(), instance.c_str()); + CHECK(status == STATUS_OK); + + ABinderProcess_joinThreadPool(); + return -1; // Should never be reached +} diff --git a/oemlock/aidl/vts/Android.bp b/oemlock/aidl/vts/Android.bp new file mode 100644 index 0000000000..a13dbe2cc7 --- /dev/null +++ b/oemlock/aidl/vts/Android.bp @@ -0,0 +1,33 @@ +// +// Copyright (C) 2020 The Android Open Source Project +// +// 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. +// + +cc_test { + name: "VtsHalOemLockTargetTest", + defaults: [ + "VtsHalTargetTestDefaults", + "use_libaidlvintf_gtest_helper_static", + ], + srcs: ["VtsHalOemLockTargetTest.cpp"], + shared_libs: [ + "libbinder_ndk", + "libbase", + ], + static_libs: ["android.hardware.oemlock-ndk_platform"], + test_suites: [ + "general-tests", + "vts", + ], +} diff --git a/oemlock/aidl/vts/OWNERS b/oemlock/aidl/vts/OWNERS new file mode 100644 index 0000000000..40d95e4bf0 --- /dev/null +++ b/oemlock/aidl/vts/OWNERS @@ -0,0 +1,2 @@ +chengyouho@google.com +frankwoo@google.com diff --git a/oemlock/aidl/vts/VtsHalOemLockTargetTest.cpp b/oemlock/aidl/vts/VtsHalOemLockTargetTest.cpp new file mode 100644 index 0000000000..6bf629810c --- /dev/null +++ b/oemlock/aidl/vts/VtsHalOemLockTargetTest.cpp @@ -0,0 +1,165 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * 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 <aidl/Gtest.h> +#include <aidl/Vintf.h> + +#include <aidl/android/hardware/oemlock/IOemLock.h> +#include <android/binder_manager.h> +#include <android/binder_process.h> + +using ::aidl::android::hardware::oemlock::IOemLock; +using ::aidl::android::hardware::oemlock::OemLockSecureStatus; + +using ndk::SpAIBinder; + +struct OemLockAidlTest : public ::testing::TestWithParam<std::string> { + virtual void SetUp() override { + oemlock = IOemLock::fromBinder( + SpAIBinder(AServiceManager_waitForService(GetParam().c_str()))); + ASSERT_NE(oemlock, nullptr); + } + + virtual void TearDown() override {} + + std::shared_ptr<IOemLock> oemlock; +}; + +/* + * Check the name can be retrieved + */ +TEST_P(OemLockAidlTest, GetName) { + std::string name; + + const auto ret = oemlock->getName(&name); + + ASSERT_TRUE(ret.isOk()); + // Any value acceptable +}; + +/* + * Check the unlock allowed by device state can be queried + */ +TEST_P(OemLockAidlTest, QueryUnlockAllowedByDevice) { + bool allowed; + + const auto ret = oemlock->isOemUnlockAllowedByDevice(&allowed); + + ASSERT_TRUE(ret.isOk()); + // Any value acceptable +} + +/* + * Check unlock allowed by device state can be toggled + */ +TEST_P(OemLockAidlTest, AllowedByDeviceCanBeToggled) { + bool allowed; + + // Get the original state so it can be restored + const auto get_ret = oemlock->isOemUnlockAllowedByDevice(&allowed); + ASSERT_TRUE(get_ret.isOk()); + const bool originallyAllowed = allowed; + + // Toggle the state + const auto set_ret = oemlock->setOemUnlockAllowedByDevice(!originallyAllowed); + ASSERT_TRUE(set_ret.isOk()); + + const auto check_set_ret = oemlock->isOemUnlockAllowedByDevice(&allowed); + ASSERT_TRUE(check_set_ret.isOk()); + ASSERT_EQ(allowed, !originallyAllowed); + + // Restore the state + const auto restore_ret = oemlock->setOemUnlockAllowedByDevice(originallyAllowed); + ASSERT_TRUE(restore_ret.isOk()); + + const auto check_restore_ret = oemlock->isOemUnlockAllowedByDevice(&allowed); + ASSERT_TRUE(check_restore_ret.isOk()); + ASSERT_EQ(allowed, originallyAllowed); +} + +/* + * Check the unlock allowed by device state can be queried + */ +TEST_P(OemLockAidlTest, QueryUnlockAllowedByCarrier) { + bool allowed; + + const auto ret = oemlock->isOemUnlockAllowedByCarrier(&allowed); + + ASSERT_TRUE(ret.isOk()); + // Any value acceptable +} + +/* + * Attempt to check unlock allowed by carrier can be toggled + * + * The implementation may involve a signature which cannot be tested here. That + * is a valid implementation so the test will pass. If there is no signature + * required, the test will toggle the value. + */ +TEST_P(OemLockAidlTest, CarrierUnlock) { + const std::vector<uint8_t> noSignature = {}; + bool allowed; + OemLockSecureStatus secure_status; + + // Get the original state so it can be restored + const auto get_ret = oemlock->isOemUnlockAllowedByCarrier(&allowed); + ASSERT_TRUE(get_ret.isOk()); + const bool originallyAllowed = allowed; + + if (originallyAllowed) { + // Only applied to locked devices + return; + } + + // Toggle the state + const auto set_ret = oemlock->setOemUnlockAllowedByCarrier(!originallyAllowed, noSignature, &secure_status); + ASSERT_TRUE(set_ret.isOk()); + ASSERT_NE(secure_status, OemLockSecureStatus::FAILED); + const auto set_status = secure_status; + + const auto check_set_ret = oemlock->isOemUnlockAllowedByCarrier(&allowed); + ASSERT_TRUE(check_set_ret.isOk()); + + if (set_status == OemLockSecureStatus::INVALID_SIGNATURE) { + // Signature is required so we cannot toggle the value in the test, but this is allowed + ASSERT_EQ(allowed, originallyAllowed); + return; + } + + ASSERT_EQ(set_status, OemLockSecureStatus::OK); + ASSERT_EQ(allowed, !originallyAllowed); + + // Restore the state + const auto restore_ret = oemlock->setOemUnlockAllowedByCarrier(originallyAllowed, noSignature, &secure_status); + ASSERT_TRUE(restore_ret.isOk()); + ASSERT_EQ(secure_status, OemLockSecureStatus::OK); + + const auto check_restore_ret = oemlock->isOemUnlockAllowedByCarrier(&allowed); + ASSERT_TRUE(check_restore_ret.isOk()); + ASSERT_EQ(allowed, originallyAllowed); +} + +GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(OemLockAidlTest); +INSTANTIATE_TEST_SUITE_P( + PerInstance, OemLockAidlTest, + testing::ValuesIn(android::getAidlHalInstanceNames(IOemLock::descriptor)), + android::PrintInstanceNameToString); + +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + ABinderProcess_setThreadPoolMaxThreadCount(1); + ABinderProcess_startThreadPool(); + return RUN_ALL_TESTS(); +} diff --git a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/Timestamp.aidl b/power/stats/aidl/aidl_api/android.hardware.power.stats/current/android/hardware/power/stats/EnergyConsumerAttribution.aidl index 4d5b6598b5..0a7cff7d01 100644 --- a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/Timestamp.aidl +++ b/power/stats/aidl/aidl_api/android.hardware.power.stats/current/android/hardware/power/stats/EnergyConsumerAttribution.aidl @@ -15,8 +15,9 @@ // with such a backward incompatible change, it has a high risk of breaking // later when a module using the interface is updated, e.g., Mainline modules. -package android.hardware.security.keymint; +package android.hardware.power.stats; @VintfStability -parcelable Timestamp { - long milliSeconds; +parcelable EnergyConsumerAttribution { + int uid; + long energyUWs; } diff --git a/power/stats/aidl/aidl_api/android.hardware.power.stats/current/android/hardware/power/stats/EnergyConsumerResult.aidl b/power/stats/aidl/aidl_api/android.hardware.power.stats/current/android/hardware/power/stats/EnergyConsumerResult.aidl index 6289a08439..815316ec92 100644 --- a/power/stats/aidl/aidl_api/android.hardware.power.stats/current/android/hardware/power/stats/EnergyConsumerResult.aidl +++ b/power/stats/aidl/aidl_api/android.hardware.power.stats/current/android/hardware/power/stats/EnergyConsumerResult.aidl @@ -21,4 +21,5 @@ parcelable EnergyConsumerResult { android.hardware.power.stats.EnergyConsumerId energyConsumerId; long timestampMs; long energyUWs; + android.hardware.power.stats.EnergyConsumerAttribution[] attribution; } diff --git a/power/stats/aidl/android/hardware/power/stats/EnergyConsumerAttribution.aidl b/power/stats/aidl/android/hardware/power/stats/EnergyConsumerAttribution.aidl new file mode 100644 index 0000000000..e07204aa33 --- /dev/null +++ b/power/stats/aidl/android/hardware/power/stats/EnergyConsumerAttribution.aidl @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * 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. + */ + +package android.hardware.power.stats; + +@VintfStability +parcelable EnergyConsumerAttribution { + /** + * Android ID / Linux UID, the accumulated energy should be attributed to + */ + int uid; + /** + * Accumulated energy since boot in microwatt-seconds (uWs) for this AID + */ + long energyUWs; +} diff --git a/power/stats/aidl/android/hardware/power/stats/EnergyConsumerResult.aidl b/power/stats/aidl/android/hardware/power/stats/EnergyConsumerResult.aidl index d2b902ae56..e8d3dd9f94 100644 --- a/power/stats/aidl/android/hardware/power/stats/EnergyConsumerResult.aidl +++ b/power/stats/aidl/android/hardware/power/stats/EnergyConsumerResult.aidl @@ -17,6 +17,7 @@ package android.hardware.power.stats; import android.hardware.power.stats.EnergyConsumerId; +import android.hardware.power.stats.EnergyConsumerAttribution; @VintfStability parcelable EnergyConsumerResult { @@ -32,5 +33,9 @@ parcelable EnergyConsumerResult { * Accumulated energy since boot in microwatt-seconds (uWs) */ long energyUWs; + /** + * Optional attribution per UID for this EnergyConsumer. + */ + EnergyConsumerAttribution[] attribution; } diff --git a/radio/1.0/vts/functional/vts_test_util.h b/radio/1.0/vts/functional/vts_test_util.h index 1625f11d5d..218e8236b0 100644 --- a/radio/1.0/vts/functional/vts_test_util.h +++ b/radio/1.0/vts/functional/vts_test_util.h @@ -35,6 +35,12 @@ enum CheckFlag { static constexpr const char* FEATURE_VOICE_CALL = "android.software.connectionservice"; +static constexpr const char* FEATURE_TELEPHONY = "android.hardware.telephony"; + +static constexpr const char* FEATURE_TELEPHONY_GSM = "android.hardware.telephony.gsm"; + +static constexpr const char* FEATURE_TELEPHONY_CDMA = "android.hardware.telephony.cdma"; + /* * Generate random serial number for radio test */ diff --git a/radio/1.4/vts/functional/radio_hidl_hal_api.cpp b/radio/1.4/vts/functional/radio_hidl_hal_api.cpp index 1b254a1edf..b0b984cf16 100644 --- a/radio/1.4/vts/functional/radio_hidl_hal_api.cpp +++ b/radio/1.4/vts/functional/radio_hidl_hal_api.cpp @@ -34,6 +34,10 @@ TEST_P(RadioHidlTest_v1_4, emergencyDial) { if (!deviceSupportsFeature(FEATURE_VOICE_CALL)) { ALOGI("Skipping emergencyDial because voice call is not supported in device"); return; + } else if (!deviceSupportsFeature(FEATURE_TELEPHONY_GSM) && + !deviceSupportsFeature(FEATURE_TELEPHONY_CDMA)) { + ALOGI("Skipping emergencyDial because gsm/cdma radio is not supported in device"); + return; } else { ALOGI("Running emergencyDial because voice call is supported in device"); } @@ -86,6 +90,10 @@ TEST_P(RadioHidlTest_v1_4, emergencyDial_withServices) { if (!deviceSupportsFeature(FEATURE_VOICE_CALL)) { ALOGI("Skipping emergencyDial because voice call is not supported in device"); return; + } else if (!deviceSupportsFeature(FEATURE_TELEPHONY_GSM) && + !deviceSupportsFeature(FEATURE_TELEPHONY_CDMA)) { + ALOGI("Skipping emergencyDial because gsm/cdma radio is not supported in device"); + return; } else { ALOGI("Running emergencyDial because voice call is supported in device"); } @@ -138,6 +146,10 @@ TEST_P(RadioHidlTest_v1_4, emergencyDial_withEmergencyRouting) { if (!deviceSupportsFeature(FEATURE_VOICE_CALL)) { ALOGI("Skipping emergencyDial because voice call is not supported in device"); return; + } else if (!deviceSupportsFeature(FEATURE_TELEPHONY_GSM) && + !deviceSupportsFeature(FEATURE_TELEPHONY_CDMA)) { + ALOGI("Skipping emergencyDial because gsm/cdma radio is not supported in device"); + return; } else { ALOGI("Running emergencyDial because voice call is supported in device"); } diff --git a/radio/1.5/vts/functional/radio_hidl_hal_api.cpp b/radio/1.5/vts/functional/radio_hidl_hal_api.cpp index 7166654814..0b49b36927 100644 --- a/radio/1.5/vts/functional/radio_hidl_hal_api.cpp +++ b/radio/1.5/vts/functional/radio_hidl_hal_api.cpp @@ -236,7 +236,12 @@ TEST_P(RadioHidlTest_v1_5, setSignalStrengthReportingCriteria_1_5_NGRAN_SSRSRP) ALOGI("setSignalStrengthReportingCriteria_1_5_NGRAN_SSRSRP, rspInfo.error = %s\n", toString(radioRsp_v1_5->rspInfo.error).c_str()); - ASSERT_TRUE(CheckAnyOfErrors(radioRsp_v1_5->rspInfo.error, {RadioError::NONE})); + + // Allow REQUEST_NOT_SUPPORTED because some non-5G device may not support NGRAN for + // setSignalStrengthReportingCriteria_1_5() + ASSERT_TRUE( + CheckAnyOfErrors(radioRsp_v1_5->rspInfo.error, + {RadioError::NONE, RadioError::REQUEST_NOT_SUPPORTED})); } /* @@ -261,7 +266,12 @@ TEST_P(RadioHidlTest_v1_5, setSignalStrengthReportingCriteria_1_5_NGRAN_SSRSRQ) ALOGI("setSignalStrengthReportingCriteria_1_5_NGRAN_SSRSRQ, rspInfo.error = %s\n", toString(radioRsp_v1_5->rspInfo.error).c_str()); - ASSERT_TRUE(CheckAnyOfErrors(radioRsp_v1_5->rspInfo.error, {RadioError::NONE})); + + // Allow REQUEST_NOT_SUPPORTED because some non-5G device may not support NGRAN for + // setSignalStrengthReportingCriteria_1_5() + ASSERT_TRUE( + CheckAnyOfErrors(radioRsp_v1_5->rspInfo.error, + {RadioError::NONE, RadioError::REQUEST_NOT_SUPPORTED})); } /* @@ -307,7 +317,12 @@ TEST_P(RadioHidlTest_v1_5, setSignalStrengthReportingCriteria_1_5_NGRAN_SSSINR) ALOGI("setSignalStrengthReportingCriteria_1_5_NGRAN_SSSINR, rspInfo.error = %s\n", toString(radioRsp_v1_5->rspInfo.error).c_str()); - ASSERT_TRUE(CheckAnyOfErrors(radioRsp_v1_5->rspInfo.error, {RadioError::NONE})); + + // Allow REQUEST_NOT_SUPPORTED because some non-5G device may not support NGRAN for + // setSignalStrengthReportingCriteria_1_5() + ASSERT_TRUE( + CheckAnyOfErrors(radioRsp_v1_5->rspInfo.error, + {RadioError::NONE, RadioError::REQUEST_NOT_SUPPORTED})); } /* diff --git a/radio/1.6/IRadioIndication.hal b/radio/1.6/IRadioIndication.hal index 1b56d40bcf..a53d7c1212 100644 --- a/radio/1.6/IRadioIndication.hal +++ b/radio/1.6/IRadioIndication.hal @@ -23,6 +23,7 @@ import @1.6::LinkCapacityEstimate; import @1.6::NetworkScanResult; import @1.6::SignalStrength; import @1.6::SetupDataCallResult; +import @1.6::PhysicalChannelConfig; /** * Interface declaring unsolicited radio indications. @@ -101,4 +102,15 @@ interface IRadioIndication extends @1.5::IRadioIndication { * CellInfo. */ oneway networkScanResult_1_6(RadioIndicationType type, NetworkScanResult result); + + /** + * Indicates physical channel configurations. + * + * An empty configs list indicates that the radio is in idle mode. + * + * @param type Type of radio indication + * @param configs Vector of PhysicalChannelConfigs + */ + oneway currentPhysicalChannelConfigs_1_6(RadioIndicationType type, + vec<PhysicalChannelConfig> configs); }; diff --git a/radio/1.6/types.hal b/radio/1.6/types.hal index 550e079885..d475ed3657 100644 --- a/radio/1.6/types.hal +++ b/radio/1.6/types.hal @@ -23,7 +23,10 @@ import @1.0::LteSignalStrength; import @1.0::RadioError; import @1.0::RadioResponseType; import @1.0::RegState; +import @1.1::EutranBands; +import @1.1::GeranBands; import @1.1::ScanStatus; +import @1.1::UtranBands; import @1.2::Call; import @1.2::CellInfoCdma; import @1.2::CellConnectionStatus; @@ -41,6 +44,7 @@ import @1.5::CellInfoGsm; import @1.5::CellInfoWcdma; import @1.5::CellInfoTdscdma; import @1.5::LinkAddress; +import @1.5::NgranBands; import @1.5::RegStateResult.AccessTechnologySpecificInfo.Cdma2000RegistrationInfo; import @1.5::RegStateResult.AccessTechnologySpecificInfo.EutranRegistrationInfo; import @1.5::RegistrationFailCause; @@ -281,7 +285,7 @@ struct SetupDataCallResult { * suggestion. 0 indicates retry should be performed immediately. 0x7fffffffffffffff indicates * the device should not retry data setup anymore. */ - uint64_t suggestedRetryTime; + int64_t suggestedRetryTime; /** Context ID, uniquely identifies this data connection. */ int32_t cid; @@ -347,7 +351,7 @@ struct SetupDataCallResult { /** * The allocated pdu session id for this data call. - * A value of -1 means no pdu session id was attached to this call. + * A value of 0 means no pdu session id was attached to this call. * * Reference: 3GPP TS 24.007 section 11.2.3.1b */ @@ -818,3 +822,70 @@ enum DataCallFailCause : @1.4::DataCallFailCause { */ SLICE_REJECTED = 0x8CC, }; + +struct PhysicalChannelConfig { + /** Connection status for cell. Valid values are PRIMARY_SERVING and SECONDARY_SERVING */ + CellConnectionStatus status; + + /** The radio technology for this physical channel */ + RadioTechnology rat; + + /** Downlink Absolute Radio Frequency Channel Number */ + int32_t downlinkChannelNumber; + + /** Uplink Absolute Radio Frequency Channel Number */ + int32_t uplinkChannelNumber; + + /** Downlink cell bandwidth, in kHz */ + int32_t cellBandwidthDownlink; + + /** Uplink cell bandwidth, in kHz */ + int32_t cellBandwidthUplink; + + /** + * A list of data calls mapped to this physical channel. The context id must match the cid of + * @1.5::SetupDataCallResult. An empty list means the physical channel has no data call mapped + * to it. + */ + vec<int32_t> contextIds; + + /** + * The physical cell identifier for this cell. + * + * In UTRAN, this value is primary scrambling code. The range is [0, 511]. + * Reference: 3GPP TS 25.213 section 5.2.2. + * + * In EUTRAN, this value is physical layer cell identity. The range is [0, 503]. + * Reference: 3GPP TS 36.211 section 6.11. + * + * In 5G RAN, this value is physical layer cell identity. The range is [0, 1007]. + * Reference: 3GPP TS 38.211 section 7.4.2.1. + */ + uint32_t physicalCellId; + + /** + * The frequency band to scan. + */ + safe_union Band { + /** Valid only if radioAccessNetwork = GERAN. */ + GeranBands geranBand; + /** Valid only if radioAccessNetwork = UTRAN. */ + UtranBands utranBand; + /** Valid only if radioAccessNetwork = EUTRAN. */ + EutranBands eutranBand; + /** Valid only if radioAccessNetwork = NGRAN. */ + NgranBands ngranBand; + } band; +}; + +/** + * Extended from @1.5 NgranBands + * IRadio 1.6 supports NGRAN bands up to V16.5.0 + */ +enum NgranBands : @1.5::NgranBands { + /** 3GPP TS 38.101-1, Table 5.2-1: FR1 bands */ + BAND_26 = 26, + BAND_46 = 46, + BAND_53 = 53, + BAND_96 = 96, +}; diff --git a/radio/1.6/vts/functional/radio_hidl_hal_utils_v1_6.h b/radio/1.6/vts/functional/radio_hidl_hal_utils_v1_6.h index fbcd7a9339..5fcfa3b0bb 100644 --- a/radio/1.6/vts/functional/radio_hidl_hal_utils_v1_6.h +++ b/radio/1.6/vts/functional/radio_hidl_hal_utils_v1_6.h @@ -857,6 +857,11 @@ class RadioIndication_v1_6 : public ::android::hardware::radio::V1_6::IRadioIndi const ::android::hardware::hidl_vec<::android::hardware::radio::V1_6::CellInfo>& records); + Return<void> currentPhysicalChannelConfigs_1_6( + RadioIndicationType type, + const ::android::hardware::hidl_vec< + ::android::hardware::radio::V1_6::PhysicalChannelConfig>& configs); + /* 1.5 Api */ Return<void> uiccApplicationsEnablementChanged(RadioIndicationType type, bool enabled); diff --git a/radio/1.6/vts/functional/radio_indication.cpp b/radio/1.6/vts/functional/radio_indication.cpp index bfc54c0ac5..e7a96807a9 100644 --- a/radio/1.6/vts/functional/radio_indication.cpp +++ b/radio/1.6/vts/functional/radio_indication.cpp @@ -30,6 +30,13 @@ Return<void> RadioIndication_v1_6::unthrottleApn(RadioIndicationType /*type*/, return Void(); } +Return<void> RadioIndication_v1_6::currentPhysicalChannelConfigs_1_6( + RadioIndicationType /*type*/, + const ::android::hardware::hidl_vec< + ::android::hardware::radio::V1_6::PhysicalChannelConfig>& /*configs*/) { + return Void(); +} + /* 1.5 Apis */ Return<void> RadioIndication_v1_6::uiccApplicationsEnablementChanged(RadioIndicationType /*type*/, bool /*enabled*/) { diff --git a/radio/config/1.3/types.hal b/radio/config/1.3/types.hal index bedb70922e..ba964bfda9 100644 --- a/radio/config/1.3/types.hal +++ b/radio/config/1.3/types.hal @@ -19,4 +19,10 @@ package android.hardware.radio.config@1.3; /** * Contains the device capabilities with respect to the Radio HAL. */ -struct HalDeviceCapabilities {}; +struct HalDeviceCapabilities { + /** + * True indicates that the modem is missing features within the current + * version of the Radio HAL. + */ + bool modemReducedFeatureSet1; +}; diff --git a/security/keymint/aidl/Android.bp b/security/keymint/aidl/Android.bp index b5adac9558..5652827a92 100644 --- a/security/keymint/aidl/Android.bp +++ b/security/keymint/aidl/Android.bp @@ -4,6 +4,9 @@ aidl_interface { srcs: [ "android/hardware/security/keymint/*.aidl", ], + imports: [ + "android.hardware.security.secureclock", + ], stability: "vintf", backend: { java: { diff --git a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/Algorithm.aidl b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/Algorithm.aidl index 46e0ae0f2f..a6c3e65a3b 100644 --- a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/Algorithm.aidl +++ b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/Algorithm.aidl @@ -2,13 +2,14 @@ // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // /////////////////////////////////////////////////////////////////////////////// -// This file is a snapshot of an AIDL interface (or parcelable). Do not try to -// edit this file. It looks like you are doing that because you have modified -// an AIDL interface in a backward-incompatible way, e.g., deleting a function -// from an interface or a field from a parcelable and it broke the build. That -// breakage is intended. +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. // -// You must not make a backward incompatible changes to the AIDL files built +// You must not make a backward incompatible change to any AIDL file built // with the aidl_interface module type with versions property set. The module // type is used to build AIDL files in a way that they can be used across // independently updatable components of the system. If a device is shipped diff --git a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/BeginResult.aidl b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/BeginResult.aidl index ed96485a0d..84395afec8 100644 --- a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/BeginResult.aidl +++ b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/BeginResult.aidl @@ -2,13 +2,14 @@ // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // /////////////////////////////////////////////////////////////////////////////// -// This file is a snapshot of an AIDL interface (or parcelable). Do not try to -// edit this file. It looks like you are doing that because you have modified -// an AIDL interface in a backward-incompatible way, e.g., deleting a function -// from an interface or a field from a parcelable and it broke the build. That -// breakage is intended. +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. // -// You must not make a backward incompatible changes to the AIDL files built +// You must not make a backward incompatible change to any AIDL file built // with the aidl_interface module type with versions property set. The module // type is used to build AIDL files in a way that they can be used across // independently updatable components of the system. If a device is shipped diff --git a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/BlockMode.aidl b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/BlockMode.aidl index dddc9d8d3c..e914823007 100644 --- a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/BlockMode.aidl +++ b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/BlockMode.aidl @@ -2,13 +2,14 @@ // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // /////////////////////////////////////////////////////////////////////////////// -// This file is a snapshot of an AIDL interface (or parcelable). Do not try to -// edit this file. It looks like you are doing that because you have modified -// an AIDL interface in a backward-incompatible way, e.g., deleting a function -// from an interface or a field from a parcelable and it broke the build. That -// breakage is intended. +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. // -// You must not make a backward incompatible changes to the AIDL files built +// You must not make a backward incompatible change to any AIDL file built // with the aidl_interface module type with versions property set. The module // type is used to build AIDL files in a way that they can be used across // independently updatable components of the system. If a device is shipped diff --git a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/ByteArray.aidl b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/ByteArray.aidl index 3d18a26cf7..cef8eca8a9 100644 --- a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/ByteArray.aidl +++ b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/ByteArray.aidl @@ -2,13 +2,14 @@ // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // /////////////////////////////////////////////////////////////////////////////// -// This file is a snapshot of an AIDL interface (or parcelable). Do not try to -// edit this file. It looks like you are doing that because you have modified -// an AIDL interface in a backward-incompatible way, e.g., deleting a function -// from an interface or a field from a parcelable and it broke the build. That -// breakage is intended. +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. // -// You must not make a backward incompatible changes to the AIDL files built +// You must not make a backward incompatible change to any AIDL file built // with the aidl_interface module type with versions property set. The module // type is used to build AIDL files in a way that they can be used across // independently updatable components of the system. If a device is shipped diff --git a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/Certificate.aidl b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/Certificate.aidl index 9e0f8dcff9..2277831c73 100644 --- a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/Certificate.aidl +++ b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/Certificate.aidl @@ -2,13 +2,14 @@ // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // /////////////////////////////////////////////////////////////////////////////// -// This file is a snapshot of an AIDL interface (or parcelable). Do not try to -// edit this file. It looks like you are doing that because you have modified -// an AIDL interface in a backward-incompatible way, e.g., deleting a function -// from an interface or a field from a parcelable and it broke the build. That -// breakage is intended. +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. // -// You must not make a backward incompatible changes to the AIDL files built +// You must not make a backward incompatible change to any AIDL file built // with the aidl_interface module type with versions property set. The module // type is used to build AIDL files in a way that they can be used across // independently updatable components of the system. If a device is shipped diff --git a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/Digest.aidl b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/Digest.aidl index 8fc4d428db..2e583ce104 100644 --- a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/Digest.aidl +++ b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/Digest.aidl @@ -2,13 +2,14 @@ // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // /////////////////////////////////////////////////////////////////////////////// -// This file is a snapshot of an AIDL interface (or parcelable). Do not try to -// edit this file. It looks like you are doing that because you have modified -// an AIDL interface in a backward-incompatible way, e.g., deleting a function -// from an interface or a field from a parcelable and it broke the build. That -// breakage is intended. +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. // -// You must not make a backward incompatible changes to the AIDL files built +// You must not make a backward incompatible change to any AIDL file built // with the aidl_interface module type with versions property set. The module // type is used to build AIDL files in a way that they can be used across // independently updatable components of the system. If a device is shipped diff --git a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/EcCurve.aidl b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/EcCurve.aidl index 7c3f2f38eb..b37282242a 100644 --- a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/EcCurve.aidl +++ b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/EcCurve.aidl @@ -2,13 +2,14 @@ // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // /////////////////////////////////////////////////////////////////////////////// -// This file is a snapshot of an AIDL interface (or parcelable). Do not try to -// edit this file. It looks like you are doing that because you have modified -// an AIDL interface in a backward-incompatible way, e.g., deleting a function -// from an interface or a field from a parcelable and it broke the build. That -// breakage is intended. +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. // -// You must not make a backward incompatible changes to the AIDL files built +// You must not make a backward incompatible change to any AIDL file built // with the aidl_interface module type with versions property set. The module // type is used to build AIDL files in a way that they can be used across // independently updatable components of the system. If a device is shipped diff --git a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/ErrorCode.aidl b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/ErrorCode.aidl index cdcb08d5c7..aa8c071bf4 100644 --- a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/ErrorCode.aidl +++ b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/ErrorCode.aidl @@ -2,13 +2,14 @@ // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // /////////////////////////////////////////////////////////////////////////////// -// This file is a snapshot of an AIDL interface (or parcelable). Do not try to -// edit this file. It looks like you are doing that because you have modified -// an AIDL interface in a backward-incompatible way, e.g., deleting a function -// from an interface or a field from a parcelable and it broke the build. That -// breakage is intended. +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. // -// You must not make a backward incompatible changes to the AIDL files built +// You must not make a backward incompatible change to any AIDL file built // with the aidl_interface module type with versions property set. The module // type is used to build AIDL files in a way that they can be used across // independently updatable components of the system. If a device is shipped @@ -94,6 +95,8 @@ enum ErrorCode { ATTESTATION_IDS_NOT_PROVISIONED = -75, INVALID_OPERATION = -76, STORAGE_KEY_UNSUPPORTED = -77, + INCOMPATIBLE_MGF_DIGEST = -78, + UNSUPPORTED_MGF_DIGEST = -79, UNIMPLEMENTED = -100, VERSION_MISMATCH = -101, UNKNOWN_ERROR = -1000, diff --git a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/HardwareAuthToken.aidl b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/HardwareAuthToken.aidl index 9ea24f5ebe..ad5bf39b33 100644 --- a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/HardwareAuthToken.aidl +++ b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/HardwareAuthToken.aidl @@ -2,13 +2,14 @@ // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // /////////////////////////////////////////////////////////////////////////////// -// This file is a snapshot of an AIDL interface (or parcelable). Do not try to -// edit this file. It looks like you are doing that because you have modified -// an AIDL interface in a backward-incompatible way, e.g., deleting a function -// from an interface or a field from a parcelable and it broke the build. That -// breakage is intended. +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. // -// You must not make a backward incompatible changes to the AIDL files built +// You must not make a backward incompatible change to any AIDL file built // with the aidl_interface module type with versions property set. The module // type is used to build AIDL files in a way that they can be used across // independently updatable components of the system. If a device is shipped @@ -22,6 +23,6 @@ parcelable HardwareAuthToken { long userId; long authenticatorId; android.hardware.security.keymint.HardwareAuthenticatorType authenticatorType; - android.hardware.security.keymint.Timestamp timestamp; + android.hardware.security.secureclock.Timestamp timestamp; byte[] mac; } diff --git a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/HardwareAuthenticatorType.aidl b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/HardwareAuthenticatorType.aidl index aef5ee049f..9ab00c100f 100644 --- a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/HardwareAuthenticatorType.aidl +++ b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/HardwareAuthenticatorType.aidl @@ -2,13 +2,14 @@ // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // /////////////////////////////////////////////////////////////////////////////// -// This file is a snapshot of an AIDL interface (or parcelable). Do not try to -// edit this file. It looks like you are doing that because you have modified -// an AIDL interface in a backward-incompatible way, e.g., deleting a function -// from an interface or a field from a parcelable and it broke the build. That -// breakage is intended. +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. // -// You must not make a backward incompatible changes to the AIDL files built +// You must not make a backward incompatible change to any AIDL file built // with the aidl_interface module type with versions property set. The module // type is used to build AIDL files in a way that they can be used across // independently updatable components of the system. If a device is shipped diff --git a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/IKeyMintDevice.aidl b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/IKeyMintDevice.aidl index 3d08cfef08..c95145d993 100644 --- a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/IKeyMintDevice.aidl +++ b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/IKeyMintDevice.aidl @@ -2,13 +2,14 @@ // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // /////////////////////////////////////////////////////////////////////////////// -// This file is a snapshot of an AIDL interface (or parcelable). Do not try to -// edit this file. It looks like you are doing that because you have modified -// an AIDL interface in a backward-incompatible way, e.g., deleting a function -// from an interface or a field from a parcelable and it broke the build. That -// breakage is intended. +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. // -// You must not make a backward incompatible changes to the AIDL files built +// You must not make a backward incompatible change to any AIDL file built // with the aidl_interface module type with versions property set. The module // type is used to build AIDL files in a way that they can be used across // independently updatable components of the system. If a device is shipped @@ -19,11 +20,10 @@ package android.hardware.security.keymint; @VintfStability interface IKeyMintDevice { android.hardware.security.keymint.KeyMintHardwareInfo getHardwareInfo(); - android.hardware.security.keymint.VerificationToken verifyAuthorization(in long challenge, in android.hardware.security.keymint.HardwareAuthToken token); void addRngEntropy(in byte[] data); - void generateKey(in android.hardware.security.keymint.KeyParameter[] keyParams, out android.hardware.security.keymint.ByteArray generatedKeyBlob, out android.hardware.security.keymint.KeyCharacteristics generatedKeyCharacteristics, out android.hardware.security.keymint.Certificate[] outCertChain); - void importKey(in android.hardware.security.keymint.KeyParameter[] inKeyParams, in android.hardware.security.keymint.KeyFormat inKeyFormat, in byte[] inKeyData, out android.hardware.security.keymint.ByteArray outImportedKeyBlob, out android.hardware.security.keymint.KeyCharacteristics outImportedKeyCharacteristics, out android.hardware.security.keymint.Certificate[] outCertChain); - void importWrappedKey(in byte[] inWrappedKeyData, in byte[] inWrappingKeyBlob, in byte[] inMaskingKey, in android.hardware.security.keymint.KeyParameter[] inUnwrappingParams, in long inPasswordSid, in long inBiometricSid, out android.hardware.security.keymint.ByteArray outImportedKeyBlob, out android.hardware.security.keymint.KeyCharacteristics outImportedKeyCharacteristics); + android.hardware.security.keymint.KeyCreationResult generateKey(in android.hardware.security.keymint.KeyParameter[] keyParams); + android.hardware.security.keymint.KeyCreationResult importKey(in android.hardware.security.keymint.KeyParameter[] keyParams, in android.hardware.security.keymint.KeyFormat keyFormat, in byte[] keyData); + android.hardware.security.keymint.KeyCreationResult importWrappedKey(in byte[] wrappedKeyData, in byte[] wrappingKeyBlob, in byte[] maskingKey, in android.hardware.security.keymint.KeyParameter[] unwrappingParams, in long passwordSid, in long biometricSid); byte[] upgradeKey(in byte[] inKeyBlobToUpgrade, in android.hardware.security.keymint.KeyParameter[] inUpgradeParams); void deleteKey(in byte[] inKeyBlob); void deleteAllKeys(); diff --git a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/IKeyMintOperation.aidl b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/IKeyMintOperation.aidl index 8e3b0fcf9b..e6ab4c8940 100644 --- a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/IKeyMintOperation.aidl +++ b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/IKeyMintOperation.aidl @@ -2,13 +2,14 @@ // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // /////////////////////////////////////////////////////////////////////////////// -// This file is a snapshot of an AIDL interface (or parcelable). Do not try to -// edit this file. It looks like you are doing that because you have modified -// an AIDL interface in a backward-incompatible way, e.g., deleting a function -// from an interface or a field from a parcelable and it broke the build. That -// breakage is intended. +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. // -// You must not make a backward incompatible changes to the AIDL files built +// You must not make a backward incompatible change to any AIDL file built // with the aidl_interface module type with versions property set. The module // type is used to build AIDL files in a way that they can be used across // independently updatable components of the system. If a device is shipped @@ -18,7 +19,7 @@ package android.hardware.security.keymint; @VintfStability interface IKeyMintOperation { - int update(in @nullable android.hardware.security.keymint.KeyParameterArray inParams, in @nullable byte[] input, in @nullable android.hardware.security.keymint.HardwareAuthToken inAuthToken, in @nullable android.hardware.security.keymint.VerificationToken inVerificationToken, out @nullable android.hardware.security.keymint.KeyParameterArray outParams, out @nullable android.hardware.security.keymint.ByteArray output); - byte[] finish(in @nullable android.hardware.security.keymint.KeyParameterArray inParams, in @nullable byte[] input, in @nullable byte[] inSignature, in @nullable android.hardware.security.keymint.HardwareAuthToken authToken, in @nullable android.hardware.security.keymint.VerificationToken inVerificationToken, out @nullable android.hardware.security.keymint.KeyParameterArray outParams); + int update(in @nullable android.hardware.security.keymint.KeyParameterArray inParams, in @nullable byte[] input, in @nullable android.hardware.security.keymint.HardwareAuthToken inAuthToken, in @nullable android.hardware.security.secureclock.TimeStampToken inTimeStampToken, out @nullable android.hardware.security.keymint.KeyParameterArray outParams, out @nullable android.hardware.security.keymint.ByteArray output); + byte[] finish(in @nullable android.hardware.security.keymint.KeyParameterArray inParams, in @nullable byte[] input, in @nullable byte[] inSignature, in @nullable android.hardware.security.keymint.HardwareAuthToken authToken, in @nullable android.hardware.security.secureclock.TimeStampToken inTimeStampToken, out @nullable android.hardware.security.keymint.KeyParameterArray outParams); void abort(); } diff --git a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/KeyCharacteristics.aidl b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/KeyCharacteristics.aidl index fb4214cd43..49ea8af911 100644 --- a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/KeyCharacteristics.aidl +++ b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/KeyCharacteristics.aidl @@ -2,13 +2,14 @@ // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // /////////////////////////////////////////////////////////////////////////////// -// This file is a snapshot of an AIDL interface (or parcelable). Do not try to -// edit this file. It looks like you are doing that because you have modified -// an AIDL interface in a backward-incompatible way, e.g., deleting a function -// from an interface or a field from a parcelable and it broke the build. That -// breakage is intended. +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. // -// You must not make a backward incompatible changes to the AIDL files built +// You must not make a backward incompatible change to any AIDL file built // with the aidl_interface module type with versions property set. The module // type is used to build AIDL files in a way that they can be used across // independently updatable components of the system. If a device is shipped @@ -18,6 +19,6 @@ package android.hardware.security.keymint; @VintfStability parcelable KeyCharacteristics { - android.hardware.security.keymint.KeyParameter[] softwareEnforced; - android.hardware.security.keymint.KeyParameter[] hardwareEnforced; + android.hardware.security.keymint.SecurityLevel securityLevel; + android.hardware.security.keymint.KeyParameter[] authorizations; } diff --git a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/KeyCreationResult.aidl b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/KeyCreationResult.aidl new file mode 100644 index 0000000000..4b9ac79711 --- /dev/null +++ b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/KeyCreationResult.aidl @@ -0,0 +1,25 @@ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. +// +// You must not make a backward incompatible change to any AIDL file built +// with the aidl_interface module type with versions property set. The module +// type is used to build AIDL files in a way that they can be used across +// independently updatable components of the system. If a device is shipped +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.hardware.security.keymint; +@VintfStability +parcelable KeyCreationResult { + byte[] keyBlob; + android.hardware.security.keymint.KeyCharacteristics[] keyCharacteristics; + android.hardware.security.keymint.Certificate[] certificateChain; +} diff --git a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/KeyFormat.aidl b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/KeyFormat.aidl index f701c808a2..4eb5a7829f 100644 --- a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/KeyFormat.aidl +++ b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/KeyFormat.aidl @@ -2,13 +2,14 @@ // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // /////////////////////////////////////////////////////////////////////////////// -// This file is a snapshot of an AIDL interface (or parcelable). Do not try to -// edit this file. It looks like you are doing that because you have modified -// an AIDL interface in a backward-incompatible way, e.g., deleting a function -// from an interface or a field from a parcelable and it broke the build. That -// breakage is intended. +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. // -// You must not make a backward incompatible changes to the AIDL files built +// You must not make a backward incompatible change to any AIDL file built // with the aidl_interface module type with versions property set. The module // type is used to build AIDL files in a way that they can be used across // independently updatable components of the system. If a device is shipped diff --git a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/KeyMintHardwareInfo.aidl b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/KeyMintHardwareInfo.aidl index 5e9f7ae635..0390ec9fa4 100644 --- a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/KeyMintHardwareInfo.aidl +++ b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/KeyMintHardwareInfo.aidl @@ -2,13 +2,14 @@ // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // /////////////////////////////////////////////////////////////////////////////// -// This file is a snapshot of an AIDL interface (or parcelable). Do not try to -// edit this file. It looks like you are doing that because you have modified -// an AIDL interface in a backward-incompatible way, e.g., deleting a function -// from an interface or a field from a parcelable and it broke the build. That -// breakage is intended. +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. // -// You must not make a backward incompatible changes to the AIDL files built +// You must not make a backward incompatible change to any AIDL file built // with the aidl_interface module type with versions property set. The module // type is used to build AIDL files in a way that they can be used across // independently updatable components of the system. If a device is shipped diff --git a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/KeyOrigin.aidl b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/KeyOrigin.aidl index 9728bf92b7..e84cf74b1a 100644 --- a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/KeyOrigin.aidl +++ b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/KeyOrigin.aidl @@ -2,13 +2,14 @@ // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // /////////////////////////////////////////////////////////////////////////////// -// This file is a snapshot of an AIDL interface (or parcelable). Do not try to -// edit this file. It looks like you are doing that because you have modified -// an AIDL interface in a backward-incompatible way, e.g., deleting a function -// from an interface or a field from a parcelable and it broke the build. That -// breakage is intended. +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. // -// You must not make a backward incompatible changes to the AIDL files built +// You must not make a backward incompatible change to any AIDL file built // with the aidl_interface module type with versions property set. The module // type is used to build AIDL files in a way that they can be used across // independently updatable components of the system. If a device is shipped diff --git a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/KeyParameter.aidl b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/KeyParameter.aidl index 498576825c..6829a2b0f6 100644 --- a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/KeyParameter.aidl +++ b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/KeyParameter.aidl @@ -2,13 +2,14 @@ // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // /////////////////////////////////////////////////////////////////////////////// -// This file is a snapshot of an AIDL interface (or parcelable). Do not try to -// edit this file. It looks like you are doing that because you have modified -// an AIDL interface in a backward-incompatible way, e.g., deleting a function -// from an interface or a field from a parcelable and it broke the build. That -// breakage is intended. +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. // -// You must not make a backward incompatible changes to the AIDL files built +// You must not make a backward incompatible change to any AIDL file built // with the aidl_interface module type with versions property set. The module // type is used to build AIDL files in a way that they can be used across // independently updatable components of the system. If a device is shipped diff --git a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/KeyParameterArray.aidl b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/KeyParameterArray.aidl index 2c3b768756..882ca89c2e 100644 --- a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/KeyParameterArray.aidl +++ b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/KeyParameterArray.aidl @@ -2,13 +2,14 @@ // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // /////////////////////////////////////////////////////////////////////////////// -// This file is a snapshot of an AIDL interface (or parcelable). Do not try to -// edit this file. It looks like you are doing that because you have modified -// an AIDL interface in a backward-incompatible way, e.g., deleting a function -// from an interface or a field from a parcelable and it broke the build. That -// breakage is intended. +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. // -// You must not make a backward incompatible changes to the AIDL files built +// You must not make a backward incompatible change to any AIDL file built // with the aidl_interface module type with versions property set. The module // type is used to build AIDL files in a way that they can be used across // independently updatable components of the system. If a device is shipped diff --git a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/KeyParameterValue.aidl b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/KeyParameterValue.aidl index ecf20ad75b..6c11a924e7 100644 --- a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/KeyParameterValue.aidl +++ b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/KeyParameterValue.aidl @@ -2,13 +2,14 @@ // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // /////////////////////////////////////////////////////////////////////////////// -// This file is a snapshot of an AIDL interface (or parcelable). Do not try to -// edit this file. It looks like you are doing that because you have modified -// an AIDL interface in a backward-incompatible way, e.g., deleting a function -// from an interface or a field from a parcelable and it broke the build. That -// breakage is intended. +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. // -// You must not make a backward incompatible changes to the AIDL files built +// You must not make a backward incompatible change to any AIDL file built // with the aidl_interface module type with versions property set. The module // type is used to build AIDL files in a way that they can be used across // independently updatable components of the system. If a device is shipped diff --git a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/KeyPurpose.aidl b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/KeyPurpose.aidl index a6fd8c3737..ff8d85a31a 100644 --- a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/KeyPurpose.aidl +++ b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/KeyPurpose.aidl @@ -2,13 +2,14 @@ // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // /////////////////////////////////////////////////////////////////////////////// -// This file is a snapshot of an AIDL interface (or parcelable). Do not try to -// edit this file. It looks like you are doing that because you have modified -// an AIDL interface in a backward-incompatible way, e.g., deleting a function -// from an interface or a field from a parcelable and it broke the build. That -// breakage is intended. +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. // -// You must not make a backward incompatible changes to the AIDL files built +// You must not make a backward incompatible change to any AIDL file built // with the aidl_interface module type with versions property set. The module // type is used to build AIDL files in a way that they can be used across // independently updatable components of the system. If a device is shipped diff --git a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/PaddingMode.aidl b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/PaddingMode.aidl index 2ecfa1e8f6..6c61312314 100644 --- a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/PaddingMode.aidl +++ b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/PaddingMode.aidl @@ -2,13 +2,14 @@ // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // /////////////////////////////////////////////////////////////////////////////// -// This file is a snapshot of an AIDL interface (or parcelable). Do not try to -// edit this file. It looks like you are doing that because you have modified -// an AIDL interface in a backward-incompatible way, e.g., deleting a function -// from an interface or a field from a parcelable and it broke the build. That -// breakage is intended. +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. // -// You must not make a backward incompatible changes to the AIDL files built +// You must not make a backward incompatible change to any AIDL file built // with the aidl_interface module type with versions property set. The module // type is used to build AIDL files in a way that they can be used across // independently updatable components of the system. If a device is shipped diff --git a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/SecurityLevel.aidl b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/SecurityLevel.aidl index 601693f127..c4812ed67a 100644 --- a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/SecurityLevel.aidl +++ b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/SecurityLevel.aidl @@ -2,13 +2,14 @@ // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // /////////////////////////////////////////////////////////////////////////////// -// This file is a snapshot of an AIDL interface (or parcelable). Do not try to -// edit this file. It looks like you are doing that because you have modified -// an AIDL interface in a backward-incompatible way, e.g., deleting a function -// from an interface or a field from a parcelable and it broke the build. That -// breakage is intended. +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. // -// You must not make a backward incompatible changes to the AIDL files built +// You must not make a backward incompatible change to any AIDL file built // with the aidl_interface module type with versions property set. The module // type is used to build AIDL files in a way that they can be used across // independently updatable components of the system. If a device is shipped diff --git a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/Tag.aidl b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/Tag.aidl index 38eb6e693c..ce12fed082 100644 --- a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/Tag.aidl +++ b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/Tag.aidl @@ -2,13 +2,14 @@ // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // /////////////////////////////////////////////////////////////////////////////// -// This file is a snapshot of an AIDL interface (or parcelable). Do not try to -// edit this file. It looks like you are doing that because you have modified -// an AIDL interface in a backward-incompatible way, e.g., deleting a function -// from an interface or a field from a parcelable and it broke the build. That -// breakage is intended. +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. // -// You must not make a backward incompatible changes to the AIDL files built +// You must not make a backward incompatible change to any AIDL file built // with the aidl_interface module type with versions property set. The module // type is used to build AIDL files in a way that they can be used across // independently updatable components of the system. If a device is shipped @@ -30,6 +31,7 @@ enum Tag { EC_CURVE = 268435466, RSA_PUBLIC_EXPONENT = 1342177480, INCLUDE_UNIQUE_ID = 1879048394, + RSA_OAEP_MGF_DIGEST = 536871115, BLOB_USAGE_REQUIREMENTS = 268435757, BOOTLOADER_ONLY = 1879048494, ROLLBACK_RESISTANCE = 1879048495, diff --git a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/TagType.aidl b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/TagType.aidl index bb2766c6bf..41c8832a0b 100644 --- a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/TagType.aidl +++ b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/TagType.aidl @@ -2,13 +2,14 @@ // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // /////////////////////////////////////////////////////////////////////////////// -// This file is a snapshot of an AIDL interface (or parcelable). Do not try to -// edit this file. It looks like you are doing that because you have modified -// an AIDL interface in a backward-incompatible way, e.g., deleting a function -// from an interface or a field from a parcelable and it broke the build. That -// breakage is intended. +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. // -// You must not make a backward incompatible changes to the AIDL files built +// You must not make a backward incompatible change to any AIDL file built // with the aidl_interface module type with versions property set. The module // type is used to build AIDL files in a way that they can be used across // independently updatable components of the system. If a device is shipped diff --git a/security/keymint/aidl/android/hardware/security/keymint/Certificate.aidl b/security/keymint/aidl/android/hardware/security/keymint/Certificate.aidl index a9538590eb..0e5d898804 100644 --- a/security/keymint/aidl/android/hardware/security/keymint/Certificate.aidl +++ b/security/keymint/aidl/android/hardware/security/keymint/Certificate.aidl @@ -17,9 +17,8 @@ package android.hardware.security.keymint; /** - * This encodes the IKeyMintDevice attestation generated certificate. + * This encodes an IKeyMintDevice certificate, generated for a KeyMint asymmetric public key. */ - @VintfStability parcelable Certificate { /** diff --git a/security/keymint/aidl/android/hardware/security/keymint/ErrorCode.aidl b/security/keymint/aidl/android/hardware/security/keymint/ErrorCode.aidl index fb24ad1baa..b20601d4a1 100644 --- a/security/keymint/aidl/android/hardware/security/keymint/ErrorCode.aidl +++ b/security/keymint/aidl/android/hardware/security/keymint/ErrorCode.aidl @@ -99,6 +99,8 @@ enum ErrorCode { ATTESTATION_IDS_NOT_PROVISIONED = -75, INVALID_OPERATION = -76, STORAGE_KEY_UNSUPPORTED = -77, + INCOMPATIBLE_MGF_DIGEST = -78, + UNSUPPORTED_MGF_DIGEST = -79, UNIMPLEMENTED = -100, VERSION_MISMATCH = -101, diff --git a/security/keymint/aidl/android/hardware/security/keymint/HardwareAuthToken.aidl b/security/keymint/aidl/android/hardware/security/keymint/HardwareAuthToken.aidl index 12d615f86f..1067540d9c 100644 --- a/security/keymint/aidl/android/hardware/security/keymint/HardwareAuthToken.aidl +++ b/security/keymint/aidl/android/hardware/security/keymint/HardwareAuthToken.aidl @@ -16,7 +16,7 @@ package android.hardware.security.keymint; -import android.hardware.security.keymint.Timestamp; +import android.hardware.security.secureclock.Timestamp; import android.hardware.security.keymint.HardwareAuthenticatorType; /** diff --git a/security/keymint/aidl/android/hardware/security/keymint/IKeyMintDevice.aidl b/security/keymint/aidl/android/hardware/security/keymint/IKeyMintDevice.aidl index 4944acb1b1..d5f7a1ff33 100644 --- a/security/keymint/aidl/android/hardware/security/keymint/IKeyMintDevice.aidl +++ b/security/keymint/aidl/android/hardware/security/keymint/IKeyMintDevice.aidl @@ -18,16 +18,15 @@ package android.hardware.security.keymint; import android.hardware.security.keymint.BeginResult; import android.hardware.security.keymint.ByteArray; -import android.hardware.security.keymint.Certificate; import android.hardware.security.keymint.HardwareAuthToken; import android.hardware.security.keymint.IKeyMintOperation; -import android.hardware.security.keymint.KeyCharacteristics; +import android.hardware.security.keymint.KeyCreationResult; import android.hardware.security.keymint.KeyFormat; import android.hardware.security.keymint.KeyParameter; import android.hardware.security.keymint.KeyMintHardwareInfo; import android.hardware.security.keymint.KeyPurpose; import android.hardware.security.keymint.SecurityLevel; -import android.hardware.security.keymint.VerificationToken; +import android.hardware.security.secureclock.TimeStampToken; /** * KeyMint device definition. @@ -126,16 +125,22 @@ import android.hardware.security.keymint.VerificationToken; * attacker can use them at will (though they're more secure than keys which can be * exfiltrated). Therefore, IKeyMintDevice must enforce access controls. * - * Access controls are defined as an "authorization list" of tag/value pairs. Authorization tags - * are 32-bit integers from the Tag enum, and the values are a variety of types, defined in the - * TagType enum. Some tags may be repeated to specify multiple values. Whether a tag may be - * repeated is specified in the documentation for the tag and in the TagType. When a key is - * created or imported, the caller specifies an authorization list. The IKeyMintDevice must divide - * the caller-provided authorizations into two lists, those it enforces in tee secure zone and - * those enforced in the strongBox hardware. These two lists are returned as the "teeEnforced" - * and "strongboxEnforced" elements of the KeyCharacteristics struct. Note that software enforced - * authorization list entries are not returned because they are not enforced by keymint. The - * IKeyMintDevice must also add the following authorizations to the appropriate list: + * Access controls are defined as "authorization lists" of tag/value pairs. Authorization tags are + * 32-bit integers from the Tag enum, and the values are a variety of types, defined in the TagType + * enum. Some tags may be repeated to specify multiple values. Whether a tag may be repeated is + * specified in the documentation for the tag and in the TagType. When a key is created or + * imported, the caller specifies a `key_description` authorization list. The IKeyMintDevice must + * determine which tags it can and cannot enforce, and at what SecurityLevel, and return an array of + * `KeyCharacteristics` structures that contains everything it will enforce, associated with the + * appropriate security level, which is one of SOFTWARE, TRUSTED_ENVIRONMENT and STRONGBOX. + * Typically, implementations will only return a single KeyCharacteristics structure, because + * everything they enforce is enforced at the same security level. There may be cases, however, for + * which multiple security levels are relevant. One example is that of a StrongBox IKeyMintDevice + * that relies on a TEE to enforce biometric user authentication. In that case, the generate/import + * methods must return two KeyCharacteristics structs, one with SecurityLevel::TRUSTED_ENVIRONMENT + * and the biometric authentication-related tags, and another with SecurityLevel::STRONGBOX and + * everything else. The IKeyMintDevice must also add the following authorizations to the + * appropriate list: * * o Tag::OS_VERSION * o Tag::OS_PATCHLEVEL @@ -148,26 +153,27 @@ import android.hardware.security.keymint.VerificationToken; * The caller must always provide the current date time in the keyParameter CREATION_DATETIME * tags. * - * All authorization tags and their values, both teeEnforced and strongboxEnforced, including - * unknown tags, must be cryptographically bound to the private/secret key material such that any - * modification of the portion of the key blob that contains the authorization list makes it - * impossible for the secure environment to obtain the private/secret key material. The - * recommended approach to meet this requirement is to use the full set of authorization tags - * associated with a key as input to a secure key derivation function used to derive a key that - * is used to encrypt the private/secret key material. - * - * IKeyMintDevice implementations ignore any tags they cannot enforce and do not return them - * in KeyCharacteristics. For example, Tag::ORIGINATION_EXPIRE_DATETIME provides the date and - * time after which a key may not be used to encrypt or sign new messages. Unless the - * IKeyMintDevice has access to a secure source of current date/time information, it is not - * possible for the IKeyMintDevice to enforce this tag. An IKeyMintDevice implementation will - * not rely on the non-secure world's notion of time, because it could be controlled by an - * attacker. Similarly, it cannot rely on GPSr time, even if it has exclusive control of the - * GPSr, because that might be spoofed by attacker RF signals. - * - * IKeyMintDevices do not use or enforce any tags they place in the softwareEnforced - * list. The IKeyMintDevice caller must enforce them, and it is unnecessary to enforce them - * twice. + * All authorization tags and their values enforced by an IKeyMintDevice must be cryptographically + * bound to the private/secret key material such that any modification of the portion of the key + * blob that contains the authorization list makes it impossible for the secure environment to + * obtain the private/secret key material. The recommended approach to meet this requirement is to + * use the full set of authorization tags associated with a key as input to a secure key derivation + * function used to derive a key (the KEK) that is used to encrypt the private/secret key material. + * Note that it is NOT acceptable to use a static KEK to encrypt the private/secret key material + * with an AEAD cipher mode, using the enforced authorization tags as AAD. This is because + * Tag::APPLICATION_DATA must not be included in the authorization tags stored in the key blob, but + * must be provided by the caller for every use. Assuming the Tag::APPLICATION_DATA value has + * sufficient entropy, this provides a cryptographic guarantee that an attacker cannot use a key + * without knowing the Tag::APPLICATION_DATA value, even if they compromise the IKeyMintDevice. + * + * IKeyMintDevice implementations must ignore any tags they cannot enforce and must not return them + * in KeyCharacteristics. For example, Tag::ORIGINATION_EXPIRE_DATETIME provides the date and time + * after which a key may not be used to encrypt or sign new messages. Unless the IKeyMintDevice has + * access to a secure source of current date/time information, it is not possible for the + * IKeyMintDevice to enforce this tag. An IKeyMintDevice implementation will not rely on the + * non-secure world's notion of time, because it could be controlled by an attacker. Similarly, it + * cannot rely on GPSr time, even if it has exclusive control of the GPSr, because that might be + * spoofed by attacker RF signals. * * Some tags must be enforced by the IKeyMintDevice. See the detailed documentation on each Tag * in Tag.aidl. @@ -217,34 +223,6 @@ interface IKeyMintDevice { KeyMintHardwareInfo getHardwareInfo(); /** - * Verify authorizations for another IKeyMintDevice instance. - * - * On systems with both a StrongBox and a TEE IKeyMintDevice instance it is sometimes useful - * to ask the TEE KeyMintDevice to verify authorizations for a key hosted in StrongBox. - * - * For every StrongBox operation, Keystore is required to call this method on the TEE KeyMint, - * passing in the StrongBox key's hardwareEnforced authorization list and the challenge - * returned by StrongBox begin(). Keystore must then pass the VerificationToken to the - * subsequent invocations of StrongBox update() and finish(). - * - * StrongBox implementations must return ErrorCode::UNIMPLEMENTED. - * - * @param the challenge returned by StrongBox's keyMint's begin(). - * - * @param authToken A HardwareAuthToken if needed to authorize key usage. - * - * @return error ErrorCode::OK on success or ErrorCode::UNIMPLEMENTED if the KeyMintDevice is - * a StrongBox. If the IKeyMintDevice cannot verify one or more elements of - * parametersToVerify it must not return an error code, but just omit the unverified - * parameter from the VerificationToken. - * - * @return token the verification token. See VerificationToken in VerificationToken.aidl for - * details. - */ - VerificationToken verifyAuthorization(in long challenge, - in HardwareAuthToken token); - - /** * Adds entropy to the RNG used by KeyMint. Entropy added through this method must not be the * only source of entropy used, and a secure mixing function must be used to mix the entropy * provided by this method with internally-generated entropy. The mixing function must be @@ -337,38 +315,9 @@ interface IKeyMintDevice { * provided in params. See above for detailed specifications of which tags are required * for which types of keys. * - * @return generatedKeyBlob Opaque descriptor of the generated key. The recommended - * implementation strategy is to include an encrypted copy of the key material, wrapped - * in a key unavailable outside secure hardware. - * - * @return generatedKeyCharacteristics Description of the generated key, divided into two sets: - * hardware-enforced and software-enforced. The description here applies equally - * to the key characteristics lists returned by generateKey, importKey and - * importWrappedKey. The characteristics returned by this parameter completely - * describe the type and usage of the specified key. - * - * The rule that IKeyMintDevice implementations must use for deciding whether a - * given tag belongs in the hardware-enforced or software-enforced list is that if - * the meaning of the tag is fully assured by secure hardware, it is hardware - * enforced. Otherwise, it's software enforced. - * - * @return outCertChain If the key is an asymmetric key, and proper keyparameters for - * attestation (such as challenge) is provided, then this parameter will return the - * attestation certificate. If the signing of the attestation certificate is from a - * factory key, additional certificates back to the root attestation certificate will - * also be provided. Clients will need to check root certificate against a known-good - * value. The certificates must be DER-encoded. Caller needs to provide - * CREATION_DATETIME as one of the attestation parameters, otherwise the attestation - * certificate will not contain the creation datetime. The first certificate in the - * vector is the attestation for the generated key itself, the next certificate is - * the key that signs the first certificate, and so forth. The last certificate in - * the chain is the root certificate. If the key is a symmetric key, then no - * certificate will be returned and this variable will return empty. TODO: change - * certificate return to a single certificate and make it nullable b/163604282. + * @return The result of key creation. See KeyCreationResult.aidl. */ - void generateKey(in KeyParameter[] keyParams, out ByteArray generatedKeyBlob, - out KeyCharacteristics generatedKeyCharacteristics, - out Certificate[] outCertChain); + KeyCreationResult generateKey(in KeyParameter[] keyParams); /** * Imports key material into an IKeyMintDevice. Key definition parameters and return values @@ -396,29 +345,10 @@ interface IKeyMintDevice { * * @param inKeyData The key material to import, in the format specified in keyFormat. * - * @return outImportedKeyBlob descriptor of the imported key. The format of the keyblob will - * be the google specified keyblob format. - * - * @return outImportedKeyCharacteristics Description of the generated key. See the - * keyCharacteristics description in generateKey. - * - * @return outCertChain If the key is an asymmetric key, and proper keyparameters for - * attestation (such as challenge) is provided, then this parameter will return the - * attestation certificate. If the signing of the attestation certificate is from a - * factory key, additional certificates back to the root attestation certificate will - * also be provided. Clients will need to check root certificate against a known-good - * value. The certificates must be DER-encoded. Caller needs to provide - * CREATION_DATETIME as one of the attestation parameters, otherwise the attestation - * certificate will not contain the creation datetime. The first certificate in the - * vector is the attestation for the generated key itself, the next certificate is - * the key that signs the first certificate, and so forth. The last certificate in - * the chain is the root certificate. If the key is a symmetric key, then no - * certificate will be returned and this variable will return empty. + * @return The result of key creation. See KeyCreationResult.aidl. */ - void importKey(in KeyParameter[] inKeyParams, in KeyFormat inKeyFormat, - in byte[] inKeyData, out ByteArray outImportedKeyBlob, - out KeyCharacteristics outImportedKeyCharacteristics, - out Certificate[] outCertChain); + KeyCreationResult importKey(in KeyParameter[] keyParams, in KeyFormat keyFormat, + in byte[] keyData); /** * Securely imports a key, or key pair, returning a key blob and a description of the imported @@ -474,45 +404,38 @@ interface IKeyMintDevice { * 5. Perform the equivalent of calling importKey(keyParams, keyFormat, keyData), except * that the origin tag should be set to SECURELY_IMPORTED. * - * @param inWrappingKeyBlob The opaque key descriptor returned by generateKey() or importKey(). + * @param wrappingKeyBlob The opaque key descriptor returned by generateKey() or importKey(). * This key must have been created with Purpose::WRAP_KEY. * - * @param inMaskingKey The 32-byte value XOR'd with the transport key in the SecureWrappedKey + * @param maskingKey The 32-byte value XOR'd with the transport key in the SecureWrappedKey * structure. * - * @param inUnwrappingParams must contain any parameters needed to perform the unwrapping - * operation. For example, if the wrapping key is an AES key the block and padding - * modes must be specified in this argument. + * @param unwrappingParams must contain any parameters needed to perform the unwrapping + * operation. For example, if the wrapping key is an AES key the block and padding modes + * must be specified in this argument. * - * @param inPasswordSid specifies the password secure ID (SID) of the user that owns the key - * being installed. If the authorization list in wrappedKeyData contains a - * Tag::USER_SECURE_IDwith a value that has the HardwareAuthenticatorType::PASSWORD - * bit set, the constructed key must be bound to the SID value provided by this - * argument. If the wrappedKeyData does not contain such a tag and value, this argument - * must be ignored. + * @param passwordSid specifies the password secure ID (SID) of the user that owns the key being + * installed. If the authorization list in wrappedKeyData contains a + * Tag::USER_SECURE_IDwith a value that has the HardwareAuthenticatorType::PASSWORD bit + * set, the constructed key must be bound to the SID value provided by this argument. If + * the wrappedKeyData does not contain such a tag and value, this argument must be + * ignored. * - * @param inBiometricSid specifies the biometric secure ID (SID) of the user that owns the key + * @param biometricSid specifies the biometric secure ID (SID) of the user that owns the key * being installed. If the authorization list in wrappedKeyData contains a * Tag::USER_SECURE_ID with a value that has the HardwareAuthenticatorType::FINGERPRINT * bit set, the constructed key must be bound to the SID value provided by this argument. * If the wrappedKeyData does not contain such a tag and value, this argument must be * ignored. * - * @return outImportedKeyBlob Opaque descriptor of the imported key. It is recommended that - * the keyBlob contain a copy of the key material, wrapped in a key unavailable outside - * secure hardware. - * - * @return outImportedKeyCharacteristics Description of the generated key. See the description - * of keyCharacteristics parameter in generateKey. + * @return The result of key creation. See KeyCreationResult.aidl. */ - void importWrappedKey(in byte[] inWrappedKeyData, - in byte[] inWrappingKeyBlob, - in byte[] inMaskingKey, - in KeyParameter[] inUnwrappingParams, - in long inPasswordSid, - in long inBiometricSid, - out ByteArray outImportedKeyBlob, - out KeyCharacteristics outImportedKeyCharacteristics); + KeyCreationResult importWrappedKey(in byte[] wrappedKeyData, + in byte[] wrappingKeyBlob, + in byte[] maskingKey, + in KeyParameter[] unwrappingParams, + in long passwordSid, + in long biometricSid); /** * Upgrades an old key blob. Keys can become "old" in two ways: IKeyMintDevice can be diff --git a/security/keymint/aidl/android/hardware/security/keymint/IKeyMintOperation.aidl b/security/keymint/aidl/android/hardware/security/keymint/IKeyMintOperation.aidl index 24960ccea0..8c49602413 100644 --- a/security/keymint/aidl/android/hardware/security/keymint/IKeyMintOperation.aidl +++ b/security/keymint/aidl/android/hardware/security/keymint/IKeyMintOperation.aidl @@ -20,7 +20,7 @@ import android.hardware.security.keymint.ByteArray; import android.hardware.security.keymint.HardwareAuthToken; import android.hardware.security.keymint.KeyParameter; import android.hardware.security.keymint.KeyParameterArray; -import android.hardware.security.keymint.VerificationToken; +import android.hardware.security.secureclock.TimeStampToken; @VintfStability interface IKeyMintOperation { @@ -119,10 +119,9 @@ interface IKeyMintOperation { * @param input Data to be processed. Note that update() may or may not consume all of the data * provided. See return value. * - * @param verificationToken Verification token, used to prove that another IKeymasterDevice HAL - * has verified some parameters, and to deliver the other HAL's current timestamp, if - * needed. If not provided, all fields must be initialized to zero and vectors must be - * empty. + * @param inTimeStampToken timestamp token, certifies the freshness of an auth token in case + * the security domain of this KeyMint instance has a different clock than the + * authenticator issuing the auth token. * * @return error Returns ErrorCode encountered in keymint as service specific errors. See the * ErrorCode enum in ErrorCode.aidl. @@ -141,7 +140,7 @@ interface IKeyMintOperation { int update(in @nullable KeyParameterArray inParams, in @nullable byte[] input, in @nullable HardwareAuthToken inAuthToken, - in @nullable VerificationToken inVerificationToken, + in @nullable TimeStampToken inTimeStampToken, out @nullable KeyParameterArray outParams, out @nullable ByteArray output); @@ -241,9 +240,9 @@ interface IKeyMintOperation { * * @param authToken Authentication token. Can be nullable if not provided. * - * @param verificationToken Verification token, used to prove that another IKeyMintDevice HAL - * has verified some parameters, and to deliver the other HAL's current timestamp, if - * needed. Can be nullable if not needed. + * @param inTimeStampToken timestamp token, certifies the freshness of an auth token in case + * the security domain of this KeyMint instance has a different clock than the + * authenticator issuing the auth token. * * @return outParams Any output parameters generated by finish(). * @@ -252,7 +251,7 @@ interface IKeyMintOperation { byte[] finish(in @nullable KeyParameterArray inParams, in @nullable byte[] input, in @nullable byte[] inSignature, in @nullable HardwareAuthToken authToken, - in @nullable VerificationToken inVerificationToken, + in @nullable TimeStampToken inTimeStampToken, out @nullable KeyParameterArray outParams); /** diff --git a/security/keymint/aidl/android/hardware/security/keymint/KeyCharacteristics.aidl b/security/keymint/aidl/android/hardware/security/keymint/KeyCharacteristics.aidl index 0801868825..edd4d8f841 100644 --- a/security/keymint/aidl/android/hardware/security/keymint/KeyCharacteristics.aidl +++ b/security/keymint/aidl/android/hardware/security/keymint/KeyCharacteristics.aidl @@ -17,25 +17,20 @@ package android.hardware.security.keymint; import android.hardware.security.keymint.KeyParameter; +import android.hardware.security.keymint.SecurityLevel; /** - * KeyCharacteristics defines the attributes of a key, including cryptographic parameters, and usage - * restrictions. It consits of two vectors of KeyParameters, one for "softwareEnforced" attributes - * and one for "hardwareEnforced" attributes. + * KeyCharacteristics defines the attributes of a key that are enforced by KeyMint, and the security + * level (see SecurityLevel.aidl) of that enforcement. * - * KeyCharacteristics objects are returned by generateKey, importKey, importWrappedKey and - * getKeyCharacteristics. The IKeyMintDevice secure environment is responsible for allocating the - * parameters, all of which are Tags with associated values, to the correct vector. The - * hardwareEnforced vector must contain only those attributes which are enforced by secure hardware. - * All others should be in the softwareEnforced vector. See the definitions of individual Tag enums - * for specification of which must be hardware-enforced, which may be software-enforced and which - * must never appear in KeyCharacteristics. + * The `generateKey` `importKey` and `importWrappedKey` methods each return an array of + * KeyCharacteristics, specifying the security levels of enforcement and the authorizations + * enforced. Note that enforcement at a given security level means that the semantics of the tag + * and value are fully enforced. See the definition of individual tags for specifications of what + * must be enforced. */ @VintfStability parcelable KeyCharacteristics { - /* TODO(seleneh) get rid of the software enforced in keymint. replace hardware enforced with - * tee enforced and strongbox enforced. - */ - KeyParameter[] softwareEnforced; - KeyParameter[] hardwareEnforced; + SecurityLevel securityLevel; + KeyParameter[] authorizations; } diff --git a/security/keymint/aidl/android/hardware/security/keymint/KeyCreationResult.aidl b/security/keymint/aidl/android/hardware/security/keymint/KeyCreationResult.aidl new file mode 100644 index 0000000000..b149ac9975 --- /dev/null +++ b/security/keymint/aidl/android/hardware/security/keymint/KeyCreationResult.aidl @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * 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. + */ + +package android.hardware.security.keymint; + +import android.hardware.security.keymint.Certificate; +import android.hardware.security.keymint.KeyCharacteristics; + +/** + * This structure is returned when a new key is created with generateKey(), importKey() or + * importWrappedKey(). + */ +@VintfStability +parcelable KeyCreationResult { + /** + * `keyBlob` is an descriptor of the generated/imported key key. + */ + byte[] keyBlob; + + /** + * `keyCharacteristics` is a description of the generated key in the form of authorization lists + * associated with security levels. The rules that IKeyMintDevice implementations must use for + * deciding whether a given tag from `keyParams` argument to the generation/import method should + * be returned in `keyCharacteristics` are: + * + * - If the IKeyMintDevice cannot fully enforce the semantics of the tag, it should be omitted. + * - If the semantics of the tag are fully enforced by the IKeyMintDevice, without any + * assistance from components running at other security levels, it should be included in an + * entry with the SecurityLevel of the IKeyMintDevice. + * - If the semantics of the tag are fully enforced, but with the assistance of components + * running at another SecurityLevel, it should be included in an entry with the minimum + * SecurityLevel of the involved components. For example if a StrongBox IKeyMintDevice relies + * on a TEE to validate biometric authentication, biometric authentication tags go in an entry + * with SecurityLevel::TRUSTED_ENVIRONMENT. + */ + KeyCharacteristics[] keyCharacteristics; + + /** + * If the generated/imported key is an asymmetric key, `certificateChain` will contain a chain + * of one or more certificates. If the key parameters provided to the generate/import method + * contains Tag::ATTESTATION_CHALLENGE the first certificate will contain an attestation + * extension, and will be signed by a factory-installed attestation key and followed by a chain + * of certificates leading to an authoritative root. If there is no attestation challenge, only + * one certificate will be returned, and it will be self-signed or contain a fake signature, + * depending on whether the key has KeyPurpose::SIGN. If the generated key is symmetric, + * certificateChain will be empty. + */ + Certificate[] certificateChain; +} diff --git a/security/keymint/aidl/android/hardware/security/keymint/Tag.aidl b/security/keymint/aidl/android/hardware/security/keymint/Tag.aidl index 3bc3f16d89..f92bf008ed 100644 --- a/security/keymint/aidl/android/hardware/security/keymint/Tag.aidl +++ b/security/keymint/aidl/android/hardware/security/keymint/Tag.aidl @@ -187,6 +187,22 @@ enum Tag { */ INCLUDE_UNIQUE_ID = (7 << 28) /* TagType:BOOL */ | 202, + /** + * Tag::RSA_OAEP_MGF_DIGEST specifies the MGF1 digest algorithms that may be used with + * RSA encryption/decryption with OAEP padding. If the key characteristics supports OAEP + * and this tag is absent then SHA1 digest is selected by default for MGF1. + * + * This tag is repeatable for key generation/import. If this tag is present in the key + * characteristics with one or more values from @4.0::Digest, then for RSA cipher + * operations with OAEP Padding, the caller must specify a digest in the additionalParams + * argument of begin operation. If this tag is missing or the specified digest is not in + * the digests associated with the key then begin operation must fail with + * ErrorCode::INCOMPATIBLE_MGF_DIGEST. + * + * Must be hardware-enforced. + */ + RSA_OAEP_MGF_DIGEST = (2 << 28) /* TagType:ENUM_REP */ | 203, + /** * TODO(seleneh) this tag needs to be deleted from all codes. * @@ -496,10 +512,10 @@ enum Tag { /** * Tag::APPLICATION_DATA. When provided to generateKey or importKey, this tag specifies data - * that is necessary during all uses of the key. In particular, calls to exportKey() and - * getKeyCharacteristics() must provide the same value to the appData parameter, and calls to - * begin must provide this tag and the same associated data as part of the inParams set. If - * the correct data is not provided, the method must return ErrorCode::INVALID_KEY_BLOB. + * that is necessary during all uses of the key. In particular, calls to begin() and + * exportKey() must provide the same value to the appData parameter, and calls to begin must + * provide this tag and the same associated data as part of the inParams set. If the correct + * data is not provided, the method must return ErrorCode::INVALID_KEY_BLOB. * * The content of this tag msut be bound to the key cryptographically, meaning it must not be * possible for an adversary who has access to all of the secure world secrets but does not have diff --git a/security/keymint/aidl/vts/functional/Android.bp b/security/keymint/aidl/vts/functional/Android.bp index c7cc3807b9..17a461366c 100644 --- a/security/keymint/aidl/vts/functional/Android.bp +++ b/security/keymint/aidl/vts/functional/Android.bp @@ -22,7 +22,6 @@ cc_test { ], srcs: [ "KeyMintTest.cpp", - "VerificationTokenTest.cpp", ], shared_libs: [ "libbinder_ndk", @@ -32,6 +31,7 @@ cc_test { ], static_libs: [ "android.hardware.security.keymint-unstable-ndk_platform", + "android.hardware.security.secureclock-unstable-ndk_platform", "libcppbor_external", "libkeymint_vts_test_utils", ], @@ -61,6 +61,7 @@ cc_test_library { ], static_libs: [ "android.hardware.security.keymint-unstable-ndk_platform", + "android.hardware.security.secureclock-unstable-ndk_platform", "libcppbor", ], } diff --git a/security/keymint/aidl/vts/functional/KeyMintAidlTestBase.cpp b/security/keymint/aidl/vts/functional/KeyMintAidlTestBase.cpp index 94bc199ca7..766c02dea9 100644 --- a/security/keymint/aidl/vts/functional/KeyMintAidlTestBase.cpp +++ b/security/keymint/aidl/vts/functional/KeyMintAidlTestBase.cpp @@ -17,6 +17,7 @@ #include "KeyMintAidlTestBase.h" #include <chrono> +#include <unordered_set> #include <vector> #include <android-base/logging.h> @@ -36,13 +37,41 @@ using std::optional; os << "(Empty)" << ::std::endl; else { os << "\n"; - for (size_t i = 0; i < set.size(); ++i) os << set[i] << ::std::endl; + for (auto& entry : set) os << entry << ::std::endl; } return os; } namespace test { +namespace { + +// Predicate for testing basic characteristics validity in generation or import. +bool KeyCharacteristicsBasicallyValid(SecurityLevel secLevel, + const vector<KeyCharacteristics>& key_characteristics) { + if (key_characteristics.empty()) return false; + + std::unordered_set<SecurityLevel> levels_seen; + for (auto& entry : key_characteristics) { + if (entry.authorizations.empty()) return false; + + if (levels_seen.find(entry.securityLevel) != levels_seen.end()) return false; + levels_seen.insert(entry.securityLevel); + + // Generally, we should only have one entry, at the same security level as the KM + // instance. There is an exception: StrongBox KM can have some authorizations that are + // enforced by the TEE. + bool isExpectedSecurityLevel = secLevel == entry.securityLevel || + (secLevel == SecurityLevel::STRONGBOX && + entry.securityLevel == SecurityLevel::TRUSTED_ENVIRONMENT); + + if (!isExpectedSecurityLevel) return false; + } + return true; +} + +} // namespace + ErrorCode KeyMintAidlTestBase::GetReturnErrorCode(const Status& result) { if (result.isOk()) return ErrorCode::OK; @@ -78,35 +107,41 @@ void KeyMintAidlTestBase::SetUp() { } ErrorCode KeyMintAidlTestBase::GenerateKey(const AuthorizationSet& key_desc, - vector<uint8_t>* keyBlob, KeyCharacteristics* keyChar) { - EXPECT_NE(keyBlob, nullptr) << "Key blob pointer must not be null. Test bug"; - EXPECT_NE(keyChar, nullptr) + vector<uint8_t>* key_blob, + vector<KeyCharacteristics>* key_characteristics) { + EXPECT_NE(key_blob, nullptr) << "Key blob pointer must not be null. Test bug"; + EXPECT_NE(key_characteristics, nullptr) << "Previous characteristics not deleted before generating key. Test bug."; // Aidl does not clear these output parameters if the function returns // error. This is different from hal where output parameter is always // cleared due to hal returning void. So now we need to do our own clearing // of the output variables prior to calling keyMint aidl libraries. - keyBlob->clear(); - keyChar->softwareEnforced.clear(); - keyChar->hardwareEnforced.clear(); - certChain_.clear(); - - Status result; - ByteArray blob; + key_blob->clear(); + key_characteristics->clear(); + cert_chain_.clear(); - result = keymint_->generateKey(key_desc.vector_data(), &blob, keyChar, &certChain_); + KeyCreationResult creationResult; + Status result = keymint_->generateKey(key_desc.vector_data(), &creationResult); - // On result, blob & characteristics should be empty. if (result.isOk()) { - if (SecLevel() != SecurityLevel::SOFTWARE) { - EXPECT_GT(keyChar->hardwareEnforced.size(), 0); + EXPECT_PRED2(KeyCharacteristicsBasicallyValid, SecLevel(), + creationResult.keyCharacteristics); + EXPECT_GT(creationResult.keyBlob.size(), 0); + *key_blob = std::move(creationResult.keyBlob); + *key_characteristics = std::move(creationResult.keyCharacteristics); + cert_chain_ = std::move(creationResult.certificateChain); + + auto algorithm = key_desc.GetTagValue(TAG_ALGORITHM); + EXPECT_TRUE(algorithm); + if (algorithm && + (algorithm.value() == Algorithm::RSA || algorithm.value() == Algorithm::EC)) { + EXPECT_GE(cert_chain_.size(), 1); + if (key_desc.Contains(TAG_ATTESTATION_CHALLENGE)) EXPECT_GT(cert_chain_.size(), 1); + } else { + // For symmetric keys there should be no certificates. + EXPECT_EQ(cert_chain_.size(), 0); } - EXPECT_GT(keyChar->softwareEnforced.size(), 0); - // TODO(seleneh) in a later version where we return @nullable - // single Certificate, check non-null single certificate is always - // non-empty. - *keyBlob = blob.data; } return GetReturnErrorCode(result); @@ -118,25 +153,37 @@ ErrorCode KeyMintAidlTestBase::GenerateKey(const AuthorizationSet& key_desc) { ErrorCode KeyMintAidlTestBase::ImportKey(const AuthorizationSet& key_desc, KeyFormat format, const string& key_material, vector<uint8_t>* key_blob, - KeyCharacteristics* key_characteristics) { + vector<KeyCharacteristics>* key_characteristics) { Status result; - certChain_.clear(); - key_characteristics->softwareEnforced.clear(); - key_characteristics->hardwareEnforced.clear(); + cert_chain_.clear(); + key_characteristics->clear(); key_blob->clear(); - ByteArray blob; + KeyCreationResult creationResult; result = keymint_->importKey(key_desc.vector_data(), format, - vector<uint8_t>(key_material.begin(), key_material.end()), &blob, - key_characteristics, &certChain_); + vector<uint8_t>(key_material.begin(), key_material.end()), + &creationResult); if (result.isOk()) { - if (SecLevel() != SecurityLevel::SOFTWARE) { - EXPECT_GT(key_characteristics->hardwareEnforced.size(), 0); + EXPECT_PRED2(KeyCharacteristicsBasicallyValid, SecLevel(), + creationResult.keyCharacteristics); + EXPECT_GT(creationResult.keyBlob.size(), 0); + + *key_blob = std::move(creationResult.keyBlob); + *key_characteristics = std::move(creationResult.keyCharacteristics); + cert_chain_ = std::move(creationResult.certificateChain); + + auto algorithm = key_desc.GetTagValue(TAG_ALGORITHM); + EXPECT_TRUE(algorithm); + if (algorithm && + (algorithm.value() == Algorithm::RSA || algorithm.value() == Algorithm::EC)) { + EXPECT_GE(cert_chain_.size(), 1); + if (key_desc.Contains(TAG_ATTESTATION_CHALLENGE)) EXPECT_GT(cert_chain_.size(), 1); + } else { + // For symmetric keys there should be no certificates. + EXPECT_EQ(cert_chain_.size(), 0); } - EXPECT_GT(key_characteristics->softwareEnforced.size(), 0); - *key_blob = blob.data; } return GetReturnErrorCode(result); @@ -151,25 +198,39 @@ ErrorCode KeyMintAidlTestBase::ImportWrappedKey(string wrapped_key, string wrapp const AuthorizationSet& wrapping_key_desc, string masking_key, const AuthorizationSet& unwrapping_params) { - Status result; EXPECT_EQ(ErrorCode::OK, ImportKey(wrapping_key_desc, KeyFormat::PKCS8, wrapping_key)); - ByteArray outBlob; - key_characteristics_.softwareEnforced.clear(); - key_characteristics_.hardwareEnforced.clear(); + key_characteristics_.clear(); - result = keymint_->importWrappedKey(vector<uint8_t>(wrapped_key.begin(), wrapped_key.end()), - key_blob_, - vector<uint8_t>(masking_key.begin(), masking_key.end()), - unwrapping_params.vector_data(), 0 /* passwordSid */, - 0 /* biometricSid */, &outBlob, &key_characteristics_); + KeyCreationResult creationResult; + Status result = keymint_->importWrappedKey( + vector<uint8_t>(wrapped_key.begin(), wrapped_key.end()), key_blob_, + vector<uint8_t>(masking_key.begin(), masking_key.end()), + unwrapping_params.vector_data(), 0 /* passwordSid */, 0 /* biometricSid */, + &creationResult); if (result.isOk()) { - key_blob_ = outBlob.data; - if (SecLevel() != SecurityLevel::SOFTWARE) { - EXPECT_GT(key_characteristics_.hardwareEnforced.size(), 0); + EXPECT_PRED2(KeyCharacteristicsBasicallyValid, SecLevel(), + creationResult.keyCharacteristics); + EXPECT_GT(creationResult.keyBlob.size(), 0); + + key_blob_ = std::move(creationResult.keyBlob); + key_characteristics_ = std::move(creationResult.keyCharacteristics); + cert_chain_ = std::move(creationResult.certificateChain); + + AuthorizationSet allAuths; + for (auto& entry : key_characteristics_) { + allAuths.push_back(AuthorizationSet(entry.authorizations)); + } + auto algorithm = allAuths.GetTagValue(TAG_ALGORITHM); + EXPECT_TRUE(algorithm); + if (algorithm && + (algorithm.value() == Algorithm::RSA || algorithm.value() == Algorithm::EC)) { + EXPECT_GE(cert_chain_.size(), 1); + } else { + // For symmetric keys there should be no certificates. + EXPECT_EQ(cert_chain_.size(), 0); } - EXPECT_GT(key_characteristics_.softwareEnforced.size(), 0); } return GetReturnErrorCode(result); @@ -754,6 +815,33 @@ vector<Digest> KeyMintAidlTestBase::ValidDigests(bool withNone, bool withMD5) { return {}; } +static const vector<KeyParameter> kEmptyAuthList{}; + +const vector<KeyParameter>& KeyMintAidlTestBase::SecLevelAuthorizations( + const vector<KeyCharacteristics>& key_characteristics) { + auto found = std::find_if(key_characteristics.begin(), key_characteristics.end(), + [this](auto& entry) { return entry.securityLevel == SecLevel(); }); + return (found == key_characteristics.end()) ? kEmptyAuthList : found->authorizations; +} + +const vector<KeyParameter>& KeyMintAidlTestBase::HwEnforcedAuthorizations( + const vector<KeyCharacteristics>& key_characteristics) { + auto found = + std::find_if(key_characteristics.begin(), key_characteristics.end(), [](auto& entry) { + return entry.securityLevel == SecurityLevel::STRONGBOX || + entry.securityLevel == SecurityLevel::TRUSTED_ENVIRONMENT; + }); + return (found == key_characteristics.end()) ? kEmptyAuthList : found->authorizations; +} + +const vector<KeyParameter>& KeyMintAidlTestBase::SwEnforcedAuthorizations( + const vector<KeyCharacteristics>& key_characteristics) { + auto found = std::find_if( + key_characteristics.begin(), key_characteristics.end(), + [](auto& entry) { return entry.securityLevel == SecurityLevel::SOFTWARE; }); + return (found == key_characteristics.end()) ? kEmptyAuthList : found->authorizations; +} + } // namespace test } // namespace aidl::android::hardware::security::keymint diff --git a/security/keymint/aidl/vts/functional/KeyMintAidlTestBase.h b/security/keymint/aidl/vts/functional/KeyMintAidlTestBase.h index f73c26d9e1..c1a1dd9034 100644 --- a/security/keymint/aidl/vts/functional/KeyMintAidlTestBase.h +++ b/security/keymint/aidl/vts/functional/KeyMintAidlTestBase.h @@ -27,7 +27,11 @@ #include <keymint_support/authorization_set.h> -namespace aidl::android::hardware::security::keymint::test { +namespace aidl::android::hardware::security::keymint { + +::std::ostream& operator<<(::std::ostream& os, const AuthorizationSet& set); + +namespace test { using ::android::sp; using Status = ::ndk::ScopedAStatus; @@ -37,8 +41,6 @@ using ::std::vector; constexpr uint64_t kOpHandleSentinel = 0xFFFFFFFFFFFFFFFF; -::std::ostream& operator<<(::std::ostream& os, const AuthorizationSet& set); - class KeyMintAidlTestBase : public ::testing::TestWithParam<string> { public: void SetUp() override; @@ -56,13 +58,13 @@ class KeyMintAidlTestBase : public ::testing::TestWithParam<string> { ErrorCode GetReturnErrorCode(const Status& result); ErrorCode GenerateKey(const AuthorizationSet& key_desc, vector<uint8_t>* key_blob, - KeyCharacteristics* key_characteristics); + vector<KeyCharacteristics>* key_characteristics); ErrorCode GenerateKey(const AuthorizationSet& key_desc); ErrorCode ImportKey(const AuthorizationSet& key_desc, KeyFormat format, const string& key_material, vector<uint8_t>* key_blob, - KeyCharacteristics* key_characteristics); + vector<KeyCharacteristics>* key_characteristics); ErrorCode ImportKey(const AuthorizationSet& key_desc, KeyFormat format, const string& key_material); @@ -147,8 +149,8 @@ class KeyMintAidlTestBase : public ::testing::TestWithParam<string> { std::pair<ErrorCode, vector<uint8_t>> UpgradeKey(const vector<uint8_t>& key_blob); - bool IsSecure() { return securityLevel_ != SecurityLevel::SOFTWARE; } - SecurityLevel SecLevel() { return securityLevel_; } + bool IsSecure() const { return securityLevel_ != SecurityLevel::SOFTWARE; } + SecurityLevel SecLevel() const { return securityLevel_; } vector<uint32_t> ValidKeySizes(Algorithm algorithm); vector<uint32_t> InvalidKeySizes(Algorithm algorithm); @@ -164,9 +166,19 @@ class KeyMintAidlTestBase : public ::testing::TestWithParam<string> { } std::shared_ptr<IKeyMintOperation> op_; - vector<Certificate> certChain_; + vector<Certificate> cert_chain_; vector<uint8_t> key_blob_; - KeyCharacteristics key_characteristics_; + vector<KeyCharacteristics> key_characteristics_; + + const vector<KeyParameter>& SecLevelAuthorizations( + const vector<KeyCharacteristics>& key_characteristics); + inline const vector<KeyParameter>& SecLevelAuthorizations() { + return SecLevelAuthorizations(key_characteristics_); + } + const vector<KeyParameter>& HwEnforcedAuthorizations( + const vector<KeyCharacteristics>& key_characteristics); + const vector<KeyParameter>& SwEnforcedAuthorizations( + const vector<KeyCharacteristics>& key_characteristics); private: std::shared_ptr<IKeyMintDevice> keymint_; @@ -184,4 +196,6 @@ class KeyMintAidlTestBase : public ::testing::TestWithParam<string> { testing::ValuesIn(KeyMintAidlTestBase::build_params()), \ ::android::PrintInstanceNameToString) -} // namespace aidl::android::hardware::security::keymint::test +} // namespace test + +} // namespace aidl::android::hardware::security::keymint diff --git a/security/keymint/aidl/vts/functional/KeyMintTest.cpp b/security/keymint/aidl/vts/functional/KeyMintTest.cpp index 30601538dd..e7c94f37a0 100644 --- a/security/keymint/aidl/vts/functional/KeyMintTest.cpp +++ b/security/keymint/aidl/vts/functional/KeyMintTest.cpp @@ -56,18 +56,16 @@ using namespace aidl::android::hardware::security::keymint; template <> struct std::equal_to<KeyCharacteristics> { bool operator()(const KeyCharacteristics& a, const KeyCharacteristics& b) const { - // This isn't very efficient. Oh, well. - AuthorizationSet a_sw(a.softwareEnforced); - AuthorizationSet b_sw(b.softwareEnforced); - AuthorizationSet a_tee(b.hardwareEnforced); - AuthorizationSet b_tee(b.hardwareEnforced); - - a_sw.Sort(); - b_sw.Sort(); - a_tee.Sort(); - b_tee.Sort(); - - return ((a_sw == b_sw) && (a_tee == b_tee)); + if (a.securityLevel != b.securityLevel) return false; + + // this isn't very efficient. Oh, well. + AuthorizationSet a_auths(a.authorizations); + AuthorizationSet b_auths(b.authorizations); + + a_auths.Sort(); + b_auths.Sort(); + + return a_auths == b_auths; } }; @@ -182,9 +180,280 @@ struct RSA_Delete { void operator()(RSA* p) { RSA_free(p); } }; -/* TODO(seleneh) add attestation verification codes like verify_chain() and - * attestation tests after we decided on the keymint 1 attestation changes. - */ +char nibble2hex[16] = {'0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; + +string bin2hex(const vector<uint8_t>& data) { + string retval; + retval.reserve(data.size() * 2 + 1); + for (uint8_t byte : data) { + retval.push_back(nibble2hex[0x0F & (byte >> 4)]); + retval.push_back(nibble2hex[0x0F & byte]); + } + return retval; +} + +X509* parse_cert_blob(const vector<uint8_t>& blob) { + const uint8_t* p = blob.data(); + return d2i_X509(nullptr, &p, blob.size()); +} + +bool verify_chain(const vector<Certificate>& chain) { + for (size_t i = 0; i < chain.size(); ++i) { + X509_Ptr key_cert(parse_cert_blob(chain[i].encodedCertificate)); + X509_Ptr signing_cert; + if (i < chain.size() - 1) { + signing_cert.reset(parse_cert_blob(chain[i + 1].encodedCertificate)); + } else { + signing_cert.reset(parse_cert_blob(chain[i].encodedCertificate)); + } + EXPECT_TRUE(!!key_cert.get() && !!signing_cert.get()); + if (!key_cert.get() || !signing_cert.get()) return false; + + EVP_PKEY_Ptr signing_pubkey(X509_get_pubkey(signing_cert.get())); + EXPECT_TRUE(!!signing_pubkey.get()); + if (!signing_pubkey.get()) return false; + + EXPECT_EQ(1, X509_verify(key_cert.get(), signing_pubkey.get())) + << "Verification of certificate " << i << " failed " + << "OpenSSL error string: " << ERR_error_string(ERR_get_error(), NULL); + + char* cert_issuer = // + X509_NAME_oneline(X509_get_issuer_name(key_cert.get()), nullptr, 0); + char* signer_subj = + X509_NAME_oneline(X509_get_subject_name(signing_cert.get()), nullptr, 0); + EXPECT_STREQ(cert_issuer, signer_subj) << "Cert " << i << " has wrong issuer."; + if (i == 0) { + char* cert_sub = X509_NAME_oneline(X509_get_subject_name(key_cert.get()), nullptr, 0); + EXPECT_STREQ("/CN=Android Keystore Key", cert_sub) + << "Cert " << i << " has wrong subject."; + OPENSSL_free(cert_sub); + } + + OPENSSL_free(cert_issuer); + OPENSSL_free(signer_subj); + + if (dump_Attestations) std::cout << bin2hex(chain[i].encodedCertificate) << std::endl; + } + + return true; +} + +// Extract attestation record from cert. Returned object is still part of cert; don't free it +// separately. +ASN1_OCTET_STRING* get_attestation_record(X509* certificate) { + ASN1_OBJECT_Ptr oid(OBJ_txt2obj(kAttestionRecordOid, 1 /* dotted string format */)); + EXPECT_TRUE(!!oid.get()); + if (!oid.get()) return nullptr; + + int location = X509_get_ext_by_OBJ(certificate, oid.get(), -1 /* search from beginning */); + EXPECT_NE(-1, location) << "Attestation extension not found in certificate"; + if (location == -1) return nullptr; + + X509_EXTENSION* attest_rec_ext = X509_get_ext(certificate, location); + EXPECT_TRUE(!!attest_rec_ext) + << "Found attestation extension but couldn't retrieve it? Probably a BoringSSL bug."; + if (!attest_rec_ext) return nullptr; + + ASN1_OCTET_STRING* attest_rec = X509_EXTENSION_get_data(attest_rec_ext); + EXPECT_TRUE(!!attest_rec) << "Attestation extension contained no data"; + return attest_rec; +} + +bool tag_in_list(const KeyParameter& entry) { + // Attestations don't contain everything in key authorization lists, so we need to filter + // the key lists to produce the lists that we expect to match the attestations. + auto tag_list = { + Tag::BLOB_USAGE_REQUIREMENTS, // + Tag::CREATION_DATETIME, // + Tag::EC_CURVE, + Tag::HARDWARE_TYPE, + Tag::INCLUDE_UNIQUE_ID, + }; + return std::find(tag_list.begin(), tag_list.end(), entry.tag) != tag_list.end(); +} + +AuthorizationSet filtered_tags(const AuthorizationSet& set) { + AuthorizationSet filtered; + std::remove_copy_if(set.begin(), set.end(), std::back_inserter(filtered), tag_in_list); + return filtered; +} + +bool avb_verification_enabled() { + char value[PROPERTY_VALUE_MAX]; + return property_get("ro.boot.vbmeta.device_state", value, "") != 0; +} + +bool verify_attestation_record(const string& challenge, // + const string& app_id, // + AuthorizationSet expected_sw_enforced, // + AuthorizationSet expected_hw_enforced, // + SecurityLevel security_level, + const vector<uint8_t>& attestation_cert) { + X509_Ptr cert(parse_cert_blob(attestation_cert)); + EXPECT_TRUE(!!cert.get()); + if (!cert.get()) return false; + + ASN1_OCTET_STRING* attest_rec = get_attestation_record(cert.get()); + EXPECT_TRUE(!!attest_rec); + if (!attest_rec) return false; + + AuthorizationSet att_sw_enforced; + AuthorizationSet att_hw_enforced; + uint32_t att_attestation_version; + uint32_t att_keymaster_version; + SecurityLevel att_attestation_security_level; + SecurityLevel att_keymaster_security_level; + vector<uint8_t> att_challenge; + vector<uint8_t> att_unique_id; + vector<uint8_t> att_app_id; + + auto error = parse_attestation_record(attest_rec->data, // + attest_rec->length, // + &att_attestation_version, // + &att_attestation_security_level, // + &att_keymaster_version, // + &att_keymaster_security_level, // + &att_challenge, // + &att_sw_enforced, // + &att_hw_enforced, // + &att_unique_id); + EXPECT_EQ(ErrorCode::OK, error); + if (error != ErrorCode::OK) return false; + + EXPECT_GE(att_attestation_version, 3U); + + expected_sw_enforced.push_back(TAG_ATTESTATION_APPLICATION_ID, + vector<uint8_t>(app_id.begin(), app_id.end())); + + EXPECT_GE(att_keymaster_version, 4U); + EXPECT_EQ(security_level, att_keymaster_security_level); + EXPECT_EQ(security_level, att_attestation_security_level); + + EXPECT_EQ(challenge.length(), att_challenge.size()); + EXPECT_EQ(0, memcmp(challenge.data(), att_challenge.data(), challenge.length())); + + char property_value[PROPERTY_VALUE_MAX] = {}; + // TODO(b/136282179): When running under VTS-on-GSI the TEE-backed + // keymaster implementation will report YYYYMM dates instead of YYYYMMDD + // for the BOOT_PATCH_LEVEL. + if (avb_verification_enabled()) { + for (int i = 0; i < att_hw_enforced.size(); i++) { + if (att_hw_enforced[i].tag == TAG_BOOT_PATCHLEVEL || + att_hw_enforced[i].tag == TAG_VENDOR_PATCHLEVEL) { + std::string date = + std::to_string(att_hw_enforced[i].value.get<KeyParameterValue::dateTime>()); + // strptime seems to require delimiters, but the tag value will + // be YYYYMMDD + date.insert(6, "-"); + date.insert(4, "-"); + EXPECT_EQ(date.size(), 10); + struct tm time; + strptime(date.c_str(), "%Y-%m-%d", &time); + + // Day of the month (0-31) + EXPECT_GE(time.tm_mday, 0); + EXPECT_LT(time.tm_mday, 32); + // Months since Jan (0-11) + EXPECT_GE(time.tm_mon, 0); + EXPECT_LT(time.tm_mon, 12); + // Years since 1900 + EXPECT_GT(time.tm_year, 110); + EXPECT_LT(time.tm_year, 200); + } + } + } + + // Check to make sure boolean values are properly encoded. Presence of a boolean tag indicates + // true. A provided boolean tag that can be pulled back out of the certificate indicates correct + // encoding. No need to check if it's in both lists, since the AuthorizationSet compare below + // will handle mismatches of tags. + if (security_level == SecurityLevel::SOFTWARE) { + EXPECT_TRUE(expected_sw_enforced.Contains(TAG_NO_AUTH_REQUIRED)); + } else { + EXPECT_TRUE(expected_hw_enforced.Contains(TAG_NO_AUTH_REQUIRED)); + } + + // Alternatively this checks the opposite - a false boolean tag (one that isn't provided in + // the authorization list during key generation) isn't being attested to in the certificate. + EXPECT_FALSE(expected_sw_enforced.Contains(TAG_TRUSTED_USER_PRESENCE_REQUIRED)); + EXPECT_FALSE(att_sw_enforced.Contains(TAG_TRUSTED_USER_PRESENCE_REQUIRED)); + EXPECT_FALSE(expected_hw_enforced.Contains(TAG_TRUSTED_USER_PRESENCE_REQUIRED)); + EXPECT_FALSE(att_hw_enforced.Contains(TAG_TRUSTED_USER_PRESENCE_REQUIRED)); + + if (att_hw_enforced.Contains(TAG_ALGORITHM, Algorithm::EC)) { + // For ECDSA keys, either an EC_CURVE or a KEY_SIZE can be specified, but one must be. + EXPECT_TRUE(att_hw_enforced.Contains(TAG_EC_CURVE) || + att_hw_enforced.Contains(TAG_KEY_SIZE)); + } + + // Test root of trust elements + vector<uint8_t> verified_boot_key; + VerifiedBoot verified_boot_state; + bool device_locked; + vector<uint8_t> verified_boot_hash; + error = parse_root_of_trust(attest_rec->data, attest_rec->length, &verified_boot_key, + &verified_boot_state, &device_locked, &verified_boot_hash); + EXPECT_EQ(ErrorCode::OK, error); + + if (avb_verification_enabled()) { + EXPECT_NE(property_get("ro.boot.vbmeta.digest", property_value, ""), 0); + string prop_string(property_value); + EXPECT_EQ(prop_string.size(), 64); + EXPECT_EQ(prop_string, bin2hex(verified_boot_hash)); + + EXPECT_NE(property_get("ro.boot.vbmeta.device_state", property_value, ""), 0); + if (!strcmp(property_value, "unlocked")) { + EXPECT_FALSE(device_locked); + } else { + EXPECT_TRUE(device_locked); + } + + // Check that the device is locked if not debuggable, e.g., user build + // images in CTS. For VTS, debuggable images are used to allow adb root + // and the device is unlocked. + if (!property_get_bool("ro.debuggable", false)) { + EXPECT_TRUE(device_locked); + } else { + EXPECT_FALSE(device_locked); + } + } + + // Verified boot key should be all 0's if the boot state is not verified or self signed + std::string empty_boot_key(32, '\0'); + std::string verified_boot_key_str((const char*)verified_boot_key.data(), + verified_boot_key.size()); + EXPECT_NE(property_get("ro.boot.verifiedbootstate", property_value, ""), 0); + if (!strcmp(property_value, "green")) { + EXPECT_EQ(verified_boot_state, VerifiedBoot::VERIFIED); + EXPECT_NE(0, memcmp(verified_boot_key.data(), empty_boot_key.data(), + verified_boot_key.size())); + } else if (!strcmp(property_value, "yellow")) { + EXPECT_EQ(verified_boot_state, VerifiedBoot::SELF_SIGNED); + EXPECT_NE(0, memcmp(verified_boot_key.data(), empty_boot_key.data(), + verified_boot_key.size())); + } else if (!strcmp(property_value, "orange")) { + EXPECT_EQ(verified_boot_state, VerifiedBoot::UNVERIFIED); + EXPECT_EQ(0, memcmp(verified_boot_key.data(), empty_boot_key.data(), + verified_boot_key.size())); + } else if (!strcmp(property_value, "red")) { + EXPECT_EQ(verified_boot_state, VerifiedBoot::FAILED); + } else { + EXPECT_EQ(verified_boot_state, VerifiedBoot::UNVERIFIED); + EXPECT_NE(0, memcmp(verified_boot_key.data(), empty_boot_key.data(), + verified_boot_key.size())); + } + + att_sw_enforced.Sort(); + expected_sw_enforced.Sort(); + EXPECT_EQ(filtered_tags(expected_sw_enforced), filtered_tags(att_sw_enforced)); + + att_hw_enforced.Sort(); + expected_hw_enforced.Sort(); + EXPECT_EQ(filtered_tags(expected_hw_enforced), filtered_tags(att_hw_enforced)); + + return true; +} std::string make_string(const uint8_t* data, size_t length) { return std::string(reinterpret_cast<const char*>(data), length); @@ -229,19 +498,20 @@ class AidlBuf : public vector<uint8_t> { class NewKeyGenerationTest : public KeyMintAidlTestBase { protected: - void CheckBaseParams(const KeyCharacteristics& keyCharacteristics) { + void CheckBaseParams(const vector<KeyCharacteristics>& keyCharacteristics) { // TODO(swillden): Distinguish which params should be in which auth list. - AuthorizationSet auths(keyCharacteristics.hardwareEnforced); - auths.push_back(AuthorizationSet(keyCharacteristics.softwareEnforced)); + AuthorizationSet auths; + for (auto& entry : keyCharacteristics) { + auths.push_back(AuthorizationSet(entry.authorizations)); + } EXPECT_TRUE(auths.Contains(TAG_ORIGIN, KeyOrigin::GENERATED)); EXPECT_TRUE(auths.Contains(TAG_PURPOSE, KeyPurpose::SIGN)); EXPECT_TRUE(auths.Contains(TAG_PURPOSE, KeyPurpose::VERIFY)); - // Verify that App ID, App data and ROT are NOT included. + // Verify that App data and ROT are NOT included. EXPECT_FALSE(auths.Contains(TAG_ROOT_OF_TRUST)); - EXPECT_FALSE(auths.Contains(TAG_APPLICATION_ID)); EXPECT_FALSE(auths.Contains(TAG_APPLICATION_DATA)); // Check that some unexpected tags/values are NOT present. @@ -249,15 +519,13 @@ class NewKeyGenerationTest : public KeyMintAidlTestBase { EXPECT_FALSE(auths.Contains(TAG_PURPOSE, KeyPurpose::DECRYPT)); EXPECT_FALSE(auths.Contains(TAG_AUTH_TIMEOUT, 301U)); - // Now check that unspecified, defaulted tags are correct. - EXPECT_TRUE(auths.Contains(TAG_CREATION_DATETIME)); + auto os_ver = auths.GetTagValue(TAG_OS_VERSION); + ASSERT_TRUE(os_ver); + EXPECT_EQ(*os_ver, os_version()); - EXPECT_TRUE(auths.Contains(TAG_OS_VERSION, os_version())) - << "OS version is " << os_version() << " key reported " - << auths.GetTagValue(TAG_OS_VERSION)->get(); - EXPECT_TRUE(auths.Contains(TAG_OS_PATCHLEVEL, os_patch_level())) - << "OS patch level is " << os_patch_level() << " key reported " - << auths.GetTagValue(TAG_OS_PATCHLEVEL)->get(); + auto os_pl = auths.GetTagValue(TAG_OS_PATCHLEVEL); + ASSERT_TRUE(os_pl); + EXPECT_EQ(*os_pl, os_patch_level()); } }; @@ -270,7 +538,7 @@ class NewKeyGenerationTest : public KeyMintAidlTestBase { TEST_P(NewKeyGenerationTest, Rsa) { for (auto key_size : ValidKeySizes(Algorithm::RSA)) { vector<uint8_t> key_blob; - KeyCharacteristics key_characteristics; + vector<KeyCharacteristics> key_characteristics; ASSERT_EQ(ErrorCode::OK, GenerateKey(AuthorizationSetBuilder() .RsaSigningKey(key_size, 65537) .Digest(Digest::NONE) @@ -280,18 +548,58 @@ TEST_P(NewKeyGenerationTest, Rsa) { ASSERT_GT(key_blob.size(), 0U); CheckBaseParams(key_characteristics); - AuthorizationSet crypto_params; - if (IsSecure()) { - crypto_params = key_characteristics.hardwareEnforced; - } else { - crypto_params = key_characteristics.softwareEnforced; - } + AuthorizationSet crypto_params = SecLevelAuthorizations(key_characteristics); + + EXPECT_TRUE(crypto_params.Contains(TAG_ALGORITHM, Algorithm::RSA)); + EXPECT_TRUE(crypto_params.Contains(TAG_KEY_SIZE, key_size)) + << "Key size " << key_size << "missing"; + EXPECT_TRUE(crypto_params.Contains(TAG_RSA_PUBLIC_EXPONENT, 65537U)); + + CheckedDeleteKey(&key_blob); + } +} + +/* + * NewKeyGenerationTest.Rsa + * + * Verifies that keymint can generate all required RSA key sizes, and that the resulting keys + * have correct characteristics. + */ +TEST_P(NewKeyGenerationTest, RsaWithAttestation) { + for (auto key_size : ValidKeySizes(Algorithm::RSA)) { + auto challenge = "hello"; + auto app_id = "foo"; + + vector<uint8_t> key_blob; + vector<KeyCharacteristics> key_characteristics; + ASSERT_EQ(ErrorCode::OK, GenerateKey(AuthorizationSetBuilder() + .RsaSigningKey(key_size, 65537) + .Digest(Digest::NONE) + .Padding(PaddingMode::NONE) + .AttestationChallenge(challenge) + .AttestationApplicationId(app_id) + .Authorization(TAG_NO_AUTH_REQUIRED), + &key_blob, &key_characteristics)); + + ASSERT_GT(key_blob.size(), 0U); + CheckBaseParams(key_characteristics); + + AuthorizationSet crypto_params = SecLevelAuthorizations(key_characteristics); EXPECT_TRUE(crypto_params.Contains(TAG_ALGORITHM, Algorithm::RSA)); EXPECT_TRUE(crypto_params.Contains(TAG_KEY_SIZE, key_size)) << "Key size " << key_size << "missing"; EXPECT_TRUE(crypto_params.Contains(TAG_RSA_PUBLIC_EXPONENT, 65537U)); + EXPECT_TRUE(verify_chain(cert_chain_)); + ASSERT_GT(cert_chain_.size(), 0); + + AuthorizationSet hw_enforced = HwEnforcedAuthorizations(key_characteristics); + AuthorizationSet sw_enforced = SwEnforcedAuthorizations(key_characteristics); + EXPECT_TRUE(verify_attestation_record(challenge, app_id, // + sw_enforced, hw_enforced, SecLevel(), + cert_chain_[0].encodedCertificate)); + CheckedDeleteKey(&key_blob); } } @@ -304,7 +612,7 @@ TEST_P(NewKeyGenerationTest, Rsa) { TEST_P(NewKeyGenerationTest, NoInvalidRsaSizes) { for (auto key_size : InvalidKeySizes(Algorithm::RSA)) { vector<uint8_t> key_blob; - KeyCharacteristics key_characteristics; + vector<KeyCharacteristics> key_characteristics; ASSERT_EQ(ErrorCode::UNSUPPORTED_KEY_SIZE, GenerateKey(AuthorizationSetBuilder() .RsaSigningKey(key_size, 65537) @@ -337,7 +645,7 @@ TEST_P(NewKeyGenerationTest, RsaNoDefaultSize) { TEST_P(NewKeyGenerationTest, Ecdsa) { for (auto key_size : ValidKeySizes(Algorithm::EC)) { vector<uint8_t> key_blob; - KeyCharacteristics key_characteristics; + vector<KeyCharacteristics> key_characteristics; ASSERT_EQ(ErrorCode::OK, GenerateKey( AuthorizationSetBuilder().EcdsaSigningKey(key_size).Digest(Digest::NONE), @@ -345,12 +653,7 @@ TEST_P(NewKeyGenerationTest, Ecdsa) { ASSERT_GT(key_blob.size(), 0U); CheckBaseParams(key_characteristics); - AuthorizationSet crypto_params; - if (IsSecure()) { - crypto_params = key_characteristics.hardwareEnforced; - } else { - crypto_params = key_characteristics.softwareEnforced; - } + AuthorizationSet crypto_params = SecLevelAuthorizations(key_characteristics); EXPECT_TRUE(crypto_params.Contains(TAG_ALGORITHM, Algorithm::EC)); EXPECT_TRUE(crypto_params.Contains(TAG_KEY_SIZE, key_size)) @@ -383,7 +686,7 @@ TEST_P(NewKeyGenerationTest, EcdsaDefaultSize) { TEST_P(NewKeyGenerationTest, EcdsaInvalidSize) { for (auto key_size : InvalidKeySizes(Algorithm::EC)) { vector<uint8_t> key_blob; - KeyCharacteristics key_characteristics; + vector<KeyCharacteristics> key_characteristics; ASSERT_EQ(ErrorCode::UNSUPPORTED_KEY_SIZE, GenerateKey( AuthorizationSetBuilder().EcdsaSigningKey(key_size).Digest(Digest::NONE), @@ -454,7 +757,7 @@ TEST_P(NewKeyGenerationTest, EcdsaAllValidCurves) { TEST_P(NewKeyGenerationTest, Hmac) { for (auto digest : ValidDigests(false /* withNone */, true /* withMD5 */)) { vector<uint8_t> key_blob; - KeyCharacteristics key_characteristics; + vector<KeyCharacteristics> key_characteristics; constexpr size_t key_size = 128; ASSERT_EQ(ErrorCode::OK, GenerateKey( @@ -465,17 +768,10 @@ TEST_P(NewKeyGenerationTest, Hmac) { ASSERT_GT(key_blob.size(), 0U); CheckBaseParams(key_characteristics); - AuthorizationSet hardwareEnforced = key_characteristics.hardwareEnforced; - AuthorizationSet softwareEnforced = key_characteristics.softwareEnforced; - if (IsSecure()) { - EXPECT_TRUE(hardwareEnforced.Contains(TAG_ALGORITHM, Algorithm::HMAC)); - EXPECT_TRUE(hardwareEnforced.Contains(TAG_KEY_SIZE, key_size)) - << "Key size " << key_size << "missing"; - } else { - EXPECT_TRUE(softwareEnforced.Contains(TAG_ALGORITHM, Algorithm::HMAC)); - EXPECT_TRUE(softwareEnforced.Contains(TAG_KEY_SIZE, key_size)) - << "Key size " << key_size << "missing"; - } + AuthorizationSet crypto_params = SecLevelAuthorizations(key_characteristics); + EXPECT_TRUE(crypto_params.Contains(TAG_ALGORITHM, Algorithm::HMAC)); + EXPECT_TRUE(crypto_params.Contains(TAG_KEY_SIZE, key_size)) + << "Key size " << key_size << "missing"; CheckedDeleteKey(&key_blob); } @@ -600,7 +896,7 @@ TEST_P(SigningOperationsTest, RsaSuccess) { /* * SigningOperationsTest.RsaUseRequiresCorrectAppIdAppData * - * Verifies that using an RSA key requires the correct app ID/data. + * Verifies that using an RSA key requires the correct app data. */ TEST_P(SigningOperationsTest, RsaUseRequiresCorrectAppIdAppData) { ASSERT_EQ(ErrorCode::OK, GenerateKey(AuthorizationSetBuilder() @@ -1412,7 +1708,7 @@ TEST_P(VerificationOperationsTest, HmacSigningKeyCannotVerify) { string key_material = "HelloThisIsAKey"; vector<uint8_t> signing_key, verification_key; - KeyCharacteristics signing_key_chars, verification_key_chars; + vector<KeyCharacteristics> signing_key_chars, verification_key_chars; EXPECT_EQ(ErrorCode::OK, ImportKey(AuthorizationSetBuilder() .Authorization(TAG_NO_AUTH_REQUIRED) @@ -1466,28 +1762,22 @@ class ImportKeyTest : public KeyMintAidlTestBase { template <TagType tag_type, Tag tag, typename ValueT> void CheckCryptoParam(TypedTag<tag_type, tag> ttag, ValueT expected) { SCOPED_TRACE("CheckCryptoParam"); - if (IsSecure()) { - EXPECT_TRUE(contains(key_characteristics_.hardwareEnforced, ttag, expected)) - << "Tag " << tag << " with value " << expected << " not found"; - EXPECT_FALSE(contains(key_characteristics_.softwareEnforced, ttag)) - << "Tag " << tag << " found"; - } else { - EXPECT_TRUE(contains(key_characteristics_.softwareEnforced, ttag, expected)) - << "Tag " << tag << " with value " << expected << " not found"; - EXPECT_FALSE(contains(key_characteristics_.hardwareEnforced, ttag)) - << "Tag " << tag << " found"; + for (auto& entry : key_characteristics_) { + if (entry.securityLevel == SecLevel()) { + EXPECT_TRUE(contains(entry.authorizations, ttag, expected)) + << "Tag " << tag << " with value " << expected + << " not found at security level" << entry.securityLevel; + } else { + EXPECT_FALSE(contains(entry.authorizations, ttag, expected)) + << "Tag " << tag << " found at security level " << entry.securityLevel; + } } } void CheckOrigin() { SCOPED_TRACE("CheckOrigin"); - if (IsSecure()) { - EXPECT_TRUE(contains(key_characteristics_.hardwareEnforced, TAG_ORIGIN, - KeyOrigin::IMPORTED)); - } else { - EXPECT_TRUE(contains(key_characteristics_.softwareEnforced, TAG_ORIGIN, - KeyOrigin::IMPORTED)); - } + // Origin isn't a crypto param, but it always lives with them. + return CheckCryptoParam(TAG_ORIGIN, KeyOrigin::IMPORTED); } }; @@ -2056,6 +2346,107 @@ TEST_P(EncryptionOperationsTest, RsaOaepTooLarge) { } /* + * EncryptionOperationsTest.RsaOaepWithMGFDigestSuccess + * + * Verifies that RSA-OAEP encryption operations work, with all SHA 256 digests and all type of MGF1 + * digests. + */ +TEST_P(EncryptionOperationsTest, RsaOaepWithMGFDigestSuccess) { + auto digests = ValidDigests(false /* withNone */, true /* withMD5 */); + + size_t key_size = 2048; // Need largish key for SHA-512 test. + ASSERT_EQ(ErrorCode::OK, GenerateKey(AuthorizationSetBuilder() + .OaepMGFDigest(digests) + .Authorization(TAG_NO_AUTH_REQUIRED) + .RsaEncryptionKey(key_size, 65537) + .Padding(PaddingMode::RSA_OAEP) + .Digest(Digest::SHA_2_256))); + + string message = "Hello"; + + for (auto digest : digests) { + auto params = AuthorizationSetBuilder() + .Authorization(TAG_RSA_OAEP_MGF_DIGEST, digest) + .Digest(Digest::SHA_2_256) + .Padding(PaddingMode::RSA_OAEP); + string ciphertext1 = EncryptMessage(message, params); + if (HasNonfatalFailure()) std::cout << "-->" << digest << std::endl; + EXPECT_EQ(key_size / 8, ciphertext1.size()); + + string ciphertext2 = EncryptMessage(message, params); + EXPECT_EQ(key_size / 8, ciphertext2.size()); + + // OAEP randomizes padding so every result should be different (with astronomically high + // probability). + EXPECT_NE(ciphertext1, ciphertext2); + + string plaintext1 = DecryptMessage(ciphertext1, params); + EXPECT_EQ(message, plaintext1) << "RSA-OAEP failed with digest " << digest; + string plaintext2 = DecryptMessage(ciphertext2, params); + EXPECT_EQ(message, plaintext2) << "RSA-OAEP failed with digest " << digest; + + // Decrypting corrupted ciphertext should fail. + size_t offset_to_corrupt = random() % ciphertext1.size(); + char corrupt_byte; + do { + corrupt_byte = static_cast<char>(random() % 256); + } while (corrupt_byte == ciphertext1[offset_to_corrupt]); + ciphertext1[offset_to_corrupt] = corrupt_byte; + + EXPECT_EQ(ErrorCode::OK, Begin(KeyPurpose::DECRYPT, params)); + string result; + EXPECT_EQ(ErrorCode::UNKNOWN_ERROR, Finish(ciphertext1, &result)); + EXPECT_EQ(0U, result.size()); + } +} + +/* + * EncryptionOperationsTest.RsaOaepWithMGFIncompatibleDigest + * + * Verifies that RSA-OAEP encryption operations fail in the correct way when asked to operate + * with incompatible MGF digest. + */ +TEST_P(EncryptionOperationsTest, RsaOaepWithMGFIncompatibleDigest) { + ASSERT_EQ(ErrorCode::OK, + GenerateKey(AuthorizationSetBuilder() + .Authorization(TAG_RSA_OAEP_MGF_DIGEST, Digest::SHA_2_256) + .Authorization(TAG_NO_AUTH_REQUIRED) + .RsaEncryptionKey(2048, 65537) + .Padding(PaddingMode::RSA_OAEP) + .Digest(Digest::SHA_2_256))); + string message = "Hello World!"; + + auto params = AuthorizationSetBuilder() + .Padding(PaddingMode::RSA_OAEP) + .Digest(Digest::SHA_2_256) + .Authorization(TAG_RSA_OAEP_MGF_DIGEST, Digest::SHA_2_224); + EXPECT_EQ(ErrorCode::INCOMPATIBLE_MGF_DIGEST, Begin(KeyPurpose::ENCRYPT, params)); +} + +/* + * EncryptionOperationsTest.RsaOaepWithMGFUnsupportedDigest + * + * Verifies that RSA-OAEP encryption operations fail in the correct way when asked to operate + * with unsupported MGF digest. + */ +TEST_P(EncryptionOperationsTest, RsaOaepWithMGFUnsupportedDigest) { + ASSERT_EQ(ErrorCode::OK, + GenerateKey(AuthorizationSetBuilder() + .Authorization(TAG_RSA_OAEP_MGF_DIGEST, Digest::SHA_2_256) + .Authorization(TAG_NO_AUTH_REQUIRED) + .RsaEncryptionKey(2048, 65537) + .Padding(PaddingMode::RSA_OAEP) + .Digest(Digest::SHA_2_256))); + string message = "Hello World!"; + + auto params = AuthorizationSetBuilder() + .Padding(PaddingMode::RSA_OAEP) + .Digest(Digest::SHA_2_256) + .Authorization(TAG_RSA_OAEP_MGF_DIGEST, Digest::NONE); + EXPECT_EQ(ErrorCode::UNSUPPORTED_MGF_DIGEST, Begin(KeyPurpose::ENCRYPT, params)); +} + +/* * EncryptionOperationsTest.RsaPkcs1Success * * Verifies that RSA PKCS encryption/decrypts works. @@ -3820,16 +4211,6 @@ TEST_P(AddEntropyTest, AddLargeEntropy) { INSTANTIATE_KEYMINT_AIDL_TEST(AddEntropyTest); -typedef KeyMintAidlTestBase AttestationTest; - -/* - * AttestationTest.RsaAttestation - * - * Verifies that attesting to RSA keys works and generates the expected output. - */ -// TODO(seleneh) add attestation tests back after decided on the new attestation -// behavior under generateKey and importKey - typedef KeyMintAidlTestBase KeyDeletionTest; /** @@ -3849,7 +4230,7 @@ TEST_P(KeyDeletionTest, DeleteKey) { // Delete must work if rollback protection is implemented if (error == ErrorCode::OK) { - AuthorizationSet hardwareEnforced(key_characteristics_.hardwareEnforced); + AuthorizationSet hardwareEnforced(SecLevelAuthorizations()); ASSERT_TRUE(hardwareEnforced.Contains(TAG_ROLLBACK_RESISTANCE)); ASSERT_EQ(ErrorCode::OK, DeleteKey(true /* keep key blob */)); @@ -3882,8 +4263,8 @@ TEST_P(KeyDeletionTest, DeleteInvalidKey) { // Delete must work if rollback protection is implemented if (error == ErrorCode::OK) { - AuthorizationSet hardwareEnforced(key_characteristics_.hardwareEnforced); - ASSERT_TRUE(hardwareEnforced.Contains(TAG_ROLLBACK_RESISTANCE)); + AuthorizationSet enforced(SecLevelAuthorizations()); + ASSERT_TRUE(enforced.Contains(TAG_ROLLBACK_RESISTANCE)); // Delete the key we don't care about the result at this point. DeleteKey(); @@ -3918,7 +4299,7 @@ TEST_P(KeyDeletionTest, DeleteAllKeys) { // Delete must work if rollback protection is implemented if (error == ErrorCode::OK) { - AuthorizationSet hardwareEnforced(key_characteristics_.hardwareEnforced); + AuthorizationSet hardwareEnforced(SecLevelAuthorizations()); ASSERT_TRUE(hardwareEnforced.Contains(TAG_ROLLBACK_RESISTANCE)); ASSERT_EQ(ErrorCode::OK, DeleteAllKeys()); diff --git a/security/keymint/aidl/vts/functional/VerificationTokenTest.cpp b/security/keymint/aidl/vts/functional/VerificationTokenTest.cpp deleted file mode 100644 index 0b1eccddfd..0000000000 --- a/security/keymint/aidl/vts/functional/VerificationTokenTest.cpp +++ /dev/null @@ -1,168 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * 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 "KeyMintAidlTestBase.h" - -namespace aidl::android::hardware::security::keymint::test { - -class VerificationTokenTest : public KeyMintAidlTestBase { - protected: - struct VerifyAuthorizationResult { - ErrorCode error; - VerificationToken token; - }; - - VerifyAuthorizationResult verifyAuthorization(uint64_t operationHandle, - const HardwareAuthToken& authToken) { - VerifyAuthorizationResult result; - - Status err; - err = keyMint().verifyAuthorization(operationHandle, // - authToken, // - &result.token); - - result.error = GetReturnErrorCode(err); - return result; - } - - uint64_t getTime() { - struct timespec timespec; - EXPECT_EQ(0, clock_gettime(CLOCK_BOOTTIME, ×pec)); - return timespec.tv_sec * 1000 + timespec.tv_nsec / 1000000; - } - - int sleep_ms(uint32_t milliseconds) { - struct timespec sleep_time = {static_cast<time_t>(milliseconds / 1000), - static_cast<long>(milliseconds % 1000) * 1000000}; - while (sleep_time.tv_sec || sleep_time.tv_nsec) { - if (nanosleep(&sleep_time /* to wait */, - &sleep_time /* remaining (on interrruption) */) == 0) { - sleep_time = {}; - } else { - if (errno != EINTR) return errno; - } - } - return 0; - } -}; - -/* - * VerificationTokens exist to facilitate cross-KeyMint verification of requirements. As - * such, the precise capabilities required will vary depending on the specific vendor - * implementations. Essentially, VerificationTokens are a "hook" to enable vendor - * implementations to communicate, so the precise usage is defined by those vendors. The only - * thing we really can test is that tokens can be created by TEE keyMints, and that the - * timestamps increase as expected. - */ -TEST_P(VerificationTokenTest, TestCreation) { - auto result1 = verifyAuthorization(1 /* operation handle */, HardwareAuthToken()); - auto result1_time = getTime(); - - if (SecLevel() == SecurityLevel::STRONGBOX) { - // StrongBox should not implement verifyAuthorization. - EXPECT_EQ(ErrorCode::UNIMPLEMENTED, result1.error); - return; - } - - ASSERT_EQ(ErrorCode::OK, result1.error); - EXPECT_EQ(1U, result1.token.challenge); - EXPECT_EQ(SecLevel(), result1.token.securityLevel); - EXPECT_GT(result1.token.timestamp.milliSeconds, 0U); - - constexpr uint32_t time_to_sleep = 200; - sleep_ms(time_to_sleep); - - auto result2 = verifyAuthorization(2 /* operation handle */, HardwareAuthToken()); - - auto result2_time = getTime(); - ASSERT_EQ(ErrorCode::OK, result2.error); - EXPECT_EQ(2U, result2.token.challenge); - EXPECT_EQ(SecLevel(), result2.token.securityLevel); - - auto host_time_delta = result2_time - result1_time; - - EXPECT_GE(host_time_delta, time_to_sleep) - << "We slept for " << time_to_sleep << " ms, the clock must have advanced by that much"; - EXPECT_LE(host_time_delta, time_to_sleep + 20) - << "The verifyAuthorization call took " << (host_time_delta - time_to_sleep) - << " ms? That's awful!"; - - auto km_time_delta = - result2.token.timestamp.milliSeconds - result1.token.timestamp.milliSeconds; - - // If not too much else is going on on the system, the time delta should be quite close. Allow - // 2 ms of slop just to avoid test flakiness. - // - // TODO(swillden): see if we can output values so they can be gathered across many runs and - // report if times aren't nearly always <1ms apart. - EXPECT_LE(host_time_delta, km_time_delta + 2); - EXPECT_LE(km_time_delta, host_time_delta + 2); - ASSERT_EQ(result1.token.mac.size(), result2.token.mac.size()); - ASSERT_NE(0, - memcmp(result1.token.mac.data(), result2.token.mac.data(), result1.token.mac.size())); -} - -/* - * Test that the mac changes when the time stamp changes. This is does not guarantee that the time - * stamp is included in the mac but on failure we know that it is not. Other than in the test - * case above we call verifyAuthorization with the exact same set of parameters. - */ -TEST_P(VerificationTokenTest, MacChangesOnChangingTimestamp) { - auto result1 = verifyAuthorization(0 /* operation handle */, HardwareAuthToken()); - auto result1_time = getTime(); - - if (SecLevel() == SecurityLevel::STRONGBOX) { - // StrongBox should not implement verifyAuthorization. - EXPECT_EQ(ErrorCode::UNIMPLEMENTED, result1.error); - return; - } - - EXPECT_EQ(ErrorCode::OK, result1.error); - EXPECT_EQ(0U, result1.token.challenge); - EXPECT_EQ(SecLevel(), result1.token.securityLevel); - EXPECT_GT(result1.token.timestamp.milliSeconds, 0U); - - constexpr uint32_t time_to_sleep = 200; - sleep_ms(time_to_sleep); - - auto result2 = verifyAuthorization(0 /* operation handle */, HardwareAuthToken()); - // ASSERT_TRUE(result2.callSuccessful); - auto result2_time = getTime(); - EXPECT_EQ(ErrorCode::OK, result2.error); - EXPECT_EQ(0U, result2.token.challenge); - EXPECT_EQ(SecLevel(), result2.token.securityLevel); - - auto host_time_delta = result2_time - result1_time; - - EXPECT_GE(host_time_delta, time_to_sleep) - << "We slept for " << time_to_sleep << " ms, the clock must have advanced by that much"; - EXPECT_LE(host_time_delta, time_to_sleep + 20) - << "The verifyAuthorization call took " << (host_time_delta - time_to_sleep) - << " ms? That's awful!"; - - auto km_time_delta = - result2.token.timestamp.milliSeconds - result1.token.timestamp.milliSeconds; - - EXPECT_LE(host_time_delta, km_time_delta + 2); - EXPECT_LE(km_time_delta, host_time_delta + 2); - ASSERT_EQ(result1.token.mac.size(), result2.token.mac.size()); - ASSERT_NE(0, - memcmp(result1.token.mac.data(), result2.token.mac.data(), result1.token.mac.size())); -} - -INSTANTIATE_KEYMINT_AIDL_TEST(VerificationTokenTest); - -} // namespace aidl::android::hardware::security::keymint::test diff --git a/security/keymint/support/authorization_set.cpp b/security/keymint/support/authorization_set.cpp index f98851cfa2..3d44dff27c 100644 --- a/security/keymint/support/authorization_set.cpp +++ b/security/keymint/support/authorization_set.cpp @@ -227,6 +227,14 @@ AuthorizationSetBuilder& AuthorizationSetBuilder::Digest(std::vector<keymint::Di return *this; } +AuthorizationSetBuilder& AuthorizationSetBuilder::OaepMGFDigest( + const std::vector<android::hardware::security::keymint::Digest>& digests) { + for (auto digest : digests) { + push_back(TAG_RSA_OAEP_MGF_DIGEST, digest); + } + return *this; +} + AuthorizationSetBuilder& AuthorizationSetBuilder::Padding( std::initializer_list<PaddingMode> paddingModes) { for (auto paddingMode : paddingModes) { diff --git a/security/keymint/support/include/keymint_support/authorization_set.h b/security/keymint/support/include/keymint_support/authorization_set.h index 4ff170526a..1407c5f62b 100644 --- a/security/keymint/support/include/keymint_support/authorization_set.h +++ b/security/keymint/support/include/keymint_support/authorization_set.h @@ -259,6 +259,12 @@ class AuthorizationSetBuilder : public AuthorizationSet { size - 1); // drop the terminating '\0' } + template <Tag tag> + AuthorizationSetBuilder& Authorization(TypedTag<TagType::BYTES, tag> ttag, + const std::string& data) { + return Authorization(ttag, reinterpret_cast<const uint8_t*>(data.data()), data.size()); + } + AuthorizationSetBuilder& Authorizations(const AuthorizationSet& set) { for (const auto& entry : set) { push_back(entry); @@ -290,9 +296,24 @@ class AuthorizationSetBuilder : public AuthorizationSet { AuthorizationSetBuilder& GcmModeMacLen(uint32_t macLength); AuthorizationSetBuilder& BlockMode(std::initializer_list<BlockMode> blockModes); + AuthorizationSetBuilder& OaepMGFDigest(const std::vector<Digest>& digests); AuthorizationSetBuilder& Digest(std::vector<Digest> digests); AuthorizationSetBuilder& Padding(std::initializer_list<PaddingMode> paddings); + AuthorizationSetBuilder& AttestationChallenge(const std::string& challenge) { + return Authorization(TAG_ATTESTATION_CHALLENGE, challenge); + } + AuthorizationSetBuilder& AttestationChallenge(std::vector<uint8_t> challenge) { + return Authorization(TAG_ATTESTATION_CHALLENGE, challenge); + } + + AuthorizationSetBuilder& AttestationApplicationId(const std::string& id) { + return Authorization(TAG_ATTESTATION_APPLICATION_ID, id); + } + AuthorizationSetBuilder& AttestationApplicationId(std::vector<uint8_t> id) { + return Authorization(TAG_ATTESTATION_APPLICATION_ID, id); + } + template <typename... T> AuthorizationSetBuilder& BlockMode(T&&... a) { return BlockMode({std::forward<T>(a)...}); diff --git a/security/keymint/support/include/keymint_support/key_param_output.h b/security/keymint/support/include/keymint_support/key_param_output.h index 5f004fe529..c2b0029cae 100644 --- a/security/keymint/support/include/keymint_support/key_param_output.h +++ b/security/keymint/support/include/keymint_support/key_param_output.h @@ -84,8 +84,10 @@ template <typename ValueT> ::std::ostream& operator<<(::std::ostream& os, const KeyParameter& param); inline ::std::ostream& operator<<(::std::ostream& os, const KeyCharacteristics& value) { - return os << "SW: " << value.softwareEnforced << ::std::endl - << "HW: " << value.hardwareEnforced << ::std::endl; + for (auto& entry : value.authorizations) { + os << value.securityLevel << ": " << entry; + } + return os; } inline ::std::ostream& operator<<(::std::ostream& os, KeyPurpose value) { diff --git a/security/keymint/support/include/keymint_support/keymint_tags.h b/security/keymint/support/include/keymint_support/keymint_tags.h index d932b40b49..76aecb71b5 100644 --- a/security/keymint/support/include/keymint_support/keymint_tags.h +++ b/security/keymint/support/include/keymint_support/keymint_tags.h @@ -124,6 +124,7 @@ DECLARE_TYPED_TAG(USER_AUTH_TYPE); DECLARE_TYPED_TAG(USER_ID); DECLARE_TYPED_TAG(USER_SECURE_ID); DECLARE_TYPED_TAG(VENDOR_PATCHLEVEL); +DECLARE_TYPED_TAG(RSA_OAEP_MGF_DIGEST); #undef DECLARE_TYPED_TAG @@ -239,6 +240,7 @@ MAKE_TAG_ENUM_VALUE_ACCESSOR(TAG_PADDING, paddingMode) MAKE_TAG_ENUM_VALUE_ACCESSOR(TAG_PURPOSE, keyPurpose) MAKE_TAG_ENUM_VALUE_ACCESSOR(TAG_USER_AUTH_TYPE, hardwareAuthenticatorType) MAKE_TAG_ENUM_VALUE_ACCESSOR(TAG_HARDWARE_TYPE, securityLevel) +MAKE_TAG_ENUM_VALUE_ACCESSOR(TAG_RSA_OAEP_MGF_DIGEST, digest) #undef MAKE_TAG_ENUM_VALUE_ACCESSOR diff --git a/security/secureclock/aidl/Android.bp b/security/secureclock/aidl/Android.bp new file mode 100644 index 0000000000..5a6d7ae538 --- /dev/null +++ b/security/secureclock/aidl/Android.bp @@ -0,0 +1,21 @@ +aidl_interface { + name: "android.hardware.security.secureclock", + vendor_available: true, + srcs: [ + "android/hardware/security/secureclock/*.aidl", + ], + stability: "vintf", + backend: { + java: { + sdk_version: "module_current", + }, + ndk: { + vndk: { + enabled: true, + }, + }, + rust: { + enabled: true, + }, + }, +} diff --git a/security/secureclock/aidl/aidl_api/android.hardware.security.secureclock/current/android/hardware/security/secureclock/ISecureClock.aidl b/security/secureclock/aidl/aidl_api/android.hardware.security.secureclock/current/android/hardware/security/secureclock/ISecureClock.aidl new file mode 100644 index 0000000000..c16b312cda --- /dev/null +++ b/security/secureclock/aidl/aidl_api/android.hardware.security.secureclock/current/android/hardware/security/secureclock/ISecureClock.aidl @@ -0,0 +1,24 @@ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. +// +// You must not make a backward incompatible change to any AIDL file built +// with the aidl_interface module type with versions property set. The module +// type is used to build AIDL files in a way that they can be used across +// independently updatable components of the system. If a device is shipped +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.hardware.security.secureclock; +@VintfStability +interface ISecureClock { + android.hardware.security.secureclock.TimeStampToken generateTimeStamp(in long challenge); + const String TIME_STAMP_MAC_LABEL = "Time Verification"; +} diff --git a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/VerificationToken.aidl b/security/secureclock/aidl/aidl_api/android.hardware.security.secureclock/current/android/hardware/security/secureclock/TimeStampToken.aidl index 5c76816a52..51b1824da1 100644 --- a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/VerificationToken.aidl +++ b/security/secureclock/aidl/aidl_api/android.hardware.security.secureclock/current/android/hardware/security/secureclock/TimeStampToken.aidl @@ -2,24 +2,24 @@ // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // /////////////////////////////////////////////////////////////////////////////// -// This file is a snapshot of an AIDL interface (or parcelable). Do not try to -// edit this file. It looks like you are doing that because you have modified -// an AIDL interface in a backward-incompatible way, e.g., deleting a function -// from an interface or a field from a parcelable and it broke the build. That -// breakage is intended. +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. // -// You must not make a backward incompatible changes to the AIDL files built +// You must not make a backward incompatible change to any AIDL file built // with the aidl_interface module type with versions property set. The module // type is used to build AIDL files in a way that they can be used across // independently updatable components of the system. If a device is shipped // with such a backward incompatible change, it has a high risk of breaking // later when a module using the interface is updated, e.g., Mainline modules. -package android.hardware.security.keymint; +package android.hardware.security.secureclock; @VintfStability -parcelable VerificationToken { +parcelable TimeStampToken { long challenge; - android.hardware.security.keymint.Timestamp timestamp; - android.hardware.security.keymint.SecurityLevel securityLevel; + android.hardware.security.secureclock.Timestamp timestamp; byte[] mac; } diff --git a/security/secureclock/aidl/aidl_api/android.hardware.security.secureclock/current/android/hardware/security/secureclock/Timestamp.aidl b/security/secureclock/aidl/aidl_api/android.hardware.security.secureclock/current/android/hardware/security/secureclock/Timestamp.aidl new file mode 100644 index 0000000000..50b8b9ff13 --- /dev/null +++ b/security/secureclock/aidl/aidl_api/android.hardware.security.secureclock/current/android/hardware/security/secureclock/Timestamp.aidl @@ -0,0 +1,23 @@ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. +// +// You must not make a backward incompatible change to any AIDL file built +// with the aidl_interface module type with versions property set. The module +// type is used to build AIDL files in a way that they can be used across +// independently updatable components of the system. If a device is shipped +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.hardware.security.secureclock; +@VintfStability +parcelable Timestamp { + long milliSeconds; +} diff --git a/security/secureclock/aidl/android/hardware/security/secureclock/ISecureClock.aidl b/security/secureclock/aidl/android/hardware/security/secureclock/ISecureClock.aidl new file mode 100644 index 0000000000..7d416dda7a --- /dev/null +++ b/security/secureclock/aidl/android/hardware/security/secureclock/ISecureClock.aidl @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * 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. + * limitations under the License. + */ + +package android.hardware.security.secureclock; +import android.hardware.security.secureclock.TimeStampToken; + +/** + * Secure Clock definition. + * + * An ISecureClock provides a keymint service to generate secure timestamp using a secure platform. + * The secure time stamp contains time in milliseconds. This time stamp also contains a 256-bit MAC + * which provides integrity protection. The MAC is generated using HMAC-SHA-256 and a shared + * secret. The shared secret must be available to secure clock service by implementing + * ISharedSecret aidl. Note: ISecureClock depends on the shared secret, without which the secure + * time stamp token cannot be generated. + */ + +@VintfStability +interface ISecureClock { + /** + * String used as context in the HMAC computation signing the generated time stamp. + * See TimeStampToken.mac for details. + */ + const String TIME_STAMP_MAC_LABEL = "Time Verification"; + + /** + * Generates an authenticated timestamp. + * + * @param A challenge value provided by the relying party. It will be included in the generated + * TimeStampToken to ensure freshness. The relying service must ensure that the + * challenge cannot be specified or predicted by an attacker. + * + * @return the TimeStampToken, see the definition for details. + */ + TimeStampToken generateTimeStamp(in long challenge); +} diff --git a/security/keymint/aidl/android/hardware/security/keymint/VerificationToken.aidl b/security/secureclock/aidl/android/hardware/security/secureclock/TimeStampToken.aidl index f76e6a8526..b24d3355e5 100644 --- a/security/keymint/aidl/android/hardware/security/keymint/VerificationToken.aidl +++ b/security/secureclock/aidl/android/hardware/security/secureclock/TimeStampToken.aidl @@ -14,45 +14,37 @@ * limitations under the License. */ -package android.hardware.security.keymint; +package android.hardware.security.secureclock; -import android.hardware.security.keymint.SecurityLevel; -import android.hardware.security.keymint.Timestamp; +import android.hardware.security.secureclock.Timestamp; /** - * VerificationToken instances are used for secure environments to authenticate one another. - * - * This version of the parcelable currently don't use the parametersVerified field since it's not - * needed for time-based verification. This can be added in a later version, if needed. + * TimeStampToken instances are used for secure environments that requires secure time information. */ + @VintfStability -parcelable VerificationToken { +parcelable TimeStampToken { /** - * The operation handle, used to ensure freshness. + * The challenge that was provided as argument to ISecureClock.generateTimeStamp by the client. */ long challenge; /** - * The current time of the secure environment that generates the VerificationToken. This can be - * checked against auth tokens generated by the same secure environment, which avoids needing to - * synchronize clocks. + * The current time of the secure environment that generates the TimeStampToken. */ Timestamp timestamp; /** - * SecurityLevel of the secure environment that generated the token. - */ - SecurityLevel securityLevel; - - /** * 32-byte HMAC-SHA256 of the above values, computed as: * * HMAC(H, - * "Auth Verification" || challenge || timestamp || securityLevel) + * ISecureClock.TIME_STAMP_MAC_LABEL || challenge || timestamp) * * where: * - * ``HMAC'' is the shared HMAC key (see computeSharedHmac() in IKeyMint). + * ``ISecureClock.TIME_STAMP_MAC_LABEL'' is a sting constant defined in ISecureClock.aidl. + * + * ``H'' is the shared HMAC key (see computeSharedHmac() in ISharedHmacSecret). * * ``||'' represents concatenation * diff --git a/security/keymint/aidl/android/hardware/security/keymint/Timestamp.aidl b/security/secureclock/aidl/android/hardware/security/secureclock/Timestamp.aidl index ebb36848df..7bd1f9eab1 100644 --- a/security/keymint/aidl/android/hardware/security/keymint/Timestamp.aidl +++ b/security/secureclock/aidl/android/hardware/security/secureclock/Timestamp.aidl @@ -14,7 +14,7 @@ * limitations under the License. */ -package android.hardware.security.keymint; +package android.hardware.security.secureclock; /** * Time in milliseconds since some arbitrary point in time. Time must be monotonically increasing, diff --git a/security/sharedsecret/aidl/Android.bp b/security/sharedsecret/aidl/Android.bp new file mode 100644 index 0000000000..ab441106f7 --- /dev/null +++ b/security/sharedsecret/aidl/Android.bp @@ -0,0 +1,21 @@ +aidl_interface { + name: "android.hardware.security.sharedsecret", + vendor_available: true, + srcs: [ + "android/hardware/security/sharedsecret/*.aidl", + ], + stability: "vintf", + backend: { + java: { + sdk_version: "module_current", + }, + ndk: { + vndk: { + enabled: true, + }, + }, + rust: { + enabled: true, + }, + }, +} diff --git a/security/sharedsecret/aidl/aidl_api/android.hardware.security.sharedsecret/current/android/hardware/security/sharedsecret/ISharedSecret.aidl b/security/sharedsecret/aidl/aidl_api/android.hardware.security.sharedsecret/current/android/hardware/security/sharedsecret/ISharedSecret.aidl new file mode 100644 index 0000000000..2509936d99 --- /dev/null +++ b/security/sharedsecret/aidl/aidl_api/android.hardware.security.sharedsecret/current/android/hardware/security/sharedsecret/ISharedSecret.aidl @@ -0,0 +1,26 @@ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. +// +// You must not make a backward incompatible change to any AIDL file built +// with the aidl_interface module type with versions property set. The module +// type is used to build AIDL files in a way that they can be used across +// independently updatable components of the system. If a device is shipped +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.hardware.security.sharedsecret; +@VintfStability +interface ISharedSecret { + android.hardware.security.sharedsecret.SharedSecretParameters getSharedSecretParameters(); + byte[] computeSharedSecret(in android.hardware.security.sharedsecret.SharedSecretParameters[] params); + const String KEY_AGREEMENT_LABEL = "KeymasterSharedMac"; + const String KEY_CHECK_LABEL = "Keymaster HMAC Verification"; +} diff --git a/security/sharedsecret/aidl/aidl_api/android.hardware.security.sharedsecret/current/android/hardware/security/sharedsecret/SharedSecretParameters.aidl b/security/sharedsecret/aidl/aidl_api/android.hardware.security.sharedsecret/current/android/hardware/security/sharedsecret/SharedSecretParameters.aidl new file mode 100644 index 0000000000..9b65046060 --- /dev/null +++ b/security/sharedsecret/aidl/aidl_api/android.hardware.security.sharedsecret/current/android/hardware/security/sharedsecret/SharedSecretParameters.aidl @@ -0,0 +1,24 @@ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. +// +// You must not make a backward incompatible change to any AIDL file built +// with the aidl_interface module type with versions property set. The module +// type is used to build AIDL files in a way that they can be used across +// independently updatable components of the system. If a device is shipped +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.hardware.security.sharedsecret; +@VintfStability +parcelable SharedSecretParameters { + byte[] seed; + byte[] nonce; +} diff --git a/security/sharedsecret/aidl/android/hardware/security/sharedsecret/ISharedSecret.aidl b/security/sharedsecret/aidl/android/hardware/security/sharedsecret/ISharedSecret.aidl new file mode 100644 index 0000000000..906303f15b --- /dev/null +++ b/security/sharedsecret/aidl/android/hardware/security/sharedsecret/ISharedSecret.aidl @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * 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. + * limitations under the License. + */ + +package android.hardware.security.sharedsecret; +import android.hardware.security.sharedsecret.SharedSecretParameters; + +/** + * Shared Secret definition. + * + * An ISharedSecret enables any service that implements this interface to establish a shared secret + * with one or more other services such as ISecureClock, TEE IKeymintDevice, StrongBox + * IKeymintDevice, etc. The shared secret is a 256-bit HMAC key and it is further used to generate + * secure tokens with integrity protection. There are two steps to establish a shared secret between + * the collaborating services: + * + * Step 1: During Android startup the system calls each service that implements this interface to + * get the shared secret parameters. This is done using getSharedSecretParameters method defined + * below. + * Step 2: The system lexicographically sorts the shared secret parameters received from each + * service and then sends these sorted parameter list to each service in a computeSharedSecret + * method defined below. The services individually computes the shared secret and returns back + * the 32 byte sharing check hash value generated by using the computed shared secret. + * Step 3: The system collects sharing check hash values from each service and evaluates them. If + * they are all equal, then the shared secret generation is considered to be successful else it is + * considered to have failed. + */ + +@VintfStability +interface ISharedSecret { + /** + * String used as label in the shared key derivation. See computeSharedSecret below. + */ + const String KEY_AGREEMENT_LABEL = "KeymasterSharedMac"; + + /** + * String used as context in the computation of the sharingCheck. See computeSharedSecret + * below. + */ + const String KEY_CHECK_LABEL = "Keymaster HMAC Verification"; + + /** + * This method is the first step in the process for agreeing on a shared key. It is called by + * Android during startup. The system calls it on each of the HAL instances and collects the + * results in preparation for the second step. + * + * @return The SharedSecretParameters to use. As specified in the SharedSecretParameters + * documentation, the seed must contain the same value in every invocation + * of the method on a given device, and the nonce must return the same value for every + * invocation during a boot session. + */ + SharedSecretParameters getSharedSecretParameters(); + + /** + * This method is the second and final step in the process for agreeing on a shared key. It is + * called by Android during startup. The system calls it on each of the keymint services, and + * sends to it all of the SharedSecretParameters returned by all keymint services. + * + * This method computes the shared 32-byte HMAC key ``H'' as follows (all keymint services + * instances perform the same computation to arrive at the same result): + * + * H = CKDF(key = K, + * context = P1 || P2 || ... || Pn, + * label = KEY_AGREEMENT_LABEL) + * + * where: + * + * ``CKDF'' is the standard AES-CMAC KDF from NIST SP 800-108 in counter mode (see Section + * 5.1 of the referenced publication). ``key'', ``context'', and ``label'' are + * defined in the standard. The counter is prefixed and length L appended, as shown + * in the construction on page 12 of the standard. The label string is UTF-8 encoded. + * + * ``K'' is a pre-established shared secret, set up during factory reset. The mechanism for + * establishing this shared secret is implementation-defined.Any method of securely + * establishing K that ensures that an attacker cannot obtain or derive its value is + * acceptable. + * + * CRITICAL SECURITY REQUIREMENT: All keys created by a IKeymintDevice instance must + * be cryptographically bound to the value of K, such that establishing a new K + * permanently destroys them. + * + * ``||'' represents concatenation. + * + * ``Pi'' is the i'th SharedSecretParameters value in the params vector. Encoding of an + * SharedSecretParameters is the concatenation of its two fields, i.e. seed || nonce. + * + * Note that the label "KeymasterSharedMac" is the 18-byte UTF-8 encoding of the string. + * + * @param params is an array of SharedSecretParameters The lexicographically sorted + * SharedSecretParameters data returned by all keymint services when getSharedSecretParameters + * was called. + * + * @return sharingCheck A 32-byte value used to verify that all the keymint services have + * computed the same shared HMAC key. The sharingCheck value is computed as follows: + * + * sharingCheck = HMAC(H, KEY_CHECK_LABEL) + * + * The string is UTF-8 encoded, 27 bytes in length. If the returned values of all + * keymint services don't match, clients must assume that HMAC agreement + * failed. + */ + byte[] computeSharedSecret(in SharedSecretParameters[] params); +} diff --git a/security/sharedsecret/aidl/android/hardware/security/sharedsecret/SharedSecretParameters.aidl b/security/sharedsecret/aidl/android/hardware/security/sharedsecret/SharedSecretParameters.aidl new file mode 100644 index 0000000000..691b3f1386 --- /dev/null +++ b/security/sharedsecret/aidl/android/hardware/security/sharedsecret/SharedSecretParameters.aidl @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * 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. + */ + +package android.hardware.security.sharedsecret; + +/** + * SharedSecretParameters holds the data used in the process of establishing a shared secret i.e. + * HMAC key between multiple keymint services. These parameters are returned in by + * getSharedSecretParameters() and send to computeShareSecret(). See the named methods in + * ISharedSecret for details of usage. + */ + +@VintfStability +parcelable SharedSecretParameters { + /** + * Either empty or contains a non zero persistent value that is associated with the pre-shared + * HMAC agreement key. It is either empty or 32 bytes in length. + */ + byte[] seed; + + /** + * A 32-byte value which is guaranteed to be different each time + * getSharedSecretParameters() is called. Probabilistic uniqueness (i.e. random) is acceptable, + * though a stronger uniqueness guarantee (e.g. counter) is recommended where possible. + */ + byte[] nonce; +} diff --git a/tests/lazy/1.1/ILazy.hal b/tests/lazy/1.1/ILazy.hal index a15e0e3641..b0a6a2aa09 100644 --- a/tests/lazy/1.1/ILazy.hal +++ b/tests/lazy/1.1/ILazy.hal @@ -18,4 +18,12 @@ package android.hardware.tests.lazy@1.1; import android.hardware.tests.lazy@1.0; -interface ILazy extends @1.0::ILazy {}; +interface ILazy extends @1.0::ILazy { + /** + * Ask the process hosting the service to install a callback that notifies + * it when the number of active (i.e. with clients) services changes. + * For testing purposes, this callback exercises the code to unregister/re-register + * the services and eventually shuts down the process. + */ + setCustomActiveServicesCountCallback(); +}; diff --git a/tv/input/1.0/default/TvInput.cpp b/tv/input/1.0/default/TvInput.cpp index 4ea1deca9b..7583a67ea8 100644 --- a/tv/input/1.0/default/TvInput.cpp +++ b/tv/input/1.0/default/TvInput.cpp @@ -142,7 +142,7 @@ Return<Result> TvInput::closeStream(int32_t deviceId, int32_t streamId) { // static void TvInput::notify(struct tv_input_device* __unused, tv_input_event_t* event, - void* __unused) { + void* optionalStatus) { if (mCallback != nullptr && event != nullptr) { // Capturing is no longer supported. if (event->type >= TV_INPUT_EVENT_CAPTURE_SUCCEEDED) { @@ -154,7 +154,17 @@ void TvInput::notify(struct tv_input_device* __unused, tv_input_event_t* event, tvInputEvent.deviceInfo.type = static_cast<TvInputType>( event->device_info.type); tvInputEvent.deviceInfo.portId = event->device_info.hdmi.port_id; - tvInputEvent.deviceInfo.cableConnectionStatus = CableConnectionStatus::UNKNOWN; + CableConnectionStatus connectionStatus = CableConnectionStatus::UNKNOWN; + if (optionalStatus != nullptr && + ((event->type == TV_INPUT_EVENT_STREAM_CONFIGURATIONS_CHANGED) || + (event->type == TV_INPUT_EVENT_DEVICE_AVAILABLE))) { + int newStatus = *reinterpret_cast<int*>(optionalStatus); + if (newStatus <= static_cast<int>(CableConnectionStatus::DISCONNECTED) && + newStatus >= static_cast<int>(CableConnectionStatus::UNKNOWN)) { + connectionStatus = static_cast<CableConnectionStatus>(newStatus); + } + } + tvInputEvent.deviceInfo.cableConnectionStatus = connectionStatus; // TODO: Ensure the legacy audio type code is the same once audio HAL default // implementation is ready. tvInputEvent.deviceInfo.audioType = static_cast<AudioDevice>( diff --git a/tv/tuner/1.0/vts/functional/DvrTests.cpp b/tv/tuner/1.0/vts/functional/DvrTests.cpp index 0dfc032d19..ba211894f7 100644 --- a/tv/tuner/1.0/vts/functional/DvrTests.cpp +++ b/tv/tuner/1.0/vts/functional/DvrTests.cpp @@ -55,6 +55,7 @@ void DvrCallback::playbackThreadLoop() { uint8_t* buffer; ALOGW("[vts] playback thread loop start %s", mInputDataFile.c_str()); if (fd < 0) { + EXPECT_TRUE(fd >= 0) << "Failed to open: " + mInputDataFile; mPlaybackThreadRunning = false; ALOGW("[vts] Error %s", strerror(errno)); } @@ -178,7 +179,7 @@ void DvrCallback::recordThreadLoop(RecordSettings* /*recordSettings*/, bool* kee // Our current implementation filter the data and write it into the filter FMQ // immediately after the DATA_READY from the VTS/framework if (!readRecordFMQ()) { - ALOGD("[vts] record data failed to be filtered. Ending thread"); + ALOGW("[vts] record data failed to be filtered. Ending thread"); mRecordThreadRunning = false; break; } diff --git a/tv/tuner/1.0/vts/functional/FilterTests.cpp b/tv/tuner/1.0/vts/functional/FilterTests.cpp index 5f5d108fd3..240aa9f149 100644 --- a/tv/tuner/1.0/vts/functional/FilterTests.cpp +++ b/tv/tuner/1.0/vts/functional/FilterTests.cpp @@ -70,6 +70,10 @@ void FilterCallback::filterThreadLoop(DemuxFilterEvent& /* event */) { } bool FilterCallback::readFilterEventData() { + if (mFilterMQ == NULL) { + ALOGW("[vts] FMQ is not configured and does not need to be tested."); + return true; + } bool result = false; DemuxFilterEvent filterEvent = mFilterEvent; ALOGW("[vts] reading from filter FMQ or buffer %d", mFilterId); @@ -223,7 +227,11 @@ AssertionResult FilterTests::configFilter(DemuxFilterSettings setting, uint32_t return AssertionResult(status == Result::SUCCESS); } -AssertionResult FilterTests::getFilterMQDescriptor(uint32_t filterId) { +AssertionResult FilterTests::getFilterMQDescriptor(uint32_t filterId, bool getMqDesc) { + if (!getMqDesc) { + ALOGE("[vts] Filter does not need FMQ."); + return success(); + } Result status; EXPECT_TRUE(mFilters[filterId]) << "Test with getNewlyOpenedFilterId first."; EXPECT_TRUE(mFilterCallbacks[filterId]) << "Test with getNewlyOpenedFilterId first."; @@ -284,16 +292,14 @@ AssertionResult FilterTests::clearTimeStamp() { AssertionResult FilterTests::closeFilter(uint32_t filterId) { EXPECT_TRUE(mFilters[filterId]) << "Test with getNewlyOpenedFilterId first."; Result status = mFilters[filterId]->close(); - if (status == Result::SUCCESS) { - for (int i = 0; i < mUsedFilterIds.size(); i++) { - if (mUsedFilterIds[i] == filterId) { - mUsedFilterIds.erase(mUsedFilterIds.begin() + i); - break; - } + for (int i = 0; i < mUsedFilterIds.size(); i++) { + if (mUsedFilterIds[i] == filterId) { + mUsedFilterIds.erase(mUsedFilterIds.begin() + i); + break; } - mFilterCallbacks.erase(filterId); - mFilters.erase(filterId); } + mFilterCallbacks.erase(filterId); + mFilters.erase(filterId); return AssertionResult(status == Result::SUCCESS); } diff --git a/tv/tuner/1.0/vts/functional/FilterTests.h b/tv/tuner/1.0/vts/functional/FilterTests.h index 8ac9c555c1..c61fa18c3c 100644 --- a/tv/tuner/1.0/vts/functional/FilterTests.h +++ b/tv/tuner/1.0/vts/functional/FilterTests.h @@ -159,7 +159,7 @@ class FilterTests { AssertionResult getTimeStamp(); AssertionResult getNewlyOpenedFilterId(uint32_t& filterId); AssertionResult configFilter(DemuxFilterSettings setting, uint32_t filterId); - AssertionResult getFilterMQDescriptor(uint32_t filterId); + AssertionResult getFilterMQDescriptor(uint32_t filterId, bool getMqDesc); AssertionResult setFilterDataSource(uint32_t sourceFilterId, uint32_t sinkFilterId); AssertionResult setFilterDataSourceToDemux(uint32_t filterId); AssertionResult startFilter(uint32_t filterId); diff --git a/tv/tuner/1.0/vts/functional/VtsHalTvTunerV1_0TargetTest.cpp b/tv/tuner/1.0/vts/functional/VtsHalTvTunerV1_0TargetTest.cpp index 078f5df391..891619a810 100644 --- a/tv/tuner/1.0/vts/functional/VtsHalTvTunerV1_0TargetTest.cpp +++ b/tv/tuner/1.0/vts/functional/VtsHalTvTunerV1_0TargetTest.cpp @@ -48,7 +48,7 @@ void TunerFilterHidlTest::configSingleFilterInDemuxTest(FilterConfig filterConf, ASSERT_TRUE(mFilterTests.openFilterInDemux(filterConf.type, filterConf.bufferSize)); ASSERT_TRUE(mFilterTests.getNewlyOpenedFilterId(filterId)); ASSERT_TRUE(mFilterTests.configFilter(filterConf.settings, filterId)); - ASSERT_TRUE(mFilterTests.getFilterMQDescriptor(filterId)); + ASSERT_TRUE(mFilterTests.getFilterMQDescriptor(filterId, filterConf.getMqDesc)); ASSERT_TRUE(mFilterTests.startFilter(filterId)); ASSERT_TRUE(mFilterTests.stopFilter(filterId)); ASSERT_TRUE(mFilterTests.closeFilter(filterId)); @@ -75,6 +75,9 @@ void TunerFilterHidlTest::testTimeFilter(TimeFilterConfig filterConf) { void TunerBroadcastHidlTest::broadcastSingleFilterTest(FilterConfig filterConf, FrontendConfig frontendConf) { + if (!frontendConf.enable) { + return; + } uint32_t feId; uint32_t demuxId; sp<IDemux> demux; @@ -99,7 +102,7 @@ void TunerBroadcastHidlTest::broadcastSingleFilterTest(FilterConfig filterConf, ASSERT_TRUE(mFilterTests.openFilterInDemux(filterConf.type, filterConf.bufferSize)); ASSERT_TRUE(mFilterTests.getNewlyOpenedFilterId(filterId)); ASSERT_TRUE(mFilterTests.configFilter(filterConf.settings, filterId)); - ASSERT_TRUE(mFilterTests.getFilterMQDescriptor(filterId)); + ASSERT_TRUE(mFilterTests.getFilterMQDescriptor(filterId, filterConf.getMqDesc)); ASSERT_TRUE(mFilterTests.startFilter(filterId)); // tune test ASSERT_TRUE(mFrontendTests.tuneFrontend(frontendConf, true /*testWithDemux*/)); @@ -145,7 +148,7 @@ void TunerPlaybackHidlTest::playbackSingleFilterTest(FilterConfig filterConf, Dv ASSERT_TRUE(mFilterTests.openFilterInDemux(filterConf.type, filterConf.bufferSize)); ASSERT_TRUE(mFilterTests.getNewlyOpenedFilterId(filterId)); ASSERT_TRUE(mFilterTests.configFilter(filterConf.settings, filterId)); - ASSERT_TRUE(mFilterTests.getFilterMQDescriptor(filterId)); + ASSERT_TRUE(mFilterTests.getFilterMQDescriptor(filterId, filterConf.getMqDesc)); mDvrTests.startPlaybackInputThread(dvrConf.playbackInputFile, dvrConf.settings.playback()); ASSERT_TRUE(mDvrTests.startDvrPlayback()); ASSERT_TRUE(mFilterTests.startFilter(filterId)); @@ -160,6 +163,9 @@ void TunerPlaybackHidlTest::playbackSingleFilterTest(FilterConfig filterConf, Dv void TunerRecordHidlTest::recordSingleFilterTest(FilterConfig filterConf, FrontendConfig frontendConf, DvrConfig dvrConf) { + if (!frontendConf.enable) { + return; + } uint32_t feId; uint32_t demuxId; sp<IDemux> demux; @@ -184,7 +190,7 @@ void TunerRecordHidlTest::recordSingleFilterTest(FilterConfig filterConf, ASSERT_TRUE(mFilterTests.openFilterInDemux(filterConf.type, filterConf.bufferSize)); ASSERT_TRUE(mFilterTests.getNewlyOpenedFilterId(filterId)); ASSERT_TRUE(mFilterTests.configFilter(filterConf.settings, filterId)); - ASSERT_TRUE(mFilterTests.getFilterMQDescriptor(filterId)); + ASSERT_TRUE(mFilterTests.getFilterMQDescriptor(filterId, filterConf.getMqDesc)); filter = mFilterTests.getFilterById(filterId); ASSERT_TRUE(filter != nullptr); mDvrTests.startRecordOutputThread(dvrConf.settings.record()); @@ -247,7 +253,7 @@ void TunerRecordHidlTest::attachSingleFilterToRecordDvrTest(FilterConfig filterC ASSERT_TRUE(mFilterTests.openFilterInDemux(filterConf.type, filterConf.bufferSize)); ASSERT_TRUE(mFilterTests.getNewlyOpenedFilterId(filterId)); ASSERT_TRUE(mFilterTests.configFilter(filterConf.settings, filterId)); - ASSERT_TRUE(mFilterTests.getFilterMQDescriptor(filterId)); + ASSERT_TRUE(mFilterTests.getFilterMQDescriptor(filterId, filterConf.getMqDesc)); filter = mFilterTests.getFilterById(filterId); ASSERT_TRUE(filter != nullptr); ASSERT_TRUE(mDvrTests.attachFilterToDvr(filter)); @@ -265,6 +271,9 @@ void TunerRecordHidlTest::attachSingleFilterToRecordDvrTest(FilterConfig filterC void TunerDescramblerHidlTest::scrambledBroadcastTest(set<struct FilterConfig> mediaFilterConfs, FrontendConfig frontendConf, DescramblerConfig descConfig) { + if (!frontendConf.enable) { + return; + } uint32_t feId; uint32_t demuxId; sp<IDemux> demux; @@ -328,17 +337,17 @@ void TunerDescramblerHidlTest::scrambledBroadcastTest(set<struct FilterConfig> m TEST_P(TunerFrontendHidlTest, TuneFrontend) { description("Tune one Frontend with specific setting and check Lock event"); - mFrontendTests.tuneTest(frontendArray[DVBT]); + mFrontendTests.tuneTest(frontendArray[defaultFrontend]); } TEST_P(TunerFrontendHidlTest, AutoScanFrontend) { description("Run an auto frontend scan with specific setting and check lock scanMessage"); - mFrontendTests.scanTest(frontendScanArray[SCAN_DVBT], FrontendScanType::SCAN_AUTO); + mFrontendTests.scanTest(frontendScanArray[defaultScanFrontend], FrontendScanType::SCAN_AUTO); } TEST_P(TunerFrontendHidlTest, BlindScanFrontend) { description("Run an blind frontend scan with specific setting and check lock scanMessage"); - mFrontendTests.scanTest(frontendScanArray[SCAN_DVBT], FrontendScanType::SCAN_BLIND); + mFrontendTests.scanTest(frontendScanArray[defaultScanFrontend], FrontendScanType::SCAN_BLIND); } TEST_P(TunerLnbHidlTest, OpenLnbByName) { @@ -374,7 +383,7 @@ TEST_P(TunerDemuxHidlTest, openDemux) { uint32_t feId; uint32_t demuxId; sp<IDemux> demux; - mFrontendTests.getFrontendIdByType(frontendArray[DVBT].type, feId); + mFrontendTests.getFrontendIdByType(frontendArray[defaultFrontend].type, feId); ASSERT_TRUE(feId != INVALID_ID); ASSERT_TRUE(mFrontendTests.openFrontendById(feId)); ASSERT_TRUE(mFrontendTests.setFrontendCallback()); @@ -394,7 +403,7 @@ TEST_P(TunerDemuxHidlTest, getAvSyncTime) { uint32_t avSyncHwId; sp<IFilter> mediaFilter; - mFrontendTests.getFrontendIdByType(frontendArray[DVBT].type, feId); + mFrontendTests.getFrontendIdByType(frontendArray[defaultFrontend].type, feId); ASSERT_TRUE(feId != INVALID_ID); ASSERT_TRUE(mFrontendTests.openFrontendById(feId)); ASSERT_TRUE(mFrontendTests.setFrontendCallback()); @@ -422,7 +431,7 @@ TEST_P(TunerDemuxHidlTest, getAvSyncTime) { TEST_P(TunerFilterHidlTest, StartFilterInDemux) { description("Open and start a filter in Demux."); // TODO use paramterized tests - configSingleFilterInDemuxTest(filterArray[TS_VIDEO0], frontendArray[DVBT]); + configSingleFilterInDemuxTest(filterArray[TS_VIDEO0], frontendArray[defaultFrontend]); } TEST_P(TunerFilterHidlTest, SetFilterLinkage) { @@ -463,22 +472,22 @@ TEST_P(TunerFilterHidlTest, testTimeFilter) { TEST_P(TunerBroadcastHidlTest, BroadcastDataFlowVideoFilterTest) { description("Test Video Filter functionality in Broadcast use case."); - broadcastSingleFilterTest(filterArray[TS_VIDEO1], frontendArray[DVBT]); + broadcastSingleFilterTest(filterArray[TS_VIDEO1], frontendArray[defaultFrontend]); } TEST_P(TunerBroadcastHidlTest, BroadcastDataFlowAudioFilterTest) { description("Test Audio Filter functionality in Broadcast use case."); - broadcastSingleFilterTest(filterArray[TS_AUDIO0], frontendArray[DVBT]); + broadcastSingleFilterTest(filterArray[TS_AUDIO0], frontendArray[defaultFrontend]); } TEST_P(TunerBroadcastHidlTest, BroadcastDataFlowSectionFilterTest) { description("Test Section Filter functionality in Broadcast use case."); - broadcastSingleFilterTest(filterArray[TS_SECTION0], frontendArray[DVBT]); + broadcastSingleFilterTest(filterArray[TS_SECTION0], frontendArray[defaultFrontend]); } TEST_P(TunerBroadcastHidlTest, IonBufferTest) { description("Test the av filter data bufferring."); - broadcastSingleFilterTest(filterArray[TS_VIDEO0], frontendArray[DVBT]); + broadcastSingleFilterTest(filterArray[TS_VIDEO0], frontendArray[defaultFrontend]); } TEST_P(TunerBroadcastHidlTest, LnbBroadcastDataFlowVideoFilterTest) { @@ -493,11 +502,11 @@ TEST_P(TunerBroadcastHidlTest, BroadcastEsDataFlowMediaFiltersTest) { sp<IDemux> demux; uint32_t filterId; - mFrontendTests.getFrontendIdByType(frontendArray[DVBT].type, feId); + mFrontendTests.getFrontendIdByType(frontendArray[defaultFrontend].type, feId); if (feId == INVALID_ID) { // TODO broadcast test on Cuttlefish needs licensed ts input, // these tests are runnable on vendor device with real frontend module - // or with manual ts installing and use DVBT frontend. + // or with manual ts installing and use defaultFrontend frontend. return; } ASSERT_TRUE(mFrontendTests.openFrontendById(feId)); @@ -510,13 +519,13 @@ TEST_P(TunerBroadcastHidlTest, BroadcastEsDataFlowMediaFiltersTest) { filterArray[TS_AUDIO1].bufferSize)); ASSERT_TRUE(mFilterTests.getNewlyOpenedFilterId(filterId)); ASSERT_TRUE(mFilterTests.configFilter(filterArray[TS_AUDIO1].settings, filterId)); - ASSERT_TRUE(mFilterTests.getFilterMQDescriptor(filterId)); + ASSERT_TRUE(mFilterTests.getFilterMQDescriptor(filterId, filterArray[TS_AUDIO1].getMqDesc)); ASSERT_TRUE(mFilterTests.startFilter(filterId)); ASSERT_TRUE(mFilterTests.openFilterInDemux(filterArray[TS_VIDEO1].type, filterArray[TS_VIDEO1].bufferSize)); ASSERT_TRUE(mFilterTests.getNewlyOpenedFilterId(filterId)); ASSERT_TRUE(mFilterTests.configFilter(filterArray[TS_VIDEO1].settings, filterId)); - ASSERT_TRUE(mFilterTests.getFilterMQDescriptor(filterId)); + ASSERT_TRUE(mFilterTests.getFilterMQDescriptor(filterId, filterArray[TS_VIDEO1].getMqDesc)); ASSERT_TRUE(mFilterTests.startFilter(filterId)); // tune test PlaybackSettings playbackSettings{ @@ -533,7 +542,8 @@ TEST_P(TunerBroadcastHidlTest, BroadcastEsDataFlowMediaFiltersTest) { }; dvrConfig.settings.playback(playbackSettings); mFrontendTests.setSoftwareFrontendDvrConfig(dvrConfig); - ASSERT_TRUE(mFrontendTests.tuneFrontend(frontendArray[DVBT], true /*testWithDemux*/)); + ASSERT_TRUE( + mFrontendTests.tuneFrontend(frontendArray[defaultFrontend], true /*testWithDemux*/)); ASSERT_TRUE(filterDataOutputTest(goldenOutputFiles)); ASSERT_TRUE(mFrontendTests.stopTuneFrontend(true /*testWithDemux*/)); ASSERT_TRUE(mFilterTests.stopFilter(filterId)); @@ -550,13 +560,14 @@ TEST_P(TunerPlaybackHidlTest, PlaybackDataFlowWithTsSectionFilterTest) { TEST_P(TunerRecordHidlTest, AttachFiltersToRecordTest) { description("Attach a single filter to the record dvr test."); // TODO use paramterized tests - attachSingleFilterToRecordDvrTest(filterArray[TS_RECORD0], frontendArray[DVBT], + attachSingleFilterToRecordDvrTest(filterArray[TS_RECORD0], frontendArray[defaultFrontend], dvrArray[DVR_RECORD0]); } TEST_P(TunerRecordHidlTest, RecordDataFlowWithTsRecordFilterTest) { description("Feed ts data from frontend to recording and test with ts record filter"); - recordSingleFilterTest(filterArray[TS_RECORD0], frontendArray[DVBT], dvrArray[DVR_RECORD0]); + recordSingleFilterTest(filterArray[TS_RECORD0], frontendArray[defaultFrontend], + dvrArray[DVR_RECORD0]); } TEST_P(TunerRecordHidlTest, LnbRecordDataFlowWithTsRecordFilterTest) { @@ -569,7 +580,7 @@ TEST_P(TunerDescramblerHidlTest, CreateDescrambler) { uint32_t feId; uint32_t demuxId; sp<IDemux> demux; - mFrontendTests.getFrontendIdByType(frontendArray[DVBT].type, feId); + mFrontendTests.getFrontendIdByType(frontendArray[defaultFrontend].type, feId); ASSERT_TRUE(feId != INVALID_ID); ASSERT_TRUE(mFrontendTests.openFrontendById(feId)); ASSERT_TRUE(mFrontendTests.setFrontendCallback()); @@ -586,7 +597,7 @@ TEST_P(TunerDescramblerHidlTest, ScrambledBroadcastDataFlowMediaFiltersTest) { set<FilterConfig> filterConfs; filterConfs.insert(filterArray[TS_AUDIO0]); filterConfs.insert(filterArray[TS_VIDEO1]); - scrambledBroadcastTest(filterConfs, frontendArray[DVBT], descramblerArray[DESC_0]); + scrambledBroadcastTest(filterConfs, frontendArray[defaultFrontend], descramblerArray[DESC_0]); } INSTANTIATE_TEST_SUITE_P( diff --git a/tv/tuner/1.0/vts/functional/VtsHalTvTunerV1_0TestConfigurations.h b/tv/tuner/1.0/vts/functional/VtsHalTvTunerV1_0TestConfigurations.h index 27c65931b8..b0a9c038e3 100644 --- a/tv/tuner/1.0/vts/functional/VtsHalTvTunerV1_0TestConfigurations.h +++ b/tv/tuner/1.0/vts/functional/VtsHalTvTunerV1_0TestConfigurations.h @@ -55,6 +55,7 @@ using android::hardware::tv::tuner::V1_0::RecordSettings; using namespace std; +const uint32_t FMQ_SIZE_512K = 0x80000; const uint32_t FMQ_SIZE_1M = 0x100000; const uint32_t FMQ_SIZE_4M = 0x400000; const uint32_t FMQ_SIZE_16M = 0x1000000; @@ -134,6 +135,7 @@ struct FilterConfig { uint32_t bufferSize; DemuxFilterType type; DemuxFilterSettings settings; + bool getMqDesc; bool operator<(const FilterConfig& /*c*/) const { return false; } }; @@ -144,6 +146,7 @@ struct TimeFilterConfig { }; struct FrontendConfig { + bool enable; bool isSoftwareFe; FrontendType type; FrontendSettings settings; @@ -191,6 +194,8 @@ static DemuxFilterType filterLinkageTypes[LINKAGE_DIR][FILTER_MAIN_TYPE_BIT_COUN static DvrConfig dvrArray[DVR_MAX]; static DescramblerConfig descramblerArray[DESC_MAX]; static vector<string> goldenOutputFiles; +static int defaultFrontend = DVBT; +static int defaultScanFrontend = SCAN_DVBT; /** Configuration array for the frontend tune test */ inline void initFrontendConfig() { @@ -216,7 +221,9 @@ inline void initFrontendConfig() { frontendArray[DVBT].tuneStatusTypes = types; frontendArray[DVBT].expectTuneStatuses = statuses; frontendArray[DVBT].isSoftwareFe = true; + frontendArray[DVBS].enable = true; frontendArray[DVBS].type = FrontendType::DVBS; + frontendArray[DVBS].enable = true; frontendArray[DVBS].isSoftwareFe = true; }; @@ -288,6 +295,7 @@ inline void initFilterConfig() { .isRaw = false, .streamId = 0xbd, }); + filterArray[TS_PES0].getMqDesc = true; // TS PCR filter setting filterArray[TS_PCR0].type.mainType = DemuxFilterMainType::TS; filterArray[TS_PCR0].type.subType.tsFilterType(DemuxTsFilterType::PCR); @@ -308,6 +316,7 @@ inline void initFilterConfig() { filterArray[TS_SECTION0].settings.ts().filterSettings.section({ .isRaw = false, }); + filterArray[TS_SECTION0].getMqDesc = true; // TS RECORD filter setting filterArray[TS_RECORD0].type.mainType = DemuxFilterMainType::TS; filterArray[TS_RECORD0].type.subType.tsFilterType(DemuxTsFilterType::RECORD); diff --git a/vibrator/aidl/aidl_api/android.hardware.vibrator/current/android/hardware/vibrator/CompositePrimitive.aidl b/vibrator/aidl/aidl_api/android.hardware.vibrator/current/android/hardware/vibrator/CompositePrimitive.aidl index 6ab7ac5b20..3071dce32b 100644 --- a/vibrator/aidl/aidl_api/android.hardware.vibrator/current/android/hardware/vibrator/CompositePrimitive.aidl +++ b/vibrator/aidl/aidl_api/android.hardware.vibrator/current/android/hardware/vibrator/CompositePrimitive.aidl @@ -26,4 +26,5 @@ enum CompositePrimitive { SLOW_RISE = 5, QUICK_FALL = 6, LIGHT_TICK = 7, + LOW_TICK = 8, } diff --git a/vibrator/aidl/android/hardware/vibrator/CompositePrimitive.aidl b/vibrator/aidl/android/hardware/vibrator/CompositePrimitive.aidl index 8e82db076b..531489824b 100644 --- a/vibrator/aidl/android/hardware/vibrator/CompositePrimitive.aidl +++ b/vibrator/aidl/android/hardware/vibrator/CompositePrimitive.aidl @@ -70,4 +70,11 @@ enum CompositePrimitive { * Support is required. */ LIGHT_TICK, + /** + * This very short low frequency effect should produce a light crisp sensation intended + * to be used repetitively for dynamic feedback. + * + * Support is required. + */ + LOW_TICK, } diff --git a/vibrator/aidl/default/Vibrator.cpp b/vibrator/aidl/default/Vibrator.cpp index c446afdea8..1021e620ce 100644 --- a/vibrator/aidl/default/Vibrator.cpp +++ b/vibrator/aidl/default/Vibrator.cpp @@ -119,6 +119,7 @@ ndk::ScopedAStatus Vibrator::getSupportedPrimitives(std::vector<CompositePrimiti CompositePrimitive::THUD, CompositePrimitive::SPIN, CompositePrimitive::QUICK_RISE, CompositePrimitive::SLOW_RISE, CompositePrimitive::QUICK_FALL, CompositePrimitive::LIGHT_TICK, + CompositePrimitive::LOW_TICK, }; return ndk::ScopedAStatus::ok(); } diff --git a/vibrator/aidl/default/vibrator-default.xml b/vibrator/aidl/default/vibrator-default.xml index 9f9cd40c4e..b5bd3ddd1e 100644 --- a/vibrator/aidl/default/vibrator-default.xml +++ b/vibrator/aidl/default/vibrator-default.xml @@ -1,10 +1,12 @@ <manifest version="1.0" type="device"> <hal format="aidl"> <name>android.hardware.vibrator</name> + <version>2</version> <fqname>IVibrator/default</fqname> </hal> <hal format="aidl"> <name>android.hardware.vibrator</name> + <version>2</version> <fqname>IVibratorManager/default</fqname> </hal> </manifest> diff --git a/wifi/1.5/IWifiChip.hal b/wifi/1.5/IWifiChip.hal index 80f2ca448c..b2960cfa7a 100644 --- a/wifi/1.5/IWifiChip.hal +++ b/wifi/1.5/IWifiChip.hal @@ -216,4 +216,22 @@ interface IWifiChip extends @1.4::IWifiChip { setCoexUnsafeChannels( vec<CoexUnsafeChannel> unsafeChannels, bitfield<CoexRestriction> restrictions) generates (WifiStatus status); + + /** + * Set country code for this Wifi chip. + * + * Country code is global setting across the Wifi chip and not Wifi + * interface (STA or AP) specific. Legacy HAL API's for country code in + * @1.0::ISupplicantStaIface::setCountryCode & + * @1.0::IWifiApIface:setCountryCode are deprecated in favor of this + * chip level API. + * + * @param code 2 byte country code (as defined in ISO 3166) to set. + * @return status Status of the operation. + * Possible status codes: + * |WifiStatusCode.SUCCESS|, + * |WifiStatusCode.FAILURE_UNKNOWN|, + * |WifiStatusCode.FAILURE_IFACE_INVALID| + */ + setCountryCode(int8_t[2] code) generates (WifiStatus status); }; diff --git a/wifi/1.5/default/wifi_chip.cpp b/wifi/1.5/default/wifi_chip.cpp index 6c9617bbc0..f3d20c8f19 100644 --- a/wifi/1.5/default/wifi_chip.cpp +++ b/wifi/1.5/default/wifi_chip.cpp @@ -732,6 +732,13 @@ Return<void> WifiChip::setCoexUnsafeChannels( hidl_status_cb, unsafeChannels, restrictions); } +Return<void> WifiChip::setCountryCode(const hidl_array<int8_t, 2>& code, + setCountryCode_cb hidl_status_cb) { + return validateAndCall(this, WifiStatusCode::ERROR_WIFI_IFACE_INVALID, + &WifiChip::setCountryCodeInternal, hidl_status_cb, + code); +} + void WifiChip::QcRemoveAndClearDynamicIfaces() { for (const auto& iface : created_ap_ifaces_) { std::string ifname = iface->getName(); @@ -1070,7 +1077,7 @@ WifiStatus WifiChip::removeIfaceInstanceFromBridgedApIfaceInternal( const std::string& ifname, const std::string& ifInstanceName) { legacy_hal::wifi_error legacy_status; const auto iface = findUsingName(ap_ifaces_, ifname); - if (!iface.get() || !ifInstanceName.empty()) { + if (!iface.get() || ifInstanceName.empty()) { return createWifiStatus(WifiStatusCode::ERROR_INVALID_ARGS); } // Requires to remove one of the instance in bridge mode @@ -1525,6 +1532,12 @@ WifiStatus WifiChip::setCoexUnsafeChannelsInternal( return createWifiStatusFromLegacyError(legacy_status); } +WifiStatus WifiChip::setCountryCodeInternal(const std::array<int8_t, 2>& code) { + auto legacy_status = + legacy_hal_.lock()->setCountryCode(getFirstActiveWlanIfaceName(), code); + return createWifiStatusFromLegacyError(legacy_status); +} + WifiStatus WifiChip::handleChipConfiguration( /* NONNULL */ std::unique_lock<std::recursive_mutex>* lock, ChipModeId mode_id) { @@ -1842,7 +1855,16 @@ bool WifiChip::isDualStaConcurrencyAllowedInCurrentMode() { std::string WifiChip::getFirstActiveWlanIfaceName() { if (sta_ifaces_.size() > 0) return sta_ifaces_[0]->getName(); - if (ap_ifaces_.size() > 0) return ap_ifaces_[0]->getName(); + if (ap_ifaces_.size() > 0) { + // If the first active wlan iface is bridged iface. + // Return first instance name. + for (auto const& it : br_ifaces_ap_instances_) { + if (it.first == ap_ifaces_[0]->getName()) { + return it.second[0]; + } + } + return ap_ifaces_[0]->getName(); + } // This could happen if the chip call is made before any STA/AP // iface is created. Default to wlan0 for such cases. LOG(WARNING) << "No active wlan interfaces in use! Using default"; diff --git a/wifi/1.5/default/wifi_chip.h b/wifi/1.5/default/wifi_chip.h index 6417b7fd6b..546b27697f 100644 --- a/wifi/1.5/default/wifi_chip.h +++ b/wifi/1.5/default/wifi_chip.h @@ -178,6 +178,8 @@ class WifiChip : public V1_5::IWifiChip { const hidl_vec<CoexUnsafeChannel>& unsafe_channels, hidl_bitfield<IfaceType> restrictions, setCoexUnsafeChannels_cb hidl_status_cb) override; + Return<void> setCountryCode(const hidl_array<int8_t, 2>& code, + setCountryCode_cb _hidl_cb) override; private: void invalidateAndRemoveAllIfaces(); @@ -258,7 +260,7 @@ class WifiChip : public V1_5::IWifiChip { WifiStatus setMultiStaUseCaseInternal(MultiStaUseCase use_case); WifiStatus setCoexUnsafeChannelsInternal( std::vector<CoexUnsafeChannel> unsafe_channels, uint32_t restrictions); - + WifiStatus setCountryCodeInternal(const std::array<int8_t, 2>& code); WifiStatus handleChipConfiguration( std::unique_lock<std::recursive_mutex>* lock, ChipModeId mode_id); WifiStatus registerDebugRingBufferCallback(); diff --git a/wifi/1.5/default/wifi_legacy_hal.cpp b/wifi/1.5/default/wifi_legacy_hal.cpp index e1a5a8c957..3e65ee0f25 100644 --- a/wifi/1.5/default/wifi_legacy_hal.cpp +++ b/wifi/1.5/default/wifi_legacy_hal.cpp @@ -1630,6 +1630,12 @@ wifi_error WifiLegacyHal::twtClearStats(const std::string& iface_name, configId); } +wifi_error WifiLegacyHal::setDtimConfig(const std::string& iface_name, + uint32_t multiplier) { + return global_func_table_.wifi_set_dtim_config(getIfaceHandle(iface_name), + multiplier); +} + void WifiLegacyHal::invalidate() { global_handle_ = nullptr; iface_name_to_handle_.clear(); diff --git a/wifi/1.5/default/wifi_legacy_hal.h b/wifi/1.5/default/wifi_legacy_hal.h index 11efe7efef..0cc1cff8cf 100644 --- a/wifi/1.5/default/wifi_legacy_hal.h +++ b/wifi/1.5/default/wifi_legacy_hal.h @@ -23,15 +23,9 @@ #include <thread> #include <vector> +#include <hardware_legacy/wifi_hal.h> #include <wifi_system/interface_tool.h> -// HACK: The include inside the namespace below also transitively includes a -// bunch of libc headers into the namespace, which leads to functions like -// socketpair being defined in -// android::hardware::wifi::V1_1::implementation::legacy_hal. Include this one -// particular header as a hacky workaround until that's fixed. -#include <sys/socket.h> - namespace android { namespace hardware { namespace wifi { @@ -40,9 +34,280 @@ namespace implementation { // This is in a separate namespace to prevent typename conflicts between // the legacy HAL types and the HIDL interface types. namespace legacy_hal { -// Wrap all the types defined inside the legacy HAL header files inside this +// Import all the types defined inside the legacy HAL header files into this // namespace. -#include <hardware_legacy/wifi_hal.h> +using ::FRAME_TYPE_80211_MGMT; +using ::FRAME_TYPE_ETHERNET_II; +using ::FRAME_TYPE_UNKNOWN; +using ::NAN_CHANNEL_24G_BAND; +using ::NAN_CHANNEL_5G_BAND_HIGH; +using ::NAN_CHANNEL_5G_BAND_LOW; +using ::NAN_DISABLE_RANGE_REPORT; +using ::NAN_DO_NOT_USE_SRF; +using ::NAN_DP_CHANNEL_NOT_REQUESTED; +using ::NAN_DP_CONFIG_NO_SECURITY; +using ::NAN_DP_CONFIG_SECURITY; +using ::NAN_DP_END; +using ::NAN_DP_FORCE_CHANNEL_SETUP; +using ::NAN_DP_INITIATOR_RESPONSE; +using ::NAN_DP_INTERFACE_CREATE; +using ::NAN_DP_INTERFACE_DELETE; +using ::NAN_DP_REQUEST_ACCEPT; +using ::NAN_DP_REQUEST_CHANNEL_SETUP; +using ::NAN_DP_REQUEST_REJECT; +using ::NAN_DP_RESPONDER_RESPONSE; +using ::NAN_GET_CAPABILITIES; +using ::NAN_MATCH_ALG_MATCH_CONTINUOUS; +using ::NAN_MATCH_ALG_MATCH_NEVER; +using ::NAN_MATCH_ALG_MATCH_ONCE; +using ::NAN_PUBLISH_TYPE_SOLICITED; +using ::NAN_PUBLISH_TYPE_UNSOLICITED; +using ::NAN_PUBLISH_TYPE_UNSOLICITED_SOLICITED; +using ::NAN_RANGING_AUTO_RESPONSE_DISABLE; +using ::NAN_RANGING_AUTO_RESPONSE_ENABLE; +using ::NAN_RANGING_DISABLE; +using ::NAN_RANGING_ENABLE; +using ::NAN_RESPONSE_BEACON_SDF_PAYLOAD; +using ::NAN_RESPONSE_CONFIG; +using ::NAN_RESPONSE_DISABLED; +using ::NAN_RESPONSE_ENABLED; +using ::NAN_RESPONSE_ERROR; +using ::NAN_RESPONSE_PUBLISH; +using ::NAN_RESPONSE_PUBLISH_CANCEL; +using ::NAN_RESPONSE_STATS; +using ::NAN_RESPONSE_SUBSCRIBE; +using ::NAN_RESPONSE_SUBSCRIBE_CANCEL; +using ::NAN_RESPONSE_TCA; +using ::NAN_RESPONSE_TRANSMIT_FOLLOWUP; +using ::NAN_SECURITY_KEY_INPUT_PASSPHRASE; +using ::NAN_SECURITY_KEY_INPUT_PASSPHRASE; +using ::NAN_SECURITY_KEY_INPUT_PMK; +using ::NAN_SECURITY_KEY_INPUT_PMK; +using ::NAN_SERVICE_ACCEPT_POLICY_ALL; +using ::NAN_SERVICE_ACCEPT_POLICY_NONE; +using ::NAN_SRF_ATTR_BLOOM_FILTER; +using ::NAN_SRF_ATTR_PARTIAL_MAC_ADDR; +using ::NAN_SRF_INCLUDE_DO_NOT_RESPOND; +using ::NAN_SRF_INCLUDE_RESPOND; +using ::NAN_SSI_NOT_REQUIRED_IN_MATCH_IND; +using ::NAN_SSI_REQUIRED_IN_MATCH_IND; +using ::NAN_STATUS_ALREADY_ENABLED; +using ::NAN_STATUS_FOLLOWUP_QUEUE_FULL; +using ::NAN_STATUS_INTERNAL_FAILURE; +using ::NAN_STATUS_INVALID_NDP_ID; +using ::NAN_STATUS_INVALID_PARAM; +using ::NAN_STATUS_INVALID_PUBLISH_SUBSCRIBE_ID; +using ::NAN_STATUS_INVALID_REQUESTOR_INSTANCE_ID; +using ::NAN_STATUS_NAN_NOT_ALLOWED; +using ::NAN_STATUS_NO_OTA_ACK; +using ::NAN_STATUS_NO_RESOURCE_AVAILABLE; +using ::NAN_STATUS_PROTOCOL_FAILURE; +using ::NAN_STATUS_SUCCESS; +using ::NAN_STATUS_UNSUPPORTED_CONCURRENCY_NAN_DISABLED; +using ::NAN_SUBSCRIBE_TYPE_ACTIVE; +using ::NAN_SUBSCRIBE_TYPE_PASSIVE; +using ::NAN_TRANSMIT_IN_DW; +using ::NAN_TRANSMIT_IN_FAW; +using ::NAN_TX_PRIORITY_HIGH; +using ::NAN_TX_PRIORITY_NORMAL; +using ::NAN_TX_TYPE_BROADCAST; +using ::NAN_TX_TYPE_UNICAST; +using ::NAN_USE_SRF; +using ::NanBeaconSdfPayloadInd; +using ::NanCapabilities; +using ::NanChannelInfo; +using ::NanConfigRequest; +using ::NanDataPathChannelCfg; +using ::NanDataPathConfirmInd; +using ::NanDataPathEndInd; +using ::NanDataPathIndicationResponse; +using ::NanDataPathInitiatorRequest; +using ::NanDataPathRequestInd; +using ::NanDataPathScheduleUpdateInd; +using ::NanDisabledInd; +using ::NanDiscEngEventInd; +using ::NanEnableRequest; +using ::NanFollowupInd; +using ::NanMatchAlg; +using ::NanMatchExpiredInd; +using ::NanMatchInd; +using ::NanPublishCancelRequest; +using ::NanPublishRequest; +using ::NanPublishTerminatedInd; +using ::NanPublishType; +using ::NanRangeReportInd; +using ::NanRangeRequestInd; +using ::NanResponseMsg; +using ::NanSRFType; +using ::NanStatusType; +using ::NanSubscribeCancelRequest; +using ::NanSubscribeRequest; +using ::NanSubscribeTerminatedInd; +using ::NanSubscribeType; +using ::NanTransmitFollowupInd; +using ::NanTransmitFollowupRequest; +using ::NanTxType; +using ::ROAMING_DISABLE; +using ::ROAMING_ENABLE; +using ::RTT_PEER_AP; +using ::RTT_PEER_NAN; +using ::RTT_PEER_P2P_CLIENT; +using ::RTT_PEER_P2P_GO; +using ::RTT_PEER_STA; +using ::RTT_STATUS_ABORTED; +using ::RTT_STATUS_FAILURE; +using ::RTT_STATUS_FAIL_AP_ON_DIFF_CHANNEL; +using ::RTT_STATUS_FAIL_BUSY_TRY_LATER; +using ::RTT_STATUS_FAIL_FTM_PARAM_OVERRIDE; +using ::RTT_STATUS_FAIL_INVALID_TS; +using ::RTT_STATUS_FAIL_NOT_SCHEDULED_YET; +using ::RTT_STATUS_FAIL_NO_CAPABILITY; +using ::RTT_STATUS_FAIL_NO_RSP; +using ::RTT_STATUS_FAIL_PROTOCOL; +using ::RTT_STATUS_FAIL_REJECTED; +using ::RTT_STATUS_FAIL_SCHEDULE; +using ::RTT_STATUS_FAIL_TM_TIMEOUT; +using ::RTT_STATUS_INVALID_REQ; +using ::RTT_STATUS_NAN_RANGING_CONCURRENCY_NOT_SUPPORTED; +using ::RTT_STATUS_NAN_RANGING_PROTOCOL_FAILURE; +using ::RTT_STATUS_NO_WIFI; +using ::RTT_STATUS_SUCCESS; +using ::RTT_TYPE_1_SIDED; +using ::RTT_TYPE_2_SIDED; +using ::RX_PKT_FATE_DRV_DROP_FILTER; +using ::RX_PKT_FATE_DRV_DROP_INVALID; +using ::RX_PKT_FATE_DRV_DROP_NOBUFS; +using ::RX_PKT_FATE_DRV_DROP_OTHER; +using ::RX_PKT_FATE_DRV_QUEUED; +using ::RX_PKT_FATE_FW_DROP_FILTER; +using ::RX_PKT_FATE_FW_DROP_INVALID; +using ::RX_PKT_FATE_FW_DROP_NOBUFS; +using ::RX_PKT_FATE_FW_DROP_OTHER; +using ::RX_PKT_FATE_FW_QUEUED; +using ::RX_PKT_FATE_SUCCESS; +using ::TX_PKT_FATE_ACKED; +using ::TX_PKT_FATE_DRV_DROP_INVALID; +using ::TX_PKT_FATE_DRV_DROP_NOBUFS; +using ::TX_PKT_FATE_DRV_DROP_OTHER; +using ::TX_PKT_FATE_DRV_QUEUED; +using ::TX_PKT_FATE_FW_DROP_INVALID; +using ::TX_PKT_FATE_FW_DROP_NOBUFS; +using ::TX_PKT_FATE_FW_DROP_OTHER; +using ::TX_PKT_FATE_FW_QUEUED; +using ::TX_PKT_FATE_SENT; +using ::WIFI_AC_BE; +using ::WIFI_AC_BK; +using ::WIFI_AC_VI; +using ::WIFI_AC_VO; +using ::WIFI_BAND_A; +using ::WIFI_BAND_ABG; +using ::WIFI_BAND_ABG_WITH_DFS; +using ::WIFI_BAND_A_DFS; +using ::WIFI_BAND_A_WITH_DFS; +using ::WIFI_BAND_BG; +using ::WIFI_BAND_UNSPECIFIED; +using ::WIFI_CHAN_WIDTH_10; +using ::WIFI_CHAN_WIDTH_160; +using ::WIFI_CHAN_WIDTH_20; +using ::WIFI_CHAN_WIDTH_40; +using ::WIFI_CHAN_WIDTH_5; +using ::WIFI_CHAN_WIDTH_5; +using ::WIFI_CHAN_WIDTH_80; +using ::WIFI_CHAN_WIDTH_80P80; +using ::WIFI_CHAN_WIDTH_INVALID; +using ::WIFI_DUAL_STA_NON_TRANSIENT_UNBIASED; +using ::WIFI_DUAL_STA_TRANSIENT_PREFER_PRIMARY; +using ::WIFI_ERROR_BUSY; +using ::WIFI_ERROR_INVALID_ARGS; +using ::WIFI_ERROR_INVALID_REQUEST_ID; +using ::WIFI_ERROR_NONE; +using ::WIFI_ERROR_NOT_AVAILABLE; +using ::WIFI_ERROR_NOT_SUPPORTED; +using ::WIFI_ERROR_OUT_OF_MEMORY; +using ::WIFI_ERROR_TIMED_OUT; +using ::WIFI_ERROR_TOO_MANY_REQUESTS; +using ::WIFI_ERROR_UNINITIALIZED; +using ::WIFI_ERROR_UNKNOWN; +using ::WIFI_INTERFACE_TYPE_AP; +using ::WIFI_INTERFACE_TYPE_NAN; +using ::WIFI_INTERFACE_TYPE_P2P; +using ::WIFI_INTERFACE_TYPE_STA; +using ::WIFI_LATENCY_MODE_LOW; +using ::WIFI_LATENCY_MODE_NORMAL; +using ::WIFI_LOGGER_CONNECT_EVENT_SUPPORTED; +using ::WIFI_LOGGER_DRIVER_DUMP_SUPPORTED; +using ::WIFI_LOGGER_MEMORY_DUMP_SUPPORTED; +using ::WIFI_LOGGER_PACKET_FATE_SUPPORTED; +using ::WIFI_LOGGER_POWER_EVENT_SUPPORTED; +using ::WIFI_LOGGER_WAKE_LOCK_SUPPORTED; +using ::WIFI_MOTION_EXPECTED; +using ::WIFI_MOTION_NOT_EXPECTED; +using ::WIFI_MOTION_UNKNOWN; +using ::WIFI_POWER_SCENARIO_ON_BODY_CELL_OFF; +using ::WIFI_POWER_SCENARIO_ON_BODY_CELL_ON; +using ::WIFI_POWER_SCENARIO_ON_HEAD_CELL_OFF; +using ::WIFI_POWER_SCENARIO_ON_HEAD_CELL_ON; +using ::WIFI_POWER_SCENARIO_VOICE_CALL; +using ::WIFI_RTT_BW_10; +using ::WIFI_RTT_BW_160; +using ::WIFI_RTT_BW_20; +using ::WIFI_RTT_BW_40; +using ::WIFI_RTT_BW_5; +using ::WIFI_RTT_BW_80; +using ::WIFI_RTT_PREAMBLE_HE; +using ::WIFI_RTT_PREAMBLE_HT; +using ::WIFI_RTT_PREAMBLE_LEGACY; +using ::WIFI_RTT_PREAMBLE_VHT; +using ::WIFI_SCAN_FLAG_INTERRUPTED; +using ::WIFI_SUCCESS; +using ::WLAN_MAC_2_4_BAND; +using ::WLAN_MAC_5_0_BAND; +using ::WLAN_MAC_6_0_BAND; +using ::frame_info; +using ::frame_type; +using ::fw_roaming_state_t; +using ::mac_addr; +using ::rtt_peer_type; +using ::ssid_t; +using ::transaction_id; +using ::wifi_band; +using ::wifi_cached_scan_results; +using ::wifi_channel_info; +using ::wifi_channel_stat; +using ::wifi_channel_width; +using ::wifi_coex_restriction; +using ::wifi_coex_unsafe_channel; +using ::wifi_error; +using ::wifi_gscan_capabilities; +using ::wifi_hal_fn; +using ::wifi_information_element; +using ::wifi_interface_type; +using ::wifi_latency_mode; +using ::wifi_lci_information; +using ::wifi_lcr_information; +using ::wifi_motion_pattern; +using ::wifi_multi_sta_use_case; +using ::wifi_power_scenario; +using ::wifi_rate; +using ::wifi_request_id; +using ::wifi_ring_buffer_status; +using ::wifi_roaming_capabilities; +using ::wifi_roaming_config; +using ::wifi_rtt_bw; +using ::wifi_rtt_capabilities; +using ::wifi_rtt_config; +using ::wifi_rtt_preamble; +using ::wifi_rtt_responder; +using ::wifi_rtt_result; +using ::wifi_rtt_status; +using ::wifi_rtt_type; +using ::wifi_rx_packet_fate; +using ::wifi_rx_report; +using ::wifi_scan_bucket_spec; +using ::wifi_scan_cmd_params; +using ::wifi_scan_result; +using ::wifi_tx_packet_fate; +using ::wifi_tx_report; // APF capabilities supported by the iface. struct PacketFilterCapabilities { @@ -425,6 +690,9 @@ class WifiLegacyHal { wifi_error twtClearStats(const std::string& iface_name, uint8_t configId); + wifi_error setDtimConfig(const std::string& iface_name, + uint32_t multiplier); + private: // Retrieve interface handles for all the available interfaces. wifi_error retrieveIfaceHandles(); diff --git a/wifi/1.5/default/wifi_legacy_hal_stubs.cpp b/wifi/1.5/default/wifi_legacy_hal_stubs.cpp index 4b005d6809..7ba5d9b804 100644 --- a/wifi/1.5/default/wifi_legacy_hal_stubs.cpp +++ b/wifi/1.5/default/wifi_legacy_hal_stubs.cpp @@ -158,6 +158,7 @@ bool initHalFuncTableWithStubs(wifi_hal_fn* hal_fn) { populateStubFor(&hal_fn->wifi_twt_info_frame_request); populateStubFor(&hal_fn->wifi_twt_get_stats); populateStubFor(&hal_fn->wifi_twt_clear_stats); + populateStubFor(&hal_fn->wifi_set_dtim_config); return true; } } // namespace legacy_hal diff --git a/wifi/1.5/default/wifi_legacy_hal_stubs.h b/wifi/1.5/default/wifi_legacy_hal_stubs.h index 7e4eb0a0fd..480389b0ce 100644 --- a/wifi/1.5/default/wifi_legacy_hal_stubs.h +++ b/wifi/1.5/default/wifi_legacy_hal_stubs.h @@ -17,13 +17,14 @@ #ifndef WIFI_LEGACY_HAL_STUBS_H_ #define WIFI_LEGACY_HAL_STUBS_H_ +#include <hardware_legacy/wifi_hal.h> + namespace android { namespace hardware { namespace wifi { namespace V1_5 { namespace implementation { namespace legacy_hal { -#include <hardware_legacy/wifi_hal.h> bool initHalFuncTableWithStubs(wifi_hal_fn* hal_fn); } // namespace legacy_hal diff --git a/wifi/1.5/types.hal b/wifi/1.5/types.hal index 3fbec82658..9fa5c800c0 100644 --- a/wifi/1.5/types.hal +++ b/wifi/1.5/types.hal @@ -19,7 +19,7 @@ package android.hardware.wifi@1.5; import @1.0::StaLinkLayerIfaceStats; import @1.0::StaLinkLayerIfacePacketStats; import @1.0::TimeStampInMs; -import @1.0::WifiBand; +import @1.4::WifiBand; import @1.0::NanCipherSuiteType; import @1.0::NanCapabilities; import @1.2::NanConfigRequestSupplemental; @@ -28,7 +28,7 @@ import @1.3::StaLinkLayerRadioStats; /** * Wifi bands defined in 80211 spec. */ -enum WifiBand : @1.0::WifiBand { +enum WifiBand : @1.4::WifiBand { /** * 60 GHz. */ diff --git a/wifi/1.5/vts/functional/Android.bp b/wifi/1.5/vts/functional/Android.bp index 2d8b412abb..118822a35f 100644 --- a/wifi/1.5/vts/functional/Android.bp +++ b/wifi/1.5/vts/functional/Android.bp @@ -14,6 +14,27 @@ // limitations under the License. // +cc_library_static { + name: "VtsHalWifiV1_5TargetTestUtil", + defaults: ["VtsHalTargetTestDefaults"], + srcs: [ + "wifi_hidl_test_utils_1_5.cpp", + ], + export_include_dirs: [ + ".", + ], + shared_libs: [ + "libnativehelper", + ], + static_libs: [ + "VtsHalWifiV1_0TargetTestUtil", + "android.hardware.wifi@1.0", + "android.hardware.wifi@1.3", + "android.hardware.wifi@1.5", + "libwifi-system-iface", + ], +} + cc_test { name: "VtsHalWifiV1_5TargetTest", defaults: ["VtsHalTargetTestDefaults"], @@ -67,9 +88,11 @@ cc_test { defaults: ["VtsHalTargetTestDefaults"], srcs: [ "wifi_chip_hidl_ap_test.cpp", + "wifi_ap_iface_hidl_test.cpp", ], static_libs: [ "VtsHalWifiV1_0TargetTestUtil", + "VtsHalWifiV1_5TargetTestUtil", "android.hardware.wifi@1.0", "android.hardware.wifi@1.1", "android.hardware.wifi@1.2", diff --git a/wifi/1.5/vts/functional/wifi_ap_iface_hidl_test.cpp b/wifi/1.5/vts/functional/wifi_ap_iface_hidl_test.cpp new file mode 100644 index 0000000000..e47b14df1e --- /dev/null +++ b/wifi/1.5/vts/functional/wifi_ap_iface_hidl_test.cpp @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * 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 <VtsCoreUtil.h> +#include <VtsHalHidlTargetCallbackBase.h> +#include <android-base/logging.h> + +#undef NAN // NAN is defined in bionic/libc/include/math.h:38 + +#include <android/hardware/wifi/1.4/IWifiChipEventCallback.h> +#include <android/hardware/wifi/1.5/IWifi.h> +#include <android/hardware/wifi/1.5/IWifiApIface.h> +#include <android/hardware/wifi/1.5/IWifiChip.h> +#include <gtest/gtest.h> +#include <hidl/GtestPrinter.h> +#include <hidl/ServiceManagement.h> + +#include "wifi_hidl_call_util.h" +#include "wifi_hidl_test_utils.h" +#include "wifi_hidl_test_utils_1_5.h" + +using ::android::sp; +using ::android::hardware::hidl_string; +using ::android::hardware::hidl_vec; +using ::android::hardware::Return; +using ::android::hardware::Void; +using ::android::hardware::wifi::V1_0::ChipModeId; +using ::android::hardware::wifi::V1_0::IfaceType; +using ::android::hardware::wifi::V1_0::IWifiIface; +using ::android::hardware::wifi::V1_0::WifiDebugRingBufferStatus; +using ::android::hardware::wifi::V1_0::WifiStatus; +using ::android::hardware::wifi::V1_0::WifiStatusCode; +using ::android::hardware::wifi::V1_4::IWifiChipEventCallback; +using ::android::hardware::wifi::V1_5::IWifiApIface; +using ::android::hardware::wifi::V1_5::IWifiChip; + +/** + * Fixture for IWifiChip tests that are conditioned on SoftAP support. + */ +class WifiApIfaceHidlTest : public ::testing::TestWithParam<std::string> { + public: + virtual void SetUp() override { + isBridgedSupport_ = testing::checkSubstringInCommandOutput( + "/system/bin/cmd wifi get-softap-supported-features", + "wifi_softap_bridged_ap_supported"); + // Make sure to start with a clean state + stopWifi(GetInstanceName()); + } + + virtual void TearDown() override { stopWifi(GetInstanceName()); } + + protected: + bool isBridgedSupport_ = false; + std::string GetInstanceName() { return GetParam(); } +}; + +/* + * resetToFactoryMacAddress + */ +TEST_P(WifiApIfaceHidlTest, resetToFactoryMacAddressInBridgedModeTest) { + if (!isBridgedSupport_) GTEST_SKIP() << "Missing Bridged AP support"; + sp<IWifiApIface> wifi_ap_iface = + getBridgedWifiApIface_1_5(GetInstanceName()); + ASSERT_NE(nullptr, wifi_ap_iface.get()); + const auto& status = HIDL_INVOKE(wifi_ap_iface, resetToFactoryMacAddress); + EXPECT_EQ(WifiStatusCode::SUCCESS, status.code); +} + +/* + * resetToFactoryMacAddress + */ +TEST_P(WifiApIfaceHidlTest, resetToFactoryMacAddressTest) { + sp<IWifiApIface> wifi_ap_iface = getWifiApIface_1_5(GetInstanceName()); + ASSERT_NE(nullptr, wifi_ap_iface.get()); + const auto& status = HIDL_INVOKE(wifi_ap_iface, resetToFactoryMacAddress); + EXPECT_EQ(WifiStatusCode::SUCCESS, status.code); +} + +GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(WifiApIfaceHidlTest); +INSTANTIATE_TEST_SUITE_P( + PerInstance, WifiApIfaceHidlTest, + testing::ValuesIn(android::hardware::getAllHalInstanceNames( + ::android::hardware::wifi::V1_5::IWifi::descriptor)), + android::hardware::PrintInstanceNameToString); diff --git a/wifi/1.5/vts/functional/wifi_chip_hidl_ap_test.cpp b/wifi/1.5/vts/functional/wifi_chip_hidl_ap_test.cpp index 395d317065..922c9a767a 100644 --- a/wifi/1.5/vts/functional/wifi_chip_hidl_ap_test.cpp +++ b/wifi/1.5/vts/functional/wifi_chip_hidl_ap_test.cpp @@ -14,6 +14,7 @@ * limitations under the License. */ +#include <VtsCoreUtil.h> #include <VtsHalHidlTargetCallbackBase.h> #include <android-base/logging.h> @@ -51,6 +52,9 @@ using ::android::hardware::wifi::V1_5::IWifiChip; class WifiChipHidlTest : public ::testing::TestWithParam<std::string> { public: virtual void SetUp() override { + isBridgedSupport_ = testing::checkSubstringInCommandOutput( + "/system/bin/cmd wifi get-softap-supported-features", + "wifi_softap_bridged_ap_supported"); // Make sure to start with a clean state stopWifi(GetInstanceName()); @@ -61,6 +65,7 @@ class WifiChipHidlTest : public ::testing::TestWithParam<std::string> { virtual void TearDown() override { stopWifi(GetInstanceName()); } protected: + bool isBridgedSupport_ = false; // Helper function to configure the Chip in one of the supported modes. // Most of the non-mode-configuration-related methods require chip // to be first configured. @@ -71,19 +76,12 @@ class WifiChipHidlTest : public ::testing::TestWithParam<std::string> { return mode_id; } - WifiStatusCode createApIface(sp<IWifiApIface>* ap_iface) { - configureChipForIfaceType(IfaceType::AP, true); - const auto& status_and_iface = HIDL_INVOKE(wifi_chip_, createApIface); - *ap_iface = IWifiApIface::castFrom(status_and_iface.second); - return status_and_iface.first.code; - } - - WifiStatusCode createBridgedApIface(sp<IWifiApIface>* ap_iface) { + void createBridgedApIface(sp<IWifiApIface>* ap_iface) { configureChipForIfaceType(IfaceType::AP, true); const auto& status_and_iface = HIDL_INVOKE(wifi_chip_, createBridgedApIface); *ap_iface = status_and_iface.second; - return status_and_iface.first.code; + EXPECT_EQ(WifiStatusCode::SUCCESS, status_and_iface.first.code); } sp<IWifiChip> wifi_chip_; @@ -92,19 +90,25 @@ class WifiChipHidlTest : public ::testing::TestWithParam<std::string> { std::string GetInstanceName() { return GetParam(); } }; -// TODO: b/173999527. Add test for bridged API. - -/* - * resetToFactoryMacAddress +/** + * createBridgedApIface & removeIfaceInstanceFromBridgedApIface */ -TEST_P(WifiChipHidlTest, resetToFactoryMacAddressTest) { +TEST_P(WifiChipHidlTest, + createBridgedApIfaceAndremoveIfaceInstanceFromBridgedApIfaceTest) { + if (!isBridgedSupport_) GTEST_SKIP() << "Missing Bridged AP support"; sp<IWifiApIface> wifi_ap_iface; - const auto& status_code = createApIface(&wifi_ap_iface); - if (status_code != WifiStatusCode::SUCCESS) { - EXPECT_EQ(WifiStatusCode::ERROR_NOT_SUPPORTED, status_code); - } - const auto& status = HIDL_INVOKE(wifi_ap_iface, resetToFactoryMacAddress); - EXPECT_EQ(WifiStatusCode::SUCCESS, status.code); + createBridgedApIface(&wifi_ap_iface); + ASSERT_NE(nullptr, wifi_ap_iface.get()); + const auto& status_and_name = HIDL_INVOKE(wifi_ap_iface, getName); + EXPECT_EQ(WifiStatusCode::SUCCESS, status_and_name.first.code); + // TODO: b/173999527, add API to get instance name to replace it. + std::string br_name = status_and_name.second; // ap_br_ is the pre-fix + std::string instance_name = + br_name.substr(6, br_name.length()); // remove the pre-fex + const auto& status_code = + HIDL_INVOKE(wifi_chip_, removeIfaceInstanceFromBridgedApIface, br_name, + instance_name); + EXPECT_EQ(WifiStatusCode::SUCCESS, status_code.code); } GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(WifiChipHidlTest); diff --git a/wifi/1.5/vts/functional/wifi_chip_hidl_test.cpp b/wifi/1.5/vts/functional/wifi_chip_hidl_test.cpp index a0657218f4..36a8448d04 100644 --- a/wifi/1.5/vts/functional/wifi_chip_hidl_test.cpp +++ b/wifi/1.5/vts/functional/wifi_chip_hidl_test.cpp @@ -173,6 +173,20 @@ TEST_P(WifiChipHidlTest, setCoexUnsafeChannels) { } } +/* + * SetCountryCode: + * Ensures that a call to set the country code will return with a success + * status code. + */ +TEST_P(WifiChipHidlTest, setCountryCode) { + const android::hardware::hidl_array<int8_t, 2> kCountryCode{ + std::array<int8_t, 2>{{0x55, 0x53}}}; + + configureChipForIfaceType(IfaceType::STA, true); + EXPECT_EQ(WifiStatusCode::SUCCESS, + HIDL_INVOKE(wifi_chip_, setCountryCode, kCountryCode).code); +} + GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(WifiChipHidlTest); INSTANTIATE_TEST_SUITE_P( PerInstance, WifiChipHidlTest, diff --git a/wifi/1.5/vts/functional/wifi_hidl_test_utils_1_5.cpp b/wifi/1.5/vts/functional/wifi_hidl_test_utils_1_5.cpp new file mode 100644 index 0000000000..f1da2eae98 --- /dev/null +++ b/wifi/1.5/vts/functional/wifi_hidl_test_utils_1_5.cpp @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * 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 <VtsHalHidlTargetCallbackBase.h> +#include <android-base/logging.h> + +#undef NAN // NAN is defined in bionic/libc/include/math.h:38 + +#include <android/hardware/wifi/1.5/IWifi.h> +#include <android/hardware/wifi/1.5/IWifiApIface.h> +#include <android/hardware/wifi/1.5/IWifiChip.h> +#include <gtest/gtest.h> +#include <hidl/GtestPrinter.h> +#include <hidl/ServiceManagement.h> + +#include "wifi_hidl_call_util.h" +#include "wifi_hidl_test_utils.h" + +using ::android::sp; +using ::android::hardware::hidl_string; +using ::android::hardware::hidl_vec; +using ::android::hardware::Return; +using ::android::hardware::Void; +using ::android::hardware::wifi::V1_0::ChipModeId; +using ::android::hardware::wifi::V1_0::IfaceType; +using ::android::hardware::wifi::V1_5::IWifiApIface; +using ::android::hardware::wifi::V1_5::IWifiChip; + +sp<IWifiChip> getWifiChip_1_5(const std::string& instance_name) { + return IWifiChip::castFrom(getWifiChip(instance_name)); +} + +sp<IWifiApIface> getWifiApIface_1_5(const std::string& instance_name) { + ChipModeId mode_id; + sp<IWifiChip> wifi_chip_ = getWifiChip_1_5(instance_name); + configureChipToSupportIfaceType(wifi_chip_, IfaceType::AP, &mode_id); + const auto& status_and_iface = HIDL_INVOKE(wifi_chip_, createApIface); + return IWifiApIface::castFrom(status_and_iface.second); +} + +sp<IWifiApIface> getBridgedWifiApIface_1_5(const std::string& instance_name) { + ChipModeId mode_id; + sp<IWifiChip> wifi_chip_ = getWifiChip_1_5(instance_name); + configureChipToSupportIfaceType(wifi_chip_, IfaceType::AP, &mode_id); + const auto& status_and_iface = + HIDL_INVOKE(wifi_chip_, createBridgedApIface); + return IWifiApIface::castFrom(status_and_iface.second); +} diff --git a/wifi/1.5/vts/functional/wifi_hidl_test_utils_1_5.h b/wifi/1.5/vts/functional/wifi_hidl_test_utils_1_5.h new file mode 100644 index 0000000000..1b8b737632 --- /dev/null +++ b/wifi/1.5/vts/functional/wifi_hidl_test_utils_1_5.h @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * 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. + */ + +#pragma once + +#include <android/hardware/wifi/1.5/IWifi.h> +#include <android/hardware/wifi/1.5/IWifiApIface.h> +#include <android/hardware/wifi/1.5/IWifiChip.h> + +#include <getopt.h> + +#include <VtsHalHidlTargetTestEnvBase.h> +// Helper functions to obtain references to the various HIDL interface objects. +// Note: We only have a single instance of each of these objects currently. +// These helper functions should be modified to return vectors if we support +// multiple instances. +android::sp<android::hardware::wifi::V1_5::IWifiChip> getWifiChip_1_5( + const std::string& instance_name); +android::sp<android::hardware::wifi::V1_5::IWifiApIface> getWifiApIface_1_5( + const std::string& instance_name); +android::sp<android::hardware::wifi::V1_5::IWifiApIface> +getBridgedWifiApIface_1_5(const std::string& instance_name); diff --git a/wifi/hostapd/1.3/vts/functional/Android.bp b/wifi/hostapd/1.3/vts/functional/Android.bp index 07cebb018a..ed18bb69cd 100644 --- a/wifi/hostapd/1.3/vts/functional/Android.bp +++ b/wifi/hostapd/1.3/vts/functional/Android.bp @@ -22,12 +22,18 @@ cc_test { ], static_libs: [ "VtsHalWifiV1_0TargetTestUtil", + "VtsHalWifiV1_5TargetTestUtil", "VtsHalWifiHostapdV1_0TargetTestUtil", "android.hardware.wifi.hostapd@1.0", "android.hardware.wifi.hostapd@1.1", "android.hardware.wifi.hostapd@1.2", "android.hardware.wifi.hostapd@1.3", "android.hardware.wifi@1.0", + "android.hardware.wifi@1.1", + "android.hardware.wifi@1.2", + "android.hardware.wifi@1.3", + "android.hardware.wifi@1.4", + "android.hardware.wifi@1.5", "libgmock", "libwifi-system", "libwifi-system-iface", diff --git a/wifi/hostapd/1.3/vts/functional/hostapd_hidl_test.cpp b/wifi/hostapd/1.3/vts/functional/hostapd_hidl_test.cpp index a22252c2a5..4e63c56d2c 100644 --- a/wifi/hostapd/1.3/vts/functional/hostapd_hidl_test.cpp +++ b/wifi/hostapd/1.3/vts/functional/hostapd_hidl_test.cpp @@ -28,6 +28,7 @@ #include "hostapd_hidl_call_util.h" #include "hostapd_hidl_test_utils.h" +#include "wifi_hidl_test_utils_1_5.h" using ::android::sp; using ::android::hardware::hidl_string; @@ -38,6 +39,8 @@ using ::android::hardware::wifi::hostapd::V1_2::HostapdStatusCode; using ::android::hardware::wifi::hostapd::V1_2::Ieee80211ReasonCode; using ::android::hardware::wifi::hostapd::V1_3::IHostapd; using ::android::hardware::wifi::V1_0::IWifi; +using ::android::hardware::wifi::V1_0::WifiStatusCode; +using ::android::hardware::wifi::V1_5::IWifiApIface; namespace { constexpr unsigned char kNwSsid[] = {'t', 'e', 's', 't', '1', @@ -71,16 +74,39 @@ class HostapdHidlTest isWpa3SaeSupport_ = testing::checkSubstringInCommandOutput( "/system/bin/cmd wifi get-softap-supported-features", "wifi_softap_wpa3_sae_supported"); + isBridgedSupport_ = testing::checkSubstringInCommandOutput( + "/system/bin/cmd wifi get-softap-supported-features", + "wifi_softap_bridged_ap_supported"); } virtual void TearDown() override { HIDL_INVOKE_VOID_WITHOUT_ARGUMENTS(hostapd_, terminate); stopHostapd(wifi_instance_name_); + // Wait 3 seconds to allow driver processing load/unload between two + // test cases. + sleep(3); } protected: bool isWpa3SaeSupport_ = false; bool isAcsSupport_ = false; + bool isBridgedSupport_ = false; + + std::string setupApIfaceAndGetName(bool isBridged) { + sp<IWifiApIface> wifi_ap_iface; + if (isBridged) { + wifi_ap_iface = getBridgedWifiApIface_1_5(wifi_instance_name_); + } else { + wifi_ap_iface = getWifiApIface_1_5(wifi_instance_name_); + } + EXPECT_NE(nullptr, wifi_ap_iface.get()); + + const auto& status_and_name = HIDL_INVOKE(wifi_ap_iface, getName); + EXPECT_EQ(WifiStatusCode::SUCCESS, status_and_name.first.code); + return status_and_name.second; + } + + // TODO: b/177483254, remove it after fix wlan1 failure case. std::string getPrimaryWlanIfaceName() { std::array<char, PROPERTY_VALUE_MAX> buffer; auto res = property_get("ro.vendor.wifi.sap.interface", buffer.data(), @@ -90,7 +116,7 @@ class HostapdHidlTest return buffer.data(); } - IHostapd::IfaceParams getIfaceParamsWithoutAcs() { + IHostapd::IfaceParams getIfaceParamsWithoutAcs(std::string iface_name) { ::android::hardware::wifi::hostapd::V1_0::IHostapd::IfaceParams iface_params; ::android::hardware::wifi::hostapd::V1_1::IHostapd::IfaceParams @@ -106,7 +132,7 @@ class HostapdHidlTest ::android::hardware::wifi::hostapd::V1_3::IHostapd::ChannelParams channelParams_1_3; - iface_params.ifaceName = getPrimaryWlanIfaceName(); + iface_params.ifaceName = iface_name; iface_params.hwModeParams.enable80211N = true; iface_params.hwModeParams.enable80211AC = false; iface_params.channelParams.enableAcs = false; @@ -133,9 +159,41 @@ class HostapdHidlTest return iface_params_1_3; } - IHostapd::IfaceParams getIfaceParamsWithAcs() { + IHostapd::IfaceParams getIfaceParamsWithBridgedModeACS( + std::string iface_name) { + // First get the settings for WithoutAcs and then make changes + IHostapd::IfaceParams iface_params_1_3 = + getIfaceParamsWithoutAcs(iface_name); + iface_params_1_3.V1_2.V1_1.V1_0.channelParams.enableAcs = true; + iface_params_1_3.V1_2.V1_1.V1_0.channelParams.acsShouldExcludeDfs = + true; + + std::vector< + ::android::hardware::wifi::hostapd::V1_3::IHostapd::ChannelParams> + vec_channelParams; + + vec_channelParams.push_back(iface_params_1_3.channelParamsList[0]); + + ::android::hardware::wifi::hostapd::V1_3::IHostapd::ChannelParams + second_channelParams_1_3; + second_channelParams_1_3.channel = 0; + second_channelParams_1_3.enableAcs = true; + second_channelParams_1_3.bandMask = 0; + second_channelParams_1_3.bandMask |= IHostapd::BandMask::BAND_5_GHZ; + second_channelParams_1_3.V1_2 = iface_params_1_3.V1_2.channelParams; + second_channelParams_1_3.V1_2.bandMask = 0; + second_channelParams_1_3.V1_2.bandMask |= + IHostapd::BandMask::BAND_5_GHZ; + vec_channelParams.push_back(second_channelParams_1_3); + + iface_params_1_3.channelParamsList = vec_channelParams; + return iface_params_1_3; + } + + IHostapd::IfaceParams getIfaceParamsWithAcs(std::string iface_name) { // First get the settings for WithoutAcs and then make changes - IHostapd::IfaceParams iface_params_1_3 = getIfaceParamsWithoutAcs(); + IHostapd::IfaceParams iface_params_1_3 = + getIfaceParamsWithoutAcs(iface_name); iface_params_1_3.V1_2.V1_1.V1_0.channelParams.enableAcs = true; iface_params_1_3.V1_2.V1_1.V1_0.channelParams.acsShouldExcludeDfs = true; @@ -153,8 +211,10 @@ class HostapdHidlTest return iface_params_1_3; } - IHostapd::IfaceParams getIfaceParamsWithAcsAndFreqRange() { - IHostapd::IfaceParams iface_params_1_3 = getIfaceParamsWithAcs(); + IHostapd::IfaceParams getIfaceParamsWithAcsAndFreqRange( + std::string iface_name) { + IHostapd::IfaceParams iface_params_1_3 = + getIfaceParamsWithAcs(iface_name); ::android::hardware::wifi::hostapd::V1_2::IHostapd::AcsFrequencyRange acsFrequencyRange; acsFrequencyRange.start = 2412; @@ -170,9 +230,10 @@ class HostapdHidlTest return iface_params_1_3; } - IHostapd::IfaceParams getIfaceParamsWithAcsAndInvalidFreqRange() { + IHostapd::IfaceParams getIfaceParamsWithAcsAndInvalidFreqRange( + std::string iface_name) { IHostapd::IfaceParams iface_params_1_3 = - getIfaceParamsWithAcsAndFreqRange(); + getIfaceParamsWithAcsAndFreqRange(iface_name); iface_params_1_3.V1_2.channelParams.acsChannelFreqRangesMhz[0].start = 222; iface_params_1_3.V1_2.channelParams.acsChannelFreqRangesMhz[0].end = @@ -250,8 +311,10 @@ class HostapdHidlTest return nw_params_1_3; } - IHostapd::IfaceParams getIfaceParamsWithInvalidChannel() { - IHostapd::IfaceParams iface_params_1_3 = getIfaceParamsWithoutAcs(); + IHostapd::IfaceParams getIfaceParamsWithInvalidChannel( + std::string iface_name) { + IHostapd::IfaceParams iface_params_1_3 = + getIfaceParamsWithoutAcs(iface_name); iface_params_1_3.V1_2.V1_1.V1_0.channelParams.channel = kIfaceInvalidChannel; iface_params_1_3.channelParamsList[0].channel = @@ -271,8 +334,11 @@ class HostapdHidlTest */ TEST_P(HostapdHidlTest, AddPskAccessPointWithAcs) { if (!isAcsSupport_) GTEST_SKIP() << "Missing ACS support"; + // TODO: Use setupApIfaceAndGetName after fixing b/177483254 + // std::string ifname = setupApIfaceAndGetName(false); + std::string ifname = getPrimaryWlanIfaceName(); auto status = HIDL_INVOKE(hostapd_, addAccessPoint_1_3, - getIfaceParamsWithAcs(), getPskNwParams()); + getIfaceParamsWithAcs(ifname), getPskNwParams()); EXPECT_EQ(HostapdStatusCode::SUCCESS, status.code); } @@ -282,9 +348,12 @@ TEST_P(HostapdHidlTest, AddPskAccessPointWithAcs) { */ TEST_P(HostapdHidlTest, AddPskAccessPointWithAcsAndFreqRange) { if (!isAcsSupport_) GTEST_SKIP() << "Missing ACS support"; - auto status = - HIDL_INVOKE(hostapd_, addAccessPoint_1_3, - getIfaceParamsWithAcsAndFreqRange(), getPskNwParams()); + // TODO: Use setupApIfaceAndGetName after fixing b/177483254 + // std::string ifname = setupApIfaceAndGetName(false); + std::string ifname = getPrimaryWlanIfaceName(); + auto status = HIDL_INVOKE(hostapd_, addAccessPoint_1_3, + getIfaceParamsWithAcsAndFreqRange(ifname), + getPskNwParams()); EXPECT_EQ(HostapdStatusCode::SUCCESS, status.code); } @@ -294,8 +363,11 @@ TEST_P(HostapdHidlTest, AddPskAccessPointWithAcsAndFreqRange) { */ TEST_P(HostapdHidlTest, AddPskAccessPointWithAcsAndInvalidFreqRange) { if (!isAcsSupport_) GTEST_SKIP() << "Missing ACS support"; + // TODO: Use setupApIfaceAndGetName after fixing b/177483254 + // std::string ifname = setupApIfaceAndGetName(false); + std::string ifname = getPrimaryWlanIfaceName(); auto status = HIDL_INVOKE(hostapd_, addAccessPoint_1_3, - getIfaceParamsWithAcsAndInvalidFreqRange(), + getIfaceParamsWithAcsAndInvalidFreqRange(ifname), getPskNwParams()); EXPECT_NE(HostapdStatusCode::SUCCESS, status.code); } @@ -306,8 +378,11 @@ TEST_P(HostapdHidlTest, AddPskAccessPointWithAcsAndInvalidFreqRange) { */ TEST_P(HostapdHidlTest, AddOpenAccessPointWithAcs) { if (!isAcsSupport_) GTEST_SKIP() << "Missing ACS support"; + // TODO: Use setupApIfaceAndGetName after fixing b/177483254 + // std::string ifname = setupApIfaceAndGetName(false); + std::string ifname = getPrimaryWlanIfaceName(); auto status = HIDL_INVOKE(hostapd_, addAccessPoint_1_3, - getIfaceParamsWithAcs(), getOpenNwParams()); + getIfaceParamsWithAcs(ifname), getOpenNwParams()); EXPECT_EQ(HostapdStatusCode::SUCCESS, status.code); } @@ -316,8 +391,12 @@ TEST_P(HostapdHidlTest, AddOpenAccessPointWithAcs) { * Access point creation should pass. */ TEST_P(HostapdHidlTest, AddPskAccessPointWithoutAcs) { - auto status = HIDL_INVOKE(hostapd_, addAccessPoint_1_3, - getIfaceParamsWithoutAcs(), getPskNwParams()); + // TODO: Use setupApIfaceAndGetName after fixing b/177483254 + // std::string ifname = setupApIfaceAndGetName(false); + std::string ifname = getPrimaryWlanIfaceName(); + auto status = + HIDL_INVOKE(hostapd_, addAccessPoint_1_3, + getIfaceParamsWithoutAcs(ifname), getPskNwParams()); EXPECT_EQ(HostapdStatusCode::SUCCESS, status.code); } @@ -326,9 +405,12 @@ TEST_P(HostapdHidlTest, AddPskAccessPointWithoutAcs) { * Access point creation should pass. */ TEST_P(HostapdHidlTest, AddPskAccessPointWithoutAcsAndNonMetered) { - auto status = - HIDL_INVOKE(hostapd_, addAccessPoint_1_3, getIfaceParamsWithoutAcs(), - getPskNwParamsWithNonMetered()); + // TODO: Use setupApIfaceAndGetName after fixing b/177483254 + // std::string ifname = setupApIfaceAndGetName(false); + std::string ifname = getPrimaryWlanIfaceName(); + auto status = HIDL_INVOKE(hostapd_, addAccessPoint_1_3, + getIfaceParamsWithoutAcs(ifname), + getPskNwParamsWithNonMetered()); EXPECT_EQ(HostapdStatusCode::SUCCESS, status.code); } @@ -337,8 +419,12 @@ TEST_P(HostapdHidlTest, AddPskAccessPointWithoutAcsAndNonMetered) { * Access point creation should pass. */ TEST_P(HostapdHidlTest, AddOpenAccessPointWithoutAcs) { - auto status = HIDL_INVOKE(hostapd_, addAccessPoint_1_3, - getIfaceParamsWithoutAcs(), getOpenNwParams()); + // TODO: Use setupApIfaceAndGetName after fixing b/177483254 + // std::string ifname = setupApIfaceAndGetName(false); + std::string ifname = getPrimaryWlanIfaceName(); + auto status = + HIDL_INVOKE(hostapd_, addAccessPoint_1_3, + getIfaceParamsWithoutAcs(ifname), getOpenNwParams()); EXPECT_EQ(HostapdStatusCode::SUCCESS, status.code); } @@ -348,9 +434,12 @@ TEST_P(HostapdHidlTest, AddOpenAccessPointWithoutAcs) { */ TEST_P(HostapdHidlTest, AddSaeTransitionAccessPointWithoutAcs) { if (!isWpa3SaeSupport_) GTEST_SKIP() << "Missing SAE support"; - auto status = - HIDL_INVOKE(hostapd_, addAccessPoint_1_3, getIfaceParamsWithoutAcs(), - getSaeTransitionNwParams()); + // TODO: Use setupApIfaceAndGetName after fixing b/177483254 + // std::string ifname = setupApIfaceAndGetName(false); + std::string ifname = getPrimaryWlanIfaceName(); + auto status = HIDL_INVOKE(hostapd_, addAccessPoint_1_3, + getIfaceParamsWithoutAcs(ifname), + getSaeTransitionNwParams()); EXPECT_EQ(HostapdStatusCode::SUCCESS, status.code); } @@ -360,8 +449,12 @@ TEST_P(HostapdHidlTest, AddSaeTransitionAccessPointWithoutAcs) { */ TEST_P(HostapdHidlTest, AddSAEAccessPointWithoutAcs) { if (!isWpa3SaeSupport_) GTEST_SKIP() << "Missing SAE support"; - auto status = HIDL_INVOKE(hostapd_, addAccessPoint_1_3, - getIfaceParamsWithoutAcs(), getSaeNwParams()); + // TODO: Use setupApIfaceAndGetName after fixing b/177483254 + // std::string ifname = setupApIfaceAndGetName(false); + std::string ifname = getPrimaryWlanIfaceName(); + auto status = + HIDL_INVOKE(hostapd_, addAccessPoint_1_3, + getIfaceParamsWithoutAcs(ifname), getSaeNwParams()); EXPECT_EQ(HostapdStatusCode::SUCCESS, status.code); } @@ -371,11 +464,14 @@ TEST_P(HostapdHidlTest, AddSAEAccessPointWithoutAcs) { */ TEST_P(HostapdHidlTest, RemoveAccessPointWithAcs) { if (!isAcsSupport_) GTEST_SKIP() << "Missing ACS support"; - auto status_1_2 = HIDL_INVOKE(hostapd_, addAccessPoint_1_3, - getIfaceParamsWithAcs(), getPskNwParams()); + // TODO: Use setupApIfaceAndGetName after fixing b/177483254 + // std::string ifname = setupApIfaceAndGetName(false); + std::string ifname = getPrimaryWlanIfaceName(); + auto status_1_2 = + HIDL_INVOKE(hostapd_, addAccessPoint_1_3, getIfaceParamsWithAcs(ifname), + getPskNwParams()); EXPECT_EQ(HostapdStatusCode::SUCCESS, status_1_2.code); - auto status = - HIDL_INVOKE(hostapd_, removeAccessPoint, getPrimaryWlanIfaceName()); + auto status = HIDL_INVOKE(hostapd_, removeAccessPoint, ifname); EXPECT_EQ( android::hardware::wifi::hostapd::V1_0::HostapdStatusCode::SUCCESS, status.code); @@ -386,11 +482,14 @@ TEST_P(HostapdHidlTest, RemoveAccessPointWithAcs) { * Access point creation & removal should pass. */ TEST_P(HostapdHidlTest, RemoveAccessPointWithoutAcs) { - auto status_1_2 = HIDL_INVOKE(hostapd_, addAccessPoint_1_3, - getIfaceParamsWithoutAcs(), getPskNwParams()); + // TODO: Use setupApIfaceAndGetName after fixing b/177483254 + // std::string ifname = setupApIfaceAndGetName(false); + std::string ifname = getPrimaryWlanIfaceName(); + auto status_1_2 = + HIDL_INVOKE(hostapd_, addAccessPoint_1_3, + getIfaceParamsWithoutAcs(ifname), getPskNwParams()); EXPECT_EQ(HostapdStatusCode::SUCCESS, status_1_2.code); - auto status = - HIDL_INVOKE(hostapd_, removeAccessPoint, getPrimaryWlanIfaceName()); + auto status = HIDL_INVOKE(hostapd_, removeAccessPoint, ifname); EXPECT_EQ( android::hardware::wifi::hostapd::V1_0::HostapdStatusCode::SUCCESS, status.code); @@ -401,9 +500,12 @@ TEST_P(HostapdHidlTest, RemoveAccessPointWithoutAcs) { * Access point creation should fail. */ TEST_P(HostapdHidlTest, AddPskAccessPointWithInvalidChannel) { + // TODO: Use setupApIfaceAndGetName after fixing b/177483254 + // std::string ifname = setupApIfaceAndGetName(false); + std::string ifname = getPrimaryWlanIfaceName(); auto status = HIDL_INVOKE(hostapd_, addAccessPoint_1_3, - getIfaceParamsWithInvalidChannel(), getPskNwParams()); + getIfaceParamsWithInvalidChannel(ifname), getPskNwParams()); EXPECT_NE(HostapdStatusCode::SUCCESS, status.code); } @@ -412,9 +514,12 @@ TEST_P(HostapdHidlTest, AddPskAccessPointWithInvalidChannel) { * Access point creation should fail. */ TEST_P(HostapdHidlTest, AddInvalidPskAccessPointWithoutAcs) { + // TODO: Use setupApIfaceAndGetName after fixing b/177483254 + // std::string ifname = setupApIfaceAndGetName(false); + std::string ifname = getPrimaryWlanIfaceName(); auto status = - HIDL_INVOKE(hostapd_, addAccessPoint_1_3, getIfaceParamsWithoutAcs(), - getInvalidPskNwParams()); + HIDL_INVOKE(hostapd_, addAccessPoint_1_3, + getIfaceParamsWithoutAcs(ifname), getInvalidPskNwParams()); EXPECT_NE(HostapdStatusCode::SUCCESS, status.code); } @@ -424,9 +529,12 @@ TEST_P(HostapdHidlTest, AddInvalidPskAccessPointWithoutAcs) { */ TEST_P(HostapdHidlTest, AddInvalidSaeTransitionAccessPointWithoutAcs) { if (!isWpa3SaeSupport_) GTEST_SKIP() << "Missing SAE support"; - auto status = - HIDL_INVOKE(hostapd_, addAccessPoint_1_3, getIfaceParamsWithoutAcs(), - getInvalidSaeTransitionNwParams()); + // TODO: Use setupApIfaceAndGetName after fixing b/177483254 + // std::string ifname = setupApIfaceAndGetName(false); + std::string ifname = getPrimaryWlanIfaceName(); + auto status = HIDL_INVOKE(hostapd_, addAccessPoint_1_3, + getIfaceParamsWithoutAcs(ifname), + getInvalidSaeTransitionNwParams()); EXPECT_NE(HostapdStatusCode::SUCCESS, status.code); } @@ -436,9 +544,12 @@ TEST_P(HostapdHidlTest, AddInvalidSaeTransitionAccessPointWithoutAcs) { */ TEST_P(HostapdHidlTest, AddInvalidSaeAccessPointWithoutAcs) { if (!isWpa3SaeSupport_) GTEST_SKIP() << "Missing SAE support"; + // TODO: Use setupApIfaceAndGetName after fixing b/177483254 + // std::string ifname = setupApIfaceAndGetName(false); + std::string ifname = getPrimaryWlanIfaceName(); auto status = - HIDL_INVOKE(hostapd_, addAccessPoint_1_3, getIfaceParamsWithoutAcs(), - getInvalidSaeNwParams()); + HIDL_INVOKE(hostapd_, addAccessPoint_1_3, + getIfaceParamsWithoutAcs(ifname), getInvalidSaeNwParams()); EXPECT_NE(HostapdStatusCode::SUCCESS, status.code); } @@ -447,21 +558,30 @@ TEST_P(HostapdHidlTest, AddInvalidSaeAccessPointWithoutAcs) { * when hotspot interface available. */ TEST_P(HostapdHidlTest, DisconnectClientWhenIfacAvailable) { + // TODO: Use setupApIfaceAndGetName after fixing b/177483254 + // std::string ifname = setupApIfaceAndGetName(false); + std::string ifname = getPrimaryWlanIfaceName(); auto status_1_2 = - HIDL_INVOKE(hostapd_, addAccessPoint_1_3, getIfaceParamsWithoutAcs(), - getOpenNwParams()); + HIDL_INVOKE(hostapd_, addAccessPoint_1_3, + getIfaceParamsWithoutAcs(ifname), getOpenNwParams()); EXPECT_EQ(HostapdStatusCode::SUCCESS, status_1_2.code); - status_1_2 = - HIDL_INVOKE(hostapd_, forceClientDisconnect, getPrimaryWlanIfaceName(), - kTestZeroMacAddr, kTestDisconnectReasonCode); + status_1_2 = HIDL_INVOKE(hostapd_, forceClientDisconnect, ifname, + kTestZeroMacAddr, kTestDisconnectReasonCode); EXPECT_EQ(HostapdStatusCode::FAILURE_CLIENT_UNKNOWN, status_1_2.code); } /** * AddAccessPointWithDualBandConfig should pass */ -// TODO: Add it after VendorHal ready & add feature support check. +TEST_P(HostapdHidlTest, AddAccessPointWithDualBandConfig) { + if (!isBridgedSupport_) GTEST_SKIP() << "Missing Bridged AP support"; + std::string ifname = setupApIfaceAndGetName(true); + auto status_1_2 = HIDL_INVOKE(hostapd_, addAccessPoint_1_3, + getIfaceParamsWithBridgedModeACS(ifname), + getOpenNwParams()); + EXPECT_EQ(HostapdStatusCode::SUCCESS, status_1_2.code); +} GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(HostapdHidlTest); INSTANTIATE_TEST_CASE_P( diff --git a/wifi/supplicant/1.4/ISupplicantStaIfaceCallback.hal b/wifi/supplicant/1.4/ISupplicantStaIfaceCallback.hal index efcebdac00..c6f05fb953 100644 --- a/wifi/supplicant/1.4/ISupplicantStaIfaceCallback.hal +++ b/wifi/supplicant/1.4/ISupplicantStaIfaceCallback.hal @@ -20,7 +20,9 @@ import @1.0::ISupplicantStaIfaceCallback.AnqpData; import @1.0::ISupplicantStaIfaceCallback.Hs20AnqpData; import @1.3::ISupplicantStaIfaceCallback; import @1.0::ISupplicantStaIfaceCallback.State; +import @1.0::ISupplicantStaIfaceCallback.StatusCode; import @1.0::Bssid; +import @1.0::Ssid; /** * Callback Interface exposed by the supplicant service @@ -32,6 +34,19 @@ import @1.0::Bssid; */ interface ISupplicantStaIfaceCallback extends @1.3::ISupplicantStaIfaceCallback { /** + * MBO spec v1.2, 4.2.4 Table 14: MBO Association disallowed reason code attribute + * values. + */ + enum MboAssocDisallowedReasonCode : uint8_t { + RESERVED = 0, + UNSPECIFIED = 1, + MAX_NUM_STA_ASSOCIATED = 2, + AIR_INTERFACE_OVERLOADED = 3, + AUTH_SERVER_OVERLOADED = 4, + INSUFFICIENT_RSSI = 5, + }; + + /** * ANQP data for IEEE Std 802.11-2016. * The format of the data within these elements follows the IEEE * Std 802.11-2016 standard, section 9.4.5. @@ -49,6 +64,83 @@ interface ISupplicantStaIfaceCallback extends @1.3::ISupplicantStaIfaceCallback }; /** + * OceRssiBasedAssocRejectAttr is extracted from (Re-)Association response + * frame from an OCE AP to indicate that the AP has rejected the + * (Re-)Association request on the basis of insufficient RSSI. + * Refer OCE spec v1.0 section 4.2.2 Table 7. + */ + struct OceRssiBasedAssocRejectAttr { + /* + * Delta RSSI - The difference in dB between the minimum RSSI at which + * the AP would accept a (Re-)Association request from the STA before + * Retry Delay expires and the AP's measurement of the RSSI at which the + * (Re-)Association request was received. + */ + uint32_t deltaRssi; + + /* + * Retry Delay - The time period in seconds for which the AP will not + * accept any subsequent (Re-)Association requests from the STA, unless + * the received RSSI has improved by Delta RSSI. + */ + uint32_t retryDelayS; + }; + + /** + * Association Rejection related information. + */ + struct AssociationRejectionData { + /** + * SSID of the AP that rejected the association. + */ + Ssid ssid; + + /** + * BSSID of the AP that rejected the association. + */ + Bssid bssid; + + /* + * 802.11 code to indicate the reject reason. + * Refer to section 8.4.1.9 of IEEE 802.11 spec. + */ + StatusCode statusCode; + + /* + * Flag to indicate that failure is due to timeout rather than + * explicit rejection response from the AP. + */ + bool timedOut; + + /** + * Flag to indicate that MboAssocDisallowedReasonCode is present + * in the (Re-)Association response frame. + */ + bool isMboAssocDisallowedReasonCodePresent; + + /** + * mboAssocDisallowedReason is extracted from MBO association disallowed attribute + * in (Re-)Association response frame to indicate that the AP is not accepting new + * associations. + * Refer MBO spec v1.2 section 4.2.4 Table 13 for the details of reason code. + * The value is undefined if isMboAssocDisallowedReasonCodePresent is false. + */ + MboAssocDisallowedReasonCode mboAssocDisallowedReason; + + /** + * Flag to indicate that OceRssiBasedAssocRejectAttr is present + * in the (Re-)Association response frame. + */ + bool isOceRssiBasedAssocRejectAttrPresent; + + /* + * OCE RSSI-based (Re-)Association rejection attribute. + * The contents are undefined if isOceRssiBasedAssocRejectAttrPresent is false. + */ + OceRssiBasedAssocRejectAttr oceRssiBasedAssocRejectData; + }; + + /** * Used to indicate a Hotspot 2.0 terms and conditions acceptance is requested from the user * before allowing the device to get internet access. * @@ -68,4 +160,12 @@ interface ISupplicantStaIfaceCallback extends @1.3::ISupplicantStaIfaceCallback * All the fields in this struct must be empty if the query failed. */ oneway onAnqpQueryDone_1_4(Bssid bssid, AnqpData data, Hs20AnqpData hs20Data); + + /** + * Used to indicate an association rejection received from the AP + * to which the connection is being attempted. + * + * @param assocRejectData Association Rejection related information. + */ + oneway onAssociationRejected_1_4(AssociationRejectionData assocRejectData); }; diff --git a/wifi/supplicant/1.4/vts/functional/supplicant_sta_iface_hidl_test.cpp b/wifi/supplicant/1.4/vts/functional/supplicant_sta_iface_hidl_test.cpp index ccd469d4b3..1794a39530 100644 --- a/wifi/supplicant/1.4/vts/functional/supplicant_sta_iface_hidl_test.cpp +++ b/wifi/supplicant/1.4/vts/functional/supplicant_sta_iface_hidl_test.cpp @@ -232,6 +232,11 @@ class IfaceCallback : public ISupplicantStaIfaceCallback { override { return Void(); } + Return<void> onAssociationRejected_1_4( + const ISupplicantStaIfaceCallback::AssociationRejectionData& /* data */) + override { + return Void(); + } }; /* |