blob: fbc71075fcf25b1797ba9297cf7541fd877b3f0b [file] [log] [blame] [edit]
// 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);
};