blob: fbfe572a1593fa4d6f5d8d4ae30cceb1e9f34d08 [file] [log] [blame]
// Copyright 2023 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 "decode.h"
#include <Python.h>
#include <cinttypes>
#include <vector>
#include <fuchsia_controller_abi/utils.h>
#include "mod.h"
#include "python_dict_visitor.h"
#include "src/lib/fidl_codec/wire_parser.h"
#include "utils.h"
namespace fuchsia_controller::fidl_codec::decode {
enum Direction : uint8_t { REQUEST, RESPONSE };
namespace {
std::unique_ptr<zx_handle_disposition_t[]> python_obj_to_handle_disps(PyObject *handles,
Py_ssize_t *c_handles_len) {
if (!PyList_Check(handles)) {
PyErr_SetString(PyExc_TypeError, "Expected handles to be a list");
return nullptr;
}
*c_handles_len = PyList_Size(handles);
std::unique_ptr<zx_handle_disposition_t[]> c_handles =
std::make_unique<zx_handle_disposition_t[]>(*c_handles_len);
for (Py_ssize_t i = 0; i < *c_handles_len; ++i) {
auto obj = PyList_GetItem(handles, i);
if (obj == nullptr) {
return nullptr;
}
zx_handle_t res = utils::PyLong_AsU32(obj);
if (res == utils::MINUS_ONE_U32 && PyErr_Occurred()) {
return nullptr;
}
c_handles[i] = zx_handle_disposition_t{.handle = res};
}
return c_handles;
}
} // namespace
PyObject *decode_fidl_message(PyObject *self, PyObject *args, PyObject *kwds, // NOLINT
Direction direction) {
PyObject *handles = nullptr;
Py_buffer bytes_raw;
static const char *kwlist[] = {
"bytes",
"handles",
nullptr,
};
if (!PyArg_ParseTupleAndKeywords(args, kwds, "y*O", const_cast<char **>(kwlist), &bytes_raw,
&handles)) {
return nullptr;
}
Py_ssize_t c_handles_len;
auto c_handles = python_obj_to_handle_disps(handles, &c_handles_len);
if (c_handles == nullptr) {
return nullptr;
}
utils::Buffer bytes(bytes_raw);
auto header = static_cast<const fidl_message_header_t *>(bytes.buf());
const std::vector<::fidl_codec::ProtocolMethod *> *methods =
mod::get_module_state()->loader->GetByOrdinal(header->ordinal);
if (methods == nullptr || methods->empty()) {
PyErr_Format(PyExc_LookupError, "Unable to find any methods for method ordinal: %" PRIu64,
header->ordinal);
return nullptr;
}
// What is the approach here if there's more than one method?
const ::fidl_codec::ProtocolMethod *method = (*methods)[0];
std::unique_ptr<::fidl_codec::Value> object;
std::ostringstream errors;
bool successful;
switch (direction) {
case Direction::RESPONSE:
successful = ::fidl_codec::DecodeResponse(
method, static_cast<const uint8_t *>(bytes.buf()), static_cast<uint64_t>(bytes.len()),
c_handles.get(), static_cast<uint64_t>(c_handles_len), &object, errors);
break;
case Direction::REQUEST:
successful = ::fidl_codec::DecodeRequest(
method, static_cast<const uint8_t *>(bytes.buf()), static_cast<uint64_t>(bytes.len()),
c_handles.get(), static_cast<uint64_t>(c_handles_len), &object, errors);
break;
}
if (!successful) {
PyErr_SetString(PyExc_IOError, errors.str().c_str());
return nullptr;
}
if (object == nullptr) {
PyErr_SetString(PyExc_RuntimeError, "Parsed object is null");
return nullptr;
}
python_dict_visitor::PythonDictVisitor visitor;
object->Visit(&visitor, nullptr);
return visitor.result();
}
PyObject *decode_standalone(PyObject *self, PyObject *args, PyObject *kwds) { // NOLINT
const char *c_type_name = nullptr;
PyObject *handles = nullptr;
Py_buffer bytes_raw;
static const char *kwlist[] = {
"type_name",
"bytes",
"handles",
nullptr,
};
if (!PyArg_ParseTupleAndKeywords(args, kwds, "sy*O", const_cast<char **>(kwlist), &c_type_name,
&bytes_raw, &handles)) {
return nullptr;
}
std::string type_name(c_type_name);
auto const idx = type_name.find('/');
if (idx == std::string::npos) {
PyErr_SetString(PyExc_ValueError,
"Protocol not formatted properly, expected {library}/{protocol}");
return nullptr;
}
const std::string library_name(c_type_name, idx);
utils::Buffer bytes(bytes_raw);
Py_ssize_t c_handles_len;
auto c_handles = python_obj_to_handle_disps(handles, &c_handles_len);
if (c_handles == nullptr) {
return nullptr;
}
auto lib = mod::get_ir_library(library_name);
if (lib == nullptr) {
return nullptr;
}
auto type = lib->TypeFromIdentifier(false, type_name);
if (type == nullptr) {
PyErr_Format(PyExc_RuntimeError, "Invalid value returned for %s in library %s",
type_name.c_str(), library_name.c_str());
return nullptr;
}
::fidl_codec::InvalidType invalid_type;
if (type->Name() == invalid_type.Name()) {
PyErr_Format(PyExc_TypeError, "Unable to find type %s in library %s", type_name.c_str(),
library_name.c_str());
return nullptr;
}
std::ostringstream errors;
::fidl_codec::MessageDecoder decoder(static_cast<uint8_t *>(bytes.buf()), bytes.len(),
c_handles.get(), c_handles_len, errors);
decoder.SkipObject(type->InlineSize(::fidl_codec::WireVersion::kWireV2));
auto value = type->Decode(&decoder, 0);
if (value == nullptr || decoder.HasError()) {
PyErr_SetString(PyExc_RuntimeError, errors.str().c_str());
return nullptr;
}
python_dict_visitor::PythonDictVisitor visitor;
value->Visit(&visitor, nullptr);
return visitor.result();
}
PyObject *decode_fidl_response(PyObject *self, PyObject *args, PyObject *kwds) { // NOLINT
return decode_fidl_message(self, args, kwds, Direction::RESPONSE);
}
PyObject *decode_fidl_request(PyObject *self, PyObject *args, PyObject *kwds) { // NOLINT
return decode_fidl_message(self, args, kwds, Direction::REQUEST);
}
PyMethodDef decode_standalone_py_def = {
"decode_standalone", reinterpret_cast<PyCFunction>(decode_standalone),
METH_VARARGS | METH_KEYWORDS,
"Decodes a standalone fidl response based on its type, bytes, and handles."};
PyMethodDef decode_fidl_response_py_def = {
"decode_fidl_response", reinterpret_cast<PyCFunction>(decode_fidl_response),
METH_VARARGS | METH_KEYWORDS, "Decodes a FIDL response message from bytes and handles."};
PyMethodDef decode_fidl_request_py_def = {
"decode_fidl_request", reinterpret_cast<PyCFunction>(decode_fidl_request),
METH_VARARGS | METH_KEYWORDS, "Decodes a FIDL request message from bytes and handles."};
} // namespace fuchsia_controller::fidl_codec::decode