Move libprotocan to hardware/interfaces/automotive domain
Bug: 203756395
Change-Id: I4d0a1dcff2d6a974b40bccd403cecdc5cd03ac4f
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 0000000..76c238b
--- /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 0000000..72fb0af
--- /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 <libprotocan/Checksum.h>
+
+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 0000000..ef9882f
--- /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 <libprotocan/MessageCounter.h>
+
+#include <android-base/logging.h>
+
+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 0000000..23ce1df
--- /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 <libprotocan/MessageDef.h>
+
+#include <android-base/logging.h>
+
+namespace android::hardware::automotive::protocan {
+
+using can::V1_0::CanMessage;
+using can::V1_0::CanMessageId;
+
+MessageDef::MessageDef(CanMessageId id, uint16_t len, std::map<std::string, Signal> signals,
+ std::optional<Signal> counter, std::optional<Checksum> 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 0000000..7c45eaa
--- /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 <libprotocan/MessageInjector.h>
+
+#include <android-base/logging.h>
+
+#include <thread>
+
+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<std::chrono::milliseconds> 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<can::V1_0::CanMessage> msgs) {
+ std::lock_guard<std::mutex> 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<std::mutex> lock(mMessagesGuard);
+
+ mCounter.read(msg);
+ processQueueLocked(bus);
+}
+
+MessageInjectorManager::MessageInjectorManager(
+ std::initializer_list<std::shared_ptr<MessageInjector>> injectors) {
+ std::transform(injectors.begin(), injectors.end(), std::inserter(mInjectors, mInjectors.end()),
+ [](const std::shared_ptr<MessageInjector>& injector) {
+ return std::make_pair(injector->kMsgDef.id, std::move(injector));
+ });
+}
+
+void MessageInjectorManager::onReceive(sp<ICanBus> 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 0000000..bc3e070
--- /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 <libprotocan/Signal.h>
+
+#include <android-base/logging.h>
+
+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 0000000..ff1dc91
--- /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 <android/hardware/automotive/can/1.0/types.h>
+#include <libprotocan/Signal.h>
+
+namespace android::hardware::automotive::protocan {
+
+class Checksum {
+ public:
+ using formula = std::function<Signal::value(const can::V1_0::CanMessage&)>;
+
+ 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 0000000..56113be
--- /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 <android/hardware/automotive/can/1.0/types.h>
+#include <libprotocan/Signal.h>
+
+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<Signal::value> 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 0000000..79b21e1
--- /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 <android/hardware/automotive/can/1.0/types.h>
+#include <libprotocan/Checksum.h>
+#include <libprotocan/MessageCounter.h>
+#include <libprotocan/Signal.h>
+
+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<std::string, Signal> signals,
+ std::optional<Signal> counter = std::nullopt,
+ std::optional<Checksum> 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<std::string, Signal> kSignals;
+ const std::optional<Signal> kCounter;
+ const std::optional<Checksum> 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 0000000..b0ea260
--- /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 <android-base/macros.h>
+#include <android/hardware/automotive/can/1.0/ICanBus.h>
+#include <libprotocan/MessageCounter.h>
+#include <libprotocan/MessageDef.h>
+#include <utils/Mutex.h>
+
+#include <queue>
+
+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<std::chrono::milliseconds> interMessageDelay);
+
+ void inject(const can::V1_0::CanMessage& msg);
+ void inject(const std::initializer_list<can::V1_0::CanMessage> msgs);
+
+ private:
+ const MessageDef kMsgDef;
+ const std::optional<std::chrono::milliseconds> kInterMessageDelay;
+ MessageCounter mCounter;
+
+ mutable std::mutex mMessagesGuard;
+ std::queue<can::V1_0::CanMessage> 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<std::shared_ptr<MessageInjector>> injectors);
+
+ void onReceive(sp<can::V1_0::ICanBus> bus, const can::V1_0::CanMessage& msg);
+
+ private:
+ std::map<can::V1_0::CanMessageId, std::shared_ptr<MessageInjector>> 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 0000000..7c0f119
--- /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 <android-base/macros.h>
+#include <android/hardware/automotive/can/1.0/types.h>
+
+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 0000000..251cc06
--- /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 0000000..19c1209
--- /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 <libprotocan/Signal.h>
+
+#include <gtest/gtest.h>
+
+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<can::V1_0::CanMessage> 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<int, Signal::value> 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<uint8_t> payload;
+ } case_t;
+
+ std::vector<case_t> 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<int, hidl_vec<uint8_t>> 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