| // Copyright 2022 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. |
| library test.largemessages; |
| |
| using zx; |
| |
| // This message is provably never a large message - any change to its contents constitutes a binary |
| // breakage. This means that the receiving side never needs to check for the `byte_overflow` flag. |
| // |
| // 16 (header) |
| // 16 (vector header) |
| // + 65504 (maximum vector allocation) |
| // --------- |
| // 65536 |
| type BoundedKnownToBeSmall = struct { |
| bytes vector<byte>:SMALL_STRUCT_BYTE_VECTOR_SIZE; |
| }; |
| |
| // If we have a one-field struct payload, where that field is a `vector<bytes>`, what is the |
| // greatest number of bytes we can have in that `vector` such it remains a small message (aka, has |
| // exactly 64KiB)? This is the size constraint used by `BoundedKnownToBeSmall`. See the comment on |
| // that type for how this magic number was selected. |
| const SMALL_STRUCT_BYTE_VECTOR_SIZE uint32 = 65504; |
| |
| // This message should be sent with a size of either 65536 (the largest small message), or 65537 |
| // bytes (the smallest large message) to validate that the small/large message boundary is at the |
| // correct limit. |
| // |
| // 16 (header) |
| // 16 (vector header) |
| // + 65505 (maximum vector allocation) |
| // --------- |
| // 65537 |
| // + 7 (padding) |
| // --------- |
| // 65544 |
| type BoundedMaybeLarge = struct { |
| bytes vector<byte>:LARGE_STRUCT_BYTE_VECTOR_SIZE; |
| }; |
| |
| // If we have a one-field struct payload, where that field is a `vector<bytes>`, what is the least |
| // number of bytes we can have in that `vector` such it remains a large message (aka, has at least |
| // 64KiB + 1)? This is the size constraint used by `BoundedMaybeLarge`. See the comment on that type |
| // for how this magic number was selected. |
| const LARGE_STRUCT_BYTE_VECTOR_SIZE uint32 = 65505; |
| |
| // This type is intentionally constructed such that it appears bounded to the receiver, but, due to |
| // its flexibility, is may actually be unbounded. This means decoders are always required to check |
| // the `byte_overlfow` flag, no matter what they think they know about the type. To test this type |
| // as a small message, use the defined union variant and fill it with the maximum number of bytes |
| // (65488). To test it as a large message, set the union's contents to an unknown variant (ordinal |
| // 2) with 65489 bytes instead. |
| // |
| // 16 (header) |
| // 16 (union ordinal + envelope header) |
| // 16 (vector header) |
| // + 65488 (maximum vector allocation) |
| // --------- |
| // 65536 |
| type SemiBoundedBelievedToBeSmall = flexible union { |
| 1: bytes vector<byte>:SMALL_UNION_BYTE_VECTOR_SIZE; |
| }; |
| |
| // If we have a one-variant union payload, where that variant is a `vector<bytes>`, what is the |
| // greatest number of bytes we can have in that `vector` such it remains a small message (aka, has |
| // exactly 64KiB)? This is the size constraint used by `SemiBoundedBelievedToBeSmall`. See the |
| // comment on that type for how this magic number was selected. |
| const SMALL_UNION_BYTE_VECTOR_SIZE uint32 = 65488; |
| |
| // This type is intentionally constructed to be unbounded. This means decoders are always required |
| // to check the `byte_overlfow` flag, no matter what they think they know about the type. |
| // |
| // 16 (header) |
| // 16 (union ordinal + envelope header) |
| // 16 (vector header) |
| // + 65489 (maximum vector allocation) |
| // --------- |
| // 65537 |
| // + 7 (padding) |
| // --------- |
| // 65544 |
| type SemiBoundedMaybeLarge = flexible union { |
| 1: bytes vector<byte>:LARGE_UNION_BYTE_VECTOR_SIZE; |
| }; |
| |
| // If we have a one-variant union payload, where that variant is a `vector<bytes>`, what is the |
| // least number of bytes we can have in that `vector` such it remains a large message (aka, has at |
| // least 64KiB + 1)? This is the size constraint used by `SemiBoundedMaybeLarge`. See the comment on |
| // that type for how this magic number was selected. |
| const LARGE_UNION_BYTE_VECTOR_SIZE uint32 = 65489; |
| |
| // This type can always be large, and there is no way for decoders to assume otherwise. To test this |
| // type as a small message, fill the vector `bytes` with 65504 bytes. To test it as a large message, |
| // fill the vector with 65505 bytes instead instead. |
| type UnboundedMaybeLargeValue = struct { |
| bytes vector<byte>; |
| }; |
| |
| // Large messages exhibit some odd behavior in regards to handles: they work fine with 63 handles, |
| // but break with 64 handles. Conversely, small messages work fine with any number of handles up to |
| // 64. This type let's us test all of these combinations at the limit: small-message-with-64-handles |
| // and large-message-with-63-handles should encode/decode successfully, while |
| // large-message-with-64-handles should not. |
| // |
| // + 8 (handle + padding) |
| // 16 (vector header) |
| // 1000 (bytes specified by `FIRST_63_ELEMENTS_BYTE_VECTOR_SIZE`) |
| // ------------ |
| // 1024 |
| // * 63 (first 63 array entries are identical) |
| // ------------ |
| // 64512 |
| // + 8 (64th handle + padding) |
| // + 16 (64th vector header) |
| // ------------ |
| // 64536 |
| // + ??? (64th struct's byte vector size, set to 984/985 to make the message small/large) |
| type UnboundedMaybeLargeResource = resource struct { |
| elements array<resource struct { |
| handle zx.Handle:optional; |
| bytes vector<byte>:FIRST_63_ELEMENTS_BYTE_VECTOR_SIZE; |
| }, HANDLE_CARRYING_ELEMENTS_COUNT>; |
| }; |
| |
| // To properly test large messages at the limit with regards to handles, we need to set up a payload |
| // (`UnboundedMaybeLargeResource` above) with 64 handle carrying elements, each with a single |
| // optional handle. These optional handles can then be present/absent as necessary to test certain |
| // boundary conditions: 0 handles, 63 handles (largest amount possible for large message), 64 |
| // handles (largest amount possible for small messages), and so on. |
| const HANDLE_CARRYING_ELEMENTS_COUNT uint32 = 64; |
| |
| // To build `UnboundedMaybeLargeResource`, we need 63 elements of |
| // `FIRST_63_ELEMENTS_BYTE_VECTOR_SIZE`, plus a 64th of either `SMALL_LAST_ELEMENT_BYTE_VECTOR_SIZE` |
| // bytes (making an `UnboundedMaybeLargeResource` that is the largest small message, or 64KiB) or |
| // `LARGE_LAST_ELEMENT_BYTE_VECTOR_SIZE` bytes (making an `UnboundedMaybeLargeResource` that is the |
| // smallest large message, or 64KiB + 1). |
| const FIRST_63_ELEMENTS_BYTE_VECTOR_SIZE uint32 = 1000; |
| |
| // While at the limit this should be set to 984 to create the "largest possible small message", |
| // because we send an extra 8-byte argument (the padding-to-8-bytes `populated_unset_handles` |
| // boolean) when passing the type that consumes this constant in for encode testing, it's much |
| // easier if we decrease this number by that amount to ensure that the "largeness" of the incoming |
| // message is the same as that of the outgoing. |
| const SMALL_LAST_ELEMENT_BYTE_VECTOR_SIZE uint32 = 976; |
| const LARGE_LAST_ELEMENT_BYTE_VECTOR_SIZE uint32 = 985; |
| |
| open protocol Overflowing { |
| @experimental_overflowing(request=true) |
| strict DecodeBoundedKnownToBeSmall(BoundedKnownToBeSmall); |
| @experimental_overflowing(request=true) |
| strict DecodeBoundedMaybeLarge(BoundedMaybeLarge); |
| @experimental_overflowing(request=true) |
| strict DecodeSemiBoundedBelievedToBeSmall(SemiBoundedBelievedToBeSmall); |
| @experimental_overflowing(request=true) |
| strict DecodeSemiBoundedMaybeLarge(SemiBoundedMaybeLarge); |
| @experimental_overflowing(request=true) |
| strict DecodeUnboundedMaybeLargeValue(UnboundedMaybeLargeValue); |
| @experimental_overflowing(request=true) |
| strict DecodeUnboundedMaybeLargeResource(UnboundedMaybeLargeResource); |
| @experimental_overflowing(request=true, response=true) |
| strict EncodeBoundedKnownToBeSmall(BoundedKnownToBeSmall) -> (BoundedKnownToBeSmall); |
| @experimental_overflowing(request=true, response=true) |
| strict EncodeBoundedMaybeLarge(BoundedMaybeLarge) -> (BoundedMaybeLarge); |
| @experimental_overflowing(request=true, response=true) |
| strict EncodeSemiBoundedBelievedToBeSmall(SemiBoundedBelievedToBeSmall) -> (SemiBoundedBelievedToBeSmall); |
| @experimental_overflowing(request=true, response=true) |
| strict EncodeSemiBoundedMaybeLarge(SemiBoundedMaybeLarge) -> (SemiBoundedMaybeLarge); |
| @experimental_overflowing(request=true, response=true) |
| strict EncodeUnboundedMaybeLargeValue(UnboundedMaybeLargeValue) -> (UnboundedMaybeLargeValue); |
| |
| // If `populate_unset_handles` is false, the implementation should echo back `data` unchanged. |
| // If `populate_unset_handles` is true, the implementation should check if any handle field is |
| // absent, and fill those fields with valid handles, then send back the `data` populated with |
| // those additional handles. |
| // |
| // Unlike the remaining methods for this target, `EncodeUnboundedMaybeLargeResource` needs to |
| // test a case where a bad encoding is possible: a large message with 64 handles. To enable |
| // this, the incoming type is wrapped in a container struct with an extra flag specifying |
| // whether or not to "fill in" all unset optional handles, thereby forcing the return message to |
| // carry 64 of them. For both of the "good" cases (small-message-with-64-handles and |
| // large-message-with-63-handles), there should be no need to set this flag, as the harness-sent |
| // type can be echoed back without issue. |
| // |
| // Note that because this method only tests encoding, and not decoding, it's fine if the size of |
| // the incoming message is a bit off due to the extra bytes used by the flag - the only behavior |
| // under test is the encoding of the response message, so the exact properties of the request |
| // message are irrelevant as long as the response is properly sized to be large/small as needed. |
| @experimental_overflowing(request=true, response=true) |
| strict EncodeUnboundedMaybeLargeResource(UnboundedMaybeLargeResource) -> (UnboundedMaybeLargeResource); |
| }; |