blob: a9d5434dc23a7f274d045a5b8d71b73d224bca5c [file] [log] [blame]
// Copyright 2019 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.
#include "wire_types.h"
#include <src/lib/fxl/logging.h>
#include <zircon/fidl.h>
#include "rapidjson/error/en.h"
#include "tools/fidlcat/lib/library_loader.h"
// See wire_types.h for details.
namespace fidlcat {
namespace {
// Objects are 8-byte aligned. This adds 7 to the current offset and masks out
// the last three bits.
inline const uint8_t* AlignToNextWordBoundary(const uint8_t* offset) {
const uintptr_t val = reinterpret_cast<const uintptr_t>(offset);
return reinterpret_cast<uint8_t*>((val + 7) & (~7));
}
} // namespace
bool ObjectTracker::RunCallbacksFrom(Marker& marker) {
marker.AdvanceBytesTo(AlignToNextWordBoundary(marker.byte_pos()));
if (!marker.is_valid()) {
return false;
}
// We can't just iterate over the callbacks, because the callbacks may add
// more callbacks.
while (!callbacks_.empty()) {
auto callback = std::move(callbacks_.front());
callback(marker);
if (!marker.is_valid()) {
return false;
}
callbacks_.erase(callbacks_.begin());
}
return true;
}
void ObjectTracker::ObjectEnqueue(
const std::string& key, ValueGeneratingCallback&& callback,
rapidjson::Value& target_object,
rapidjson::Document::AllocatorType& allocator) {
callbacks_.push_back([this, cb = std::move(callback), &target_object,
key_string = key, &allocator](Marker& marker) {
rapidjson::Value key;
key.SetString(key_string.c_str(), allocator);
rapidjson::Value& object =
target_object.AddMember(key, rapidjson::Value(), allocator);
cb(this, marker, object[key_string.c_str()], allocator);
});
}
void ObjectTracker::ArrayEnqueue(
ValueGeneratingCallback&& callback, rapidjson::Value& target_array,
rapidjson::Document::AllocatorType& allocator) {
callbacks_.push_back([this, cb = std::move(callback), &target_array,
&allocator](Marker& marker) {
rapidjson::Value element;
cb(this, marker, element, allocator);
if (!marker.is_valid()) {
return;
}
target_array.PushBack(element, allocator);
});
}
void Marker::AdvanceBytesBy(size_t amount) {
const uint8_t* pos = byte_pos_ + amount;
AdvanceBytesTo(pos);
}
void Marker::AdvanceBytesTo(const uint8_t* pos) { byte_pos_ = pos; }
void Marker::AdvanceHandlesBy(size_t amount) {
const zx_handle_t* pos = handle_pos_ + amount;
AdvanceHandlesTo(pos);
}
void Marker::AdvanceHandlesTo(const zx_handle_t* pos) { handle_pos_ = pos; }
bool Marker::is_valid() const {
return (end_byte_pos_ == nullptr || byte_pos_ <= end_byte_pos_) &&
(end_byte_pos_ == nullptr || handle_pos_ <= end_handle_pos_);
}
namespace {
ValueGeneratingCallback NullCallback() {
return [](ObjectTracker* tracker, Marker& marker, rapidjson::Value& value,
rapidjson::Document::AllocatorType& allocator) { value.SetNull(); };
}
} // namespace
// Prints out raw bytes as a C string of hex pairs ("af b0 1e..."). Useful for
// debugging / unknown data.
Marker UnknownType::GetValueCallback(Marker marker, size_t length,
ObjectTracker* tracker,
ValueGeneratingCallback& callback) const {
const uint8_t* bytes = marker.byte_pos();
marker.AdvanceBytesBy(length);
if (marker.is_valid()) {
callback = [length, bytes](ObjectTracker* tracker, Marker marker,
rapidjson::Value& value,
rapidjson::Document::AllocatorType& allocator) {
size_t size = length * 3 + 1;
char output[size];
for (size_t i = 0; i < length; i++) {
snprintf(output + (i * 3), 4, "%02x ", bytes[i]);
}
output[size - 2] = '\0';
value.SetString(output, size, allocator);
return;
};
}
return marker;
}
bool Type::ValueEquals(Marker marker, size_t length,
const rapidjson::Value& value) const {
FXL_LOG(FATAL) << "Equality operator for type not implemented";
return false;
}
size_t Type::InlineSize() const {
FXL_LOG(FATAL) << "Size for type not implemented";
return 0;
}
Marker StringType::GetValueCallback(Marker marker, size_t length,
ObjectTracker* tracker,
ValueGeneratingCallback& callback) const {
const uint8_t* bytes = marker.byte_pos();
marker.AdvanceBytesBy(length);
if (!marker.is_valid()) {
return marker;
}
// Strings: First 8 bytes is length
uint64_t string_length = internal::MemoryFrom<uint64_t>(bytes);
// next 8 bytes are 0 if the string is null, and 0xffffffff otherwise.
bool is_null = bytes[sizeof(uint64_t)] == 0x0;
callback = [is_null, string_length](
ObjectTracker* tracker, Marker& marker,
rapidjson::Value& value,
rapidjson::Document::AllocatorType& allocator) {
if (is_null) {
value.SetString("(null)", allocator);
return;
}
const uint8_t* bytes = marker.byte_pos();
marker.AdvanceBytesTo(
AlignToNextWordBoundary(marker.byte_pos() + string_length));
if (marker.is_valid()) {
// everything after that is the string.
value.SetString(reinterpret_cast<const char*>(bytes), string_length,
allocator);
}
return;
};
return marker;
}
Marker BoolType::GetValueCallback(Marker marker, size_t length,
ObjectTracker* tracker,
ValueGeneratingCallback& callback) const {
const uint8_t* bytes = marker.byte_pos();
marker.AdvanceBytesBy(sizeof(bool));
if (marker.is_valid()) {
callback = [val = *bytes](ObjectTracker* tracker, Marker& marker,
rapidjson::Value& value,
rapidjson::Document::AllocatorType& allocator) {
// assert that length == 1
if (val) {
value.SetString("true", allocator);
} else {
value.SetString("false", allocator);
}
return;
};
}
return marker;
}
Marker StructType::GetValueCallback(Marker marker, size_t length,
ObjectTracker* tracker,
ValueGeneratingCallback& callback) const {
const Struct& str = struct_;
callback = [str, mp = marker](ObjectTracker* tracker, Marker& marker,
rapidjson::Value& value,
rapidjson::Document::AllocatorType& allocator) {
value.SetObject();
Marker inline_marker = mp;
Marker prev_marker = inline_marker;
for (auto& member : str.members()) {
std::unique_ptr<Type> member_type = member.GetType();
ValueGeneratingCallback value_callback;
Marker value_marker(inline_marker.byte_pos() + member.offset(),
prev_marker.handle_pos(), tracker->end());
if (!value_marker.is_valid()) {
marker = value_marker;
return;
}
prev_marker = member_type->GetValueCallback(value_marker, member.size(),
tracker, value_callback);
if (!prev_marker.is_valid()) {
marker = value_marker;
return;
}
tracker->ObjectEnqueue(member.name(), std::move(value_callback), value,
allocator);
}
};
marker.AdvanceBytesBy(length);
return marker;
}
size_t StructType::InlineSize() const { return struct_.size(); }
UnionType::UnionType(const Union& uni) : union_(uni) {}
Marker UnionType::GetValueCallback(Marker marker, size_t length,
ObjectTracker* tracker,
ValueGeneratingCallback& callback) const {
const Union& uni = union_;
callback = [uni, mp = marker](ObjectTracker* tracker, Marker& marker,
rapidjson::Value& value,
rapidjson::Document::AllocatorType& allocator) {
value.SetObject();
Marker inline_marker = mp;
const uint8_t* final_pos = inline_marker.byte_pos() + uni.size();
// Read tag
const uint8_t* bytes = inline_marker.byte_pos();
uint32_t tag = internal::MemoryFrom<uint32_t>(bytes);
inline_marker.AdvanceBytesBy(uni.alignment());
if (!inline_marker.is_valid()) {
marker = inline_marker;
return;
}
// Determine member type and get appropriate callback
const UnionMember& member = uni.MemberWithTag(tag);
std::unique_ptr<Type> member_type = member.GetType();
ValueGeneratingCallback raw_value_callback;
inline_marker = member_type->GetValueCallback(inline_marker, member.size(),
tracker, raw_value_callback);
ValueGeneratingCallback value_callback =
[rvc = std::move(raw_value_callback), final_pos, tracker,
inline_marker](ObjectTracker* ignored, Marker& tracker_marker,
rapidjson::Value& value,
rapidjson::Document::AllocatorType& allocator) {
Marker marker = inline_marker;
rvc(tracker, marker, value, allocator);
// Advance marker's pos to the real end, which may or may not be the
// end of the value.
marker.AdvanceBytesTo(final_pos);
if (!marker.is_valid()) {
tracker_marker = marker;
}
};
tracker->ObjectEnqueue(member.name(), std::move(value_callback), value,
allocator);
};
marker.AdvanceBytesBy(length);
return marker;
}
size_t UnionType::InlineSize() const { return union_.size(); }
PointerType::PointerType(Type* target_type) : target_type_(target_type) {}
Marker PointerType::GetValueCallback(Marker marker, size_t length,
ObjectTracker* tracker,
ValueGeneratingCallback& callback) const {
// Check the intptr and maybe return a callback that does nothing but set a
// null value.
const uint8_t* bytes = marker.byte_pos();
uintptr_t data;
marker.AdvanceBytesBy(sizeof(data));
if (!marker.is_valid()) {
return marker;
}
data = internal::MemoryFrom<uint64_t>(bytes);
if (data == FIDL_ALLOC_ABSENT) {
callback = NullCallback();
return marker;
}
if (data != FIDL_ALLOC_PRESENT) {
// TODO: add "name" to the type, print it here instead of "object".
FXL_LOG(INFO) << "Illegally encoded object";
}
// If the intptr is non-null, return a callback that will track an out of line
// object of the wrapped type.
marker.AdvanceBytesBy(sizeof(data));
callback = [target_type = target_type_](
ObjectTracker* tracker, Marker& marker,
rapidjson::Value& value,
rapidjson::Document::AllocatorType& allocator) {
ValueGeneratingCallback callback;
ObjectTracker local_tracker(tracker->end());
Marker val = target_type->GetValueCallback(
marker, target_type->InlineSize(), &local_tracker, callback);
if (!val.is_valid()) {
marker = val;
return;
}
callback(&local_tracker, marker, value, allocator);
local_tracker.RunCallbacksFrom(val);
if (!val.is_valid()) {
marker = val;
return;
}
};
return marker;
}
ElementSequenceType::ElementSequenceType(std::unique_ptr<Type>&& component_type)
: component_type_(std::move(component_type)) {
FXL_DCHECK(component_type_.get() != nullptr);
}
ElementSequenceType::ElementSequenceType(std::shared_ptr<Type> component_type)
: component_type_(component_type) {
FXL_DCHECK(component_type_.get() != nullptr);
}
ValueGeneratingCallback ElementSequenceType::GetIteratingCallback(
ObjectTracker* tracker, size_t count, Marker marker, size_t length) const {
std::shared_ptr<Type> component_type = component_type_;
return [tracker, component_type, count, captured_marker = marker, length](
ObjectTracker* ignored, Marker& inline_marker,
rapidjson::Value& value,
rapidjson::Document::AllocatorType& allocator) {
value.SetArray();
Marker marker = captured_marker;
for (uint32_t i = 0; i < count; i++) {
ValueGeneratingCallback value_callback;
marker = component_type->GetValueCallback(marker, length / count, tracker,
value_callback);
if (!marker.is_valid()) {
inline_marker = marker;
return;
}
tracker->ArrayEnqueue(std::move(value_callback), value, allocator);
}
};
}
ArrayType::ArrayType(std::unique_ptr<Type>&& component_type, uint32_t count)
: ElementSequenceType(std::move(component_type)), count_(count) {}
Marker ArrayType::GetValueCallback(Marker marker, size_t length,
ObjectTracker* tracker,
ValueGeneratingCallback& callback) const {
callback = GetIteratingCallback(tracker, count_, marker, length);
marker.AdvanceBytesBy(length);
return marker;
}
VectorType::VectorType(std::unique_ptr<Type>&& component_type)
: ElementSequenceType(std::move(component_type)) {}
VectorType::VectorType(std::shared_ptr<Type> component_type,
size_t element_size)
: ElementSequenceType(component_type) {}
Marker VectorType::GetValueCallback(Marker marker, size_t length,
ObjectTracker* tracker,
ValueGeneratingCallback& callback) const {
const uint8_t* bytes = marker.byte_pos();
marker.AdvanceBytesBy(length);
if (!marker.is_valid()) {
return marker;
}
uint64_t count = internal::MemoryFrom<uint64_t>(bytes);
uint64_t data = internal::MemoryFrom<uint64_t>(bytes + sizeof(uint64_t));
size_t element_size = component_type_->InlineSize();
if (data == UINTPTR_MAX) {
VectorType vt(component_type_, element_size);
callback = [vt, tracker, element_size, count](
ObjectTracker* ignored, Marker& marker,
rapidjson::Value& value,
rapidjson::Document::AllocatorType& allocator) {
ValueGeneratingCallback value_cb =
vt.GetIteratingCallback(tracker, count, marker, element_size * count);
value_cb(tracker, marker, value, allocator);
marker.AdvanceBytesBy(element_size * count);
};
} else if (data == 0) {
// TODO: Validate this is a nullable vector.
callback = NullCallback();
}
return marker;
}
EnumType::EnumType(const Enum& e) : enum_(e) {}
Marker EnumType::GetValueCallback(Marker marker, size_t length,
ObjectTracker* tracker,
ValueGeneratingCallback& callback) const {
std::string name = enum_.GetNameFromBytes(marker.byte_pos(), length);
marker.AdvanceBytesBy(length);
if (!marker.is_valid()) {
return marker;
}
callback = [name](ObjectTracker* tracker, Marker& marker,
rapidjson::Value& value,
rapidjson::Document::AllocatorType& allocator) {
value.SetString(name, allocator);
};
return marker;
}
Marker HandleType::GetValueCallback(Marker marker, size_t length,
ObjectTracker* tracker,
ValueGeneratingCallback& callback) const {
const uint8_t* bytes = marker.byte_pos();
marker.AdvanceBytesBy(sizeof(zx_handle_t));
if (!marker.is_valid()) {
return marker;
}
zx_handle_t val = internal::MemoryFrom<zx_handle_t>(bytes);
if (val == FIDL_HANDLE_PRESENT) {
// Handle is out-of-line
callback = [](ObjectTracker* tracker, Marker& marker,
rapidjson::Value& value,
rapidjson::Document::AllocatorType& allocator) {
const zx_handle_t* handles = marker.handle_pos();
marker.AdvanceHandlesBy(1);
if (!marker.is_valid()) {
return;
}
zx_handle_t val = internal::MemoryFrom<zx_handle_t>(handles);
value.SetString(std::to_string(val).c_str(), allocator);
return;
};
} else if (val == FIDL_HANDLE_ABSENT) {
callback = [val](ObjectTracker* tracker, Marker& marker,
rapidjson::Value& value,
rapidjson::Document::AllocatorType& allocator) {
value.SetString(std::to_string(val).c_str(), allocator);
};
} else {
FXL_LOG(INFO) << "Illegally encoded handle";
}
return marker;
}
std::unique_ptr<Type> Type::get_illegal() {
return std::unique_ptr<Type>(new UnknownType());
}
std::unique_ptr<Type> Type::ScalarTypeFromName(const std::string& type_name) {
static std::map<std::string, std::function<std::unique_ptr<Type>()>>
scalar_type_map_{
{"bool", []() { return std::make_unique<BoolType>(); }},
{"float32", []() { return std::make_unique<Float32Type>(); }},
{"float64", []() { return std::make_unique<Float64Type>(); }},
{"int8", []() { return std::make_unique<Int8Type>(); }},
{"int16", []() { return std::make_unique<Int16Type>(); }},
{"int32", []() { return std::make_unique<Int32Type>(); }},
{"int64", []() { return std::make_unique<Int64Type>(); }},
{"uint8", []() { return std::make_unique<Uint8Type>(); }},
{"uint16", []() { return std::make_unique<Uint16Type>(); }},
{"uint32", []() { return std::make_unique<Uint32Type>(); }},
{"uint64", []() { return std::make_unique<Uint64Type>(); }},
};
auto it = scalar_type_map_.find(type_name);
if (it != scalar_type_map_.end()) {
return it->second();
}
return Type::get_illegal();
}
std::unique_ptr<Type> Type::TypeFromPrimitive(const rapidjson::Value& type) {
if (!type.HasMember("subtype")) {
FXL_LOG(ERROR) << "Invalid type";
return Type::get_illegal();
}
std::string subtype = type["subtype"].GetString();
return ScalarTypeFromName(subtype);
}
std::unique_ptr<Type> Type::TypeFromIdentifier(const LibraryLoader& loader,
const rapidjson::Value& type) {
if (!type.HasMember("identifier")) {
FXL_LOG(ERROR) << "Invalid type";
return std::unique_ptr<Type>();
}
std::string id = type["identifier"].GetString();
size_t split_index = id.find('/');
std::string library_name = id.substr(0, split_index);
const Library* library;
if (!loader.GetLibraryFromName(library_name, &library)) {
FXL_LOG(ERROR) << "Unknown type for identifier: " << library_name;
// TODO: Something else here
return std::unique_ptr<Type>();
}
bool is_nullable = false;
if (type.HasMember("nullable")) {
is_nullable = type["nullable"].GetBool();
}
return library->TypeFromIdentifier(is_nullable, id);
}
std::unique_ptr<Type> Type::GetType(const LibraryLoader& loader,
const rapidjson::Value& type) {
// TODO: This is creating a new type every time we need one. That's pretty
// inefficient. Find a way of caching them if it becomes a problem.
if (!type.HasMember("kind")) {
FXL_LOG(ERROR) << "Invalid type";
return Type::get_illegal();
}
std::string kind = type["kind"].GetString();
if (kind == "array") {
const rapidjson::Value& element_type = type["element_type"];
uint32_t element_count =
std::strtol(type["element_count"].GetString(), nullptr, 10);
return std::make_unique<ArrayType>(GetType(loader, element_type),
element_count);
} else if (kind == "vector") {
const rapidjson::Value& element_type = type["element_type"];
return std::make_unique<VectorType>(GetType(loader, element_type));
} else if (kind == "string") {
return std::make_unique<StringType>();
} else if (kind == "handle") {
return std::make_unique<HandleType>();
} else if (kind == "request") {
return std::make_unique<HandleType>();
} else if (kind == "primitive") {
return Type::TypeFromPrimitive(type);
} else if (kind == "identifier") {
return Type::TypeFromIdentifier(loader, type);
}
FXL_LOG(ERROR) << "Invalid type " << kind;
return get_illegal();
}
} // namespace fidlcat