1 /*
2  * Copyright (C) 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include "chpp/clients/loopback.h"
18 
19 #include <stdbool.h>
20 #include <stddef.h>
21 #include <stdint.h>
22 #include <string.h>
23 
24 #include "chpp/app.h"
25 #include "chpp/clients.h"
26 #include "chpp/log.h"
27 #include "chpp/memory.h"
28 #include "chpp/transport.h"
29 
30 #include "chpp/clients/discovery.h"
31 
32 /************************************************
33  *  Prototypes
34  ***********************************************/
35 
36 /************************************************
37  *  Private Definitions
38  ***********************************************/
39 
40 /**
41  * Structure to maintain state for the loopback client and its Request/Response
42  * (RR) functionality.
43  */
44 struct ChppLoopbackClientState {
45   struct ChppClientState client;                    // Loopback client state
46   struct ChppRequestResponseState runLoopbackTest;  // Loopback test state
47 
48   struct ChppLoopbackTestResult testResult;  // Last test result
49   const uint8_t *loopbackData;               // Pointer to loopback data
50 };
51 
52 /************************************************
53  *  Public Functions
54  ***********************************************/
55 
chppLoopbackClientInit(struct ChppAppState * context)56 void chppLoopbackClientInit(struct ChppAppState *context) {
57   CHPP_LOGD("Loopback client init");
58 
59   context->loopbackClientContext =
60       chppMalloc(sizeof(struct ChppLoopbackClientState));
61   CHPP_NOT_NULL(context->loopbackClientContext);
62   memset(context->loopbackClientContext, 0,
63          sizeof(struct ChppLoopbackClientState));
64 
65   context->loopbackClientContext->client.appContext = context;
66   chppClientInit(&context->loopbackClientContext->client, CHPP_HANDLE_LOOPBACK);
67   context->loopbackClientContext->testResult.error = CHPP_APP_ERROR_NONE;
68   context->loopbackClientContext->client.openState = CHPP_OPEN_STATE_OPENED;
69 }
70 
chppLoopbackClientDeinit(struct ChppAppState * context)71 void chppLoopbackClientDeinit(struct ChppAppState *context) {
72   CHPP_LOGD("Loopback client deinit");
73 
74   CHPP_NOT_NULL(context);
75   CHPP_NOT_NULL(context->loopbackClientContext);
76 
77   chppClientDeinit(&context->loopbackClientContext->client);
78   CHPP_FREE_AND_NULLIFY(context->loopbackClientContext);
79 }
80 
chppDispatchLoopbackServiceResponse(struct ChppAppState * context,const uint8_t * response,size_t len)81 bool chppDispatchLoopbackServiceResponse(struct ChppAppState *context,
82                                          const uint8_t *response, size_t len) {
83   CHPP_LOGD("Loopback client dispatch service response");
84 
85   CHPP_ASSERT(len >= CHPP_LOOPBACK_HEADER_LEN);
86   CHPP_NOT_NULL(response);
87   CHPP_NOT_NULL(context->loopbackClientContext);
88   CHPP_NOT_NULL(context->loopbackClientContext->loopbackData);
89 
90   CHPP_ASSERT(chppClientTimestampResponse(
91       &context->loopbackClientContext->client,
92       &context->loopbackClientContext->runLoopbackTest,
93       (const struct ChppAppHeader *)response));
94 
95   context->loopbackClientContext->testResult.error = CHPP_APP_ERROR_NONE;
96   context->loopbackClientContext->testResult.responseLen = len;
97   context->loopbackClientContext->testResult.firstError = len;
98   context->loopbackClientContext->testResult.byteErrors = 0;
99   context->loopbackClientContext->testResult.rttNs =
100       context->loopbackClientContext->runLoopbackTest.responseTimeNs -
101       context->loopbackClientContext->runLoopbackTest.requestTimeNs;
102 
103   if (context->loopbackClientContext->testResult.requestLen !=
104       context->loopbackClientContext->testResult.responseLen) {
105     context->loopbackClientContext->testResult.error =
106         CHPP_APP_ERROR_INVALID_LENGTH;
107     context->loopbackClientContext->testResult.firstError =
108         MIN(context->loopbackClientContext->testResult.requestLen,
109             context->loopbackClientContext->testResult.responseLen);
110   }
111 
112   for (size_t loc = CHPP_LOOPBACK_HEADER_LEN;
113        loc < MIN(context->loopbackClientContext->testResult.requestLen,
114                  context->loopbackClientContext->testResult.responseLen);
115        loc++) {
116     if (context->loopbackClientContext
117             ->loopbackData[loc - CHPP_LOOPBACK_HEADER_LEN] != response[loc]) {
118       context->loopbackClientContext->testResult.error =
119           CHPP_APP_ERROR_UNSPECIFIED;
120       context->loopbackClientContext->testResult.firstError =
121           MIN(context->loopbackClientContext->testResult.firstError,
122               loc - CHPP_LOOPBACK_HEADER_LEN);
123       context->loopbackClientContext->testResult.byteErrors++;
124     }
125   }
126 
127   CHPP_LOGI("Loopback client RX response. Error code=0x%" PRIx16
128             ", response len=%" PRIuSIZE ", request len=%" PRIuSIZE
129             ", first err=%" PRIuSIZE ", total err=%" PRIuSIZE,
130             context->loopbackClientContext->testResult.error,
131             context->loopbackClientContext->testResult.responseLen,
132             context->loopbackClientContext->testResult.requestLen,
133             context->loopbackClientContext->testResult.firstError,
134             context->loopbackClientContext->testResult.byteErrors);
135 
136   // Notify waiting (synchronous) client
137   chppMutexLock(&context->loopbackClientContext->client.responseMutex);
138   context->loopbackClientContext->client.responseReady = true;
139   chppConditionVariableSignal(
140       &context->loopbackClientContext->client.responseCondVar);
141   chppMutexUnlock(&context->loopbackClientContext->client.responseMutex);
142 
143   return true;
144 }
145 
chppRunLoopbackTest(struct ChppAppState * context,const uint8_t * buf,size_t len)146 struct ChppLoopbackTestResult chppRunLoopbackTest(struct ChppAppState *context,
147                                                   const uint8_t *buf,
148                                                   size_t len) {
149   CHPP_LOGI("Loopback test. payload len=%" PRIuSIZE ", request len=%" PRIuSIZE,
150             len, len + CHPP_LOOPBACK_HEADER_LEN);
151 
152   if (!chppWaitForDiscoveryComplete(context, 0 /* timeoutMs */)) {
153     static const struct ChppLoopbackTestResult result = {
154         .error = CHPP_APP_ERROR_NOT_READY,
155     };
156     return result;
157   }
158 
159   CHPP_NOT_NULL(context->loopbackClientContext);
160   if (context->loopbackClientContext->testResult.error ==
161       CHPP_APP_ERROR_BLOCKED) {
162     CHPP_LOGE("Loopback test cannot be run while another is in progress");
163     CHPP_DEBUG_ASSERT(false);
164 
165   } else {
166     context->loopbackClientContext->testResult.error = CHPP_APP_ERROR_BLOCKED;
167     context->loopbackClientContext->testResult.requestLen =
168         len + CHPP_LOOPBACK_HEADER_LEN;
169     context->loopbackClientContext->testResult.responseLen = 0;
170     context->loopbackClientContext->testResult.firstError = 0;
171     context->loopbackClientContext->testResult.byteErrors = 0;
172     context->loopbackClientContext->testResult.rttNs = 0;
173     context->loopbackClientContext->runLoopbackTest.requestTimeNs =
174         CHPP_TIME_NONE;
175     context->loopbackClientContext->runLoopbackTest.responseTimeNs =
176         CHPP_TIME_NONE;
177 
178     if (len == 0) {
179       CHPP_LOGE("Loopback payload too short (0)");
180       context->loopbackClientContext->testResult.error =
181           CHPP_APP_ERROR_INVALID_LENGTH;
182 
183     } else {
184       uint8_t *loopbackRequest = (uint8_t *)chppAllocClientRequest(
185           &context->loopbackClientContext->client,
186           context->loopbackClientContext->testResult.requestLen);
187 
188       if (loopbackRequest == NULL) {
189         // OOM
190         context->loopbackClientContext->testResult.requestLen = 0;
191         context->loopbackClientContext->testResult.error = CHPP_APP_ERROR_OOM;
192         CHPP_LOG_OOM();
193 
194       } else {
195         context->loopbackClientContext->loopbackData = buf;
196         memcpy(&loopbackRequest[CHPP_LOOPBACK_HEADER_LEN], buf, len);
197 
198         if (!chppSendTimestampedRequestAndWaitTimeout(
199                 &context->loopbackClientContext->client,
200                 &context->loopbackClientContext->runLoopbackTest,
201                 loopbackRequest,
202                 context->loopbackClientContext->testResult.requestLen,
203                 CHPP_NSEC_PER_SEC /* 1s */)) {
204           context->loopbackClientContext->testResult.error =
205               CHPP_APP_ERROR_UNSPECIFIED;
206         }  // else {context->loopbackClientContext->testResult is now populated}
207       }
208     }
209   }
210 
211   return context->loopbackClientContext->testResult;
212 }
213