/* * 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 "transport_test.h" #include #include #include #include #include #include #include "chpp/app.h" #include "chpp/common/discovery.h" #include "chpp/common/gnss.h" #include "chpp/common/gnss_types.h" #include "chpp/common/standard_uuids.h" #include "chpp/common/wifi.h" #include "chpp/common/wifi_types.h" #include "chpp/common/wwan.h" #include "chpp/crc.h" #include "chpp/macros.h" #include "chpp/memory.h" #include "chpp/platform/utils.h" #include "chpp/services/discovery.h" #include "chpp/services/loopback.h" #include "chpp/transport.h" #include "chre/pal/wwan.h" namespace { // Preamble as separate bytes for testing constexpr uint8_t kChppPreamble0 = 0x68; constexpr uint8_t kChppPreamble1 = 0x43; // Max size of payload sent to chppRxDataCb (bytes) constexpr size_t kMaxChunkSize = 20000; constexpr size_t kMaxPacketSize = kMaxChunkSize + CHPP_PREAMBLE_LEN_BYTES + sizeof(ChppTransportHeader) + sizeof(ChppTransportFooter); // Input sizes to test the entire range of sizes with a few tests constexpr int kChunkSizes[] = {0, 1, 2, 3, 4, 21, 100, 1000, 10001, 20000}; // Number of services constexpr int kServiceCount = 3; /* * Test suite for the CHPP Transport Layer */ class TransportTests : public testing::TestWithParam { protected: void SetUp() override { chppClearTotalAllocBytes(); memset(&mTransportContext.linkParams, 0, sizeof(mTransportContext.linkParams)); mTransportContext.linkParams.linkEstablished = true; chppTransportInit(&mTransportContext, &mAppContext); chppAppInit(&mAppContext, &mTransportContext); mTransportContext.resetState = CHPP_RESET_STATE_NONE; // Make sure CHPP has a correct count of the number of registered services // on this platform, (in this case, 1,) as registered in the function // chppRegisterCommonServices(). ASSERT_EQ(mAppContext.registeredServiceCount, kServiceCount); } void TearDown() override { chppAppDeinit(&mAppContext); chppTransportDeinit(&mTransportContext); EXPECT_EQ(chppGetTotalAllocBytes(), 0); } ChppTransportState mTransportContext = {}; ChppAppState mAppContext = {}; uint8_t mBuf[kMaxPacketSize] = {}; }; /** * Wait for chppTransportDoWork() to finish after it is notified by * chppEnqueueTxPacket to run. * * TODO: (b/177616847) Improve test robustness / synchronization without adding * overhead to CHPP */ void WaitForTransport(struct ChppTransportState *transportContext) { // Wait for linkParams.notifier.signal to be triggered and processed volatile uint32_t k = 1; while (transportContext->linkParams.notifier.signal == 0 && k > 0) { k++; } while (transportContext->linkParams.notifier.signal != 0 && k > 0) { k++; } ASSERT_FALSE(k == 0); while (k < UINT16_MAX) { k++; } while (k > 0) { k--; } // Should have reset loc and length for next packet / datagram EXPECT_EQ(transportContext->rxStatus.locInDatagram, 0); EXPECT_EQ(transportContext->rxDatagram.length, 0); } /** * Validates a ChppTestResponse. Since the error field within the * ChppAppHeader struct is optional (and not used for common services), this * function returns the error field to be checked if desired, depending on the * service. * * @param buf Buffer containing response. * @param ackSeq Ack sequence to be verified. * @param handle Handle number to be verified * @param transactionID Transaction ID to be verified. * * @return The error field within the ChppAppHeader struct that is used by some * but not all services. */ uint8_t validateChppTestResponse(void *buf, uint8_t ackSeq, uint8_t handle, uint8_t transactionID) { struct ChppTestResponse *response = (ChppTestResponse *)buf; // Check preamble EXPECT_EQ(response->preamble0, kChppPreamble0); EXPECT_EQ(response->preamble1, kChppPreamble1); // Check response transport headers EXPECT_EQ(response->transportHeader.packetCode, CHPP_TRANSPORT_ERROR_NONE); EXPECT_EQ(response->transportHeader.ackSeq, ackSeq); // Check response app headers EXPECT_EQ(response->appHeader.handle, handle); EXPECT_EQ(response->appHeader.type, CHPP_MESSAGE_TYPE_SERVICE_RESPONSE); EXPECT_EQ(response->appHeader.transaction, transactionID); // Return optional response error to be checked if desired return response->appHeader.error; } /** * Aborts a packet and validates state. * * @param transportcontext Maintains status for each transport layer instance. */ void endAndValidatePacket(struct ChppTransportState *transportContext) { chppRxPacketCompleteCb(transportContext); EXPECT_EQ(transportContext->rxStatus.state, CHPP_STATE_PREAMBLE); EXPECT_EQ(transportContext->rxStatus.locInDatagram, 0); EXPECT_EQ(transportContext->rxDatagram.length, 0); } /** * Adds a preamble to a certain location in a buffer, and increases the location * accordingly, to account for the length of the added preamble. * * @param buf Buffer. * @param location Location to add the preamble, which its value will be * increased accordingly. */ void addPreambleToBuf(uint8_t *buf, size_t *location) { buf[(*location)++] = kChppPreamble0; buf[(*location)++] = kChppPreamble1; } /** * Adds a transport header (with default values) to a certain location in a * buffer, and increases the location accordingly, to account for the length of * the added transport header. * * @param buf Buffer. * @param location Location to add the transport header, which its value will be * increased accordingly. * * @return Pointer to the added transport header (e.g. to modify its fields). */ ChppTransportHeader *addTransportHeaderToBuf(uint8_t *buf, size_t *location) { size_t oldLoc = *location; // Default values for initial, minimum size request packet ChppTransportHeader transHeader = {}; transHeader.flags = CHPP_TRANSPORT_FLAG_FINISHED_DATAGRAM; transHeader.packetCode = CHPP_TRANSPORT_ERROR_NONE; transHeader.ackSeq = 1; transHeader.seq = 0; transHeader.length = sizeof(ChppAppHeader); transHeader.reserved = 0; memcpy(&buf[*location], &transHeader, sizeof(transHeader)); *location += sizeof(transHeader); return (ChppTransportHeader *)&buf[oldLoc]; } /** * Adds an app header (with default values) to a certain location in a buffer, * and increases the location accordingly, to account for the length of the * added app header. * * @param buf Buffer. * @param location Location to add the app header, which its value will be * increased accordingly. * * @return Pointer to the added app header (e.g. to modify its fields). */ ChppAppHeader *addAppHeaderToBuf(uint8_t *buf, size_t *location) { size_t oldLoc = *location; // Default values - to be updated later as necessary ChppAppHeader appHeader = {}; appHeader.handle = CHPP_HANDLE_NEGOTIATED_RANGE_START; appHeader.type = CHPP_MESSAGE_TYPE_CLIENT_REQUEST; appHeader.transaction = 0; appHeader.error = CHPP_APP_ERROR_NONE; appHeader.command = 0; memcpy(&buf[*location], &appHeader, sizeof(appHeader)); *location += sizeof(appHeader); return (ChppAppHeader *)&buf[oldLoc]; } /** * Adds a transport footer to a certain location in a buffer, and increases the * location accordingly, to account for the length of the added preamble. * * @param buf Buffer. * @param location Location to add the footer. The value of location will be * increased accordingly. * */ void addTransportFooterToBuf(uint8_t *buf, size_t *location) { uint32_t *checksum = (uint32_t *)&buf[*location]; *checksum = chppCrc32(0, &buf[CHPP_PREAMBLE_LEN_BYTES], *location - CHPP_PREAMBLE_LEN_BYTES); *location += sizeof(ChppTransportFooter); } /** * Opens a service and checks to make sure it was opened correctly. * * @param transportContext Transport layer context. * @param buf Buffer. * @param ackSeq Ack sequence of the packet to be sent out * @param seq Sequence number of the packet to be sent out. * @param handle Handle of the service to be opened. * @param transactionID Transaction ID for the open request. * @param command Open command. */ void openService(ChppTransportState *transportContext, uint8_t *buf, uint8_t ackSeq, uint8_t seq, uint8_t handle, uint8_t transactionID, uint16_t command) { size_t len = 0; addPreambleToBuf(buf, &len); ChppTransportHeader *transHeader = addTransportHeaderToBuf(buf, &len); transHeader->ackSeq = ackSeq; transHeader->seq = seq; ChppAppHeader *appHeader = addAppHeaderToBuf(buf, &len); appHeader->handle = handle; appHeader->transaction = transactionID; appHeader->command = command; addTransportFooterToBuf(buf, &len); // Send header + payload (if any) + footer EXPECT_TRUE(chppRxDataCb(transportContext, buf, len)); // Check for correct state uint8_t nextSeq = transHeader->seq + 1; EXPECT_EQ(transportContext->rxStatus.expectedSeq, nextSeq); EXPECT_EQ(transportContext->rxStatus.state, CHPP_STATE_PREAMBLE); // Wait for response WaitForTransport(transportContext); // Validate common response fields EXPECT_EQ(validateChppTestResponse(transportContext->pendingTxPacket.payload, nextSeq, handle, transactionID), CHPP_APP_ERROR_NONE); // Check response length EXPECT_EQ(sizeof(ChppTestResponse), CHPP_PREAMBLE_LEN_BYTES + sizeof(ChppTransportHeader) + sizeof(ChppAppHeader)); EXPECT_EQ(transportContext->pendingTxPacket.length, sizeof(ChppTestResponse) + sizeof(ChppTransportFooter)); } /** * Sends a command to a service and checks for errors. * * @param transportContext Transport layer context. * @param buf Buffer. * @param ackSeq Ack sequence of the packet to be sent out * @param seq Sequence number of the packet to be sent out. * @param handle Handle of the service to be opened. * @param transactionID Transaction ID for the open request. * @param command Command to be sent. */ void sendCommandToService(ChppTransportState *transportContext, uint8_t *buf, uint8_t ackSeq, uint8_t seq, uint8_t handle, uint8_t transactionID, uint16_t command) { size_t len = 0; addPreambleToBuf(buf, &len); ChppTransportHeader *transHeader = addTransportHeaderToBuf(buf, &len); transHeader->ackSeq = ackSeq; transHeader->seq = seq; ChppAppHeader *appHeader = addAppHeaderToBuf(buf, &len); appHeader->handle = handle; appHeader->transaction = transactionID; appHeader->command = command; addTransportFooterToBuf(buf, &len); // Send header + payload (if any) + footer EXPECT_TRUE(chppRxDataCb(transportContext, buf, len)); // Check for correct state uint8_t nextSeq = transHeader->seq + 1; EXPECT_EQ(transportContext->rxStatus.expectedSeq, nextSeq); EXPECT_EQ(transportContext->rxStatus.state, CHPP_STATE_PREAMBLE); // Wait for response WaitForTransport(transportContext); // Validate common response fields EXPECT_EQ(validateChppTestResponse(transportContext->pendingTxPacket.payload, nextSeq, handle, transactionID), CHPP_APP_ERROR_NONE); } /** * A series of zeros shouldn't change state from CHPP_STATE_PREAMBLE */ TEST_P(TransportTests, ZeroNoPreambleInput) { size_t len = static_cast(GetParam()); if (len <= kMaxChunkSize) { EXPECT_TRUE(chppRxDataCb(&mTransportContext, mBuf, len)); EXPECT_EQ(mTransportContext.rxStatus.state, CHPP_STATE_PREAMBLE); } } /** * A preamble after a series of zeros input should change state from * CHPP_STATE_PREAMBLE to CHPP_STATE_HEADER */ TEST_P(TransportTests, ZeroThenPreambleInput) { size_t len = static_cast(GetParam()); if (len <= kMaxChunkSize) { // Add preamble at the end of mBuf, as individual bytes instead of using // chppAddPreamble(&mBuf[preambleLoc]) size_t preambleLoc = MAX(0, len - CHPP_PREAMBLE_LEN_BYTES); mBuf[preambleLoc] = kChppPreamble0; mBuf[preambleLoc + 1] = kChppPreamble1; if (len >= CHPP_PREAMBLE_LEN_BYTES) { EXPECT_FALSE(chppRxDataCb(&mTransportContext, mBuf, len)); EXPECT_EQ(mTransportContext.rxStatus.state, CHPP_STATE_HEADER); } else { EXPECT_TRUE(chppRxDataCb(&mTransportContext, mBuf, len)); EXPECT_EQ(mTransportContext.rxStatus.state, CHPP_STATE_PREAMBLE); } } } /** * Rx Testing with various length payloads of zeros */ TEST_P(TransportTests, RxPayloadOfZeros) { mTransportContext.rxStatus.state = CHPP_STATE_PREAMBLE; size_t len = static_cast(GetParam()); bool validLen = (len <= CHPP_TRANSPORT_RX_MTU_BYTES); mTransportContext.txStatus.hasPacketsToSend = true; std::thread t1(chppWorkThreadStart, &mTransportContext); WaitForTransport(&mTransportContext); if (len <= kMaxChunkSize) { size_t loc = 0; addPreambleToBuf(mBuf, &loc); ChppTransportHeader *transHeader = addTransportHeaderToBuf(mBuf, &loc); transHeader->length = static_cast(len); loc += len; addTransportFooterToBuf(mBuf, &loc); // Send header and check for correct state EXPECT_EQ( chppRxDataCb(&mTransportContext, mBuf, CHPP_PREAMBLE_LEN_BYTES + sizeof(ChppTransportHeader)), !validLen); if (!validLen) { EXPECT_EQ(mTransportContext.rxStatus.state, CHPP_STATE_PREAMBLE); } else if (len > 0) { EXPECT_EQ(mTransportContext.rxStatus.state, CHPP_STATE_PAYLOAD); } else { EXPECT_EQ(mTransportContext.rxStatus.state, CHPP_STATE_FOOTER); } // Correct decoding of packet length EXPECT_EQ(mTransportContext.rxHeader.length, len); EXPECT_EQ(mTransportContext.rxStatus.locInDatagram, 0); EXPECT_EQ(mTransportContext.rxDatagram.length, validLen ? len : 0); // Send payload if any and check for correct state if (len > 0) { EXPECT_EQ( chppRxDataCb( &mTransportContext, &mBuf[CHPP_PREAMBLE_LEN_BYTES + sizeof(ChppTransportHeader)], len), !validLen); EXPECT_EQ(mTransportContext.rxStatus.state, validLen ? CHPP_STATE_FOOTER : CHPP_STATE_PREAMBLE); } // Should have complete packet payload by now EXPECT_EQ(mTransportContext.rxStatus.locInDatagram, validLen ? len : 0); // But no ACK yet EXPECT_EQ(mTransportContext.rxStatus.expectedSeq, transHeader->seq); // Send footer EXPECT_TRUE(chppRxDataCb( &mTransportContext, &mBuf[CHPP_PREAMBLE_LEN_BYTES + sizeof(ChppTransportHeader) + len], sizeof(ChppTransportFooter))); // The next expected packet sequence # should incremented only if the // received packet is payload-bearing. uint8_t nextSeq = transHeader->seq + ((validLen && len > 0) ? 1 : 0); EXPECT_EQ(mTransportContext.rxStatus.expectedSeq, nextSeq); // Check for correct ACK crafting if applicable (i.e. if the received packet // is payload-bearing). if (validLen && len > 0) { // TODO: Remove later as can cause flaky tests // These are expected to change shortly afterwards, as chppTransportDoWork // is run // EXPECT_TRUE(mTransportContext.txStatus.hasPacketsToSend); EXPECT_EQ(mTransportContext.txStatus.packetCodeToSend, CHPP_TRANSPORT_ERROR_NONE); EXPECT_EQ(mTransportContext.txDatagramQueue.pending, 0); WaitForTransport(&mTransportContext); // Check response packet fields struct ChppTransportHeader *txHeader = (struct ChppTransportHeader *)&mTransportContext.pendingTxPacket .payload[CHPP_PREAMBLE_LEN_BYTES]; EXPECT_EQ(txHeader->flags, CHPP_TRANSPORT_FLAG_FINISHED_DATAGRAM); EXPECT_EQ(txHeader->packetCode, CHPP_TRANSPORT_ERROR_NONE); EXPECT_EQ(txHeader->ackSeq, nextSeq); EXPECT_EQ(txHeader->length, 0); // Check outgoing packet length EXPECT_EQ(mTransportContext.pendingTxPacket.length, CHPP_PREAMBLE_LEN_BYTES + sizeof(struct ChppTransportHeader) + sizeof(struct ChppTransportFooter)); } // Check for correct state EXPECT_EQ(mTransportContext.rxStatus.state, CHPP_STATE_PREAMBLE); // Should have reset loc and length for next packet / datagram EXPECT_EQ(mTransportContext.rxStatus.locInDatagram, 0); EXPECT_EQ(mTransportContext.rxDatagram.length, 0); } chppWorkThreadStop(&mTransportContext); t1.join(); } /** * End of Packet Link Notification during preamble */ TEST_F(TransportTests, LinkSendDonePreamble) { size_t payloadLen = 1000; size_t partLenPreamble = CHPP_PREAMBLE_LEN_BYTES - 1; mTransportContext.rxStatus.state = CHPP_STATE_PREAMBLE; mTransportContext.txStatus.hasPacketsToSend = true; std::thread t1(chppWorkThreadStart, &mTransportContext); WaitForTransport(&mTransportContext); size_t loc = 0; addPreambleToBuf(mBuf, &loc); ChppTransportHeader *transHeader = addTransportHeaderToBuf(mBuf, &loc); transHeader->length = static_cast(payloadLen); loc += payloadLen; addTransportFooterToBuf(mBuf, &loc); EXPECT_FALSE(chppRxDataCb(&mTransportContext, mBuf, partLenPreamble)); EXPECT_EQ(mTransportContext.rxStatus.state, CHPP_STATE_PREAMBLE); endAndValidatePacket(&mTransportContext); chppWorkThreadStop(&mTransportContext); t1.join(); } /** * End of Packet Link Notification during header */ TEST_F(TransportTests, LinkSendDoneHeader) { size_t payloadLen = 1000; size_t partLenHeader = CHPP_PREAMBLE_LEN_BYTES + sizeof(ChppTransportHeader) - 1; mTransportContext.rxStatus.state = CHPP_STATE_PREAMBLE; mTransportContext.txStatus.hasPacketsToSend = true; std::thread t1(chppWorkThreadStart, &mTransportContext); WaitForTransport(&mTransportContext); size_t loc = 0; addPreambleToBuf(mBuf, &loc); ChppTransportHeader *transHeader = addTransportHeaderToBuf(mBuf, &loc); transHeader->length = static_cast(payloadLen); loc += payloadLen; addTransportFooterToBuf(mBuf, &loc); EXPECT_FALSE(chppRxDataCb(&mTransportContext, mBuf, partLenHeader)); EXPECT_EQ(mTransportContext.rxStatus.state, CHPP_STATE_HEADER); EXPECT_EQ(mTransportContext.rxHeader.length, payloadLen); endAndValidatePacket(&mTransportContext); chppWorkThreadStop(&mTransportContext); t1.join(); } /** * End of Packet Link Notification during payload */ TEST_F(TransportTests, LinkSendDonePayload) { size_t payloadLen = 1000; size_t partLenPayload = 500; mTransportContext.rxStatus.state = CHPP_STATE_PREAMBLE; mTransportContext.txStatus.hasPacketsToSend = true; std::thread t1(chppWorkThreadStart, &mTransportContext); WaitForTransport(&mTransportContext); size_t loc = 0; addPreambleToBuf(mBuf, &loc); ChppTransportHeader *transHeader = addTransportHeaderToBuf(mBuf, &loc); transHeader->length = static_cast(payloadLen); loc += payloadLen; addTransportFooterToBuf(mBuf, &loc); EXPECT_FALSE(chppRxDataCb(&mTransportContext, mBuf, partLenPayload)); EXPECT_EQ(mTransportContext.rxStatus.state, CHPP_STATE_PAYLOAD); EXPECT_EQ(mTransportContext.rxHeader.length, payloadLen); EXPECT_EQ( mTransportContext.rxStatus.locInDatagram, partLenPayload - CHPP_PREAMBLE_LEN_BYTES - sizeof(ChppTransportHeader)); EXPECT_EQ(mTransportContext.rxDatagram.length, payloadLen); endAndValidatePacket(&mTransportContext); chppWorkThreadStop(&mTransportContext); t1.join(); } /** * End of Packet Link Notification during footer */ TEST_F(TransportTests, LinkSendDoneFooter) { size_t payloadLen = 1000; size_t partLenFooter = CHPP_PREAMBLE_LEN_BYTES + sizeof(ChppTransportHeader) + payloadLen + sizeof(ChppTransportFooter) - 1; mTransportContext.rxStatus.state = CHPP_STATE_PREAMBLE; mTransportContext.txStatus.hasPacketsToSend = true; std::thread t1(chppWorkThreadStart, &mTransportContext); WaitForTransport(&mTransportContext); size_t loc = 0; addPreambleToBuf(mBuf, &loc); ChppTransportHeader *transHeader = addTransportHeaderToBuf(mBuf, &loc); transHeader->length = static_cast(payloadLen); loc += payloadLen; addTransportFooterToBuf(mBuf, &loc); EXPECT_FALSE(chppRxDataCb(&mTransportContext, mBuf, partLenFooter)); EXPECT_EQ(mTransportContext.rxStatus.state, CHPP_STATE_FOOTER); EXPECT_EQ(mTransportContext.rxHeader.length, payloadLen); EXPECT_EQ(mTransportContext.rxStatus.locInDatagram, payloadLen); EXPECT_EQ(mTransportContext.rxDatagram.length, payloadLen); endAndValidatePacket(&mTransportContext); chppWorkThreadStop(&mTransportContext); t1.join(); } TEST_P(TransportTests, EnqueueDatagrams) { size_t len = static_cast(GetParam()); if (len <= CHPP_TX_DATAGRAM_QUEUE_LEN) { // Add (len) datagrams of various length to queue int fr = 0; for (int j = 0; j == CHPP_TX_DATAGRAM_QUEUE_LEN; j++) { for (size_t i = 1; i <= len; i++) { uint8_t *mBuf = (uint8_t *)chppMalloc(i + 100); EXPECT_TRUE( chppEnqueueTxDatagramOrFail(&mTransportContext, mBuf, i + 100)); EXPECT_EQ(mTransportContext.txDatagramQueue.pending, i); EXPECT_EQ(mTransportContext.txDatagramQueue.front, fr); EXPECT_EQ(mTransportContext.txDatagramQueue .datagram[(i - 1 + fr) % CHPP_TX_DATAGRAM_QUEUE_LEN] .length, i + 100); } if (mTransportContext.txDatagramQueue.pending == CHPP_TX_DATAGRAM_QUEUE_LEN) { uint8_t *mBuf = (uint8_t *)chppMalloc(100); EXPECT_FALSE( chppEnqueueTxDatagramOrFail(&mTransportContext, mBuf, 100)); CHPP_FREE_AND_NULLIFY(mBuf); } for (size_t i = len; i > 0; i--) { fr++; fr %= CHPP_TX_DATAGRAM_QUEUE_LEN; EXPECT_TRUE(chppDequeueTxDatagram(&mTransportContext)); EXPECT_EQ(mTransportContext.txDatagramQueue.front, fr); EXPECT_EQ(mTransportContext.txDatagramQueue.pending, i - 1); } EXPECT_FALSE(chppDequeueTxDatagram(&mTransportContext)); EXPECT_EQ(mTransportContext.txDatagramQueue.front, fr); EXPECT_EQ(mTransportContext.txDatagramQueue.pending, 0); } } } /** * Loopback testing with various length payloads of zeros */ TEST_P(TransportTests, LoopbackPayloadOfZeros) { mTransportContext.rxStatus.state = CHPP_STATE_PREAMBLE; size_t len = static_cast(GetParam()); mTransportContext.txStatus.hasPacketsToSend = true; std::thread t1(chppWorkThreadStart, &mTransportContext); WaitForTransport(&mTransportContext); chppWorkThreadStop(&mTransportContext); t1.join(); if (len > 1 && len <= kMaxChunkSize) { // Loopback App header (only 2 fields required) mBuf[0] = CHPP_HANDLE_LOOPBACK; mBuf[1] = CHPP_MESSAGE_TYPE_CLIENT_REQUEST; EXPECT_TRUE(chppDispatchLoopbackClientRequest(&mAppContext, mBuf, len)); uint16_t end = (mTransportContext.txDatagramQueue.front + mTransportContext.txDatagramQueue.pending - 1) % CHPP_TX_DATAGRAM_QUEUE_LEN; EXPECT_EQ(mTransportContext.txDatagramQueue.datagram[end].length, len); EXPECT_EQ(mTransportContext.txDatagramQueue.datagram[end].payload[0], CHPP_HANDLE_LOOPBACK); EXPECT_EQ(mTransportContext.txDatagramQueue.datagram[end].payload[1], CHPP_MESSAGE_TYPE_SERVICE_RESPONSE); } } /** * Discovery service + Transaction ID */ TEST_P(TransportTests, DiscoveryAndTransactionID) { uint8_t transactionID = static_cast(GetParam()); size_t len = 0; mTransportContext.txStatus.hasPacketsToSend = true; std::thread t1(chppWorkThreadStart, &mTransportContext); WaitForTransport(&mTransportContext); chppWorkThreadStop(&mTransportContext); t1.join(); ChppAppHeader *appHeader = addAppHeaderToBuf(mBuf, &len); appHeader->handle = CHPP_HANDLE_DISCOVERY; appHeader->transaction = transactionID; appHeader->command = CHPP_DISCOVERY_COMMAND_DISCOVER_ALL; EXPECT_TRUE(chppDispatchDiscoveryClientRequest(&mAppContext, mBuf, len)); uint16_t end = (mTransportContext.txDatagramQueue.front + mTransportContext.txDatagramQueue.pending - 1) % CHPP_TX_DATAGRAM_QUEUE_LEN; struct ChppAppHeader *responseHeader = (ChppAppHeader *)mTransportContext.txDatagramQueue.datagram[end].payload; EXPECT_EQ(responseHeader->handle, CHPP_HANDLE_DISCOVERY); EXPECT_EQ(responseHeader->type, CHPP_MESSAGE_TYPE_SERVICE_RESPONSE); EXPECT_EQ(responseHeader->transaction, transactionID); EXPECT_EQ(responseHeader->error, CHPP_APP_ERROR_NONE); EXPECT_EQ(responseHeader->command, CHPP_DISCOVERY_COMMAND_DISCOVER_ALL); // Decode discovery response ChppServiceDescriptor *services = (ChppServiceDescriptor *)&mTransportContext.txDatagramQueue.datagram[end] .payload[sizeof(ChppAppHeader)]; // Check total length (and implicit service count) EXPECT_EQ( mTransportContext.txDatagramQueue.datagram[end].length, sizeof(ChppAppHeader) + kServiceCount * sizeof(ChppServiceDescriptor)); // Check service configuration response ChppServiceDescriptor wwanServiceDescriptor = {}; static const uint8_t uuid[CHPP_SERVICE_UUID_LEN] = CHPP_UUID_WWAN_STANDARD; memcpy(&wwanServiceDescriptor.uuid, &uuid, sizeof(wwanServiceDescriptor.uuid)); static const char name[CHPP_SERVICE_NAME_MAX_LEN] = "WWAN"; memcpy(&wwanServiceDescriptor.name, &name, sizeof(wwanServiceDescriptor.name)); wwanServiceDescriptor.version.major = 1; wwanServiceDescriptor.version.minor = 0; wwanServiceDescriptor.version.patch = 0; EXPECT_EQ(std::memcmp(services[0].uuid, wwanServiceDescriptor.uuid, sizeof(wwanServiceDescriptor.uuid)), 0); EXPECT_EQ(std::memcmp(services[0].name, wwanServiceDescriptor.name, sizeof(wwanServiceDescriptor.name)), 0); EXPECT_EQ(services[0].version.major, wwanServiceDescriptor.version.major); EXPECT_EQ(services[0].version.minor, wwanServiceDescriptor.version.minor); EXPECT_EQ(services[0].version.patch, wwanServiceDescriptor.version.patch); } /** * CRC-32 calculation for several pre-known test vectors. */ TEST_F(TransportTests, CRC32Basic) { static const char kTest1Str[] = "Hello World Test!"; static const uint8_t *kTest1 = (const uint8_t *)kTest1Str; EXPECT_EQ(chppCrc32(0, kTest1, 17), 0x613B1D74); EXPECT_EQ(chppCrc32(0, kTest1, 16), 0x5F88D7D9); EXPECT_EQ(chppCrc32(0, kTest1, 1), 0xAA05262F); EXPECT_EQ(chppCrc32(0, kTest1, 0), 0x00000000); static const uint8_t kTest2[] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff}; EXPECT_EQ(chppCrc32(0, kTest2, 6), 0x41D9ED00); EXPECT_EQ(chppCrc32(0, kTest2, 5), 0xD2FD1072); EXPECT_EQ(chppCrc32(0, kTest2, 4), 0xFFFFFFFF); EXPECT_EQ(chppCrc32(0, kTest2, 3), 0xFFFFFF00); EXPECT_EQ(chppCrc32(0, kTest2, 2), 0xFFFF0000); EXPECT_EQ(chppCrc32(0, kTest2, 1), 0xFF000000); EXPECT_EQ(chppCrc32(0, kTest2, 0), 0x00000000); static const char kTest3Str[] = "123456789"; static const uint8_t *kTest3 = (const uint8_t *)kTest3Str; EXPECT_EQ(chppCrc32(0, kTest3, 9), 0xCBF43926); static const uint8_t kTest4[] = {0x00, 0x00, 0x00, 0x00}; EXPECT_EQ(chppCrc32(0, kTest4, sizeof(kTest4)), 0x2144DF1C); static const uint8_t kTest5[] = {0xF2, 0x01, 0x83}; EXPECT_EQ(chppCrc32(0, kTest5, sizeof(kTest5)), 0x24AB9D77); static const uint8_t kTest6[] = {0x0F, 0xAA, 0x00, 0x55}; EXPECT_EQ(chppCrc32(0, kTest6, sizeof(kTest6)), 0xB6C9B287); static const uint8_t kTest7[] = {0x00, 0xFF, 0x55, 0x11}; EXPECT_EQ(chppCrc32(0, kTest7, sizeof(kTest7)), 0x32A06212); static const uint8_t kTest8[] = {0x33, 0x22, 0x55, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF}; EXPECT_EQ(chppCrc32(0, kTest8, sizeof(kTest8)), 0xB0AE863D); static const uint8_t kTest9[] = {0x92, 0x6B, 0x55}; EXPECT_EQ(chppCrc32(0, kTest9, sizeof(kTest9)), 0x9CDEA29B); } /** * CRC-32 calculation for daisy-chained input. */ TEST_F(TransportTests, CRC32DaisyChained) { static const size_t kMaxLen = 10000; uint8_t test[kMaxLen]; // Populate test with 8-bit LFSR // Feedback polynomial is x^8 + x^6 + x^5 + x^4 + 1 static uint8_t lfsr = 1; for (size_t i = 0; i < kMaxLen; i++) { test[i] = lfsr; lfsr = (lfsr >> 1) | (((lfsr << 7) ^ (lfsr << 5) ^ (lfsr << 4) ^ (lfsr << 3)) & 0x80); } for (size_t len = 0; len < kMaxLen; len += 1000) { uint32_t fullCRC = chppCrc32(0, &test[0], len); for (size_t partition = 0; partition <= len; partition++) { uint32_t partialCRC = chppCrc32(0, &test[0], partition); EXPECT_EQ(chppCrc32(partialCRC, &test[partition], len - partition), fullCRC); } } } /** * WWAN service Open and GetCapabilities. */ TEST_F(TransportTests, WwanOpen) { mTransportContext.txStatus.hasPacketsToSend = true; std::thread t1(chppWorkThreadStart, &mTransportContext); WaitForTransport(&mTransportContext); uint8_t ackSeq = 1; uint8_t seq = 0; uint8_t handle = CHPP_HANDLE_NEGOTIATED_RANGE_START; uint8_t transactionID = 0; size_t len = 0; openService(&mTransportContext, mBuf, ackSeq++, seq++, handle, transactionID++, CHPP_WWAN_OPEN); addPreambleToBuf(mBuf, &len); uint16_t command = CHPP_WWAN_GET_CAPABILITIES; sendCommandToService(&mTransportContext, mBuf, ackSeq++, seq++, handle, transactionID++, command); size_t responseLoc = sizeof(ChppTestResponse); // Validate capabilities uint32_t *capabilities = (uint32_t *)&mTransportContext.pendingTxPacket.payload[responseLoc]; responseLoc += sizeof(uint32_t); // Cleanup chppWorkThreadStop(&mTransportContext); t1.join(); uint32_t capabilitySet = CHRE_WWAN_GET_CELL_INFO; EXPECT_EQ((*capabilities) & ~(capabilitySet), 0); // Check total length EXPECT_EQ(responseLoc, CHPP_PREAMBLE_LEN_BYTES + sizeof(ChppTransportHeader) + sizeof(ChppWwanGetCapabilitiesResponse)); } /** * WiFi service Open and GetCapabilities. */ TEST_F(TransportTests, WifiOpen) { mTransportContext.txStatus.hasPacketsToSend = true; std::thread t1(chppWorkThreadStart, &mTransportContext); WaitForTransport(&mTransportContext); uint8_t ackSeq = 1; uint8_t seq = 0; uint8_t handle = CHPP_HANDLE_NEGOTIATED_RANGE_START + 1; uint8_t transactionID = 0; openService(&mTransportContext, mBuf, ackSeq++, seq++, handle, transactionID++, CHPP_WIFI_OPEN); uint16_t command = CHPP_WIFI_GET_CAPABILITIES; sendCommandToService(&mTransportContext, mBuf, ackSeq++, seq++, handle, transactionID++, command); size_t responseLoc = sizeof(ChppTestResponse); // Cleanup chppWorkThreadStop(&mTransportContext); t1.join(); // Validate capabilities uint32_t *capabilities = (uint32_t *)&mTransportContext.pendingTxPacket.payload[responseLoc]; responseLoc += sizeof(uint32_t); uint32_t capabilitySet = CHRE_WIFI_CAPABILITIES_SCAN_MONITORING | CHRE_WIFI_CAPABILITIES_ON_DEMAND_SCAN | CHRE_WIFI_CAPABILITIES_RADIO_CHAIN_PREF | CHRE_WIFI_CAPABILITIES_RTT_RANGING; EXPECT_EQ((*capabilities) & ~(capabilitySet), 0); // Check total length EXPECT_EQ(responseLoc, CHPP_PREAMBLE_LEN_BYTES + sizeof(ChppTransportHeader) + sizeof(ChppWwanGetCapabilitiesResponse)); } /** * GNSS service Open and GetCapabilities. */ TEST_F(TransportTests, GnssOpen) { mTransportContext.txStatus.hasPacketsToSend = true; std::thread t1(chppWorkThreadStart, &mTransportContext); WaitForTransport(&mTransportContext); uint8_t ackSeq = 1; uint8_t seq = 0; uint8_t handle = CHPP_HANDLE_NEGOTIATED_RANGE_START + 2; uint8_t transactionID = 0; size_t len = 0; openService(&mTransportContext, mBuf, ackSeq++, seq++, handle, transactionID++, CHPP_GNSS_OPEN); addPreambleToBuf(mBuf, &len); uint16_t command = CHPP_GNSS_GET_CAPABILITIES; sendCommandToService(&mTransportContext, mBuf, ackSeq++, seq++, handle, transactionID++, command); size_t responseLoc = sizeof(ChppTestResponse); // Cleanup chppWorkThreadStop(&mTransportContext); t1.join(); // Validate capabilities uint32_t *capabilities = (uint32_t *)&mTransportContext.pendingTxPacket.payload[responseLoc]; responseLoc += sizeof(uint32_t); uint32_t capabilitySet = CHRE_GNSS_CAPABILITIES_LOCATION | CHRE_GNSS_CAPABILITIES_MEASUREMENTS | CHRE_GNSS_CAPABILITIES_GNSS_ENGINE_BASED_PASSIVE_LISTENER; EXPECT_EQ((*capabilities) & ~(capabilitySet), 0); // Check total length EXPECT_EQ(responseLoc, CHPP_PREAMBLE_LEN_BYTES + sizeof(ChppTransportHeader) + sizeof(ChppGnssGetCapabilitiesResponse)); } /** * Discovery client. */ TEST_F(TransportTests, Discovery) { size_t len = 0; mTransportContext.txStatus.hasPacketsToSend = true; std::thread t1(chppWorkThreadStart, &mTransportContext); WaitForTransport(&mTransportContext); addPreambleToBuf(mBuf, &len); ChppTransportHeader *transHeader = addTransportHeaderToBuf(mBuf, &len); ChppAppHeader *appHeader = addAppHeaderToBuf(mBuf, &len); appHeader->handle = CHPP_HANDLE_DISCOVERY; appHeader->command = CHPP_DISCOVERY_COMMAND_DISCOVER_ALL; appHeader->type = CHPP_MESSAGE_TYPE_SERVICE_RESPONSE; ChppServiceDescriptor wwanServiceDescriptor = {}; static const uint8_t uuid[CHPP_SERVICE_UUID_LEN] = CHPP_UUID_WWAN_STANDARD; memcpy(&wwanServiceDescriptor.uuid, &uuid, sizeof(wwanServiceDescriptor.uuid)); static const char name[CHPP_SERVICE_NAME_MAX_LEN] = "WWAN"; memcpy(&wwanServiceDescriptor.name, &name, sizeof(wwanServiceDescriptor.name)); wwanServiceDescriptor.version.major = 1; wwanServiceDescriptor.version.minor = 0; wwanServiceDescriptor.version.patch = 0; memcpy(&mBuf[len], &wwanServiceDescriptor, sizeof(ChppServiceDescriptor)); len += sizeof(ChppServiceDescriptor); transHeader->length = static_cast( len - sizeof(ChppTransportHeader) - CHPP_PREAMBLE_LEN_BYTES); addTransportFooterToBuf(mBuf, &len); // Initialize clientIndexOfServiceIndex[0] to see if it correctly updated // upon discovery mAppContext.clientIndexOfServiceIndex[0] = CHPP_CLIENT_INDEX_NONE; // Send header + payload (if any) + footer EXPECT_TRUE(chppRxDataCb(&mTransportContext, mBuf, len)); // Cleanup chppWorkThreadStop(&mTransportContext); t1.join(); // Check for correct state EXPECT_EQ(mAppContext.clientIndexOfServiceIndex[0], 0); uint8_t nextSeq = transHeader->seq + 1; EXPECT_EQ(mTransportContext.rxStatus.expectedSeq, nextSeq); EXPECT_EQ(mTransportContext.rxStatus.state, CHPP_STATE_PREAMBLE); } /** * Unopened clients should not crash upon an unsolicitated service response. */ TEST_F(TransportTests, UnopenedClient) { size_t len = 0; uint8_t *buf = (uint8_t *)chppMalloc(100); mTransportContext.txStatus.hasPacketsToSend = true; std::thread t1(chppWorkThreadStart, &mTransportContext); WaitForTransport(&mTransportContext); chppWorkThreadStop(&mTransportContext); t1.join(); ChppAppHeader *appHeader = addAppHeaderToBuf(buf, &len); appHeader->handle = CHPP_HANDLE_NEGOTIATED_RANGE_START + 1; appHeader->command = CHPP_WIFI_CONFIGURE_SCAN_MONITOR_ASYNC; appHeader->type = CHPP_MESSAGE_TYPE_SERVICE_RESPONSE; len = sizeof(struct ChppWifiConfigureScanMonitorAsyncResponse); ASSERT_EQ(mAppContext.registeredServiceCount, kServiceCount); chppAppProcessRxDatagram(&mAppContext, buf, len); EXPECT_EQ(mTransportContext.txStatus.packetCodeToSend, CHPP_TRANSPORT_ERROR_APPLAYER); } TEST_F(TransportTests, DiscardedPacketTest) { mTransportContext.txStatus.hasPacketsToSend = true; std::thread t1(chppWorkThreadStart, &mTransportContext); WaitForTransport(&mTransportContext); // Send packet to RX thread after manually setting to resetting state. // We expect this packet to get dropped, but this test checks for any // problematic behavior (e.g. memory leaks). mTransportContext.resetState = CHPP_RESET_STATE_RESETTING; size_t loc = 0; addPreambleToBuf(mBuf, &loc); ChppTransportHeader *transHeader = addTransportHeaderToBuf(mBuf, &loc); ChppAppHeader *appHeader = addAppHeaderToBuf(mBuf, &loc); appHeader->handle = CHPP_HANDLE_DISCOVERY; appHeader->command = CHPP_DISCOVERY_COMMAND_DISCOVER_ALL; appHeader->type = CHPP_MESSAGE_TYPE_SERVICE_RESPONSE; ChppServiceDescriptor wwanServiceDescriptor = {}; static const uint8_t uuid[CHPP_SERVICE_UUID_LEN] = CHPP_UUID_WWAN_STANDARD; memcpy(&wwanServiceDescriptor.uuid, &uuid, sizeof(wwanServiceDescriptor.uuid)); static const char name[CHPP_SERVICE_NAME_MAX_LEN] = "WWAN"; memcpy(&wwanServiceDescriptor.name, &name, sizeof(wwanServiceDescriptor.name)); wwanServiceDescriptor.version.major = 1; wwanServiceDescriptor.version.minor = 0; wwanServiceDescriptor.version.patch = 0; memcpy(&mBuf[loc], &wwanServiceDescriptor, sizeof(ChppServiceDescriptor)); loc += sizeof(ChppServiceDescriptor); transHeader->length = static_cast( loc - sizeof(ChppTransportHeader) - CHPP_PREAMBLE_LEN_BYTES); addTransportFooterToBuf(mBuf, &loc); mAppContext.clientIndexOfServiceIndex[0] = CHPP_CLIENT_INDEX_NONE; EXPECT_TRUE(chppRxDataCb(&mTransportContext, mBuf, loc)); chppWorkThreadStop(&mTransportContext); t1.join(); } /* * Correctly handle messages directed to clients / services with an invalid * handle number. */ void messageToInvalidHandle(ChppTransportState *transportContext, uint8_t type) { size_t len = 0; uint8_t *buf = (uint8_t *)chppMalloc(100); transportContext->txStatus.hasPacketsToSend = true; std::thread t1(chppWorkThreadStart, transportContext); WaitForTransport(transportContext); chppWorkThreadStop(transportContext); t1.join(); ChppAppHeader *appHeader = addAppHeaderToBuf(buf, &len); appHeader->handle = CHPP_HANDLE_NEGOTIATED_RANGE_START + CHPP_MAX_REGISTERED_CLIENTS; appHeader->type = type; len = sizeof(struct ChppAppHeader); chppAppProcessRxDatagram(transportContext->appContext, buf, len); EXPECT_EQ(transportContext->txStatus.packetCodeToSend, CHPP_TRANSPORT_ERROR_APPLAYER); } TEST_F(TransportTests, RequestToInvalidService) { messageToInvalidHandle(&mTransportContext, CHPP_MESSAGE_TYPE_CLIENT_REQUEST); } TEST_F(TransportTests, ResponseToInvalidClient) { messageToInvalidHandle(&mTransportContext, CHPP_MESSAGE_TYPE_SERVICE_RESPONSE); } TEST_F(TransportTests, NotificationToInvalidService) { messageToInvalidHandle(&mTransportContext, CHPP_MESSAGE_TYPE_CLIENT_NOTIFICATION); } TEST_F(TransportTests, NotificationToInvalidClient) { messageToInvalidHandle(&mTransportContext, CHPP_MESSAGE_TYPE_SERVICE_NOTIFICATION); } INSTANTIATE_TEST_SUITE_P(TransportTestRange, TransportTests, testing::ValuesIn(kChunkSizes)); } // namespace