diff --git a/automotive/can/1.0/tools/libprotocan/Android.bp b/automotive/can/1.0/tools/libprotocan/Android.bp new file mode 100644 index 0000000000..76c238b046 --- /dev/null +++ b/automotive/can/1.0/tools/libprotocan/Android.bp @@ -0,0 +1,42 @@ +// +// Copyright (C) 2020 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. +// + +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "hardware_interfaces_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["hardware_interfaces_license"], +} + +cc_library_static { + name: "libprotocan", + defaults: ["android.hardware.automotive.vehicle@2.0-protocan-defaults"], + vendor: true, + srcs: [ + "Checksum.cpp", + "MessageCounter.cpp", + "MessageDef.cpp", + "MessageInjector.cpp", + "Signal.cpp", + ], + export_include_dirs: ["include"], + + shared_libs: [ + "android.hardware.automotive.can@1.0", + ], +} diff --git a/automotive/can/1.0/tools/libprotocan/Checksum.cpp b/automotive/can/1.0/tools/libprotocan/Checksum.cpp new file mode 100644 index 0000000000..72fb0af1a7 --- /dev/null +++ b/automotive/can/1.0/tools/libprotocan/Checksum.cpp @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2020 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 + +namespace android::hardware::automotive::protocan { + +Checksum::Checksum(Signal signal, formula f) : mSignal(signal), mFormula(f) {} + +void Checksum::update(can::V1_0::CanMessage& msg) const { + mSignal.set(msg, 0); + mSignal.set(msg, mFormula(msg) % (mSignal.maxValue + 1)); +} + +} // namespace android::hardware::automotive::protocan diff --git a/automotive/can/1.0/tools/libprotocan/MessageCounter.cpp b/automotive/can/1.0/tools/libprotocan/MessageCounter.cpp new file mode 100644 index 0000000000..ef9882fd8c --- /dev/null +++ b/automotive/can/1.0/tools/libprotocan/MessageCounter.cpp @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2020 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 + +#include + +namespace android::hardware::automotive::protocan { + +/** Whether to log counter state messages. */ +static constexpr bool kSuperVerbose = false; + +MessageCounter::MessageCounter(Signal signal) : upperBound(signal.maxValue + 1), mSignal(signal) {} + +Signal::value MessageCounter::next() const { + CHECK(mCurrent.has_value()) << "Counter not initialized. Did you call isReady?"; + return (*mCurrent + 1) % upperBound; +} + +void MessageCounter::read(const can::V1_0::CanMessage& msg) { + auto val = mSignal.get(msg); + + if (!mCurrent.has_value()) { + LOG(VERBOSE) << "Got first counter val of " << val; + mCurrent = val; + return; + } + + auto nextVal = next(); + if (nextVal == val) { + if constexpr (kSuperVerbose) { + LOG(VERBOSE) << "Got next counter val of " << nextVal; + } + mCurrent = nextVal; + } else { + LOG(DEBUG) << "Ignoring next counter val of " << val << ", waiting for " << nextVal; + } +} + +bool MessageCounter::isReady() const { return mCurrent.has_value(); } + +void MessageCounter::increment(can::V1_0::CanMessage& msg) { + auto newVal = next(); + mCurrent = newVal; + mSignal.set(msg, newVal); +} + +} // namespace android::hardware::automotive::protocan diff --git a/automotive/can/1.0/tools/libprotocan/MessageDef.cpp b/automotive/can/1.0/tools/libprotocan/MessageDef.cpp new file mode 100644 index 0000000000..23ce1df640 --- /dev/null +++ b/automotive/can/1.0/tools/libprotocan/MessageDef.cpp @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2020 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 + +#include + +namespace android::hardware::automotive::protocan { + +using can::V1_0::CanMessage; +using can::V1_0::CanMessageId; + +MessageDef::MessageDef(CanMessageId id, uint16_t len, std::map signals, + std::optional counter, std::optional checksum) + : id(id), kLen(len), kSignals(std::move(signals)), kCounter(counter), kChecksum(checksum) {} + +const Signal& MessageDef::operator[](const std::string& signalName) const { + auto it = kSignals.find(signalName); + CHECK(it != kSignals.end()) << "Signal " << signalName << " doesn't exist"; + return it->second; +} + +CanMessage MessageDef::makeDefault() const { + CanMessage msg = {}; + msg.id = id; + msg.payload.resize(kLen); + + for (auto const& [name, signal] : kSignals) { + signal.setDefault(msg); + } + + return msg; +} + +MessageCounter MessageDef::makeCounter() const { + CHECK(kCounter.has_value()) << "Can't build a counter for message without such signal"; + return MessageCounter(*kCounter); +} + +void MessageDef::updateChecksum(can::V1_0::CanMessage& msg) const { + if (!kChecksum.has_value()) return; + kChecksum->update(msg); +} + +bool MessageDef::validate(const can::V1_0::CanMessage& msg) const { + return msg.payload.size() >= kLen; +} + +} // namespace android::hardware::automotive::protocan diff --git a/automotive/can/1.0/tools/libprotocan/MessageInjector.cpp b/automotive/can/1.0/tools/libprotocan/MessageInjector.cpp new file mode 100644 index 0000000000..7c45eaa025 --- /dev/null +++ b/automotive/can/1.0/tools/libprotocan/MessageInjector.cpp @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2020 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 + +#include + +#include + +namespace android::hardware::automotive::protocan { + +/** Whether to log injected messages. */ +static constexpr bool kSuperVerbose = true; + +using namespace std::literals::chrono_literals; + +using can::V1_0::CanMessage; +using can::V1_0::CanMessageId; +using can::V1_0::ICanBus; +using can::V1_0::Result; + +MessageInjector::MessageInjector(MessageDef msgDef, + std::optional interMessageDelay) + : kMsgDef(std::move(msgDef)), + kInterMessageDelay(interMessageDelay), + mCounter(msgDef.makeCounter()) {} + +void MessageInjector::inject(const CanMessage& msg) { inject({msg}); } + +void MessageInjector::inject(const std::initializer_list msgs) { + std::lock_guard lock(mMessagesGuard); + for (const auto& msg : msgs) { + if constexpr (kSuperVerbose) { + LOG(VERBOSE) << "Message scheduled for injection: " << toString(msg); + } + + mMessages.push(msg); + } +} + +void MessageInjector::processQueueLocked(can::V1_0::ICanBus& bus) { + if (mMessages.empty() || !mCounter.isReady()) return; + + auto paddingMessagesCount = mCounter.upperBound - (mMessages.size() % mCounter.upperBound); + auto padMessage = kMsgDef.makeDefault(); + for (unsigned i = 0; i < paddingMessagesCount; i++) { + mMessages.push(padMessage); + } + + while (!mMessages.empty()) { + auto&& outMsg = mMessages.front(); + + mCounter.increment(outMsg); + kMsgDef.updateChecksum(outMsg); + + if constexpr (kSuperVerbose) { + LOG(VERBOSE) << "Injecting message: " << toString(outMsg); + } + auto result = bus.send(outMsg); + if (result != Result::OK) { + LOG(ERROR) << "Message injection failed: " << toString(result); + } + + mMessages.pop(); + + // This would block onReceive, but the class is not supposed to be used in production anyway + // (see MessageInjector docstring). + if (kInterMessageDelay.has_value()) { + std::this_thread::sleep_for(*kInterMessageDelay); + } + } +} + +void MessageInjector::onReceive(ICanBus& bus, const CanMessage& msg) { + if (!kMsgDef.validate(msg)) return; + + std::lock_guard lock(mMessagesGuard); + + mCounter.read(msg); + processQueueLocked(bus); +} + +MessageInjectorManager::MessageInjectorManager( + std::initializer_list> injectors) { + std::transform(injectors.begin(), injectors.end(), std::inserter(mInjectors, mInjectors.end()), + [](const std::shared_ptr& injector) { + return std::make_pair(injector->kMsgDef.id, std::move(injector)); + }); +} + +void MessageInjectorManager::onReceive(sp bus, const CanMessage& msg) { + auto it = mInjectors.find(msg.id); + if (it == mInjectors.end()) return; + it->second->onReceive(*bus, msg); +} + +} // namespace android::hardware::automotive::protocan diff --git a/automotive/can/1.0/tools/libprotocan/Signal.cpp b/automotive/can/1.0/tools/libprotocan/Signal.cpp new file mode 100644 index 0000000000..bc3e070ca1 --- /dev/null +++ b/automotive/can/1.0/tools/libprotocan/Signal.cpp @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2020 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 + +#include + +namespace android::hardware::automotive::protocan { + +static uint8_t calculateLastByteMask(uint16_t start, uint8_t length) { + unsigned lastByteBits = (start + length) % 8; + unsigned lastBytePadding = (8 - lastByteBits) % 8; + return 0xFF >> lastBytePadding; +} + +static uint8_t calculateFirstByteMask(uint16_t firstByte, uint8_t firstBit, uint16_t lastByte, + uint8_t lastMask) { + uint8_t firstMask = 0xFF << firstBit; + if (firstByte == lastByte) firstMask &= lastMask; + return firstMask; +} + +Signal::Signal(uint16_t start, uint8_t length, value defVal) + : maxValue((1u << length) - 1), + kFirstByte(start / 8), + kFirstBit(start % 8), + kFirstByteBits(8 - kFirstBit), + kLastByte((start + length - 1) / 8), + kLastMask(calculateLastByteMask(start, length)), + kFirstMask(calculateFirstByteMask(kFirstByte, kFirstBit, kLastByte, kLastMask)), + kDefVal(defVal) { + CHECK(length > 0) << "Signal length must not be zero"; +} + +Signal::value Signal::get(const can::V1_0::CanMessage& msg) const { + CHECK(msg.payload.size() > kLastByte) + << "Message is too short. Did you call MessageDef::validate?"; + + Signal::value v = 0; + if (kLastByte != kFirstByte) v = kLastMask & msg.payload[kLastByte]; + + for (int i = kLastByte - 1; i > kFirstByte; i--) { + v = (v << 8) | msg.payload[i]; + } + + return (v << kFirstByteBits) | ((msg.payload[kFirstByte] & kFirstMask) >> kFirstBit); +} + +void Signal::set(can::V1_0::CanMessage& msg, Signal::value val) const { + CHECK(msg.payload.size() > kLastByte) + << "Signal requires message of length " << (kLastByte + 1) + << " which is beyond message length of " << msg.payload.size(); + + uint8_t firstByte = val << kFirstBit; + val >>= kFirstByteBits; + + msg.payload[kFirstByte] = (msg.payload[kFirstByte] & ~kFirstMask) | (firstByte & kFirstMask); + + for (int i = kFirstByte + 1; i < kLastByte; i++) { + msg.payload[i] = val & 0xFF; + val >>= 8; + } + + if (kLastByte != kFirstByte) { + msg.payload[kLastByte] = (msg.payload[kLastByte] & ~kLastMask) | (val & kLastMask); + } +} + +void Signal::setDefault(can::V1_0::CanMessage& msg) const { set(msg, kDefVal); } + +} // namespace android::hardware::automotive::protocan diff --git a/automotive/can/1.0/tools/libprotocan/include/libprotocan/Checksum.h b/automotive/can/1.0/tools/libprotocan/include/libprotocan/Checksum.h new file mode 100644 index 0000000000..ff1dc91a50 --- /dev/null +++ b/automotive/can/1.0/tools/libprotocan/include/libprotocan/Checksum.h @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2020 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. + */ + +#pragma once + +#include +#include + +namespace android::hardware::automotive::protocan { + +class Checksum { + public: + using formula = std::function; + + Checksum(Signal signal, formula f); + + void update(can::V1_0::CanMessage& msg) const; + + private: + const Signal mSignal; + const formula mFormula; +}; + +} // namespace android::hardware::automotive::protocan diff --git a/automotive/can/1.0/tools/libprotocan/include/libprotocan/MessageCounter.h b/automotive/can/1.0/tools/libprotocan/include/libprotocan/MessageCounter.h new file mode 100644 index 0000000000..56113be61f --- /dev/null +++ b/automotive/can/1.0/tools/libprotocan/include/libprotocan/MessageCounter.h @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2020 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. + */ + +#pragma once + +#include +#include + +namespace android::hardware::automotive::protocan { + +class MessageCounter { + public: + const Signal::value upperBound; + + MessageCounter(Signal signal); + + /** + * Parse CAN message sent by external ECU to determine current counter value. + */ + void read(const can::V1_0::CanMessage& msg); + + /** + * States whether current counter value is determined. + */ + bool isReady() const; + + /** + * Increment current counter value and set it in a new message. + * + * Caller must check isReady() at least once before calling this method. + */ + void increment(can::V1_0::CanMessage& msg); + + private: + const Signal mSignal; + + std::optional mCurrent = std::nullopt; + + Signal::value next() const; +}; + +} // namespace android::hardware::automotive::protocan diff --git a/automotive/can/1.0/tools/libprotocan/include/libprotocan/MessageDef.h b/automotive/can/1.0/tools/libprotocan/include/libprotocan/MessageDef.h new file mode 100644 index 0000000000..79b21e17b4 --- /dev/null +++ b/automotive/can/1.0/tools/libprotocan/include/libprotocan/MessageDef.h @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2020 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. + */ + +#pragma once + +#include +#include +#include +#include + +namespace android::hardware::automotive::protocan { + +/** + * CAN message definition (not the actual message data). + * + * Describes static message properties (message ID, signals etc). + */ +class MessageDef { + public: + const can::V1_0::CanMessageId id; + + /** + * Create message definition. + * + * Currently only constant length messages are supported. + * + * \param id CAN message ID + * \param len CAN message length + * \param signals CAN signal definitions + * \param counter Designated CAN signal definition for message counter, if the message has one + * \param checksum Designated CAN signal definition for payload checksum, if the message has one + */ + MessageDef(can::V1_0::CanMessageId id, uint16_t len, std::map signals, + std::optional counter = std::nullopt, + std::optional checksum = std::nullopt); + + const Signal& operator[](const std::string& signalName) const; + + can::V1_0::CanMessage makeDefault() const; + MessageCounter makeCounter() const; + + void updateChecksum(can::V1_0::CanMessage& msg) const; + + /** + * Validate the message payload is large enough to hold all the signals. + */ + bool validate(const can::V1_0::CanMessage& msg) const; + +private: + const uint16_t kLen; + const std::map kSignals; + const std::optional kCounter; + const std::optional kChecksum; +}; + +} // namespace android::hardware::automotive::protocan diff --git a/automotive/can/1.0/tools/libprotocan/include/libprotocan/MessageInjector.h b/automotive/can/1.0/tools/libprotocan/include/libprotocan/MessageInjector.h new file mode 100644 index 0000000000..b0ea260640 --- /dev/null +++ b/automotive/can/1.0/tools/libprotocan/include/libprotocan/MessageInjector.h @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2020 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. + */ + +#pragma once + +#include +#include +#include +#include +#include + +#include + +namespace android::hardware::automotive::protocan { + +class MessageInjectorManager; + +/** + * Injects CAN messages with a counter to an existing system. + * + * This class is NOT meant to use in production - there should be no need to inject counted CAN + * messages where the other sender is also broadcasting them. If this is the case, it may be a sign + * your CAN network needs a redesign. This tool is intended for use for testing and demo purposes. + */ +class MessageInjector { + public: + MessageInjector(MessageDef msgDef, std::optional interMessageDelay); + + void inject(const can::V1_0::CanMessage& msg); + void inject(const std::initializer_list msgs); + + private: + const MessageDef kMsgDef; + const std::optional kInterMessageDelay; + MessageCounter mCounter; + + mutable std::mutex mMessagesGuard; + std::queue mMessages GUARDED_BY(mMessagesGuard); + + void onReceive(can::V1_0::ICanBus& bus, const can::V1_0::CanMessage& msg); + void processQueueLocked(can::V1_0::ICanBus& bus); + + friend class MessageInjectorManager; + + DISALLOW_COPY_AND_ASSIGN(MessageInjector); +}; + +/** + * Routes intercepted messages to MessageInjector instances configured to handle specific CAN + * message (CAN message ID). Intercepted messages from other nodes in CAN network are used to read + * current counter value in order to spoof the next packet. + */ +class MessageInjectorManager { + public: + MessageInjectorManager(std::initializer_list> injectors); + + void onReceive(sp bus, const can::V1_0::CanMessage& msg); + + private: + std::map> mInjectors; + + DISALLOW_COPY_AND_ASSIGN(MessageInjectorManager); +}; + +} // namespace android::hardware::automotive::protocan diff --git a/automotive/can/1.0/tools/libprotocan/include/libprotocan/Signal.h b/automotive/can/1.0/tools/libprotocan/include/libprotocan/Signal.h new file mode 100644 index 0000000000..7c0f11933a --- /dev/null +++ b/automotive/can/1.0/tools/libprotocan/include/libprotocan/Signal.h @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2020 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. + */ + +#pragma once + +#include +#include + +namespace android::hardware::automotive::protocan { + +/** + * TODO(twasilczyk): right now, only Little Endian signals are supported. + */ +class Signal { + public: + using value = uint64_t; + + const value maxValue; + + Signal(uint16_t start, uint8_t length, value defVal = 0); + + value get(const can::V1_0::CanMessage& msg) const; + void set(can::V1_0::CanMessage& msg, value val) const; + void setDefault(can::V1_0::CanMessage& msg) const; + + private: + const uint16_t kFirstByte; ///< Index of first byte that holds the signal + const uint8_t kFirstBit; ///< Index of first bit within first byte + const uint8_t kFirstByteBits; ///< How many bits of the first byte belong to the signal + const uint16_t kLastByte; ///< Index of last byte that holds the signal + const uint8_t kLastMask; ///< Bits of the last byte that belong to the signal + const uint8_t kFirstMask; ///< Bits of the first byte that belong to the signal + + const value kDefVal; +}; + +} // namespace android::hardware::automotive::protocan diff --git a/automotive/can/1.0/tools/libprotocan/tests/Android.bp b/automotive/can/1.0/tools/libprotocan/tests/Android.bp new file mode 100644 index 0000000000..251cc061cd --- /dev/null +++ b/automotive/can/1.0/tools/libprotocan/tests/Android.bp @@ -0,0 +1,39 @@ +// +// Copyright (C) 2020 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. +// + +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "hardware_interfaces_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["hardware_interfaces_license"], +} + +cc_test { + name: "libprotocan_signal_test", + defaults: ["android.hardware.automotive.can@defaults"], + vendor: true, + gtest: true, + srcs: ["libprotocan_signal_test.cpp"], + static_libs: [ + "libprotocan", + ], + shared_libs: [ + "android.hardware.automotive.can@1.0", + "libhidlbase", + ], +} diff --git a/automotive/can/1.0/tools/libprotocan/tests/libprotocan_signal_test.cpp b/automotive/can/1.0/tools/libprotocan/tests/libprotocan_signal_test.cpp new file mode 100644 index 0000000000..19c12099e6 --- /dev/null +++ b/automotive/can/1.0/tools/libprotocan/tests/libprotocan_signal_test.cpp @@ -0,0 +1,218 @@ +/* + * Copyright (C) 2020 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 + +#include + +namespace android::hardware::automotive::protocan::unittest { + +TEST(SignalTest, TestGetSingleBytes) { + can::V1_0::CanMessage msg = {}; + msg.payload = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; + for (unsigned i = 0; i < msg.payload.size(); i++) { + Signal signal(8 * i, 8); + ASSERT_EQ(i, signal.get(msg)); + } +} + +TEST(SignalTest, TestSetSingleBytes) { + std::vector msgs = {{}, {}, {}}; + msgs[0].payload = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; + msgs[1].payload = {0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB}; + msgs[2].payload = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + for (unsigned i = 0; i < msgs[0].payload.size(); i++) { + Signal signal(8 * i, 8); + + for (auto&& msgOriginal : msgs) { + auto msgModified = msgOriginal; + signal.set(msgModified, 0xBA); + + auto msgExpected = msgOriginal; + msgExpected.payload[i] = 0xBA; + + ASSERT_EQ(msgExpected, msgModified) << "i=" << i; + } + } +} + +TEST(SignalTest, TestGetStart4) { + /* Data generated with Python3: + * + * from cantools.database.can import * + * hex(Message(1, 'm', 4, [Signal('s', 4, 16, byte_order='little_endian')]). + * decode(b'\xde\xad\xbe\xef')['s']) + */ + + can::V1_0::CanMessage msg = {}; + msg.payload = {0xDE, 0xAD, 0xBE, 0xEF}; + can::V1_0::CanMessage msg2 = {}; + msg2.payload = {0xDE, 0xAD, 0xBE, 0xEF, 0xDE, 0xAD}; + + Signal s0_4(0, 4); + Signal s4_4(4, 4); + Signal s4_8(4, 8); + Signal s4_16(4, 16); + Signal s4_28(4, 28); + Signal s12_8(12, 8); + Signal s12_12(12, 12); + Signal s12_16(12, 16); + Signal s12_20(12, 20); + Signal s12_32(12, 32); + + ASSERT_EQ(0xEu, s0_4.get(msg)); + ASSERT_EQ(0xDu, s4_4.get(msg)); + ASSERT_EQ(0xDDu, s4_8.get(msg)); + ASSERT_EQ(0xEADDu, s4_16.get(msg)); + ASSERT_EQ(0xEFBEADDu, s4_28.get(msg)); + ASSERT_EQ(0xEAu, s12_8.get(msg)); + ASSERT_EQ(0xBEAu, s12_12.get(msg)); + ASSERT_EQ(0xFBEAu, s12_16.get(msg)); + ASSERT_EQ(0xEFBEAu, s12_20.get(msg)); + ASSERT_EQ(0xDDEEFBEAu, s12_32.get(msg2)); +} + +TEST(SignalTest, TestGet64) { + /* Data generated with Python3: + * + * from cantools.database.can import * + * hex(Message(1, 'm', 9, [Signal('s', 4, 64, byte_order='little_endian')]). + * decode(b'\xde\xad\xbe\xef\xab\xbc\xcd\xde\xef')['s']) + */ + + can::V1_0::CanMessage msg = {}; + msg.payload = {0xDE, 0xAD, 0xBE, 0xEF, 0xAB, 0xBC, 0xCD, 0xDE, 0xEF}; + + Signal s0_64(0, 64); + Signal s8_64(8, 64); + Signal s4_64(4, 64); + Signal s1_64(1, 64); + + ASSERT_EQ(0xDECDBCABEFBEADDEu, s0_64.get(msg)); + ASSERT_EQ(0xEFDECDBCABEFBEADu, s8_64.get(msg)); + ASSERT_EQ(0xFDECDBCABEFBEADDu, s4_64.get(msg)); + ASSERT_EQ(0xEF66DE55F7DF56EFu, s1_64.get(msg)); +} + +TEST(SignalTest, TestGetAllStarts) { + /* Data generated with Python3: + * + * from cantools.database.can import * + * hex(Message(1, 'm', 6, [Signal('s', 0, 20, byte_order='little_endian')]). + * decode(b'\xde\xad\xbe\xef\xde\xad')['s']) + */ + + std::map shifts = { + {0, 0xEADDEu}, {1, 0xF56EFu}, {2, 0xFAB77u}, {3, 0x7D5BBu}, {4, 0xBEADDu}, {5, 0xDF56Eu}, + {6, 0xEFAB7u}, {7, 0xF7D5Bu}, {8, 0xFBEADu}, {9, 0x7DF56u}, {10, 0xBEFABu}, {11, 0xDF7D5u}, + }; + + can::V1_0::CanMessage msg = {}; + msg.payload = {0xDE, 0xAD, 0xBE, 0xEF, 0xCC, 0xCC}; + + for (auto const& [start, expected] : shifts) { + Signal s(start, 20); + ASSERT_EQ(expected, s.get(msg)) << "shift of " << start << " failed"; + } +} + +TEST(SignalTest, TestSetStart4) { + /* Data generated with Python3: + * + * from cantools.database.can import * + * so=4 ; sl=8 + * md = Message(1, 'm', 4, [Signal('a1', 0, so), Signal('a2', so+sl, 32-so-sl), + * Signal('s', so, sl, byte_order='little_endian')]) + * m = md.decode(b'\xcc\xcc\xcc\xcc') + * m['s'] = 0xDE + * binascii.hexlify(md.encode(m)).upper() + */ + typedef struct { + int start; + int length; + Signal::value setValue; + hidl_vec payload; + } case_t; + + std::vector cases = { + {0, 4, 0xDu, {0xCD, 0xCC, 0xCC, 0xCC}}, {4, 4, 0xDu, {0xDC, 0xCC, 0xCC, 0xCC}}, + {4, 8, 0xDEu, {0xEC, 0xCD, 0xCC, 0xCC}}, {4, 16, 0xDEADu, {0xDC, 0xEA, 0xCD, 0xCC}}, + {4, 24, 0xDEADBEu, {0xEC, 0xDB, 0xEA, 0xCD}}, {4, 28, 0xDEADBEEu, {0xEC, 0xBE, 0xAD, 0xDE}}, + {12, 8, 0xDEu, {0xCC, 0xEC, 0xCD, 0xCC}}, {12, 12, 0xDEAu, {0xCC, 0xAC, 0xDE, 0xCC}}, + {12, 16, 0xDEADu, {0xCC, 0xDC, 0xEA, 0xCD}}, {12, 20, 0xDEADBu, {0xCC, 0xBC, 0xAD, 0xDE}}, + }; + + can::V1_0::CanMessage msg = {}; + msg.payload = {0xCC, 0xCC, 0xCC, 0xCC}; + + for (auto const& tcase : cases) { + Signal s(tcase.start, tcase.length); + + can::V1_0::CanMessage expectedMsg = {}; + expectedMsg.payload = tcase.payload; + + can::V1_0::CanMessage editedMsg = msg; + s.set(editedMsg, tcase.setValue); + + ASSERT_EQ(expectedMsg, editedMsg) << " set(" << tcase.start << ", " << tcase.length << ")"; + } +} + +TEST(SignalTest, TestSetAllStarts) { + /* Data generated with Python3: + * from cantools.database.can import * + * import binascii + * import textwrap + * + * length = 20 + * for start in range(0, 32 - length): + * signals = [Signal('s', start, length, byte_order='little_endian')] + * if start > 0: signals.append(Signal('a', 0, start, byte_order='little_endian')) + * signals.append(Signal('b', start + length, 32 - start - length, + * byte_order='little_endian')) + * + * md = Message(1, 'm', 4, signals) + * m = md.decode(b'\xcc\xcc\xcc\xcc') + * m['s'] = 0xDEADB + * out = binascii.hexlify(md.encode(m)).decode('ascii').upper() + * out = ', '.join(['0x{}'.format(v) for v in textwrap.wrap(out, 2)]) + * print('{{ {:d}, {{ {:s} }}}},'.format(start, out)) + */ + + std::map> shifts = { + {0, {0xDB, 0xEA, 0xCD, 0xCC}}, {1, {0xB6, 0xD5, 0xDB, 0xCC}}, {2, {0x6C, 0xAB, 0xF7, 0xCC}}, + {3, {0xDC, 0x56, 0xEF, 0xCC}}, {4, {0xBC, 0xAD, 0xDE, 0xCC}}, {5, {0x6C, 0x5B, 0xBD, 0xCD}}, + {6, {0xCC, 0xB6, 0x7A, 0xCF}}, {7, {0xCC, 0x6D, 0xF5, 0xCE}}, {8, {0xCC, 0xDB, 0xEA, 0xCD}}, + {9, {0xCC, 0xB6, 0xD5, 0xDB}}, {10, {0xCC, 0x6C, 0xAB, 0xF7}}, {11, {0xCC, 0xDC, 0x56, 0xEF}}, + }; + + can::V1_0::CanMessage msg = {}; + msg.payload = {0xCC, 0xCC, 0xCC, 0xCC}; + + for (auto const& [start, expectedPayload] : shifts) { + Signal s(start, 20); + + can::V1_0::CanMessage expectedMsg = {}; + expectedMsg.payload = expectedPayload; + + can::V1_0::CanMessage editedMsg = msg; + s.set(editedMsg, 0xDEADB); + + ASSERT_EQ(expectedMsg, editedMsg) << "shift of " << start << " failed"; + } +} + +} // namespace android::hardware::automotive::protocan::unittest