// Copyright 2017 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.

#pragma once

namespace fidl {
namespace internal {

// Some assumptions about data type layout.
static_assert(offsetof(fidl_string_t, size) == 0u, "");
static_assert(offsetof(fidl_string_t, data) == 8u, "");

static_assert(offsetof(fidl_vector_t, count) == 0u, "");
static_assert(offsetof(fidl_vector_t, data) == 8u, "");

static_assert(ZX_HANDLE_INVALID == FIDL_HANDLE_ABSENT, "");

template <bool kConst, class U>
struct SetPtrConst;

template <class U>
struct SetPtrConst<false, U> {
    typedef U* type;
};

template <class U>
struct SetPtrConst<true, U> {
    typedef const U* type;
};

// Walks over a FIDL buffer and validates/encodes/decodes it per-Derived.
//
// kMutating controls whether this deals with mutable bytes or immutable bytes
// (validation wants immutable, encode/decode wants mutable)
//
// kContinueAfterErrors controls whether parsing is continued upon failure (encode needs this to
// see all available handles).
//
// Derived should offer the following methods:
//
//   const? uint8_t* bytes() - returns the start of the buffer of bytes
//   uint32_t num_bytes() - returns the number of bytes in said buffer
//   uint32_t num_handles() - returns the number of handles that are claimable
//   bool ValidateOutOfLineStorageClaim(const void* a, const void* b)
//      - returns true if a legally points to b
//   void UnclaimedHandle(zx_handle_t*) - notes that a handle was skipped
//   void ClaimedHandle(zx_handle_t*, uint32_t idx) - notes that a handle was claimed
//   PointerState GetPointerState(const void* ptr) - returns whether a pointer is present or not
//   HandleState GetHandleState(zx_handle_t) - returns if a handle is present or not
//   void UpdatePointer(T**p, T*v) - mutates a pointer representation for a present pointer
//   void SetError(const char* error_msg) - flags that an error occurred
template <class Derived, bool kMutating, bool kContinueAfterErrors>
class BufferWalker {
public:
    explicit BufferWalker(const fidl_type* type)
        : type_(type) {}

    void Walk() {
        // The first decode is special. It must be a struct or a table.
        // We need to know the size of the first element to compute the start of
        // the out-of-line allocations.

        if (type_ == nullptr) {
            SetError("Cannot decode a null fidl type");
            return;
        }

        if (bytes() == nullptr) {
            SetError("Cannot decode null bytes");
            return;
        }

        switch (type_->type_tag) {
        case fidl::kFidlTypeStruct:
            if (num_bytes() < type_->coded_struct.size) {
                SetError("Message size is smaller than expected");
                return;
            }
            out_of_line_offset_ = static_cast<uint32_t>(fidl::FidlAlign(type_->coded_struct.size));
            break;
        case fidl::kFidlTypeTable:
            if (num_bytes() < sizeof(fidl_vector_t)) {
                SetError("Message size is smaller than expected");
                return;
            }
            out_of_line_offset_ = static_cast<uint32_t>(fidl::FidlAlign(sizeof(fidl_vector_t)));
            break;
        default:
            SetError("Message must be a struct or a table");
            return;
        }

        Push(Frame::DoneSentinel());
        Push(Frame(type_, 0u));

// Macro to insert the relevant goop required to support two control flows here:
// one where we keep reading after error, and another where we return immediately.
// No runtime overhead thanks to if constexpr magic.
#define FIDL_POP_AND_CONTINUE_OR_RETURN \
    if (kContinueAfterErrors) {         \
        Pop();                          \
        continue;                       \
    } else {                            \
        return;                         \
    }

        for (;;) {
            Frame* frame = Peek();

            switch (frame->state) {
            case Frame::kStateStruct: {
                const uint32_t field_index = frame->NextStructField();
                if (field_index == frame->struct_state.field_count) {
                    Pop();
                    continue;
                }
                const fidl::FidlField& field = frame->struct_state.fields[field_index];
                const fidl_type_t* field_type = field.type;
                const uint32_t field_offset = frame->offset + field.offset;
                if (!Push(Frame(field_type, field_offset))) {
                    SetError("recursion depth exceeded processing struct");
                    FIDL_POP_AND_CONTINUE_OR_RETURN;
                }
                continue;
            }
            case Frame::kStateStructPointer: {
                switch (GetPointerState(TypedAt<void>(frame->offset))) {
                case PointerState::PRESENT:
                    break;
                case PointerState::ABSENT:
                    Pop();
                    continue;
                default:
                    SetError("Tried to decode a bad struct pointer");
                    FIDL_POP_AND_CONTINUE_OR_RETURN;
                }
                auto struct_ptr_ptr = TypedAt<void*>(frame->offset);
                if (!ClaimOutOfLineStorage(frame->struct_pointer_state.struct_type->size,
                                           *struct_ptr_ptr, &frame->offset)) {
                    SetError("message wanted to store too large of a nullable struct");
                    FIDL_POP_AND_CONTINUE_OR_RETURN;
                }
                UpdatePointer(struct_ptr_ptr, TypedAt<void>(frame->offset));
                const fidl::FidlCodedStruct* coded_struct = frame->struct_pointer_state.struct_type;
                *frame = Frame(coded_struct, frame->offset);
                continue;
            }
            case Frame::kStateTable: {
                if (frame->field == 0u) {
                    auto envelope_vector_ptr = TypedAt<fidl_vector_t>(frame->offset);
                    switch (GetPointerState(&envelope_vector_ptr->data)) {
                    case PointerState::PRESENT:
                        break;
                    case PointerState::ABSENT:
                        SetError("Table data cannot be absent");
                        FIDL_POP_AND_CONTINUE_OR_RETURN;
                    default:
                        SetError("message tried to decode a non-present vector");
                        FIDL_POP_AND_CONTINUE_OR_RETURN;
                    }
                    uint32_t size;
                    if (mul_overflow(envelope_vector_ptr->count, 2 * sizeof(uint64_t), &size)) {
                        SetError("integer overflow calculating table size");
                        FIDL_POP_AND_CONTINUE_OR_RETURN;
                    }
                    if (!ClaimOutOfLineStorage(size, envelope_vector_ptr->data, &frame->offset)) {
                        SetError("message wanted to store too large of a table");
                        FIDL_POP_AND_CONTINUE_OR_RETURN;
                    }
                    UpdatePointer(&envelope_vector_ptr->data, TypedAt<void>(frame->offset));
                    frame->field = 1;
                    frame->table_state.known_index = 0;
                    frame->table_state.present_count = static_cast<uint32_t>(envelope_vector_ptr->count);
                    frame->table_state.end_offset = out_of_line_offset_;
                    frame->table_state.end_handle = handle_idx_;
                    continue;
                }
                if (frame->table_state.end_offset != out_of_line_offset_) {
                    SetError("Table field was mis-sized");
                    FIDL_POP_AND_CONTINUE_OR_RETURN;
                }
                if (frame->table_state.end_handle != handle_idx_) {
                    SetError("Table handles were mis-sized");
                    FIDL_POP_AND_CONTINUE_OR_RETURN;
                }
                if (frame->field > frame->table_state.present_count) {
                    Pop();
                    continue;
                }
                const fidl::FidlTableField* known_field = nullptr;
                if (frame->table_state.known_index < frame->table_state.field_count) {
                    const fidl::FidlTableField* field =
                        &frame->table_state.fields[frame->table_state.known_index];
                    if (field->ordinal == frame->field) {
                        known_field = field;
                        frame->table_state.known_index++;
                    }
                }
                const uint32_t tag_offset = static_cast<uint32_t>(
                    frame->offset + (frame->field - 1) * 2 * sizeof(uint64_t));
                const uint32_t data_offset = static_cast<uint32_t>(
                    tag_offset + sizeof(uint64_t));
                const uint64_t packed_sizes = *TypedAt<uint64_t>(tag_offset);
                frame->field++;
                switch (GetPointerState(TypedAt<void>(data_offset))) {
                case PointerState::PRESENT:
                    if (packed_sizes != 0)
                        break; // expected

                    SetError("Table envelope has present data pointer, but no data, and no handles");
                    FIDL_POP_AND_CONTINUE_OR_RETURN;
                case PointerState::ABSENT:
                    if (packed_sizes == 0)
                        continue; // skip

                    SetError("Table envelope has absent data pointer, yet has data and/or handles");
                    FIDL_POP_AND_CONTINUE_OR_RETURN;
                default:
                    SetError("Table envelope has bad data pointer");
                    FIDL_POP_AND_CONTINUE_OR_RETURN;
                }
                uint32_t offset;
                uint32_t handles;
                const uint32_t table_bytes = static_cast<uint32_t>(packed_sizes & 0xffffffffu);
                const uint32_t table_handles = static_cast<uint32_t>(packed_sizes >> 32);
                if (add_overflow(out_of_line_offset_, table_bytes, &offset) || offset > num_bytes()) {
                    SetError("integer overflow decoding table field");
                    FIDL_POP_AND_CONTINUE_OR_RETURN;
                }
                if (add_overflow(handle_idx_, table_handles, &handles) ||
                    handles > num_handles()) {
                    SetError("integer overflow decoding table handles");
                    FIDL_POP_AND_CONTINUE_OR_RETURN;
                }
                frame->table_state.end_offset = offset;
                frame->table_state.end_handle = handles;
                if (known_field != nullptr) {
                    const fidl_type_t* field_type = known_field->type;
                    uint32_t field_offset;
                    if (!ClaimOutOfLineStorage(TypeSize(field_type), TypedAt<void*>(data_offset), &field_offset)) {
                        SetError("table wanted too many bytes in field");
                        FIDL_POP_AND_CONTINUE_OR_RETURN;
                    }
                    UpdatePointer(TypedAt<void*>(data_offset), TypedAt<void>(field_offset));
                    if (!Push(Frame(field_type, field_offset))) {
                        SetError("recursion depth exceeded decoding table");
                        FIDL_POP_AND_CONTINUE_OR_RETURN;
                    }
                } else {
                    // Table data will not be processed: discard it.
                    uint32_t field_offset;
                    if (!ClaimOutOfLineStorage(table_bytes, TypedAt<void*>(data_offset), &field_offset)) {
                        SetError("table wanted too many bytes in field");
                        FIDL_POP_AND_CONTINUE_OR_RETURN;
                    }
                    UpdatePointer(TypedAt<void*>(data_offset), TypedAt<void>(field_offset));
                    for (uint32_t i = 0; i < table_handles; i++) {
                        if (!ClaimHandle(nullptr)) {
                            SetError("expected handle not present");
                            FIDL_POP_AND_CONTINUE_OR_RETURN;
                        }
                    }
                }
                continue;
            }
            case Frame::kStateTablePointer: {
                switch (GetPointerState(TypedAt<void>(frame->offset))) {
                case PointerState::PRESENT:
                    break;
                case PointerState::ABSENT:
                    Pop();
                    continue;
                default:
                    SetError("Tried to decode a bad table pointer");
                    FIDL_POP_AND_CONTINUE_OR_RETURN;
                }
                auto table_ptr_ptr = TypedAt<void*>(frame->offset);
                if (!ClaimOutOfLineStorage(sizeof(fidl_vector_t), *table_ptr_ptr, &frame->offset)) {
                    SetError("message wanted to store too large of a nullable table");
                    FIDL_POP_AND_CONTINUE_OR_RETURN;
                }
                UpdatePointer(table_ptr_ptr, TypedAt<void>(frame->offset));
                const fidl::FidlCodedTable* coded_table = frame->table_pointer_state.table_type;
                *frame = Frame(coded_table, frame->offset);
                continue;
            }
            case Frame::kStateUnion: {
                fidl_union_tag_t union_tag = *TypedAt<fidl_union_tag_t>(frame->offset);
                if (union_tag >= frame->union_state.type_count) {
                    SetError("Tried to decode a bad union discriminant");
                    FIDL_POP_AND_CONTINUE_OR_RETURN;
                }
                const fidl_type_t* member = frame->union_state.types[union_tag];
                if (!member) {
                    Pop();
                    continue;
                }
                frame->offset += frame->union_state.data_offset;
                *frame = Frame(member, frame->offset);
                continue;
            }
            case Frame::kStateUnionPointer: {
                auto union_ptr_ptr = TypedAt<fidl_union_tag_t*>(frame->offset);
                switch (GetPointerState(TypedAt<void>(frame->offset))) {
                case PointerState::PRESENT:
                    break;
                case PointerState::ABSENT:
                    Pop();
                    continue;
                default:
                    SetError("Tried to decode a bad union pointer");
                    FIDL_POP_AND_CONTINUE_OR_RETURN;
                }
                if (!ClaimOutOfLineStorage(frame->union_pointer_state.union_type->size, *union_ptr_ptr,
                                           &frame->offset)) {
                    SetError("message wanted to store too large of a nullable union");
                    FIDL_POP_AND_CONTINUE_OR_RETURN;
                }
                UpdatePointer(union_ptr_ptr, TypedAt<fidl_union_tag_t>(frame->offset));
                const fidl::FidlCodedUnion* coded_union = frame->union_pointer_state.union_type;
                *frame = Frame(coded_union, frame->offset);
                continue;
            }
            case Frame::kStateArray: {
                const uint32_t element_offset = frame->NextArrayOffset();
                if (element_offset == frame->array_state.array_size) {
                    Pop();
                    continue;
                }
                const fidl_type_t* element_type = frame->array_state.element;
                const uint32_t offset = frame->offset + element_offset;
                if (!Push(Frame(element_type, offset))) {
                    SetError("recursion depth exceeded decoding array");
                    FIDL_POP_AND_CONTINUE_OR_RETURN;
                }
                continue;
            }
            case Frame::kStateString: {
                auto string_ptr = TypedAt<fidl_string_t>(frame->offset);
                // The string storage may be Absent for nullable strings and must
                // otherwise be Present. No other values are allowed.
                switch (GetPointerState(&string_ptr->data)) {
                case PointerState::PRESENT:
                    break;
                case PointerState::ABSENT:
                    if (!frame->string_state.nullable) {
                        SetError("message tried to decode an absent non-nullable string");
                        FIDL_POP_AND_CONTINUE_OR_RETURN;
                    }
                    if (string_ptr->size != 0u) {
                        SetError("message tried to decode an absent string of non-zero length");
                        FIDL_POP_AND_CONTINUE_OR_RETURN;
                    }
                    Pop();
                    continue;
                default:
                    SetError(
                        "message tried to decode a string that is neither present nor absent");
                    FIDL_POP_AND_CONTINUE_OR_RETURN;
                }
                uint64_t bound = frame->string_state.max_size;
                uint64_t size = string_ptr->size;
                if (size > bound) {
                    SetError("message tried to decode too large of a bounded string");
                    FIDL_POP_AND_CONTINUE_OR_RETURN;
                }
                uint32_t string_data_offset = 0u;
                if (!ClaimOutOfLineStorage(static_cast<uint32_t>(size), string_ptr->data, &string_data_offset)) {
                    SetError("decoding a string overflowed buffer");
                    FIDL_POP_AND_CONTINUE_OR_RETURN;
                }
                UpdatePointer(&string_ptr->data, TypedAt<char>(string_data_offset));
                Pop();
                continue;
            }
            case Frame::kStateHandle: {
                auto handle_ptr = TypedAt<zx_handle_t>(frame->offset);
                // The handle storage may be Absent for nullable handles and must
                // otherwise be Present. No other values are allowed.
                switch (GetHandleState(*handle_ptr)) {
                case HandleState::ABSENT:
                    if (frame->handle_state.nullable) {
                        Pop();
                        continue;
                    }
                    SetError("message tried to decode a non-present handle");
                    FIDL_POP_AND_CONTINUE_OR_RETURN;
                case HandleState::PRESENT:
                    if (!ClaimHandle(handle_ptr)) {
                        SetError("message decoded too many handles");
                        FIDL_POP_AND_CONTINUE_OR_RETURN;
                    }
                    Pop();
                    continue;
                default:
                    // The value at the handle was garbage.
                    SetError("message tried to decode a garbage handle");
                    FIDL_POP_AND_CONTINUE_OR_RETURN;
                }
            }
            case Frame::kStateVector: {
                auto vector_ptr = TypedAt<fidl_vector_t>(frame->offset);
                // The vector storage may be Absent for nullable vectors and must
                // otherwise be Present. No other values are allowed.
                switch (GetPointerState(&vector_ptr->data)) {
                case PointerState::PRESENT:
                    break;
                case PointerState::ABSENT:
                    if (!frame->vector_state.nullable) {
                        SetError("message tried to decode an absent non-nullable vector");
                        FIDL_POP_AND_CONTINUE_OR_RETURN;
                    }
                    if (vector_ptr->count != 0u) {
                        SetError("message tried to decode an absent vector of non-zero elements");
                        FIDL_POP_AND_CONTINUE_OR_RETURN;
                    }
                    Pop();
                    continue;
                default:
                    SetError("message tried to decode a non-present vector");
                    FIDL_POP_AND_CONTINUE_OR_RETURN;
                }
                if (vector_ptr->count > frame->vector_state.max_count) {
                    SetError("message tried to decode too large of a bounded vector");
                    FIDL_POP_AND_CONTINUE_OR_RETURN;
                }
                uint32_t size;
                if (mul_overflow(vector_ptr->count, frame->vector_state.element_size, &size)) {
                    SetError("integer overflow calculating vector size");
                    FIDL_POP_AND_CONTINUE_OR_RETURN;
                }
                if (!ClaimOutOfLineStorage(size, vector_ptr->data, &frame->offset)) {
                    SetError("message wanted to store too large of a vector");
                    FIDL_POP_AND_CONTINUE_OR_RETURN;
                }
                UpdatePointer(&vector_ptr->data, TypedAt<void>(frame->offset));
                if (frame->vector_state.element) {
                    // Continue by decoding the vector elements as an array.
                    *frame = Frame(frame->vector_state.element, size,
                                   frame->vector_state.element_size, frame->offset);
                } else {
                    // If there is no element type pointer, there is
                    // nothing to decode in the vector secondary
                    // payload. So just continue.
                    Pop();
                }
                continue;
            }
            case Frame::kStateDone: {
                if (out_of_line_offset_ != num_bytes()) {
                    SetError("message did not decode all provided bytes");
                }
                return;
            }
            }
        }

#undef FIDL_POP_AND_CONTINUE_OR_RETURN
    }

protected:
    void SetError(const char* error_msg) {
        derived()->SetError(error_msg);
    }

    template <typename T>
    typename SetPtrConst<!kMutating, T>::type TypedAt(uint32_t offset) const {
        return reinterpret_cast<typename SetPtrConst<!kMutating, T>::type>(bytes() + offset);
    }

    enum class PointerState : uintptr_t {
        PRESENT = FIDL_ALLOC_PRESENT,
        ABSENT = FIDL_ALLOC_ABSENT,
        INVALID = 1 // *OR* *ANY* non PRESENT/ABSENT value.
    };

    enum class HandleState : zx_handle_t {
        PRESENT = FIDL_HANDLE_PRESENT,
        ABSENT = FIDL_HANDLE_ABSENT,
        INVALID = 1 // *OR* *ANY* non PRESENT/ABSENT value.
    };

    uint32_t handle_idx() const { return handle_idx_; }

private:
    Derived* derived() {
        return static_cast<Derived*>(this);
    }

    const Derived* derived() const {
        return static_cast<const Derived*>(this);
    }

    // Returns a pointer to the bytes in the message.
    auto bytes() const {
        return derived()->bytes();
    }

    // Returns the number of bytes in the message.
    auto num_bytes() const {
        return derived()->num_bytes();
    }

    // Returns the number of handles in the message (encoding: the max number of handles in the message).
    auto num_handles() const {
        return derived()->num_handles();
    }

    // Returns PRESENT/ABSENT/INVALID for a given pointer value.
    PointerState GetPointerState(const void* ptr) const {
        return derived()->GetPointerState(ptr);
    }

    // Returns PRESENT/ABSENT/INVALID for a given handle value.
    HandleState GetHandleState(zx_handle_t p) const {
        return derived()->GetHandleState(p);
    }

    // If required: mutate a pointer to the dual representation.
    template <class T2, class T1>
    void UpdatePointer(T2 p, T1 v) {
        derived()->UpdatePointer(p, v);
    }

    // Returns true when a handle was claimed, and false when the
    // handles are exhausted.
    template <class ZxHandleTPointer>
    bool ClaimHandle(ZxHandleTPointer out_handle) {
        if (handle_idx_ == num_handles()) {
            derived()->UnclaimedHandle(out_handle);
            return false;
        }
        derived()->ClaimedHandle(out_handle, handle_idx_);
        ++handle_idx_;
        return true;
    }

    // Returns true when the buffer space is claimed, and false when
    // the requested claim is too large for bytes_.
    bool ClaimOutOfLineStorage(uint32_t size, const void* storage, uint32_t* out_offset) {
        if (!derived()->ValidateOutOfLineStorageClaim(storage, &bytes()[out_of_line_offset_])) {
            return false;
        }

        // We have to manually maintain alignment here. For example, a pointer
        // to a struct that is 4 bytes still needs to advance the next
        // out-of-line offset by 8 to maintain the aligned-to-FIDL_ALIGNMENT
        // property.
        static constexpr uint32_t mask = FIDL_ALIGNMENT - 1;
        uint32_t offset = out_of_line_offset_;
        if (add_overflow(offset, size, &offset) ||
            add_overflow(offset, mask, &offset)) {
            return false;
        }
        offset &= ~mask;

        if (offset > num_bytes()) {
            return false;
        }
        *out_offset = out_of_line_offset_;
        out_of_line_offset_ = offset;
        return true;
    }

    uint32_t TypeSize(const fidl_type_t* type) {
        switch (type->type_tag) {
        case fidl::kFidlTypeStructPointer:
        case fidl::kFidlTypeTablePointer:
        case fidl::kFidlTypeUnionPointer:
            return sizeof(uint64_t);
        case fidl::kFidlTypeHandle:
            return sizeof(zx_handle_t);
        case fidl::kFidlTypeStruct:
            return type->coded_struct.size;
        case fidl::kFidlTypeTable:
            return sizeof(fidl_vector_t);
        case fidl::kFidlTypeUnion:
            return type->coded_union.size;
        case fidl::kFidlTypeString:
            return sizeof(fidl_string_t);
        case fidl::kFidlTypeArray:
            return type->coded_array.array_size;
        case fidl::kFidlTypeVector:
            return sizeof(fidl_vector_t);
        }
        abort();
        return 0;
    }

    // Functions that manipulate the decoding stack frames.
    struct Frame {
        Frame(const fidl_type_t* fidl_type, uint32_t offset)
            : offset(offset) {
            switch (fidl_type->type_tag) {
            case fidl::kFidlTypeStruct:
                state = kStateStruct;
                struct_state.fields = fidl_type->coded_struct.fields;
                struct_state.field_count = fidl_type->coded_struct.field_count;
                break;
            case fidl::kFidlTypeStructPointer:
                state = kStateStructPointer;
                struct_pointer_state.struct_type = fidl_type->coded_struct_pointer.struct_type;
                break;
            case fidl::kFidlTypeTable:
                state = kStateTable;
                table_state.fields = fidl_type->coded_table.fields;
                table_state.field_count = fidl_type->coded_table.field_count;
                table_state.present_count = 0;
                break;
            case fidl::kFidlTypeTablePointer:
                state = kStateTablePointer;
                table_pointer_state.table_type = fidl_type->coded_table_pointer.table_type;
                break;
            case fidl::kFidlTypeUnion:
                state = kStateUnion;
                union_state.types = fidl_type->coded_union.types;
                union_state.type_count = fidl_type->coded_union.type_count;
                union_state.data_offset = fidl_type->coded_union.data_offset;
                break;
            case fidl::kFidlTypeUnionPointer:
                state = kStateUnionPointer;
                union_pointer_state.union_type = fidl_type->coded_union_pointer.union_type;
                break;
            case fidl::kFidlTypeArray:
                state = kStateArray;
                array_state.element = fidl_type->coded_array.element;
                array_state.array_size = fidl_type->coded_array.array_size;
                array_state.element_size = fidl_type->coded_array.element_size;
                break;
            case fidl::kFidlTypeString:
                state = kStateString;
                string_state.max_size = fidl_type->coded_string.max_size;
                string_state.nullable = fidl_type->coded_string.nullable;
                break;
            case fidl::kFidlTypeHandle:
                state = kStateHandle;
                handle_state.nullable = fidl_type->coded_handle.nullable;
                break;
            case fidl::kFidlTypeVector:
                state = kStateVector;
                vector_state.element = fidl_type->coded_vector.element;
                vector_state.max_count = fidl_type->coded_vector.max_count;
                vector_state.element_size = fidl_type->coded_vector.element_size;
                vector_state.nullable = fidl_type->coded_vector.nullable;
                break;
            }
        }

        Frame(const fidl::FidlCodedStruct* coded_struct, uint32_t offset)
            : offset(offset) {
            state = kStateStruct;
            struct_state.fields = coded_struct->fields;
            struct_state.field_count = coded_struct->field_count;
        }

        Frame(const fidl::FidlCodedTable* coded_table, uint32_t offset)
            : offset(offset) {
            state = kStateStruct;
            table_state.fields = coded_table->fields;
            table_state.field_count = coded_table->field_count;
        }

        Frame(const fidl::FidlCodedUnion* coded_union, uint32_t offset)
            : offset(offset) {
            state = kStateUnion;
            union_state.types = coded_union->types;
            union_state.type_count = coded_union->type_count;
            union_state.data_offset = coded_union->data_offset;
        }

        Frame(const fidl_type_t* element, uint32_t array_size, uint32_t element_size,
              uint32_t offset)
            : offset(offset) {
            state = kStateArray;
            array_state.element = element;
            array_state.array_size = array_size;
            array_state.element_size = element_size;
        }

        // The default constructor does nothing when initializing the stack of frames.
        Frame() {}

        static Frame DoneSentinel() {
            Frame frame;
            frame.state = kStateDone;
            return frame;
        }

        uint32_t NextStructField() {
            ZX_DEBUG_ASSERT(state == kStateStruct);

            uint32_t current = field;
            field += 1;
            return current;
        }

        uint32_t NextArrayOffset() {
            ZX_DEBUG_ASSERT(state == kStateArray);

            uint32_t current = field;
            field += array_state.element_size;
            return current;
        }

        enum : int {
            kStateStruct,
            kStateStructPointer,
            kStateTable,
            kStateTablePointer,
            kStateUnion,
            kStateUnionPointer,
            kStateArray,
            kStateString,
            kStateHandle,
            kStateVector,

            kStateDone,
        } state;
        // A byte offset into bytes_;
        uint32_t offset;

        // This is a subset of the information recorded in the
        // fidl_type structures needed for decoding state. For
        // example, struct sizes do not need to be present here.
        union {
            struct {
                const fidl::FidlField* fields;
                uint32_t field_count;
            } struct_state;
            struct {
                const fidl::FidlCodedStruct* struct_type;
            } struct_pointer_state;
            struct {
                const fidl::FidlTableField* fields;
                uint32_t known_index;
                uint32_t field_count;
                uint32_t present_count;
                uint32_t end_offset;
                uint32_t end_handle;
            } table_state;
            struct {
                const fidl::FidlCodedTable* table_type;
            } table_pointer_state;
            struct {
                const fidl_type_t* const* types;
                uint32_t type_count;
                uint32_t data_offset;
            } union_state;
            struct {
                const fidl::FidlCodedUnion* union_type;
            } union_pointer_state;
            struct {
                const fidl_type_t* element;
                uint32_t array_size;
                uint32_t element_size;
            } array_state;
            struct {
                uint32_t max_size;
                bool nullable;
            } string_state;
            struct {
                bool nullable;
            } handle_state;
            struct {
                const fidl_type* element;
                uint32_t max_count;
                uint32_t element_size;
                bool nullable;
            } vector_state;
        };

        uint32_t field = 0u;
    };

    // Returns true on success and false on recursion overflow.
    bool Push(Frame frame) {
        if (depth_ == FIDL_RECURSION_DEPTH) {
            return false;
        }
        decoding_frames_[depth_] = frame;
        ++depth_;
        return true;
    }

    void Pop() {
        ZX_DEBUG_ASSERT(depth_ != 0u);
        --depth_;
    }

    Frame* Peek() {
        ZX_DEBUG_ASSERT(depth_ != 0u);
        return &decoding_frames_[depth_ - 1];
    }

    // Message state passed in to the constructor.
    const fidl_type_t* const type_;

    // Internal state.
    uint32_t handle_idx_ = 0u;
    uint32_t out_of_line_offset_ = 0u;

    // Decoding stack state.
    uint32_t depth_ = 0u;
    Frame decoding_frames_[FIDL_RECURSION_DEPTH];
};

} // namespace internal
} // namespace fidl