diff options
author | Will Drewry <wad@google.com> | 2017-07-28 15:25:17 +0000 |
---|---|---|
committer | android-build-merger <android-build-merger@google.com> | 2017-07-28 15:25:17 +0000 |
commit | b70f271595801f040b57908227d6dd034fae30f0 (patch) | |
tree | ea86f37c73cd5e07c177c3ed849488de3e2c8d07 | |
parent | e02e4334e291a41d16397799daeebe28491092b3 (diff) | |
parent | ee7f6a6b4ae809f1a9c1d21a1f5f384f822dad93 (diff) | |
download | libese-b70f271595801f040b57908227d6dd034fae30f0.tar.gz |
libese-teq1: global resync maximum
am: ee7f6a6b4a
Change-Id: I4835a671189c1462387e8b096a900efd5bf1abad
-rw-r--r-- | libese-teq1/teq1.c | 32 | ||||
-rw-r--r-- | libese-teq1/tests/Android.bp | 2 | ||||
-rw-r--r-- | libese-teq1/tests/ese_operations_interface.h | 38 | ||||
-rw-r--r-- | libese-teq1/tests/ese_operations_wrapper.cpp | 81 | ||||
-rw-r--r-- | libese-teq1/tests/ese_operations_wrapper.h | 42 | ||||
-rw-r--r-- | libese-teq1/tests/teq1_unittests.cpp | 393 |
6 files changed, 579 insertions, 9 deletions
diff --git a/libese-teq1/teq1.c b/libese-teq1/teq1.c index e17fbb1..84b7f0f 100644 --- a/libese-teq1/teq1.c +++ b/libese-teq1/teq1.c @@ -280,11 +280,13 @@ uint8_t teq1_frame_error_check(struct Teq1State *state, case kPcbTypeSupervisory: if (rx_frame->header.PCB != S(RESYNC, RESPONSE) && rx_frame->header.LEN != 1) { + ALOGE("Invalid supervisory RX frame."); return R(0, 1, 0); } break; case kPcbTypeReceiveReady: if (rx_frame->header.LEN != 0) { + ALOGE("Invalid ReceiveReady RX frame."); return R(0, 1, 0); } break; @@ -296,6 +298,7 @@ uint8_t teq1_frame_error_check(struct Teq1State *state, ALOGW("Got seq %d expected %d", bs_get(PCB.I.send_seq, rx_frame->header.PCB), state->card_state->seq.card); + ALOGE("Invalid Info RX frame."); return R(0, 1, 0); } /* Update the card's last I-block seq. */ @@ -564,6 +567,8 @@ ESE_API uint32_t teq1_transceive(struct EseInterface *ese, struct Teq1Frame *tx = &tx_frame[0]; int active = 0; bool was_reset = false; + bool needs_hw_reset = false; + int session_resets = 0; bool done = false; enum RuleResult result = kRuleResultComplete; uint32_t rx_total = ese_sg_length(rx_bufs, rx_segs); @@ -623,6 +628,7 @@ ESE_API uint32_t teq1_transceive(struct EseInterface *ese, } ALOGE("More than three retransmits have occurred"); if (tx->header.PCB == S(RESYNC, REQUEST)) { + /* More than three RESYNC retranmits have occurred. */ ese_set_error(ese, kTeq1ErrorHardFail); return 0; } @@ -632,8 +638,9 @@ ESE_API uint32_t teq1_transceive(struct EseInterface *ese, case kRuleResultContinue: active = !active; tx = &tx_frame[active]; + /* Reset this to 0 to use the counter for RESYNC transmits. */ state.retransmits = 0; - state.errors = 0; + /* Errors are not reset until the session is reset. */ continue; case kRuleResultHardFail: ese_set_error(ese, kTeq1ErrorHardFail); @@ -650,14 +657,25 @@ ESE_API uint32_t teq1_transceive(struct EseInterface *ese, tx = &tx_frame[!active]; continue; case kRuleResultResetDevice: - if (was_reset || !ese->ops->hw_reset || ese->ops->hw_reset(ese) == -1) { - ese_set_error(ese, kTeq1ErrorDeviceReset); - return 0; /* Don't keep resetting -- hard fail. */ - } - was_reset = true; + needs_hw_reset = true; /* Fall through to session reset. */ case kRuleResultResetSession: - /* Roll back state and reset. */ + /* Reset to initial state and possibly do hw reset */ + if (session_resets++ > 4) { + /* If there have been more than 4 resyncs without a + * physical reset, we should pull the plug. + */ + needs_hw_reset = true; + } + if (needs_hw_reset) { + needs_hw_reset = false; + if (was_reset || !ese->ops->hw_reset || ese->ops->hw_reset(ese) == -1) { + ese_set_error(ese, kTeq1ErrorDeviceReset); + return 0; /* Don't keep resetting -- hard fail. */ + } + was_reset = true; + session_resets = 0; + } state = init_state; TEQ1_INIT_CARD_STATE(state.card_state); /* Reset the active frame. */ diff --git a/libese-teq1/tests/Android.bp b/libese-teq1/tests/Android.bp index aa10a8c..a6f1621 100644 --- a/libese-teq1/tests/Android.bp +++ b/libese-teq1/tests/Android.bp @@ -17,7 +17,7 @@ cc_test { name: "ese_teq1_unittests", proprietary: true, - srcs: ["teq1_unittests.cpp"], + srcs: ["teq1_unittests.cpp", "ese_operations_wrapper.cpp" ], host_supported: true, shared_libs: [ "libese", diff --git a/libese-teq1/tests/ese_operations_interface.h b/libese-teq1/tests/ese_operations_interface.h new file mode 100644 index 0000000..54a9c56 --- /dev/null +++ b/libese-teq1/tests/ese_operations_interface.h @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2017 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. + * + */ + +#ifndef ESE_OPERATIONS_INTERFACE_H_ +#define ESE_OPERATIONS_INTERFACE_H_ 1 + +#include <ese/ese.h> + +class EseOperationsInterface { + public: + EseOperationsInterface() { } + virtual ~EseOperationsInterface() { }; + + virtual int EseOpen(struct EseInterface *ese, void *data) = 0; + virtual uint32_t EseHwReceive(struct EseInterface *ese, uint8_t *data, uint32_t len, int complete) = 0; + virtual uint32_t EseHwTransmit(struct EseInterface *ese, const uint8_t *data, uint32_t len, int complete) = 0; + virtual int EseReset(struct EseInterface *ese) = 0; + virtual uint32_t EseTransceive(struct EseInterface *ese, const struct EseSgBuffer *tx_sg, uint32_t tx_nsg, + struct EseSgBuffer *rx_sg, uint32_t rx_nsg) = 0; + virtual int EsePoll(struct EseInterface *ese, uint8_t poll_for, float timeout, int complete) = 0; + virtual void EseClose(struct EseInterface *ese) = 0; +}; + +#endif // ESE_OPERATIONS_INTERFACE_H_ diff --git a/libese-teq1/tests/ese_operations_wrapper.cpp b/libese-teq1/tests/ese_operations_wrapper.cpp new file mode 100644 index 0000000..fc3e65b --- /dev/null +++ b/libese-teq1/tests/ese_operations_wrapper.cpp @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2017 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 <ese/ese.h> +#include <ese/teq1.h> + +#include "ese_operations_interface.h" +#include "ese_operations_wrapper.h" + +void EseOperationsWrapper::InitializeEse( + struct EseInterface *ese, EseOperationsInterface *ops_interface) { + EseOperationsWrapperData data; + data.ops_interface = ops_interface; + ese_init(ese, data.wrapper); +} + +static int EseOpen(struct EseInterface *ese, void *data) { + return EseOperationsWrapperData::ops_interface->EseOpen(ese, data); +} + +static uint32_t EseHwReceive(struct EseInterface *ese, uint8_t *data, uint32_t len, int complete) { + return EseOperationsWrapperData::ops_interface->EseHwReceive(ese, data, len, complete); +} + +static uint32_t EseHwTransmit(struct EseInterface *ese, const uint8_t *data, uint32_t len, int complete) { + return EseOperationsWrapperData::ops_interface->EseHwTransmit(ese, data, len, complete); +} + +static int EseReset(struct EseInterface *ese) { + return EseOperationsWrapperData::ops_interface->EseReset(ese); +} + +static uint32_t EseTransceive(struct EseInterface *ese, const struct EseSgBuffer *tx_sg, uint32_t tx_nsg, + struct EseSgBuffer *rx_sg, uint32_t rx_nsg) { + return EseOperationsWrapperData::ops_interface->EseTransceive(ese, tx_sg, tx_nsg, rx_sg, rx_nsg); +} + +static int EsePoll(struct EseInterface *ese, uint8_t poll_for, float timeout, int complete) { + return EseOperationsWrapperData::ops_interface->EsePoll(ese, poll_for, timeout, complete); +} + +static void EseClose(struct EseInterface *ese) { + return EseOperationsWrapperData::ops_interface->EseClose(ese); +} + +EseOperationsInterface *EseOperationsWrapperData::ops_interface = + reinterpret_cast<EseOperationsInterface *>(NULL); + +static const char *kErrors[] = { + TEQ1_ERROR_MESSAGES, +}; + +const struct EseOperations EseOperationsWrapperData::ops = { + .name = "EseOperationsWrapper HW", + .open = &EseOpen, + .hw_receive = &EseHwReceive, + .hw_transmit = &EseHwTransmit, + .hw_reset = &EseReset, + .poll = &EsePoll, + .transceive = &EseTransceive, + .close = &EseClose, + .opts = NULL, + .errors = kErrors, + .errors_count = sizeof(kErrors), +}; +const struct EseOperations *EseOperationsWrapperData::wrapper_ops = + &EseOperationsWrapperData::ops; diff --git a/libese-teq1/tests/ese_operations_wrapper.h b/libese-teq1/tests/ese_operations_wrapper.h new file mode 100644 index 0000000..4d1d57f --- /dev/null +++ b/libese-teq1/tests/ese_operations_wrapper.h @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2017 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. + * + */ + +#ifndef ESE_OPERATIONS_WRAPPER_H_ +#define ESE_OPERATIONS_WRAPPER_H_ 1 + +#include <ese/ese.h> +#include "ese_operations_interface.h" + +// Wraps a supplied interface object with static calls. +// This acts as a singleton. If mulitple instances are needed, the +// multion pattern would be more appropriate. +class EseOperationsWrapperData { + public: + static EseOperationsInterface *ops_interface; + static const struct EseOperations ops; + static const struct EseOperations *wrapper_ops; +}; + +class EseOperationsWrapper { + public: + // Naming convention to be compatible with ese_init(); + EseOperationsWrapper() = default; + virtual ~EseOperationsWrapper() = default; + static void InitializeEse(struct EseInterface *ese, EseOperationsInterface *ops_interface); +}; + +#endif // ESE_OPERATIONS_WRAPPER_H_ diff --git a/libese-teq1/tests/teq1_unittests.cpp b/libese-teq1/tests/teq1_unittests.cpp index 8eaaac1..48da12b 100644 --- a/libese-teq1/tests/teq1_unittests.cpp +++ b/libese-teq1/tests/teq1_unittests.cpp @@ -26,9 +26,12 @@ #define LOG_TAG "TEQ1_UNITTESTS" #include <ese/log.h> +#include "ese_operations_interface.h" +#include "ese_operations_wrapper.h" + #include "teq1_private.h" -ESE_INCLUDE_HW(ESE_HW_FAKE); +#define UNUSED(x) UNUSED_ ## x __attribute__((__unused__)) using ::testing::Test; @@ -368,4 +371,392 @@ TEST_F(Teq1ErrorHandlingTest, I00_I00_bad_lrc) { << "Actual result name: " << teq1_rule_result_to_name(result); }; +static const struct Teq1ProtocolOptions kTeq1Options = { + .host_address = 0xA5, + .node_address = 0x5A, + .bwt = 1.624f, + .etu = 0.00015f, /* elementary time unit, in seconds */ + .preprocess = NULL, +}; + +std::string to_hex(const std::vector<uint8_t>& data) { + static constexpr char hex[] = "0123456789ABCDEF"; + std::string out; + out.reserve(data.size() * 2); + for (uint8_t c : data) { + out.push_back(hex[c / 16]); + out.push_back(hex[c % 16]); + } + return out; +} + +class EseWireFake : public EseOperationsInterface { + public: + EseWireFake() : tx_cursor_(0), rx_cursor_(0) { } + virtual ~EseWireFake() = default; + + virtual int EseOpen(struct EseInterface *UNUSED(ese), void *UNUSED(data)) { + return 0; + } + virtual int EseReset(struct EseInterface *UNUSED(ese)) { + ALOGI("EseReset called!"); // Add to invocations + // Using the RX cursor, check for a reset expected. + // This is on RX because the s(resync) global counter is on session resets. + EXPECT_EQ(1, invocations.at(tx_cursor_).expect_reset); + return 0; + } + virtual int EsePoll(struct EseInterface *UNUSED(ese), uint8_t UNUSED(poll_for), + float UNUSED(timeout), int UNUSED(complete)) { + return 0; + } + virtual void EseClose(struct EseInterface *UNUSED(ese)) { }; + + virtual uint32_t EseTransceive(struct EseInterface *ese, const struct EseSgBuffer *tx_sg, uint32_t tx_nsg, + struct EseSgBuffer *rx_sg, uint32_t rx_nsg) { + rx_cursor_ = 0; + return teq1_transceive(ese, &kTeq1Options, tx_sg, tx_nsg, rx_sg, rx_nsg); + } + + virtual uint32_t EseHwTransmit(struct EseInterface *UNUSED(ese), const uint8_t *data, + uint32_t len, int UNUSED(complete)) { + EXPECT_GT(invocations.size(), tx_cursor_); + if (invocations.size() <= tx_cursor_) { + return 0; + } + if (!len) { + return 0; + } + if (!invocations.size()) { + return 0; + } + // Just called once per teq1_transmit -- no partials. + const struct Invocation &invocation = invocations.at(tx_cursor_++); + + EXPECT_EQ(invocation.expected_tx.size(), len); + int eq = memcmp(data, invocation.expected_tx.data(), len); + const std::vector<uint8_t> vec_data(data, data + len); + EXPECT_EQ(0, eq) + << "Got: '" << to_hex(vec_data) << "' " + << "Expected: '" << to_hex(invocation.expected_tx) << "'"; + + return len; + } + + virtual uint32_t EseHwReceive(struct EseInterface *UNUSED(ese), uint8_t *data, + uint32_t len, int UNUSED(complete)) { + if (!len) { + return 0; + } + // Get this calls expected data. + EXPECT_GT(invocations.size(), rx_cursor_); + if (!invocations.size()) + return 0; + struct Invocation &invocation = invocations.at(rx_cursor_); + + // Supply the golden return data and pop off the invocation. + // Allows partial reads from the invocation stack. + uint32_t rx_total = 0; + if (len <= invocation.rx.size()) { + rx_total = len; + memcpy(data, invocation.rx.data(), invocation.rx.size()); + } + uint32_t remaining = invocation.rx.size() - rx_total; + if (remaining && rx_total) { + invocation.rx.erase(invocation.rx.begin(), + invocation.rx.begin() + rx_total); + } else { + rx_cursor_++; + // RX shouldn't get ahead of TX. + EXPECT_GE(tx_cursor_, rx_cursor_); + // We could delete, but this make test bugs a little easier to see. + } + return rx_total; + } + + struct Invocation { + std::vector<uint8_t> rx; + std::vector<uint8_t> expected_tx; + int expect_reset; + }; + + std::vector<Invocation> invocations; + private: + uint32_t tx_cursor_; + uint32_t rx_cursor_; +}; + +class Teq1TransceiveTest : public virtual Test { + public: + Teq1TransceiveTest() { } + virtual ~Teq1TransceiveTest() { } + + void SetUp() { + // Configure ese with our internal ops. + EseOperationsWrapper::InitializeEse(&ese_, &wire_); + // Start with normal seq's. + TEQ1_INIT_CARD_STATE((struct Teq1CardState *)(&(ese_.pad[0]))); + } + + void TearDown() { + wire_.invocations.resize(0); + } + + protected: + EseWireFake wire_; + EseInterface ese_; +}; + + +TEST_F(Teq1TransceiveTest, NormalTransceiveUnchained) { + EXPECT_EQ(0, ese_open(&ese_, NULL)); + + // I(0,0) -> + // <- I(0, 0) + wire_.invocations.resize(1); + struct Teq1Frame frame; + size_t frame_size = 0; + frame.header.NAD = kTeq1Options.node_address; + frame.header.PCB = TEQ1_I(0, 0); + frame.header.LEN = 4; + frame.INF[0] = 'A'; + frame.INF[1] = 'B'; + frame.INF[2] = 'C'; + frame.INF[3] = 'D'; + frame.INF[frame.header.LEN] = teq1_compute_LRC(&frame); + frame_size = sizeof(frame.header) + frame.header.LEN + 1; + wire_.invocations[0].expected_tx.resize(frame_size); + memcpy(wire_.invocations[0].expected_tx.data(), &frame.val[0], frame_size); + ALOGI("Planning to send:"); + teq1_trace_transmit(frame.header.PCB, frame.header.LEN); + + frame.header.LEN = 0; + frame.header.NAD = kTeq1Options.host_address; + frame.INF[frame.header.LEN] = teq1_compute_LRC(&frame); + frame_size = sizeof(frame.header) + frame.header.LEN + 1; + wire_.invocations[0].rx.resize(frame_size); + memcpy(wire_.invocations[0].rx.data(), &frame, frame_size); + ALOGI("Expecting to receive:"); + teq1_trace_receive(frame.header.PCB, frame.header.LEN); + + const uint8_t payload[] = { 'A', 'B', 'C', 'D' }; + uint8_t reply[5]; // Should stay empty. + EXPECT_EQ(0, ese_transceive(&ese_, payload, sizeof(payload), reply, sizeof(reply))); +}; + + +TEST_F(Teq1TransceiveTest, NormalUnchainedRetransmitRecovery) { + EXPECT_EQ(0, ese_open(&ese_, NULL)); + + // I(0,0) [4] -> + // <- R(0, 1, 0) + // I(0,0) [4] -> + // <- I(0, 0) + wire_.invocations.resize(2); + struct Teq1Frame frame; + size_t frame_size = 0; + frame.header.NAD = kTeq1Options.node_address; + frame.header.PCB = TEQ1_I(0, 0); + frame.header.LEN = 4; + frame.INF[0] = 'A'; + frame.INF[1] = 'B'; + frame.INF[2] = 'C'; + frame.INF[3] = 'D'; + frame.INF[frame.header.LEN] = teq1_compute_LRC(&frame); + frame_size = sizeof(frame.header) + frame.header.LEN + 1; + wire_.invocations[0].expected_tx.resize(frame_size); + memcpy(wire_.invocations[0].expected_tx.data(), &frame.val[0], frame_size); + wire_.invocations[1].expected_tx.resize(frame_size); + memcpy(wire_.invocations[1].expected_tx.data(), &frame.val[0], frame_size); + + frame.header.LEN = 0; + frame.header.NAD = kTeq1Options.host_address; + frame.header.PCB = TEQ1_R(0, 1, 0); + frame.INF[frame.header.LEN] = teq1_compute_LRC(&frame); + frame_size = sizeof(frame.header) + frame.header.LEN + 1; + wire_.invocations[0].rx.resize(frame_size); + memcpy(wire_.invocations[0].rx.data(), &frame, frame_size); + + frame.header.LEN = 0; + frame.header.NAD = kTeq1Options.host_address; + frame.header.PCB = TEQ1_I(0, 0); + frame.INF[frame.header.LEN] = teq1_compute_LRC(&frame); + frame_size = sizeof(frame.header) + frame.header.LEN + 1; + wire_.invocations[1].rx.resize(frame_size); + memcpy(wire_.invocations[1].rx.data(), &frame, frame_size); + + const uint8_t payload[] = { 'A', 'B', 'C', 'D' }; + uint8_t reply[5]; // Should stay empty. + EXPECT_EQ(0, ese_transceive(&ese_, payload, sizeof(payload), reply, sizeof(reply))); +}; + +TEST_F(Teq1TransceiveTest, RetransmitResyncRecovery) { + EXPECT_EQ(0, ese_open(&ese_, NULL)); + + // I(0,0) [4] -> + // <- R(0, 1, 0) + // I(0,0) [4] -> + // <- R(0, 1, 0) + // I(0,0) [4] -> + // <- R(0, 1, 0) + // I(0,0) [4] -> + // <- R(0, 1, 0) + // S(RESYNC, REQUEST) -> (retran this is another case) + // <- S(RESYNC, RESPONSE) + // I(0, 0) [4] -> + // <- I(0, 0) [0] + wire_.invocations.resize(6); + struct Teq1Frame frame; + size_t frame_size = 0; + frame.header.NAD = kTeq1Options.node_address; + frame.header.PCB = TEQ1_I(0, 0); + frame.header.LEN = 4; + frame.INF[0] = 'A'; + frame.INF[1] = 'B'; + frame.INF[2] = 'C'; + frame.INF[3] = 'D'; + frame.INF[frame.header.LEN] = teq1_compute_LRC(&frame); + frame_size = sizeof(frame.header) + frame.header.LEN + 1; + wire_.invocations[0].expected_tx.resize(frame_size); + memcpy(wire_.invocations[0].expected_tx.data(), &frame.val[0], frame_size); + wire_.invocations[1].expected_tx.resize(frame_size); + memcpy(wire_.invocations[1].expected_tx.data(), &frame.val[0], frame_size); + wire_.invocations[2].expected_tx.resize(frame_size); + memcpy(wire_.invocations[2].expected_tx.data(), &frame.val[0], frame_size); + wire_.invocations[3].expected_tx.resize(frame_size); + memcpy(wire_.invocations[3].expected_tx.data(), &frame.val[0], frame_size); + wire_.invocations[5].expected_tx.resize(frame_size); + memcpy(wire_.invocations[5].expected_tx.data(), &frame.val[0], frame_size); + + frame.header.LEN = 0; + frame.header.NAD = kTeq1Options.node_address; + frame.header.PCB = TEQ1_S_RESYNC(0); + frame.INF[frame.header.LEN] = teq1_compute_LRC(&frame); + frame_size = sizeof(frame.header) + frame.header.LEN + 1; + wire_.invocations[4].expected_tx.resize(frame_size); + memcpy(wire_.invocations[4].expected_tx.data(), &frame, frame_size); + + frame.header.LEN = 0; + frame.header.NAD = kTeq1Options.host_address; + frame.header.PCB = TEQ1_R(0, 1, 0); + frame.INF[frame.header.LEN] = teq1_compute_LRC(&frame); + frame_size = sizeof(frame.header) + frame.header.LEN + 1; + wire_.invocations[0].rx.resize(frame_size); + memcpy(wire_.invocations[0].rx.data(), &frame, frame_size); + wire_.invocations[1].rx.resize(frame_size); + memcpy(wire_.invocations[1].rx.data(), &frame, frame_size); + wire_.invocations[2].rx.resize(frame_size); + memcpy(wire_.invocations[2].rx.data(), &frame, frame_size); + wire_.invocations[3].rx.resize(frame_size); + memcpy(wire_.invocations[3].rx.data(), &frame, frame_size); + + frame.header.LEN = 0; + frame.header.NAD = kTeq1Options.host_address; + frame.header.PCB = TEQ1_S_RESYNC(1); + frame.INF[frame.header.LEN] = teq1_compute_LRC(&frame); + frame_size = sizeof(frame.header) + frame.header.LEN + 1; + wire_.invocations[4].rx.resize(frame_size); + memcpy(wire_.invocations[4].rx.data(), &frame, frame_size); + + frame.header.LEN = 0; + frame.header.NAD = kTeq1Options.host_address; + frame.header.PCB = TEQ1_I(0, 0); + frame.INF[frame.header.LEN] = teq1_compute_LRC(&frame); + frame_size = sizeof(frame.header) + frame.header.LEN + 1; + wire_.invocations[5].rx.resize(frame_size); + memcpy(wire_.invocations[5].rx.data(), &frame, frame_size); + + const uint8_t payload[] = { 'A', 'B', 'C', 'D' }; + uint8_t reply[5]; // Should stay empty. + EXPECT_EQ(0, ese_transceive(&ese_, payload, sizeof(payload), reply, sizeof(reply))); +}; + +// Error case described in b/63546784 +TEST_F(Teq1TransceiveTest, RetransmitResyncLoop) { + EXPECT_EQ(0, ese_open(&ese_, NULL)); + + // I(0,0) [4] -> + // <- R(0, 1, 0) + // I(0,0) [4] -> + // <- R(0, 1, 0) + // I(0,0) [4] -> + // <- R(0, 1, 0) + // I(0,0) [4] -> + // <- R(0, 1, 0) + // S(RESYNC, REQUEST) -> + // <- S(RESYNC, RESPONSE) + // I(0,0) [4] -> + // <- R(0, 1, 0) + // I(0,0) [4] -> + // <- R(0, 1, 0) + // I(0,0) [4] -> + // <- R(0, 1, 0) + // I(0,0) [4] -> + // <- R(0, 1, 0) + // S(RESYNC, REQUEST) -> + // <- S(RESYNC, RESPONSE) + // ... + // 6 failure loops before a reset then 6 more before a hard failure. + wire_.invocations.resize(5 * 12); + struct Teq1Frame frame; + size_t frame_size = 0; + + frame.header.NAD = kTeq1Options.node_address; + frame.header.PCB = TEQ1_I(0, 0); + frame.header.LEN = 4; + frame.INF[0] = 'A'; + frame.INF[1] = 'B'; + frame.INF[2] = 'C'; + frame.INF[3] = 'D'; + frame.INF[frame.header.LEN] = teq1_compute_LRC(&frame); + frame_size = sizeof(frame.header) + frame.header.LEN + 1; + // Initialize all invocations to I/R then overwrite with resyncs. + for (auto &invocation : wire_.invocations) { + invocation.expected_tx.resize(frame_size); + memcpy(invocation.expected_tx.data(), &frame.val[0], frame_size); + } + + frame.header.LEN = 0; + frame.header.NAD = kTeq1Options.host_address; + frame.header.PCB = TEQ1_R(0, 1, 0); + frame.INF[frame.header.LEN] = teq1_compute_LRC(&frame); + frame_size = sizeof(frame.header) + frame.header.LEN + 1; + for (auto &invocation : wire_.invocations) { + invocation.rx.resize(frame_size); + memcpy(invocation.rx.data(), &frame.val[0], frame_size); + } + + frame.header.LEN = 0; + frame.header.NAD = kTeq1Options.node_address; + frame.header.PCB = TEQ1_S_RESYNC(0); + frame.INF[frame.header.LEN] = teq1_compute_LRC(&frame); + frame_size = sizeof(frame.header) + frame.header.LEN + 1; + int count = 0; + for (auto &invocation : wire_.invocations) { + if (++count % 5 == 0) { + invocation.expected_tx.resize(frame_size); + memcpy(invocation.expected_tx.data(), &frame, frame_size); + } + } + + frame.header.LEN = 0; + frame.header.NAD = kTeq1Options.host_address; + frame.header.PCB = TEQ1_S_RESYNC(1); + frame.INF[frame.header.LEN] = teq1_compute_LRC(&frame); + frame_size = sizeof(frame.header) + frame.header.LEN + 1; + count = 0; + for (auto &invocation : wire_.invocations) { + if (++count % 5 == 0) { + invocation.rx.resize(frame_size); + memcpy(invocation.rx.data(), &frame, frame_size); + } + } + + wire_.invocations[30].expect_reset = 1; + + const uint8_t payload[] = { 'A', 'B', 'C', 'D' }; + uint8_t reply[5]; // Should stay empty. + EXPECT_EQ(-1, ese_transceive(&ese_, payload, sizeof(payload), reply, sizeof(reply))); + EXPECT_NE(0, ese_error(&ese_)); +}; + |