/* * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "canprototools.h" #include #include #include #include #include #include #include #include namespace android::hardware::automotive::can::config { using ::aidl::android::hardware::automotive::can::BusConfig; using ::aidl::android::hardware::automotive::can::IndexedInterface; using ::aidl::android::hardware::automotive::can::InterfaceType; using ::aidl::android::hardware::automotive::can::NativeInterface; using ::aidl::android::hardware::automotive::can::Result; using ::aidl::android::hardware::automotive::can::SlcanInterface; using ::aidl::android::hardware::automotive::can::VirtualInterface; /** * Helper function for parseConfigFile. readString is used to get the fist n characters (n) from an * istream object (s) and return it as a string object. * * \param s istream of the file you intend to read. * \param n streamsize object of the number of characters you'd like. * \return optional string containing up to n characters from the stream(s) you provided. */ static std::optional readString(std::istream& s, std::streamsize n) { char buff[n]; auto got = s.read(buff, n).gcount(); if (!s.good() && !s.eof()) return std::nullopt; return std::string(buff, got); } std::optional parseConfigFile(const std::string& filepath) { std::ifstream cfg_stream(filepath); // text headers that would be present in a plaintext proto config file. static const std::array text_headers = {"buses", "#", "controller"}; auto cfg_file_snippet = readString(cfg_stream, 10); if (!cfg_file_snippet.has_value()) { LOG(ERROR) << "Can't open " << filepath << " for reading"; return std::nullopt; } cfg_stream.seekg(0); // check if any of the textHeaders are at the start of the config file. bool text_format = false; for (auto const& header : text_headers) { if (cfg_file_snippet->compare(0, header.length(), header) == 0) { text_format = true; break; } } CanBusConfig config; if (text_format) { google::protobuf::io::IstreamInputStream pb_stream(&cfg_stream); if (!google::protobuf::TextFormat::Parse(&pb_stream, &config)) { LOG(ERROR) << "Failed to parse (text format) " << filepath; return std::nullopt; } } else if (!config.ParseFromIstream(&cfg_stream)) { LOG(ERROR) << "Failed to parse (binary format) " << filepath; return std::nullopt; } return config; } std::optional fromPbBus(const Bus& pb_bus) { BusConfig bus_cfg = {}; bus_cfg.name = pb_bus.name(); switch (pb_bus.iface_type_case()) { case Bus::kNative: { const std::string ifname = pb_bus.native().ifname(); const std::vector serials = {pb_bus.native().serialno().begin(), pb_bus.native().serialno().end()}; if (ifname.empty() == serials.empty()) { LOG(ERROR) << "Invalid config: native type bus must have an iface name xor a " << "serial number"; return std::nullopt; } bus_cfg.bitrate = pb_bus.bitrate(); NativeInterface nativeif = {}; if (!ifname.empty()) nativeif.interfaceId.set(ifname); if (!serials.empty()) nativeif.interfaceId.set(serials); bus_cfg.interfaceId.set(nativeif); break; } case Bus::kSlcan: { const std::string ttyname = pb_bus.slcan().ttyname(); const std::vector serials = {pb_bus.slcan().serialno().begin(), pb_bus.slcan().serialno().end()}; if (ttyname.empty() == serials.empty()) { LOG(ERROR) << "Invalid config: slcan type bus must have a tty name xor a serial " << "number"; return std::nullopt; } bus_cfg.bitrate = pb_bus.bitrate(); SlcanInterface slcan = {}; if (!ttyname.empty()) slcan.interfaceId.set(ttyname); if (!serials.empty()) slcan.interfaceId.set(serials); bus_cfg.interfaceId.set(slcan); break; } case Bus::kVirtual: { // Theoretically, we could just create the next available vcan iface. const std::string ifname = pb_bus.virtual_().ifname(); if (ifname.empty()) { LOG(ERROR) << "Invalid config: native type bus must have an iface name"; return std::nullopt; } VirtualInterface virtualif = {}; virtualif.ifname = ifname; bus_cfg.interfaceId.set(virtualif); break; } case Bus::kIndexed: { const uint8_t index = pb_bus.indexed().index(); if (index > UINT8_MAX) { LOG(ERROR) << "Interface index out of range: " << index; return std::nullopt; } IndexedInterface indexedif = {}; indexedif.index = index; bus_cfg.interfaceId.set(indexedif); break; } default: LOG(ERROR) << "Invalid config: bad interface type for " << bus_cfg.name; return std::nullopt; } return bus_cfg; } std::optional getHalIftype(const Bus& pb_bus) { switch (pb_bus.iface_type_case()) { case Bus::kNative: return InterfaceType::NATIVE; case Bus::kSlcan: return InterfaceType::SLCAN; case Bus::kVirtual: return InterfaceType::VIRTUAL; case Bus::kIndexed: return InterfaceType::INDEXED; default: return std::nullopt; } } std::string resultStringFromStatus(const ndk::ScopedAStatus& status) { const auto res = static_cast(status.getServiceSpecificError()); switch (res) { case Result::OK: return "OK"; case Result::UNKNOWN_ERROR: return "UNKNOWN_ERROR"; case Result::INVALID_STATE: return "INVALID_STATE"; case Result::NOT_SUPPORTED: return "NOT_SUPPORTED"; case Result::BAD_INTERFACE_ID: return "BAD_INTERFACE_ID"; case Result::BAD_BITRATE: return "BAD_BITRATE"; case Result::BAD_BUS_NAME: return "BAD_BUS_NAME"; case Result::INTERFACE_DOWN: return "INTERFACE_DOWN"; default: return "Invalid Result!"; } } } // namespace android::hardware::automotive::can::config