blob: 636504cc30fb15cac5310cc421fa7897b147198c [file] [log] [blame]
// 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.
#ifndef LIB_FIDL_LLCPP_INCLUDE_LIB_FIDL_LLCPP_WIRE_ENCODER_H_
#define LIB_FIDL_LLCPP_INCLUDE_LIB_FIDL_LLCPP_WIRE_ENCODER_H_
#include <lib/fidl/llcpp/coding_errors.h>
#include <lib/fidl/llcpp/internal/transport.h>
#include <lib/fidl/llcpp/wire_coding_common.h>
#include <lib/fitx/result.h>
namespace fidl::internal {
class WireEncoder {
public:
explicit WireEncoder(const CodingConfig* coding_config, zx_channel_iovec_t* iovecs,
size_t iovec_capacity, fidl_handle_t* handles,
fidl_handle_metadata_t* handle_metadata, size_t handle_capacity,
uint8_t* backing_buffer, size_t backing_buffer_capacity);
// Alloc allocates a new buffer.
// It has been optimized to be small in size so that it can be inlined.
__ALWAYS_INLINE bool Alloc(size_t size, WirePosition* position) {
// For inline types, the value is non-zero and bounded (<= uint32_t max).
// For vector / string / table it is necessary to check both bounds
// (done outside of this function in order to prevent unnecessary checks.)
ZX_DEBUG_ASSERT(size != 0);
ZX_DEBUG_ASSERT(size <= std::numeric_limits<uint32_t>::max());
*position = WirePosition(current_iovec_bytes_);
current_iovec_bytes_ += FIDL_ALIGN_64(size);
if (likely(current_iovec_bytes_ <= current_iovec_bytes_end_)) {
*reinterpret_cast<uint64_t*>(current_iovec_bytes_ - 8) = 0;
return true;
}
// Errors are detected in |Finish| by observing that
// current_iovec_bytes_ > current_iovec_bytes_end_.
// The motivation for this is to reduce binary size by not using additional instructions
// to set the error here.
return false;
}
// Use iovec optimizations to encode memcpyable vector bodies.
void EncodeMemcpyableVector(const void* data, size_t count, size_t stride) {
size_t size = count * stride;
if (unlikely(iovec_actual_ + 2 >= iovec_capacity_)) {
// There aren't enough iovecs available. Copy into the existing iovec.
// There must be at least one iovec for the pointed buffer and one for
// the remaining content to point to an iovec.
WirePosition position;
if (unlikely(!Alloc(size, &position))) {
return;
}
memcpy(position.As<void>(), data, size);
return;
}
// Normally, FIDL "aligns up" up to the nearest 8 byte value.
// Here, we align down to only reference an aligned region of the iovec.
size_t aligned_down = size & ~7ull;
if (likely(aligned_down > 0)) {
size_t consumed_capacity = ConsumedBytesInCurrentIovec();
// Note: consumed_capacity may be 0 for instance if there are multiple
// byte vectors in a struct.
if (likely(consumed_capacity > 0)) {
iovecs_[iovec_actual_] = {
.buffer = current_iovec_bytes_begin_,
.capacity = static_cast<uint32_t>(consumed_capacity),
};
iovec_actual_++;
byte_actual_ += consumed_capacity;
current_iovec_bytes_begin_ = current_iovec_bytes_;
current_iovec_bytes_ = current_iovec_bytes_begin_;
}
iovecs_[iovec_actual_] = {
.buffer = data,
.capacity = static_cast<uint32_t>(aligned_down),
};
iovec_actual_++;
byte_actual_ += aligned_down;
}
// Note: the old coding-table based encoder left an unaligned size in the iovec
// and referenced padding bytes from the next backing_buffer block.
// Memcpying avoids alignment issues for copying to the kernel, but it isn't
// clear that this is better - the extra memcpy carries overhead.
// Consider re-evaluating.
size_t unaligned_portion = size - aligned_down;
if (unaligned_portion > 0) {
WirePosition position;
if (unlikely(!Alloc(unaligned_portion, &position))) {
return;
}
memcpy(position.As<void>(), static_cast<const uint8_t*>(data) + aligned_down,
unaligned_portion);
}
}
void EncodeHandle(fidl_handle_t handle, HandleAttributes attr, WirePosition position,
bool is_optional);
size_t CurrentLength() const { return byte_actual_ + ConsumedBytesInCurrentIovec(); }
size_t CurrentHandleCount() const { return handle_actual_; }
void SetError(const char* error) {
// Only set error_ rather than both error_ and error_status_.
// This allows SetError to be cheaply inlined and reduces generated code.
if (error_ != nullptr) {
return;
}
error_ = error;
}
void SetError(zx_status_t status, const char* error) {
if (error_ != nullptr) {
return;
}
error_status_ = status;
error_ = error;
}
bool HasError() const { return error_ != nullptr || AvailableBytesInCurrentIovec() < 0; }
struct Result {
size_t iovec_actual;
size_t handle_actual;
};
fitx::result<fidl::Error, Result> Finish() {
if (unlikely(AvailableBytesInCurrentIovec() < 0)) {
// |Alloc| deferred error checking. Report an error if the buffer size was exceeded.
SetError(ZX_ERR_BUFFER_TOO_SMALL, kCodingErrorBackingBufferSizeExceeded);
}
if (unlikely(error_ != nullptr)) {
coding_config_->close_many(handles_, handle_actual_);
return fitx::error(fidl::Status::EncodeError(error_status_, error_));
}
if (likely(ConsumedBytesInCurrentIovec() > 0)) {
ZX_DEBUG_ASSERT(iovec_actual_ < iovec_capacity_);
iovecs_[iovec_actual_] = {
.buffer = current_iovec_bytes_begin_,
.capacity = static_cast<uint32_t>(ConsumedBytesInCurrentIovec()),
};
iovec_actual_++;
}
return fitx::ok(Result{
.iovec_actual = iovec_actual_,
.handle_actual = handle_actual_,
});
}
private:
int64_t AvailableBytesInCurrentIovec() const {
return static_cast<int64_t>(current_iovec_bytes_end_ - current_iovec_bytes_);
}
int64_t ConsumedBytesInCurrentIovec() const {
return static_cast<int64_t>(current_iovec_bytes_ - current_iovec_bytes_begin_);
}
const CodingConfig* coding_config_;
zx_channel_iovec_t* iovecs_;
size_t iovec_capacity_;
size_t iovec_actual_ = 0;
fidl_handle_t* handles_;
fidl_handle_metadata_t* handle_metadata_;
size_t handle_capacity_;
size_t handle_actual_ = 0;
uint8_t* current_iovec_bytes_;
uint8_t* current_iovec_bytes_begin_;
uint8_t* current_iovec_bytes_end_;
// All bytes across all iovecs.
size_t byte_actual_ = 0;
// An error is considered to be set if |error_| is not null.
// The error_status_ value (whether set to a new value or not) is then used to determine
// the error code.
zx_status_t error_status_ = ZX_ERR_INVALID_ARGS;
const char* error_ = nullptr;
};
} // namespace fidl::internal
#endif // LIB_FIDL_LLCPP_INCLUDE_LIB_FIDL_LLCPP_WIRE_ENCODER_H_