| // Copyright 2019 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #ifndef SRC_LIB_FIDL_LLCPP_TESTS_CONFORMANCE_CONFORMANCE_UTILS_H_ |
| #define SRC_LIB_FIDL_LLCPP_TESTS_CONFORMANCE_CONFORMANCE_UTILS_H_ |
| |
| #include <lib/fidl/llcpp/coding.h> |
| #include <lib/fidl/llcpp/internal/transport_channel.h> |
| #include <lib/fidl/llcpp/message.h> |
| #include <lib/fidl/llcpp/traits.h> |
| #include <lib/fidl/transformer.h> |
| #include <zircon/fidl.h> |
| #include <zircon/status.h> |
| #include <zircon/types.h> |
| |
| #ifdef __Fuchsia__ |
| #include <lib/zx/channel.h> |
| #endif |
| |
| #include <cstddef> |
| #include <cstdint> |
| #include <iostream> |
| #include <vector> |
| |
| #ifndef __Fuchsia__ |
| // The current build rules for zircon/system/ulib/zircon don't allow linking |
| // zx_status_get_string on host. Consider changing in the future. |
| #define zx_status_get_string(status) ((status)) |
| #endif |
| |
| // Testing utilities indended for GIDL-generated conformance tests. |
| namespace llcpp_conformance_utils { |
| |
| inline bool operator==(zx_handle_disposition_t a, zx_handle_disposition_t b) { |
| return a.operation == b.operation && a.handle == b.handle && a.type == b.type && |
| a.rights == b.rights && a.result == b.result; |
| } |
| inline bool operator!=(zx_handle_disposition_t a, zx_handle_disposition_t b) { return !(a == b); } |
| inline std::ostream& operator<<(std::ostream& os, const zx_handle_disposition_t& hd) { |
| return os << "zx_handle_disposition_t{\n" |
| << " .operation = " << hd.operation << "\n" |
| << " .handle = " << hd.handle << "\n" |
| << " .type = " << hd.type << "\n" |
| << " .rights = " << hd.rights << "\n" |
| << " .result = " << hd.result << "\n" |
| << "}\n"; |
| } |
| |
| template <typename T> |
| bool ComparePayload(const T* actual, size_t actual_size, const T* expected, size_t expected_size) { |
| bool pass = true; |
| for (size_t i = 0; i < actual_size && i < expected_size; i++) { |
| if (actual[i] != expected[i]) { |
| pass = false; |
| if constexpr (std::is_same_v<T, zx_handle_disposition_t>) { |
| std::cout << std::dec << "element[" << i << "]: actual=" << actual[i] |
| << " expected=" << expected[i]; |
| } else { |
| std::cout << std::dec << "element[" << i << "]: " << std::hex << "actual=0x" << +actual[i] |
| << " " |
| << "expected=0x" << +expected[i] << "\n"; |
| } |
| } |
| } |
| if (actual_size != expected_size) { |
| pass = false; |
| std::cout << std::dec << "element[...]: " |
| << "actual.size=" << +actual_size << " " |
| << "expected.size=" << +expected_size << "\n"; |
| } |
| return pass; |
| } |
| |
| // Verifies that |value| encodes to |bytes|. |
| // Note: This is destructive to |value| - a new value must be created with each call. |
| template <typename FidlType> |
| bool EncodeSuccess(fidl::internal::WireFormatVersion wire_format_version, FidlType* value, |
| const std::vector<uint8_t>& bytes, |
| const std::vector<zx_handle_disposition_t>& handle_dispositions, |
| bool check_handle_rights) { |
| static_assert(fidl::IsFidlType<FidlType>::value, "FIDL type required"); |
| |
| ::fidl::OwnedEncodedMessage<FidlType> encoded_v1(fidl::internal::AllowUnownedInputRef{}, value); |
| if (!encoded_v1.ok()) { |
| std::cout << "Encoding failed: " << encoded_v1.error() << std::endl; |
| return false; |
| } |
| ::fidl::OutgoingMessage& outgoing_v1 = encoded_v1.GetOutgoingMessage(); |
| for (uint32_t i = 0; i < outgoing_v1.iovec_actual(); i++) { |
| if (outgoing_v1.iovecs()[i].buffer == nullptr) { |
| std::cout << "Iovec " << i << " unexpectedly had a null buffer" << std::endl; |
| return false; |
| } |
| if (outgoing_v1.iovecs()[i].capacity == 0) { |
| std::cout << "Iovec " << i << " had zero capacity" << std::endl; |
| return false; |
| } |
| if (outgoing_v1.iovecs()[i].reserved != 0) { |
| std::cout << "Iovec " << i << " had a non-zero reserved field" << std::endl; |
| return false; |
| } |
| } |
| |
| // Populate c_msg for the given wire format-encoded bytes. |
| // For v1, just re-use the already encoded v1 bytes. |
| // For v2, encode v1, transcode v1 to v2, decode v2 and re-encode v2 to get |
| // the message bytes. |
| // TODO(fxbug.dev/83220) Re-enable disabled GIDL tests when removing this. |
| fidl_outgoing_msg_t c_msg; |
| std::unique_ptr<uint8_t[]> transformer_buffer; |
| std::unique_ptr<zx_channel_iovec_t[]> iovec_buffer; |
| std::unique_ptr<uint8_t[]> backing_buffer; |
| std::unique_ptr<zx_handle_t[]> handle_buffer; |
| std::unique_ptr<fidl_channel_handle_metadata_t[]> handle_metadata_buffer; |
| switch (wire_format_version) { |
| case fidl::internal::WireFormatVersion::kV1: { |
| c_msg = std::move(outgoing_v1).ReleaseToEncodedCMessage(); |
| break; |
| } |
| case fidl::internal::WireFormatVersion::kV2: { |
| auto copied_bytes = outgoing_v1.CopyBytes(); |
| std::vector<zx_handle_info_t> handle_infos; |
| fidl_channel_handle_metadata_t* handle_metadata = |
| static_cast<fidl_channel_handle_metadata_t*>(outgoing_v1.handle_metadata()); |
| for (uint32_t i = 0; i < outgoing_v1.handle_actual(); i++) { |
| handle_infos.push_back({ |
| .handle = outgoing_v1.handles()[i], |
| .type = handle_metadata[i].obj_type, |
| .rights = handle_metadata[i].rights, |
| }); |
| } |
| |
| transformer_buffer = std::make_unique<uint8_t[]>(ZX_CHANNEL_MAX_MSG_BYTES); |
| uint32_t num_transformer_bytes; |
| const char* error; |
| zx_status_t status = internal__fidl_transform__may_break( |
| FIDL_TRANSFORMATION_V1_TO_V2, FidlType::Type, copied_bytes.data(), |
| static_cast<uint32_t>(copied_bytes.size()), transformer_buffer.get(), |
| ZX_CHANNEL_MAX_MSG_BYTES, &num_transformer_bytes, &error); |
| if (status != ZX_OK) { |
| std::cout << "Transformer exited with status: " << status << " (error: " << error << ")" |
| << std::endl; |
| return false; |
| } |
| |
| status = internal_fidl_decode_etc__v2__may_break( |
| FidlType::Type, transformer_buffer.get(), num_transformer_bytes, handle_infos.data(), |
| static_cast<uint32_t>(handle_infos.size()), &error); |
| if (status != ZX_OK) { |
| std::cout << "V2 decoder exited with status: " << status << " (error: " << error << ")" |
| << std::endl; |
| return false; |
| } |
| |
| iovec_buffer = std::make_unique<zx_channel_iovec_t[]>(ZX_CHANNEL_MAX_MSG_IOVECS); |
| backing_buffer = std::make_unique<uint8_t[]>(ZX_CHANNEL_MAX_MSG_BYTES); |
| handle_buffer = std::make_unique<zx_handle_t[]>(ZX_CHANNEL_MAX_MSG_HANDLES); |
| handle_metadata_buffer = |
| std::make_unique<fidl_channel_handle_metadata_t[]>(ZX_CHANNEL_MAX_MSG_HANDLES); |
| uint32_t actual_iovecs; |
| uint32_t actual_handles; |
| status = ::fidl::internal::EncodeIovecEtc<FIDL_WIRE_FORMAT_VERSION_V2>( |
| fidl::internal::ChannelTransport::EncodingConfiguration, FidlType::Type, |
| transformer_buffer.get(), iovec_buffer.get(), ZX_CHANNEL_MAX_MSG_IOVECS, |
| handle_buffer.get(), handle_metadata_buffer.get(), ZX_CHANNEL_MAX_MSG_HANDLES, |
| backing_buffer.get(), ZX_CHANNEL_MAX_MSG_BYTES, &actual_iovecs, &actual_handles, &error); |
| if (status != ZX_OK) { |
| std::cout << "V2 encoder exited with status: " << status << " (error: " << error << ")" |
| << std::endl; |
| return false; |
| } |
| |
| c_msg.type = FIDL_OUTGOING_MSG_TYPE_IOVEC; |
| c_msg.iovec.iovecs = iovec_buffer.get(); |
| c_msg.iovec.num_iovecs = actual_iovecs; |
| c_msg.iovec.handles = handle_buffer.get(); |
| c_msg.iovec.handle_metadata = handle_metadata_buffer.get(); |
| c_msg.iovec.num_handles = actual_handles; |
| |
| break; |
| } |
| } |
| |
| auto outgoing = fidl::OutgoingMessage::FromEncodedCMessage(&c_msg); |
| for (uint32_t i = 0; i < outgoing.iovec_actual(); i++) { |
| if (outgoing.iovecs()[i].buffer == nullptr) { |
| std::cout << "Iovec " << i << " unexpectedly had a null buffer" << std::endl; |
| return false; |
| } |
| if (outgoing.iovecs()[i].capacity == 0) { |
| std::cout << "Iovec " << i << " had zero capacity" << std::endl; |
| return false; |
| } |
| if (outgoing.iovecs()[i].reserved != 0) { |
| std::cout << "Iovec " << i << " had a non-zero reserved field" << std::endl; |
| return false; |
| } |
| } |
| auto encoded_bytes = outgoing.CopyBytes(); |
| bool bytes_match = |
| ComparePayload(encoded_bytes.data(), encoded_bytes.size(), bytes.data(), bytes.size()); |
| bool handles_match = false; |
| if (check_handle_rights) { |
| std::unique_ptr<zx_handle_disposition_t[]> outgoing_handle_dispositions = |
| std::make_unique<zx_handle_disposition_t[]>(outgoing.handle_actual()); |
| fidl_channel_handle_metadata_t* handle_metadata = |
| static_cast<fidl_channel_handle_metadata_t*>(outgoing.handle_metadata()); |
| for (uint32_t i = 0; i < outgoing.handle_actual(); i++) { |
| outgoing_handle_dispositions[i] = zx_handle_disposition_t{ |
| .operation = ZX_HANDLE_OP_MOVE, |
| .handle = outgoing.handles()[i], |
| .type = handle_metadata[i].obj_type, |
| .rights = handle_metadata[i].rights, |
| }; |
| } |
| handles_match = ComparePayload(outgoing_handle_dispositions.get(), outgoing.handle_actual(), |
| handle_dispositions.data(), handle_dispositions.size()); |
| } else { |
| std::vector<zx_handle_t> outgoing_msg_handles; |
| std::vector<zx_handle_t> expected_handles; |
| for (size_t i = 0; i < outgoing.handle_actual(); i++) { |
| outgoing_msg_handles.push_back(outgoing.handles()[i]); |
| } |
| for (const auto& handle_disposition : handle_dispositions) { |
| expected_handles.push_back(handle_disposition.handle); |
| } |
| handles_match = ComparePayload(outgoing_msg_handles.data(), outgoing_msg_handles.size(), |
| expected_handles.data(), expected_handles.size()); |
| } |
| return bytes_match && handles_match; |
| } |
| |
| // Verifies that |value| fails to encode, with the expected error code. |
| // Note: This is destructive to |value| - a new value must be created with each call. |
| template <typename FidlType> |
| bool EncodeFailure(FidlType* value, zx_status_t expected_error_code) { |
| static_assert(fidl::IsFidlType<FidlType>::value, "FIDL type required"); |
| |
| ::fidl::OwnedEncodedMessage<FidlType> encoded(fidl::internal::AllowUnownedInputRef{}, value); |
| if (encoded.ok()) { |
| std::cout << "Encoding unexpectedly succeeded" << std::endl; |
| return false; |
| } |
| if (encoded.status() != expected_error_code) { |
| std::cout << "Encoding failed with error code " << zx_status_get_string(encoded.status()) |
| << " (" << encoded.error() << "), but expected error code " |
| << zx_status_get_string(expected_error_code) << std::endl; |
| return false; |
| } |
| return true; |
| } |
| |
| // Verifies that |bytes| decodes to an object that is the same as |value|. |
| // EqualityCheck is a callable with the signature |bool EqualityCheck(FidlType& actual)| |
| // that performs deep equality and compares handles based on koid, type and rights. |
| template <typename FidlType, typename EqualityCheck> |
| bool DecodeSuccess(fidl::internal::WireFormatVersion wire_format_version, FidlType* value, |
| std::vector<uint8_t> bytes, std::vector<zx_handle_info_t> handle_infos, |
| EqualityCheck equality_check) { |
| static_assert(fidl::IsFidlType<FidlType>::value, "FIDL type required"); |
| std::vector<zx_handle_t> handles; |
| std::vector<fidl_channel_handle_metadata_t> handle_metadata; |
| for (zx_handle_info_t handle_info : handle_infos) { |
| handles.push_back(handle_info.handle); |
| handle_metadata.push_back(fidl_channel_handle_metadata_t{ |
| .obj_type = handle_info.type, |
| .rights = handle_info.rights, |
| }); |
| } |
| fidl::DecodedMessage<FidlType> decoded( |
| wire_format_version, bytes.data(), static_cast<uint32_t>(bytes.size()), handles.data(), |
| handle_metadata.data(), static_cast<uint32_t>(handle_infos.size())); |
| if (!decoded.ok()) { |
| std::cout << "Decoding failed: " << decoded.error() << std::endl; |
| return false; |
| } |
| return equality_check(*decoded.PrimaryObject()); |
| } |
| |
| // Verifies that |bytes| fails to decode as |FidlType|, with the expected error |
| // code. |
| template <typename FidlType> |
| bool DecodeFailure(fidl::internal::WireFormatVersion wire_format_version, |
| std::vector<uint8_t> bytes, std::vector<zx_handle_info_t> handle_infos, |
| zx_status_t expected_error_code) { |
| static_assert(fidl::IsFidlType<FidlType>::value, "FIDL type required"); |
| std::vector<zx_handle_t> handles; |
| std::vector<fidl_channel_handle_metadata_t> handle_metadata; |
| for (zx_handle_info_t handle_info : handle_infos) { |
| handles.push_back(handle_info.handle); |
| handle_metadata.push_back(fidl_channel_handle_metadata_t{ |
| .obj_type = handle_info.type, |
| .rights = handle_info.rights, |
| }); |
| } |
| fidl::DecodedMessage<FidlType> decoded( |
| wire_format_version, bytes.data(), static_cast<uint32_t>(bytes.size()), handles.data(), |
| handle_metadata.data(), static_cast<uint32_t>(handle_infos.size())); |
| if (decoded.ok()) { |
| std::cout << "Decoding unexpectedly succeeded" << std::endl; |
| return false; |
| } |
| if (decoded.status() != expected_error_code) { |
| std::cout << "Decoding failed with error code " << zx_status_get_string(decoded.status()) |
| << " (" << decoded.error() << "), but expected error code " |
| << zx_status_get_string(expected_error_code) << std::endl; |
| return false; |
| } |
| return true; |
| } |
| |
| constexpr inline uint64_t FidlAlign(uint32_t offset) { |
| constexpr uint64_t alignment_mask = FIDL_ALIGNMENT - 1; |
| return (offset + alignment_mask) & ~alignment_mask; |
| } |
| |
| } // namespace llcpp_conformance_utils |
| |
| #endif // SRC_LIB_FIDL_LLCPP_TESTS_CONFORMANCE_CONFORMANCE_UTILS_H_ |