| # Copyright 2023 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. | |
| # Import needed for tests to work | |
| import typing | |
| import unittest # NOQA | |
| import common | |
| from fidl_codec import decode_fidl_request, encode_fidl_message, method_ordinal | |
| class EncodeObj(object): | |
| """Just a generic object to be used for encoding tests.""" | |
| class Encode(common.FuchsiaControllerTest): | |
| """Fuchsia Controller FIDL Encoding Tests""" | |
| def test_encode_noop_function(self) -> None: | |
| EXPECTED = bytearray.fromhex( | |
| "020000000200000139300000000000000600000000000000ffffffffffffffff666f6f6261720000" | |
| ) | |
| obj = EncodeObj() | |
| setattr(obj, "value", "foobar") | |
| (b, h) = encode_fidl_message( | |
| object=obj, | |
| library="fuchsia.controller.test", | |
| type_name="fuchsia.controller.test/NoopDoStringNoopRequest", | |
| txid=2, | |
| ordinal=12345, | |
| ) | |
| self.assertEqual(b, EXPECTED) | |
| self.assertEqual(h, []) | |
| def test_encode_struct_missing_field(self) -> None: | |
| with self.assertRaises(AttributeError): | |
| encode_fidl_message( | |
| object=object(), | |
| library="fuchsia.controller.test", | |
| type_name="fuchsia.controller.test/NoopDoStringNoopRequest", | |
| txid=1, | |
| ordinal=12345, | |
| ) | |
| def test_encode_non_nullable_value(self) -> None: | |
| obj = EncodeObj() | |
| setattr(obj, "value", None) | |
| with self.assertRaises(TypeError): | |
| encode_fidl_message( | |
| object=obj, | |
| library="fuchsia.controller.test", | |
| type_name="fuchsia.controller.test/NoopDoIntNoopRequest", | |
| txid=3, | |
| ordinal=12345, | |
| ) | |
| def test_encode_table_and_union(self) -> None: | |
| EXPECTED = bytearray.fromhex( | |
| "4d00000002000001b8220000000000000300000000000000ffffffffffffffff0000004000000100180000000000000018000000000000000600000000000000ffffffffffffffff666f6f6261720000030000000000000008000000000000000500000000000000" | |
| ) | |
| obj = typing.cast(typing.Any, EncodeObj()) | |
| setattr(obj, "tab", EncodeObj()) | |
| setattr(obj.tab, "dub", 2.0) | |
| setattr(obj.tab, "str_", "foobar") | |
| setattr(obj.tab, "union_field", EncodeObj()) | |
| setattr(obj.tab.union_field, "union_int", 5) | |
| # This intentionally omits the integer field rather than explicitly setting it to None. | |
| # This test covers that the error bit is successfully cleared when converting a table to | |
| # something where an attribute is missing rather than set to None. Without clearing the | |
| # error properly this will raise an exception. This is the same reason union_int is used | |
| # above. When iterating over the object, "union_str" will be checked first, which will | |
| # return nullptr and set a "AttributeError" exception. | |
| def encode_fn(x: typing.Any) -> typing.Any: | |
| return encode_fidl_message( | |
| object=x, | |
| library="fuchsia.controller.test", | |
| type_name="fuchsia.controller.test/NoopDoTableNoopRequest", | |
| txid=77, | |
| ordinal=8888, | |
| ) | |
| (b, h) = encode_fn(obj) | |
| self.assertEqual(b, EXPECTED) | |
| self.assertEqual(h, []) | |
| # Encode again with an explicit None to verify the behavior is the same as omitting a field | |
| # altogether. | |
| setattr(obj.tab, "integer", None) | |
| (b, h) = encode_fn(obj) | |
| self.assertEqual(b, EXPECTED) | |
| self.assertEqual(h, []) | |
| def test_encode_decode_table_and_union(self) -> None: | |
| obj = typing.cast(typing.Any, EncodeObj()) | |
| setattr(obj, "tab", EncodeObj()) | |
| setattr(obj.tab, "dub", 2.0) | |
| setattr(obj.tab, "str_", "foobar") | |
| setattr(obj.tab, "union_field", EncodeObj()) | |
| setattr(obj.tab.union_field, "union_int", 5) | |
| (b, h) = encode_fidl_message( | |
| object=obj, | |
| library="fuchsia.controller.test", | |
| type_name="fuchsia.controller.test/NoopDoTableNoopRequest", | |
| txid=5, | |
| ordinal=method_ordinal( | |
| protocol="fuchsia.controller.test/Noop", method="DoTableNoop" | |
| ), | |
| ) | |
| self.assertEqual(h, []) | |
| msg = decode_fidl_request(bytes=b, handles=h) | |
| self.assertEqual( | |
| msg, | |
| { | |
| "tab": { | |
| "dub": 2.0, | |
| "integer": None, | |
| "str_": "foobar", | |
| "union_field": { | |
| "union_int": 5, | |
| }, | |
| } | |
| }, | |
| ) | |
| def test_encode_handle(self) -> None: | |
| EXPECTED = bytearray.fromhex( | |
| "7b000000020000010f27000000000000ffffffff00000000" | |
| ) | |
| obj = EncodeObj() | |
| setattr(obj, "server_end", 5) | |
| (b, h) = encode_fidl_message( | |
| object=obj, | |
| library="fuchsia.controller.test", | |
| type_name="fuchsia.controller.test/NoopDoHandleNoopRequest", | |
| txid=123, | |
| ordinal=9999, | |
| ) | |
| self.assertEqual(b, EXPECTED) | |
| self.assertEqual(len(h), 1) | |
| self.assertEqual(h[0][1], 5) | |
| def test_encode_decode_handle(self) -> None: | |
| obj = EncodeObj() | |
| setattr(obj, "server_end", 10) | |
| (b, h) = encode_fidl_message( | |
| object=obj, | |
| library="fuchsia.controller.test", | |
| type_name="fuchsia.controller.test/NoopDoHandleNoopRequest", | |
| txid=22, | |
| ordinal=method_ordinal( | |
| protocol="fuchsia.controller.test/Noop", method="DoHandleNoop" | |
| ), | |
| ) | |
| updated_handles = [hdl[1] for hdl in h] | |
| msg = decode_fidl_request(bytes=b, handles=updated_handles) | |
| self.assertEqual(msg, {"server_end": 10}) | |
| def test_encode_string_vector(self) -> None: | |
| EXPECTED = bytearray.fromhex( | |
| "3930000002000001b3150000000000000400000000000000ffffffffffffffff0300000000000000ffffffffffffffff0300000000000000ffffffffffffffff0300000000000000ffffffffffffffff0300000000000000ffffffffffffffff666f6f0000000000626172000000000062617a00000000007175780000000000" | |
| ) | |
| obj = Encode() | |
| setattr(obj, "v", ["foo", "bar", "baz", "qux"]) | |
| (b, h) = encode_fidl_message( | |
| object=obj, | |
| library="fuchsia.controller.test", | |
| type_name="fuchsia.controller.test/NoopDoVectorNoopRequest", | |
| txid=12345, | |
| ordinal=5555, | |
| ) | |
| self.assertEqual(b, EXPECTED) | |
| self.assertEqual(h, []) | |
| def test_handle_overflow(self) -> None: | |
| obj = Encode() | |
| # Should be too large for a u32. | |
| setattr(obj, "server_end", 0xFFFFFFFFFF) | |
| with self.assertRaises(OverflowError): | |
| encode_fidl_message( | |
| object=obj, | |
| library="fuchsia.controller.test", | |
| type_name="fuchsia.controller.test/NoopDoHandleNoopRequest", | |
| txid=1234, | |
| ordinal=5555, | |
| ) | |
| def test_bits_overflow(self) -> None: | |
| obj = Encode() | |
| setattr(obj, "b", 0xFFFFFFFFFFFFFFFFFFFFFF) | |
| with self.assertRaises(OverflowError): | |
| encode_fidl_message( | |
| object=obj, | |
| library="fuchsia.controller.test", | |
| type_name="fuchsia.controller.test/NoopDoBitsNoopRequest", | |
| txid=2222, | |
| ordinal=3333, | |
| ) | |
| def test_encode_method_call_no_args(self) -> None: | |
| (b, h) = encode_fidl_message( | |
| object=None, library=None, type_name=None, txid=123, ordinal=345 | |
| ) | |
| def test_encode_decode_string_vector(self) -> None: | |
| obj = Encode() | |
| setattr(obj, "v", ["foo", "bar", "baz", "qux"]) | |
| (b, h) = encode_fidl_message( | |
| object=obj, | |
| library="fuchsia.controller.test", | |
| type_name="fuchsia.controller.test/NoopDoVectorNoopRequest", | |
| txid=12345, | |
| ordinal=method_ordinal( | |
| protocol="fuchsia.controller.test/Noop", method="DoVectorNoop" | |
| ), | |
| ) | |
| msg = decode_fidl_request(bytes=b, handles=h) | |
| self.assertEqual(msg, {"v": ["foo", "bar", "baz", "qux"]}) | |
| def test_encode_decode_string_vector_with_sequence(self) -> None: | |
| """ | |
| We accept not just lists, but also more generally sequences as input | |
| for FIDL arrays and vectors. Strings themselves, in Python, are also | |
| sequences of bytes. The docs say that since there is no separate | |
| "character" class, strings of length 1 are used to represent | |
| characters. Therefore, the expected outcome should be that we accept | |
| a string for a vector, and chunk it into its individual characters | |
| in FIDL. | |
| Ref: https://docs.python.org/3/library/stdtypes.html#textseq | |
| """ | |
| obj = Encode() | |
| setattr(obj, "v", "sequence") | |
| (b, h) = encode_fidl_message( | |
| object=obj, | |
| library="fuchsia.controller.test", | |
| type_name="fuchsia.controller.test/NoopDoVectorNoopRequest", | |
| txid=12345, | |
| ordinal=method_ordinal( | |
| protocol="fuchsia.controller.test/Noop", method="DoVectorNoop" | |
| ), | |
| ) | |
| msg = decode_fidl_request(bytes=b, handles=h) | |
| self.assertEqual(msg, {"v": ["s", "e", "q", "u", "e", "n", "c", "e"]}) | |
| def test_encode_decode_int_array_with_list(self) -> None: | |
| obj = Encode() | |
| setattr(obj, "a", [1, 2, 3, 4]) | |
| (b, h) = encode_fidl_message( | |
| object=obj, | |
| library="fuchsia.controller.test", | |
| type_name="fuchsia.controller.test/NoopDoArrayNoopRequest", | |
| txid=12345, | |
| ordinal=method_ordinal( | |
| protocol="fuchsia.controller.test/Noop", method="DoArrayNoop" | |
| ), | |
| ) | |
| msg = decode_fidl_request(bytes=b, handles=h) | |
| self.assertEqual(msg, {"a": [1, 2, 3, 4]}) | |
| def test_encode_decode_int_array_with_sequence(self) -> None: | |
| """ | |
| We accept not just lists, but also more generally sequences as input | |
| for FIDL arrays and vectors. The `bytes` type is a common example of | |
| a sequence that is _not_ a list, so use it in the test here. | |
| """ | |
| obj = Encode() | |
| setattr(obj, "a", bytes([1, 2, 3, 4])) | |
| (b, h) = encode_fidl_message( | |
| object=obj, | |
| library="fuchsia.controller.test", | |
| type_name="fuchsia.controller.test/NoopDoArrayNoopRequest", | |
| txid=12345, | |
| ordinal=method_ordinal( | |
| protocol="fuchsia.controller.test/Noop", method="DoArrayNoop" | |
| ), | |
| ) | |
| msg = decode_fidl_request(bytes=b, handles=h) | |
| self.assertEqual(msg, {"a": [1, 2, 3, 4]}) | |
| def test_encode_decode_string(self) -> None: | |
| obj = EncodeObj() | |
| setattr(obj, "value", "foobar") | |
| ordinal = method_ordinal( | |
| protocol="fuchsia.controller.test/Noop", method="DoStringNoop" | |
| ) | |
| (b, h) = encode_fidl_message( | |
| object=obj, | |
| library="fuchsia.controller.test", | |
| type_name="fuchsia.controller.test/NoopDoStringNoopRequest", | |
| txid=3, | |
| ordinal=ordinal, | |
| ) | |
| msg = decode_fidl_request(bytes=b, handles=h) | |
| self.assertEqual(msg, {"value": "foobar"}) | |
| def test_encode_decode_int(self) -> None: | |
| obj = EncodeObj() | |
| setattr(obj, "value", 2) | |
| ordinal = method_ordinal( | |
| protocol="fuchsia.controller.test/Noop", method="DoIntNoop" | |
| ) | |
| (b, h) = encode_fidl_message( | |
| object=obj, | |
| library="fuchsia.controller.test", | |
| type_name="fuchsia.controller.test/NoopDoIntNoopRequest", | |
| txid=3, | |
| ordinal=ordinal, | |
| ) | |
| msg = decode_fidl_request(bytes=b, handles=h) | |
| self.assertEqual(msg, {"value": 2}) | |
| def test_encode_decode_negative_enum(self) -> None: | |
| obj = EncodeObj() | |
| # This is a sepcial case because there is also a positive 2 represented in this enum. | |
| # without a negative value check in the encoder path, this test will fail. | |
| setattr(obj, "enum_thing", -2) | |
| ordinal = method_ordinal( | |
| protocol="fuchsia.controller.othertest/CrossLibraryNoop", | |
| method="EnumMethod", | |
| ) | |
| (b, h) = encode_fidl_message( | |
| object=obj, | |
| library="fuchsia.controller.othertest", | |
| type_name="fuchsia.controller.othertest/CrossLibraryNoopEnumMethodRequest", | |
| txid=5, | |
| ordinal=ordinal, | |
| ) | |
| msg = decode_fidl_request(bytes=b, handles=h) | |
| self.assertEqual(msg, {"enum_thing": -2}) |