| // Copyright 2020 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 "tools/fidlcat/lib/event.h" |
| |
| #include "src/lib/fidl_codec/proto_value.h" |
| #include "tools/fidlcat/lib/syscall_decoder_dispatcher.h" |
| #include "tools/fidlcat/proto/session.pb.h" |
| |
| namespace fidlcat { |
| |
| void Process::LoadHandleInfo(Inference* inference) { |
| zxdb::Process* zxdb_process = zxdb_process_.get(); |
| if (zxdb_process == nullptr) { |
| return; |
| } |
| if (loading_handle_info_) { |
| // We are currently loading information about the handles. If we are unlucky, the result won't |
| // include information about handles we are now needing. Ask the process to do another load just |
| // after the current one to be sure to have all the handles we need (including the handle only |
| // needed after the start of the load). |
| needs_to_load_handle_info_ = true; |
| return; |
| } |
| loading_handle_info_ = true; |
| needs_to_load_handle_info_ = false; |
| zxdb_process->LoadInfoHandleTable( |
| [this, inference](zxdb::ErrOr<std::vector<debug_ipc::InfoHandle>> handles) { |
| loading_handle_info_ = false; |
| if (!handles.ok()) { |
| FX_LOGS(ERROR) << "msg: " << handles.err().msg(); |
| } else { |
| for (const auto& handle : handles.value()) { |
| HandleInfo* handle_info = SearchHandleInfo(handle.handle_value); |
| if (handle_info != nullptr) { |
| // Associate the koid and the object type to the handle only if the handle is |
| // currently used by the monitored process. That is if the handle if referenced by an |
| // event. |
| // That means that we may need an extra load if the handle is already known by the |
| // kernel but not yet needed by the monitored process. This way we avoid creating |
| // a Handle object for handles we don't know the semantic. |
| handle_info->set_object_type(handle.type); |
| handle_info->set_rights(handle.rights); |
| handle_info->set_koid(handle.koid); |
| inference->AddKoidHandleInfo(handle.koid, handle_info); |
| } |
| if (handle.related_koid != ZX_HANDLE_INVALID) { |
| // However, the association of koids is always useful. |
| inference->AddLinkedKoids(handle.koid, handle.related_koid); |
| } |
| } |
| if (needs_to_load_handle_info_) { |
| needs_to_load_handle_info_ = false; |
| LoadHandleInfo(inference); |
| } |
| } |
| }); |
| } |
| |
| void Protocol::AddEvent(const OutputEvent* event, const fidl_codec::FidlMessageValue* message) { |
| Method* method = GetMethod(message->ordinal(), message->method()); |
| method->AddEvent(event); |
| ++event_count_; |
| } |
| |
| void Process::AddEvent(const OutputEvent* event, const fidl_codec::FidlMessageValue* message) { |
| Protocol* protocol = GetProtocol( |
| (message->method() != nullptr) ? &message->method()->enclosing_protocol() : nullptr); |
| protocol->AddEvent(event, message); |
| ++event_count_; |
| } |
| |
| void ProcessLaunchedEvent::Write(proto::Event* dst) const { |
| dst->set_timestamp(timestamp()); |
| proto::ProcessLaunchedEvent* event = dst->mutable_process_launched(); |
| event->set_command(command()); |
| event->set_error_message(error_message()); |
| } |
| |
| void ProcessMonitoredEvent::Write(proto::Event* dst) const { |
| dst->set_timestamp(timestamp()); |
| proto::ProcessMonitoredEvent* event = dst->mutable_process_monitored(); |
| event->set_process_koid(process()->koid()); |
| event->set_error_message(error_message()); |
| } |
| |
| void StopMonitoringEvent::Write(proto::Event* dst) const { |
| dst->set_timestamp(timestamp()); |
| proto::StopMonitoringEvent* event = dst->mutable_stop_monitoring(); |
| event->set_process_koid(process()->koid()); |
| } |
| |
| bool SyscallEvent::NeedsToLoadHandleInfo(Inference* inference) { |
| for (const auto& field : inline_fields_) { |
| if (field.second->NeedsToLoadHandleInfo(timestamp(), thread()->koid(), inference)) { |
| return true; |
| } |
| } |
| for (const auto& field : outline_fields_) { |
| if (field.second->NeedsToLoadHandleInfo(timestamp(), thread()->koid(), inference)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| const fidl_codec::FidlMessageValue* SyscallEvent::GetMessage() const { |
| if (outline_fields_.size() == 0) { |
| return nullptr; |
| } |
| return outline_fields_.begin()->second->AsFidlMessageValue(); |
| } |
| |
| const fidl_codec::Value* SyscallEvent::GetValue(const fidl_codec::StructMember* member) const { |
| if (member == nullptr) { |
| return nullptr; |
| } |
| auto result = inline_fields_.find(member); |
| if (result != inline_fields_.end()) { |
| return result->second.get(); |
| } |
| auto result2 = outline_fields_.find(member); |
| if (result2 != outline_fields_.end()) { |
| return result2->second.get(); |
| } |
| return nullptr; |
| } |
| |
| const fidl_codec::HandleValue* SyscallEvent::GetHandleValue( |
| const fidl_codec::StructMember* member) const { |
| if (member == nullptr) { |
| return nullptr; |
| } |
| auto result = inline_fields_.find(member); |
| if (result == inline_fields_.end()) { |
| return nullptr; |
| } |
| return result->second->AsHandleValue(); |
| } |
| |
| HandleInfo* SyscallEvent::GetHandleInfo(const fidl_codec::StructMember* member) const { |
| if (member == nullptr) { |
| return nullptr; |
| } |
| auto result = inline_fields_.find(member); |
| if (result == inline_fields_.end()) { |
| return nullptr; |
| } |
| const fidl_codec::HandleValue* value = result->second->AsHandleValue(); |
| if (value == nullptr) { |
| return nullptr; |
| } |
| return thread()->process()->SearchHandleInfo(value->handle().handle); |
| } |
| |
| void InvokedEvent::ComputeHandleInfo(SyscallDisplayDispatcher* dispatcher) { |
| switch (syscall()->kind()) { |
| case SyscallKind::kChannelRead: |
| case SyscallKind::kChannelWrite: |
| case SyscallKind::kChannelCall: { |
| // Compute the handle which is used to read/write a message. |
| FX_DCHECK(!syscall()->input_inline_members().empty()); |
| auto value = inline_fields().find(syscall()->input_inline_members()[0].get()); |
| FX_DCHECK(value != inline_fields().end()); |
| handle_info_ = |
| thread()->process()->SearchHandleInfo(value->second->AsHandleValue()->handle().handle); |
| if (handle_info_ == nullptr) { |
| handle_info_ = dispatcher->CreateHandleInfo( |
| thread(), value->second->AsHandleValue()->handle().handle, 0, false); |
| } |
| break; |
| } |
| default: |
| break; |
| } |
| } |
| |
| void InvokedEvent::Write(proto::Event* dst) const { |
| dst->set_timestamp(timestamp()); |
| proto::InvokedEvent* event = dst->mutable_invoked(); |
| event->set_thread_koid(thread()->koid()); |
| for (const auto& location : stack_frame_) { |
| proto::Location* proto_location = event->add_frame(); |
| proto_location->set_path(location.path()); |
| proto_location->set_line(location.line()); |
| proto_location->set_column(location.column()); |
| proto_location->set_address(location.address()); |
| proto_location->set_symbol(location.symbol()); |
| } |
| event->set_syscall(syscall()->name()); |
| for (const auto& field : inline_fields()) { |
| fidl_codec::proto::Value value; |
| fidl_codec::ProtoVisitor visitor(&value); |
| field.second->Visit(&visitor, nullptr); |
| if (field.first->id() != 0) { |
| event->mutable_inline_id_fields()->insert( |
| google::protobuf::MapPair(static_cast<uint32_t>(field.first->id()), value)); |
| } else { |
| event->mutable_inline_fields()->insert(google::protobuf::MapPair(field.first->name(), value)); |
| } |
| } |
| for (const auto& field : outline_fields()) { |
| fidl_codec::proto::Value value; |
| fidl_codec::ProtoVisitor visitor(&value); |
| field.second->Visit(&visitor, nullptr); |
| if (field.first->id() != 0) { |
| event->mutable_outline_id_fields()->insert( |
| google::protobuf::MapPair(static_cast<uint32_t>(field.first->id()), value)); |
| } else { |
| event->mutable_outline_fields()->insert( |
| google::protobuf::MapPair(field.first->name(), value)); |
| } |
| } |
| } |
| |
| void InvokedEvent::PrettyPrint(FidlcatPrinter& printer) const { |
| if (printer.display_stack_frame()) { |
| printer.DisplayStackFrame(stack_frame_); |
| } |
| printer << syscall()->name(); |
| printer.DisplayInline(syscall()->input_inline_members(), inline_fields()); |
| printer << '\n'; |
| printer.DisplayOutline(syscall()->input_outline_members(), outline_fields()); |
| } |
| |
| void OutputEvent::Write(proto::Event* dst) const { |
| dst->set_timestamp(timestamp()); |
| proto::OutputEvent* event = dst->mutable_output(); |
| event->set_thread_koid(thread()->koid()); |
| event->set_syscall(syscall()->name()); |
| event->set_returned_value(returned_value()); |
| event->set_invoked_event_id(invoked_event()->id()); |
| for (const auto& field : inline_fields()) { |
| fidl_codec::proto::Value value; |
| fidl_codec::ProtoVisitor visitor(&value); |
| field.second->Visit(&visitor, nullptr); |
| if (field.first->id() != 0) { |
| event->mutable_inline_id_fields()->insert( |
| google::protobuf::MapPair(static_cast<uint32_t>(field.first->id()), value)); |
| } else { |
| event->mutable_inline_fields()->insert(google::protobuf::MapPair(field.first->name(), value)); |
| } |
| } |
| for (const auto& field : outline_fields()) { |
| fidl_codec::proto::Value value; |
| fidl_codec::ProtoVisitor visitor(&value); |
| field.second->Visit(&visitor, nullptr); |
| if (field.first->id() != 0) { |
| event->mutable_outline_id_fields()->insert( |
| google::protobuf::MapPair(static_cast<uint32_t>(field.first->id()), value)); |
| } else { |
| event->mutable_outline_fields()->insert( |
| google::protobuf::MapPair(field.first->name(), value)); |
| } |
| } |
| } |
| |
| void OutputEvent::Display(FidlcatPrinter& printer, bool with_channel) const { |
| const fidl_codec::FidlMessageValue* message = invoked_event_->GetMessage(); |
| if (message == nullptr) { |
| message = GetMessage(); |
| if (message == nullptr) { |
| return; |
| } |
| } |
| printer << fidl_codec::Green << printer.dispatcher()->GetTime(timestamp()) |
| << fidl_codec::ResetColor << ' '; |
| switch (syscall()->kind()) { |
| case SyscallKind::kChannelRead: |
| printer << "read "; |
| break; |
| case SyscallKind::kChannelWrite: |
| printer << "write "; |
| break; |
| case SyscallKind::kChannelCall: |
| printer << "call "; |
| break; |
| default: |
| return; |
| } |
| const fidl_codec::ProtocolMethod* method = message->method(); |
| if (message->ordinal() == kFidlOrdinalEpitaph) { |
| printer << fidl_codec::WhiteOnMagenta << "epitaph " << fidl_codec::ResetColor << ' ' |
| << ((message->epitaph_error() == "ZX_OK") ? fidl_codec::Green : fidl_codec::Red) |
| << message->epitaph_error() << fidl_codec::ResetColor; |
| } else { |
| if (method == nullptr) { |
| printer << " ordinal=" << std::hex << message->ordinal() << std::dec; |
| } else { |
| printer << fidl_codec::WhiteOnMagenta |
| << (message->is_request() |
| ? "request " |
| : ((method->request() != nullptr) ? "response" : "event ")) |
| << fidl_codec::ResetColor << ' ' << fidl_codec::Green |
| << method->enclosing_protocol().name() << '.' << method->name() |
| << fidl_codec::ResetColor; |
| } |
| } |
| bool first_argument = true; |
| if (with_channel && (invoked_event()->handle_info() != nullptr)) { |
| printer << '('; |
| printer.DisplayHandleInfo(invoked_event()->handle_info()); |
| first_argument = false; |
| } |
| if ((method != nullptr) && (method->short_display() != nullptr)) { |
| fidl_codec::Indent indent(printer); |
| const fidl_codec::PayloadableValue* request = |
| (syscall()->kind() == SyscallKind::kChannelRead) |
| ? GetMessage()->decoded_request() |
| : invoked_event()->GetMessage()->decoded_request(); |
| fidl_codec::semantic::SemanticContext context(&printer.inference(), printer.process()->koid(), |
| (invoked_event()->handle_info() == nullptr) |
| ? ZX_HANDLE_INVALID |
| : invoked_event()->handle_info()->handle(), |
| request, nullptr, invoked_event()->timestamp()); |
| for (const auto& expression : method->short_display()->inputs()) { |
| if (first_argument) { |
| printer << '('; |
| first_argument = false; |
| } else { |
| printer << ", "; |
| } |
| expression->PrettyPrint(printer, &context); |
| } |
| } |
| if (!first_argument) { |
| printer << ')'; |
| } |
| printer << '\n'; |
| if ((method != nullptr) && (method->short_display() != nullptr)) { |
| fidl_codec::Indent indent(printer); |
| const fidl_codec::PayloadableValue* request = |
| (syscall()->kind() == SyscallKind::kChannelRead) |
| ? GetMessage()->decoded_request() |
| : invoked_event()->GetMessage()->decoded_request(); |
| fidl_codec::semantic::SemanticContext context(&printer.inference(), printer.process()->koid(), |
| (invoked_event()->handle_info() == nullptr) |
| ? ZX_HANDLE_INVALID |
| : invoked_event()->handle_info()->handle(), |
| request, nullptr, invoked_event()->timestamp()); |
| bool first_result = true; |
| for (const auto& expression : method->short_display()->results()) { |
| printer << (first_result ? "-> " : ", "); |
| first_result = false; |
| expression->PrettyPrint(printer, &context); |
| } |
| if (!first_result) { |
| printer << '\n'; |
| } |
| } |
| } |
| |
| void OutputEvent::PrettyPrint(FidlcatPrinter& printer) const { |
| fidl_codec::Indent indent(printer); |
| |
| switch (syscall()->return_type()) { |
| case SyscallReturnType::kNoReturn: |
| return; |
| case SyscallReturnType::kVoid: |
| if (inline_fields().empty() && outline_fields().empty()) { |
| return; |
| } |
| printer << "-> "; |
| break; |
| case SyscallReturnType::kStatus: |
| printer << "-> "; |
| printer.DisplayStatus(static_cast<zx_status_t>(returned_value_)); |
| break; |
| case SyscallReturnType::kTicks: |
| printer << "-> " << fidl_codec::Green << "ticks" << fidl_codec::ResetColor << ": " |
| << fidl_codec::Blue << static_cast<uint64_t>(returned_value_) |
| << fidl_codec::ResetColor; |
| break; |
| case SyscallReturnType::kTime: |
| printer << "-> " << fidl_codec::Green << "time" << fidl_codec::ResetColor << ": "; |
| printer.DisplayTime(static_cast<zx_time_t>(returned_value_)); |
| break; |
| case SyscallReturnType::kUint32: |
| printer << "-> " << fidl_codec::Blue << static_cast<uint32_t>(returned_value_) |
| << fidl_codec::ResetColor; |
| break; |
| case SyscallReturnType::kUint64: |
| printer << "-> " << fidl_codec::Blue << static_cast<uint64_t>(returned_value_) |
| << fidl_codec::ResetColor; |
| break; |
| } |
| // Adds the inline output arguments (if any). |
| if (!inline_fields().empty()) { |
| printer << ' '; |
| printer.DisplayInline(syscall()->output_inline_members(), inline_fields()); |
| } |
| printer << '\n'; |
| printer.DisplayOutline(syscall()->output_outline_members(), outline_fields()); |
| } |
| |
| void ExceptionEvent::Write(proto::Event* dst) const { |
| dst->set_timestamp(timestamp()); |
| proto::ExceptionEvent* event = dst->mutable_exception(); |
| event->set_thread_koid(thread()->koid()); |
| for (const auto& location : stack_frame_) { |
| proto::Location* proto_location = event->add_frame(); |
| proto_location->set_path(location.path()); |
| proto_location->set_line(location.line()); |
| proto_location->set_column(location.column()); |
| proto_location->set_address(location.address()); |
| proto_location->set_symbol(location.symbol()); |
| } |
| } |
| |
| void ExceptionEvent::PrettyPrint(FidlcatPrinter& printer) const { |
| printer.DisplayStackFrame(stack_frame_); |
| printer << fidl_codec::Red << "thread stopped on exception" << fidl_codec::ResetColor << '\n'; |
| } |
| |
| bool EventDecoder::DecodeAndDispatchEvent(const proto::Event& proto_event) { |
| switch (proto_event.Kind_case()) { |
| case proto::Event::kProcessLaunched: { |
| const proto::ProcessLaunchedEvent& content = proto_event.process_launched(); |
| dispatcher_->AddProcessLaunchedEvent(std::make_shared<ProcessLaunchedEvent>( |
| proto_event.timestamp(), content.command(), content.error_message())); |
| return true; |
| } |
| case proto::Event::kProcessMonitored: { |
| const proto::ProcessMonitoredEvent& content = proto_event.process_monitored(); |
| Process* process = dispatcher_->SearchProcess(content.process_koid()); |
| if (process == nullptr) { |
| FX_LOGS(ERROR) << "Process " << content.process_koid() << " not found for event ."; |
| return false; |
| } |
| dispatcher_->AddProcessMonitoredEvent(std::make_shared<ProcessMonitoredEvent>( |
| proto_event.timestamp(), process, content.error_message())); |
| return true; |
| } |
| case proto::Event::kStopMonitoring: { |
| const proto::StopMonitoringEvent& content = proto_event.stop_monitoring(); |
| Process* process = dispatcher_->SearchProcess(content.process_koid()); |
| if (process == nullptr) { |
| FX_LOGS(ERROR) << "Process " << content.process_koid() << " not found for event ."; |
| return false; |
| } |
| dispatcher_->AddStopMonitoringEvent( |
| std::make_shared<StopMonitoringEvent>(proto_event.timestamp(), process)); |
| return true; |
| } |
| case proto::Event::kInvoked: { |
| const proto::InvokedEvent& content = proto_event.invoked(); |
| Thread* thread = dispatcher_->SearchThread(content.thread_koid()); |
| if (thread == nullptr) { |
| FX_LOGS(ERROR) << "Thread " << content.thread_koid() << " not found for event."; |
| return false; |
| } |
| Syscall* syscall = dispatcher_->SearchSyscall(content.syscall()); |
| if (syscall == nullptr) { |
| FX_LOGS(ERROR) << "Syscall " << content.syscall() << " not found."; |
| return false; |
| } |
| auto event = std::make_shared<InvokedEvent>(proto_event.timestamp(), thread, syscall); |
| if (!DecodeValues(event.get(), content.inline_fields(), content.inline_id_fields(), |
| content.outline_fields(), content.outline_id_fields(), |
| /*invoked=*/true)) { |
| return false; |
| } |
| for (int index = 0; index < content.frame_size(); ++index) { |
| const proto::Location& proto_location = content.frame(index); |
| event->stack_frame().emplace_back(proto_location.path(), proto_location.line(), |
| proto_location.column(), proto_location.address(), |
| proto_location.symbol()); |
| } |
| invoked_events_.emplace(std::make_pair(invoked_events_.size(), event)); |
| dispatcher_->AddInvokedEvent(std::move(event)); |
| return true; |
| } |
| case proto::Event::kOutput: { |
| const proto::OutputEvent& content = proto_event.output(); |
| Thread* thread = dispatcher_->SearchThread(content.thread_koid()); |
| if (thread == nullptr) { |
| FX_LOGS(ERROR) << "Thread " << content.thread_koid() << " not found for event."; |
| return false; |
| } |
| Syscall* syscall = dispatcher_->SearchSyscall(content.syscall()); |
| if (syscall == nullptr) { |
| FX_LOGS(ERROR) << "Syscall " << content.syscall() << " not found."; |
| return false; |
| } |
| auto invoked_event = invoked_events_.find(content.invoked_event_id()); |
| if (invoked_event == invoked_events_.end()) { |
| FX_LOGS(ERROR) << "Invoked event " << content.invoked_event_id() |
| << " not found for ouput event."; |
| return false; |
| } |
| auto event = std::make_shared<OutputEvent>(proto_event.timestamp(), thread, syscall, |
| content.returned_value(), invoked_event->second); |
| if (!DecodeValues(event.get(), content.inline_fields(), content.inline_id_fields(), |
| content.outline_fields(), content.outline_id_fields(), |
| /*invoked=*/false)) { |
| return false; |
| } |
| dispatcher_->AddOutputEvent(std::move(event)); |
| return true; |
| } |
| case proto::Event::kException: { |
| const proto::ExceptionEvent& content = proto_event.exception(); |
| Thread* thread = dispatcher_->SearchThread(content.thread_koid()); |
| if (thread == nullptr) { |
| FX_LOGS(ERROR) << "Thread " << content.thread_koid() << " not found for event."; |
| return false; |
| } |
| auto event = std::make_shared<ExceptionEvent>(proto_event.timestamp(), thread); |
| for (int index = 0; index < content.frame_size(); ++index) { |
| const proto::Location& proto_location = content.frame(index); |
| event->stack_frame().emplace_back(proto_location.path(), proto_location.line(), |
| proto_location.column(), proto_location.address(), |
| proto_location.symbol()); |
| } |
| dispatcher_->AddExceptionEvent(std::move(event)); |
| return true; |
| } |
| default: |
| FX_LOGS(ERROR) << "Bad kind for event."; |
| return false; |
| } |
| } |
| |
| bool EventDecoder::DecodeValues( |
| SyscallEvent* event, |
| const ::google::protobuf::Map<::std::string, ::fidl_codec::proto::Value>& inline_fields, |
| const ::google::protobuf::Map<uint32_t, ::fidl_codec::proto::Value>& inline_id_fields, |
| const ::google::protobuf::Map<::std::string, ::fidl_codec::proto::Value>& outline_fields, |
| const ::google::protobuf::Map<uint32_t, ::fidl_codec::proto::Value>& outline_id_fields, |
| bool invoked) { |
| bool ok = true; |
| for (const auto& proto_value : inline_fields) { |
| const fidl_codec::StructMember* member = |
| event->syscall()->SearchInlineMember(proto_value.first, invoked); |
| if (member == nullptr) { |
| FX_LOGS(ERROR) << "Member " << proto_value.first << " not found for " |
| << event->syscall()->name() << '.'; |
| ok = false; |
| } else { |
| std::unique_ptr<fidl_codec::Value> value = |
| fidl_codec::DecodeValue(dispatcher_->loader(), proto_value.second, member->type()); |
| if (value == nullptr) { |
| ok = false; |
| } else { |
| event->AddInlineField(member, std::move(value)); |
| } |
| } |
| } |
| for (const auto& proto_value : inline_id_fields) { |
| const fidl_codec::StructMember* member = |
| event->syscall()->SearchInlineMember(proto_value.first, invoked); |
| if (member == nullptr) { |
| FX_LOGS(ERROR) << "Member " << proto_value.first << " not found for " |
| << event->syscall()->name() << '.'; |
| ok = false; |
| } else { |
| std::unique_ptr<fidl_codec::Value> value = |
| fidl_codec::DecodeValue(dispatcher_->loader(), proto_value.second, member->type()); |
| if (value == nullptr) { |
| ok = false; |
| } else { |
| event->AddInlineField(member, std::move(value)); |
| } |
| } |
| } |
| for (const auto& proto_value : outline_fields) { |
| const fidl_codec::StructMember* member = |
| event->syscall()->SearchOutlineMember(proto_value.first, invoked); |
| if (member == nullptr) { |
| FX_LOGS(ERROR) << "Member " << proto_value.first << " not found for " |
| << event->syscall()->name() << '.'; |
| ok = false; |
| } else { |
| std::unique_ptr<fidl_codec::Value> value = |
| fidl_codec::DecodeValue(dispatcher_->loader(), proto_value.second, member->type()); |
| if (value == nullptr) { |
| ok = false; |
| } else { |
| event->AddOutlineField(member, std::move(value)); |
| } |
| } |
| } |
| for (const auto& proto_value : outline_id_fields) { |
| const fidl_codec::StructMember* member = |
| event->syscall()->SearchOutlineMember(proto_value.first, invoked); |
| if (member == nullptr) { |
| FX_LOGS(ERROR) << "Member " << proto_value.first << " not found for " |
| << event->syscall()->name() << '.'; |
| ok = false; |
| } else { |
| std::unique_ptr<fidl_codec::Value> value = |
| fidl_codec::DecodeValue(dispatcher_->loader(), proto_value.second, member->type()); |
| if (value == nullptr) { |
| ok = false; |
| } else { |
| event->AddOutlineField(member, std::move(value)); |
| } |
| } |
| } |
| return ok; |
| } |
| |
| } // namespace fidlcat |