blob: c5eee9675b12ae9df195c2afcf0c54dc63ff047c [file] [log] [blame]
// Copyright 2018 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.
#include <limits.h>
#include <memory>
#include <stddef.h>
#include <lib/fidl/coding.h>
#include <lib/fidl/cpp/string_view.h>
#include <lib/fidl/cpp/vector_view.h>
#include <unittest/unittest.h>
#include <zircon/syscalls.h>
#include "fidl_coded_types.h"
#include "fidl_structs.h"
#include "fidl/extra_messages.h"
namespace fidl {
namespace {
bool linearize_present_nonnullable_string() {
BEGIN_TEST;
unbounded_nonnullable_string_inline_data message = {};
constexpr const char* kStr = "hello!";
constexpr size_t kLength = 6;
char some_other_string[kLength] = {0};
message.string = fidl_string_t{kLength, some_other_string};
memcpy(some_other_string, kStr, kLength);
const char* error = nullptr;
zx_status_t status;
uint32_t actual_num_bytes = 0;
// Manually compute linearized size
constexpr uint32_t buf_size = 32u + FIDL_ALIGN(kLength);
// Allocate a buffer of the specified size
std::unique_ptr<uint8_t[]> buf(new uint8_t[buf_size]);
status = fidl_linearize(&unbounded_nonnullable_string_message_type,
&message, buf.get(), buf_size, &actual_num_bytes, &error);
EXPECT_EQ(status, ZX_OK);
EXPECT_NULL(error, error);
EXPECT_EQ(actual_num_bytes, buf_size);
// Verify pointers and object packing
auto inline_data = reinterpret_cast<unbounded_nonnullable_string_inline_data*>(buf.get());
EXPECT_EQ(static_cast<void*>(inline_data->string.data),
static_cast<void*>(&buf[FIDL_ALIGN(sizeof(message))]));
EXPECT_BYTES_EQ(reinterpret_cast<const uint8_t*>("hello!"),
reinterpret_cast<const uint8_t*>(inline_data->string.data),
kLength,
"Secondary object string must be hello!");
END_TEST;
}
bool linearize_present_nonnullable_string_unaligned_error() {
BEGIN_TEST;
unbounded_nonnullable_string_inline_data message = {};
constexpr const char* kStr = "hello!";
constexpr size_t kLength = 6;
char some_other_string[kLength] = {0};
message.string = fidl_string_t{kLength, some_other_string};
memcpy(some_other_string, kStr, kLength);
const char* error = nullptr;
zx_status_t status;
uint32_t actual_num_bytes = 0;
// Pass in an unaligned storage
constexpr uint32_t buf_size = 32u + FIDL_ALIGN(kLength);
std::unique_ptr<uint8_t[]> buf(new uint8_t[buf_size * 2]);
uint8_t* unaligned_ptr = buf.get() + 1;
status = fidl_linearize(&unbounded_nonnullable_string_message_type,
&message, unaligned_ptr, buf_size, &actual_num_bytes, &error);
EXPECT_EQ(status, ZX_ERR_INVALID_ARGS);
EXPECT_NONNULL(error);
ASSERT_STR_STR(error, "must be aligned to FIDL_ALIGNMENT");
END_TEST;
}
bool linearize_present_nonnullable_longer_string() {
BEGIN_TEST;
unbounded_nonnullable_string_inline_data message = {};
constexpr const char* kStr = "hello world!";
constexpr size_t kLength = 12;
char some_other_string[kLength] = {0};
message.string = fidl_string_t{kLength, some_other_string};
memcpy(some_other_string, kStr, kLength);
const char* error = nullptr;
zx_status_t status;
uint32_t actual_num_bytes = 0;
// Manually compute linearized size
constexpr uint32_t buf_size = 32u + FIDL_ALIGN(kLength);
// For non-handle-containing structures, linearizing should not change anything
const unbounded_nonnullable_string_inline_data message_shallow_copy = message;
// Allocate a buffer of the specified size
std::unique_ptr<uint8_t[]> buf(new uint8_t[buf_size]);
status = fidl_linearize(&unbounded_nonnullable_string_message_type,
&message, buf.get(), buf_size, &actual_num_bytes, &error);
EXPECT_EQ(status, ZX_OK);
EXPECT_NULL(error, error);
EXPECT_EQ(actual_num_bytes, buf_size);
// Verify pointers and object packing
auto inline_data = reinterpret_cast<unbounded_nonnullable_string_inline_data*>(buf.get());
EXPECT_EQ(static_cast<void*>(inline_data->string.data),
static_cast<void*>(&buf[FIDL_ALIGN(sizeof(message))]));
EXPECT_BYTES_EQ(reinterpret_cast<const uint8_t*>("hello world!"),
reinterpret_cast<const uint8_t*>(inline_data->string.data),
kLength,
"Secondary object string must be hello world!");
// Verify that message is not destroyed
EXPECT_BYTES_EQ(reinterpret_cast<const uint8_t*>(&message_shallow_copy),
reinterpret_cast<uint8_t*>(&message),
sizeof(message),
"Input object should not change");
// Linearizing with a buffer size smaller than allowed should error out
status = fidl_linearize(&unbounded_nonnullable_string_message_type,
&message, buf.get(), buf_size - 1, nullptr, &error);
EXPECT_EQ(status, ZX_ERR_BUFFER_TOO_SMALL);
EXPECT_NONNULL(error, "Should report error when buffer too small");
END_TEST;
}
bool linearize_vector_of_uint32() {
BEGIN_TEST;
// Linearizing this array...
constexpr uint32_t array_len = 40;
std::unique_ptr<uint32_t[]> numbers(new uint32_t[array_len]);
for (uint32_t i = 0; i < array_len; i++) {
numbers[i] = i;
}
// into this buffer, which is big enough for the entire message
constexpr uint32_t buf_size = 512;
std::unique_ptr<uint8_t[]> buffer(new uint8_t[buf_size]);
unbounded_nonnullable_vector_of_uint32_inline_data message = {};
message.header.flags = 123;
message.header.ordinal = 456;
message.header.txid = 789;
message.vector = (fidl_vector_t) {
.count = array_len,
.data = numbers.get(),
};
const char* error = nullptr;
zx_status_t status;
uint32_t actual_num_bytes = 0;
status = fidl_linearize(&unbounded_nonnullable_vector_of_uint32_message_type,
&message,
buffer.get(),
buf_size,
&actual_num_bytes,
&error);
ASSERT_EQ(status, ZX_OK);
ASSERT_NULL(error, error);
ASSERT_GT(actual_num_bytes, sizeof(message));
// Verify that vector contents have been copied copied correctly
auto linearized_message =
reinterpret_cast<unbounded_nonnullable_vector_of_uint32_inline_data*>(buffer.get());
EXPECT_NONNULL(linearized_message->vector.data);
EXPECT_NE(linearized_message->vector.data, message.vector.data);
auto copied_numbers = reinterpret_cast<uint32_t*>(linearized_message->vector.data);
for (uint32_t i = 0; i < array_len; i++) {
EXPECT_EQ(copied_numbers[i], i);
}
EXPECT_EQ(memcmp(&message.header,
&linearized_message->header,
sizeof(message.header)), 0);
// Verify that linearizing with less number of bytes does fail
status = fidl_linearize(&unbounded_nonnullable_vector_of_uint32_message_type,
&message,
buffer.get(),
actual_num_bytes - 1,
nullptr,
&error);
EXPECT_EQ(status, ZX_ERR_BUFFER_TOO_SMALL);
EXPECT_NONNULL(error);
END_TEST;
}
bool linearize_vector_of_string() {
BEGIN_TEST;
// Define the memory-layout of the inline object
struct VectorOfStringRequest {
alignas(FIDL_ALIGNMENT)
fidl_message_header_t header;
fidl::VectorView<fidl::StringView> vector;
};
// Serialize these strings...
const char str1[] = "Open connection,";
const char str2[] = "Send the wrong FIDL message,";
const char str3[] = "Get an epitaph.";
// into this buffer, which is big enough for the entire message
constexpr uint32_t buf_size = 512;
std::unique_ptr<uint8_t[]> buffer(new uint8_t[buf_size]);
fidl::StringView strings[3] = {};
strings[0].set_data(const_cast<char*>(str1));
strings[0].set_size(sizeof(str1));
strings[1].set_data(const_cast<char*>(str2));
strings[1].set_size(sizeof(str2));
strings[2].set_data(const_cast<char*>(str3));
strings[2].set_size(sizeof(str3));
VectorOfStringRequest message;
message.vector.set_data(strings);
message.vector.set_count(3);
const char* error = nullptr;
zx_status_t status;
uint32_t actual_num_bytes = 0;
status = fidl_linearize(&fidl_test_coding_LinearizerTestVectorOfStringRequestTable,
&message,
buffer.get(),
buf_size,
&actual_num_bytes,
&error);
ASSERT_EQ(status, ZX_OK);
ASSERT_NULL(error, error);
ASSERT_GT(actual_num_bytes, sizeof(message));
// Verify that vector contents have been copied copied correctly
auto linearized_message =
reinterpret_cast<VectorOfStringRequest*>(buffer.get());
EXPECT_NONNULL(linearized_message->vector.data());
EXPECT_EQ(linearized_message->vector.count(), 3);
EXPECT_NE(linearized_message->vector[0].data(), str1);
EXPECT_BYTES_EQ(
reinterpret_cast<const uint8_t*>(linearized_message->vector[0].data()),
reinterpret_cast<const uint8_t*>(str1),
sizeof(str1),
str1);
EXPECT_NE(linearized_message->vector[1].data(), str2);
EXPECT_BYTES_EQ(
reinterpret_cast<const uint8_t*>(linearized_message->vector[1].data()),
reinterpret_cast<const uint8_t*>(str2),
sizeof(str2),
str2);
EXPECT_NE(linearized_message->vector[2].data(), str3);
EXPECT_BYTES_EQ(
reinterpret_cast<const uint8_t*>(linearized_message->vector[2].data()),
reinterpret_cast<const uint8_t*>(str3),
sizeof(str3),
str3);
END_TEST;
}
bool linearize_struct_with_handle() {
BEGIN_TEST;
constexpr zx_handle_t dummy_handle = static_cast<zx_handle_t>(42);
// Define the memory-layout of the inline object
struct StructWithHandle {
alignas(FIDL_ALIGNMENT)
zx_handle_t h;
int32_t foo;
};
// Since there are no out-of-line objects, the size is known
constexpr uint32_t buf_size = sizeof(StructWithHandle);
StructWithHandle message = {
.h = dummy_handle,
.foo = 0,
};
uint8_t buffer[buf_size];
const char* error = nullptr;
zx_status_t status;
uint32_t actual_num_bytes = 0;
status = fidl_linearize(&fidl_test_coding_StructWithHandleTable,
&message,
buffer,
buf_size,
&actual_num_bytes,
&error);
ASSERT_EQ(status, ZX_OK);
ASSERT_EQ(actual_num_bytes, sizeof(StructWithHandle));
ASSERT_NULL(error, error);
// Handles in the original object are moved
auto linearized_message = reinterpret_cast<StructWithHandle*>(buffer);
EXPECT_EQ(message.h, ZX_HANDLE_INVALID);
EXPECT_EQ(linearized_message->h, dummy_handle);
END_TEST;
}
bool linearize_struct_with_many_handles() {
BEGIN_TEST;
zx_handle_t dummy_handles[4] = {};
auto handle_value_at = [] (int i) ->zx_handle_t { return static_cast<zx_handle_t>(100 + i); };
for (int i = 0; i < 4; i++) {
dummy_handles[i] = handle_value_at(i);
}
// Define the memory-layout of the inline object
struct StructWithManyHandles {
alignas(FIDL_ALIGNMENT)
zx_handle_t h1;
zx_handle_t h2;
fidl::VectorView<zx_handle_t> hs;
};
fidl::VectorView<zx_handle_t> hs;
hs.set_count(2);
hs.set_data(&dummy_handles[2]);
constexpr uint32_t buf_size = 512;
StructWithManyHandles message = {
.h1 = dummy_handles[0],
.h2 = dummy_handles[1],
.hs = hs,
};
uint8_t buffer[buf_size];
const char* error = nullptr;
zx_status_t status;
uint32_t actual_num_bytes = 0;
status = fidl_linearize(&fidl_test_coding_StructWithManyHandlesTable,
&message,
buffer,
buf_size,
&actual_num_bytes,
&error);
ASSERT_EQ(status, ZX_OK);
ASSERT_GT(actual_num_bytes, sizeof(StructWithManyHandles));
ASSERT_NULL(error, error);
// Handles in the original object are moved
auto linearized_message = reinterpret_cast<StructWithManyHandles*>(buffer);
EXPECT_EQ(message.h1, ZX_HANDLE_INVALID);
EXPECT_EQ(message.h2, ZX_HANDLE_INVALID);
EXPECT_EQ(message.hs[0], ZX_HANDLE_INVALID);
EXPECT_EQ(message.hs[1], ZX_HANDLE_INVALID);
EXPECT_EQ(linearized_message->h1, handle_value_at(0));
EXPECT_EQ(linearized_message->h2, handle_value_at(1));
EXPECT_EQ(linearized_message->hs[0], handle_value_at(2));
EXPECT_EQ(linearized_message->hs[1], handle_value_at(3));
END_TEST;
}
bool linearize_simple_table() {
BEGIN_TEST;
SimpleTableEnvelopes envelopes = {};
SimpleTable simple_table;
simple_table.set_count(5);
simple_table.set_data(&envelopes.x);
IntStruct x = {10};
IntStruct y = {20};
envelopes.x.data = &x;
envelopes.y.data = &y;
// Attempt to linearize with different table schemas to verify evolution-compatibility
for (auto coding_table : {
&fidl_test_coding_SimpleTableTable,
&fidl_test_coding_NewerSimpleTableTable
}) {
constexpr uint32_t buf_size = 512;
uint8_t buffer[buf_size];
const char* error = nullptr;
zx_status_t status;
uint32_t actual_num_bytes = 0;
status = fidl_linearize(coding_table,
&simple_table,
buffer,
buf_size,
&actual_num_bytes,
&error);
EXPECT_EQ(status, ZX_OK);
EXPECT_GT(actual_num_bytes, sizeof(SimpleTable));
EXPECT_NULL(error, error);
// Verify object placement
const auto& linearized = *reinterpret_cast<SimpleTable*>(buffer);
EXPECT_EQ(reinterpret_cast<IntStruct*>(linearized[0].data)->v, 10);
EXPECT_EQ(reinterpret_cast<IntStruct*>(linearized[4].data)->v, 20);
// Verify auto-filling envelope header
for (int i = 0; i < 5; i++) {
if (i == 0 || i == 4) {
EXPECT_EQ(linearized[i].num_bytes, 8);
EXPECT_EQ(linearized[i].num_handles, 0);
} else {
EXPECT_EQ(linearized[i].num_bytes, 0);
EXPECT_EQ(linearized[i].num_handles, 0);
}
}
}
// Alternative version with only x set, such that we can use OlderSimpleTable
envelopes.y.data = nullptr;
// Attempt to linearize with different table schemas to verify evolution-compatibility
for (auto coding_table : {
&fidl_test_coding_OlderSimpleTableTable,
&fidl_test_coding_SimpleTableTable,
&fidl_test_coding_NewerSimpleTableTable
}) {
constexpr uint32_t buf_size = 512;
uint8_t buffer[buf_size];
const char* error = nullptr;
zx_status_t status;
uint32_t actual_num_bytes = 0;
status = fidl_linearize(coding_table,
&simple_table,
buffer,
buf_size,
&actual_num_bytes,
&error);
EXPECT_EQ(status, ZX_OK);
EXPECT_GT(actual_num_bytes, sizeof(SimpleTable));
EXPECT_NULL(error, error);
// Verify object placement
const auto& linearized = *reinterpret_cast<SimpleTable*>(buffer);
EXPECT_EQ(reinterpret_cast<IntStruct*>(linearized[0].data)->v, 10);
// Verify auto-filling envelope header
EXPECT_EQ(linearized[0].num_bytes, 8);
EXPECT_EQ(linearized[0].num_handles, 0);
for (int i = 1; i < 5; i++) {
EXPECT_EQ(linearized[i].num_bytes, 0);
EXPECT_EQ(linearized[i].num_handles, 0);
}
}
// If y is set,
envelopes.y.data = &y;
// but OlderSimpleTable is used, it should error as the walker does not know how to process y.
{
constexpr uint32_t buf_size = 512;
uint8_t buffer[buf_size];
const char* error = nullptr;
zx_status_t status;
uint32_t actual_num_bytes = 0;
status = fidl_linearize(&fidl_test_coding_OlderSimpleTableTable,
&simple_table,
buffer,
buf_size,
&actual_num_bytes,
&error);
EXPECT_EQ(status, ZX_ERR_INVALID_ARGS);
EXPECT_EQ(actual_num_bytes, 0);
EXPECT_NONNULL(error);
}
END_TEST;
}
namespace {
// Define the memory-layout of the inline object, for the group of tests below
using TableOfStruct = fidl::VectorView<fidl_envelope_t>;
struct TableOfStructEnvelopes {
alignas(FIDL_ALIGNMENT)
fidl_envelope_t a;
fidl_envelope_t b;
};
struct OrdinalOneStructWithHandle {
alignas(FIDL_ALIGNMENT)
zx_handle_t h;
int32_t foo;
};
struct OrdinalTwoStructWithManyHandles {
alignas(FIDL_ALIGNMENT)
zx_handle_t h1;
zx_handle_t h2;
fidl::VectorView<zx_handle_t> hs;
};
bool linearize_table_field_1() {
BEGIN_TEST;
TableOfStructEnvelopes envelopes = {};
TableOfStruct table;
table.set_count(1);
table.set_data(&envelopes.a);
constexpr zx_handle_t dummy_handle = static_cast<zx_handle_t>(42);
OrdinalOneStructWithHandle ordinal1 = {
.h = dummy_handle,
.foo = 0
};
envelopes.a.data = &ordinal1;
constexpr uint32_t buf_size = 512;
uint8_t buffer[buf_size];
const char* error = nullptr;
zx_status_t status;
uint32_t actual_num_bytes = 0;
status = fidl_linearize(&fidl_test_coding_TableOfStructWithHandleTable,
&table,
buffer,
buf_size,
&actual_num_bytes,
&error);
EXPECT_EQ(status, ZX_OK);
EXPECT_GT(actual_num_bytes, sizeof(TableOfStruct));
EXPECT_NULL(error, error);
// Verify that handles have been moved
const auto& linearized = *reinterpret_cast<TableOfStruct*>(buffer);
EXPECT_EQ(reinterpret_cast<OrdinalOneStructWithHandle*>(linearized[0].data)->h, dummy_handle);
EXPECT_EQ(ordinal1.h, ZX_HANDLE_INVALID);
// Verify auto-filling envelope header
EXPECT_EQ(linearized[0].num_bytes, sizeof(ordinal1));
EXPECT_EQ(linearized[0].num_handles, 1);
END_TEST;
}
bool linearize_table_field_2() {
BEGIN_TEST;
TableOfStructEnvelopes envelopes = {};
TableOfStruct table;
table.set_count(2);
table.set_data(&envelopes.a);
zx_handle_t dummy_handles[4] = {};
auto handle_value_at = [] (int i) ->zx_handle_t { return static_cast<zx_handle_t>(100 + i); };
for (int i = 0; i < 4; i++) {
dummy_handles[i] = handle_value_at(i);
}
fidl::VectorView<zx_handle_t> hs;
hs.set_count(2);
hs.set_data(&dummy_handles[2]);
OrdinalTwoStructWithManyHandles ordinal2 = {
.h1 = dummy_handles[0],
.h2 = dummy_handles[1],
.hs = hs,
};
envelopes.b.data = &ordinal2;
constexpr uint32_t buf_size = 512;
uint8_t buffer[buf_size];
const char* error = nullptr;
zx_status_t status;
uint32_t actual_num_bytes = 0;
status = fidl_linearize(&fidl_test_coding_TableOfStructWithHandleTable,
&table,
buffer,
buf_size,
&actual_num_bytes,
&error);
EXPECT_EQ(status, ZX_OK);
EXPECT_GT(actual_num_bytes, sizeof(TableOfStruct));
EXPECT_NULL(error, error);
// Verify that handles have been moved
const auto& linearized = *reinterpret_cast<TableOfStruct*>(buffer);
auto linearized_field = reinterpret_cast<OrdinalTwoStructWithManyHandles*>(linearized[1].data);
ASSERT_NONNULL(linearized_field);
EXPECT_EQ(linearized_field->h1, handle_value_at(0));
EXPECT_EQ(ordinal2.h1, ZX_HANDLE_INVALID);
EXPECT_EQ(linearized_field->h2, handle_value_at(1));
EXPECT_EQ(ordinal2.h2, ZX_HANDLE_INVALID);
EXPECT_EQ(linearized_field->hs[0], handle_value_at(2));
EXPECT_EQ(ordinal2.hs[0], ZX_HANDLE_INVALID);
EXPECT_EQ(linearized_field->hs[1], handle_value_at(3));
EXPECT_EQ(ordinal2.hs[1], ZX_HANDLE_INVALID);
// Verify auto-filling envelope header
EXPECT_EQ(linearized[0].num_bytes, 0);
EXPECT_EQ(linearized[0].num_handles, 0);
EXPECT_EQ(linearized[1].num_bytes,
FIDL_ALIGN(sizeof(ordinal2)) + sizeof(zx_handle_t) * hs.count());
EXPECT_EQ(linearized[1].num_handles, 4);
END_TEST;
}
} // namespace
bool linearize_xunion_empty_invariant_empty() {
BEGIN_TEST;
// Non-zero ordinal with empty envelope is an error
SampleXUnionStruct xunion = {};
xunion.xu.header = (fidl_xunion_t) {
.tag = kSampleXUnionIntStructOrdinal,
.padding = 0,
.envelope = {}
};
constexpr uint32_t buf_size = 512;
uint8_t buffer[buf_size];
const char* error = nullptr;
zx_status_t status;
uint32_t actual_num_bytes = 0;
status = fidl_linearize(&fidl_test_coding_SampleXUnionStructTable,
&xunion,
buffer,
buf_size,
&actual_num_bytes,
&error);
EXPECT_EQ(status, ZX_ERR_INVALID_ARGS);
EXPECT_NONNULL(error);
EXPECT_STR_EQ(error, "empty xunion must have zero as ordinal");
END_TEST;
}
bool linearize_xunion_empty_invariant_zero_ordinal() {
BEGIN_TEST;
// Zero ordinal with non-empty envelope is an error
IntStruct int_struct = {
.v = 100
};
SampleXUnionStruct xunion = {};
xunion.xu.header = (fidl_xunion_t) {
.tag = 0,
.padding = 0,
.envelope = (fidl_envelope_t) {
.num_bytes = 8,
.num_handles = 0,
.data = &int_struct
}
};
constexpr uint32_t buf_size = 512;
uint8_t buffer[buf_size];
const char* error = nullptr;
zx_status_t status;
uint32_t actual_num_bytes = 0;
status = fidl_linearize(&fidl_test_coding_SampleXUnionStructTable,
&xunion,
buffer,
buf_size,
&actual_num_bytes,
&error);
EXPECT_EQ(status, ZX_ERR_INVALID_ARGS);
EXPECT_NONNULL(error);
EXPECT_STR_EQ(error, "xunion with zero as ordinal must be empty");
END_TEST;
}
BEGIN_TEST_CASE(strings)
RUN_TEST(linearize_present_nonnullable_string)
RUN_TEST(linearize_present_nonnullable_longer_string)
END_TEST_CASE(strings)
BEGIN_TEST_CASE(unaligned)
RUN_TEST(linearize_present_nonnullable_string_unaligned_error)
END_TEST_CASE(unaligned)
BEGIN_TEST_CASE(vectors)
RUN_TEST(linearize_vector_of_uint32)
RUN_TEST(linearize_vector_of_string)
END_TEST_CASE(vectors)
BEGIN_TEST_CASE(handles)
RUN_TEST(linearize_struct_with_handle)
RUN_TEST(linearize_struct_with_many_handles)
END_TEST_CASE(handles)
BEGIN_TEST_CASE(tables)
RUN_TEST(linearize_simple_table)
RUN_TEST(linearize_table_field_1)
RUN_TEST(linearize_table_field_2)
END_TEST_CASE(tables)
BEGIN_TEST_CASE(xunions)
RUN_TEST(linearize_xunion_empty_invariant_empty)
RUN_TEST(linearize_xunion_empty_invariant_zero_ordinal)
END_TEST_CASE(xunions)
} // namespace
} // namespace fidl