| /* |
| * 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 "chpp/clients/timesync.h" |
| |
| #include <stdbool.h> |
| #include <stddef.h> |
| #include <stdint.h> |
| #include <string.h> |
| |
| #include "chpp/app.h" |
| #include "chpp/clients.h" |
| #include "chpp/common/timesync.h" |
| #include "chpp/log.h" |
| #include "chpp/memory.h" |
| #include "chpp/time.h" |
| #include "chpp/transport.h" |
| |
| #include "chpp/clients/discovery.h" |
| |
| /************************************************ |
| * Private Definitions |
| ***********************************************/ |
| |
| /** |
| * Structure to maintain state for the Timesync client and its Request/Response |
| * (RR) functionality. |
| */ |
| struct ChppTimesyncClientState { |
| struct ChppClientState client; // Timesync client state |
| struct ChppRequestResponseState measureOffset; // Request response state |
| |
| struct ChppTimesyncResult timesyncResult; // Result of measureOffset |
| }; |
| |
| /************************************************ |
| * Public Functions |
| ***********************************************/ |
| |
| void chppTimesyncClientInit(struct ChppAppState *appState) { |
| CHPP_LOGD("Timesync client init"); |
| CHPP_DEBUG_NOT_NULL(appState); |
| |
| appState->timesyncClientContext = |
| chppMalloc(sizeof(struct ChppTimesyncClientState)); |
| CHPP_NOT_NULL(appState->timesyncClientContext); |
| struct ChppTimesyncClientState *state = appState->timesyncClientContext; |
| |
| memset(state, 0, sizeof(struct ChppTimesyncClientState)); |
| state->client.appContext = appState; |
| state->timesyncResult.error = CHPP_APP_ERROR_NONE; |
| |
| chppClientInit(&state->client, CHPP_HANDLE_TIMESYNC); |
| state->timesyncResult.error = CHPP_APP_ERROR_UNSPECIFIED; |
| state->client.openState = CHPP_OPEN_STATE_OPENED; |
| } |
| |
| void chppTimesyncClientDeinit(struct ChppAppState *appState) { |
| CHPP_LOGD("Timesync client deinit"); |
| CHPP_DEBUG_NOT_NULL(appState); |
| CHPP_NOT_NULL(appState->timesyncClientContext); |
| chppClientDeinit(&appState->timesyncClientContext->client); |
| CHPP_FREE_AND_NULLIFY(appState->timesyncClientContext); |
| } |
| |
| void chppTimesyncClientReset(struct ChppAppState *appState) { |
| CHPP_LOGD("Timesync client reset"); |
| CHPP_DEBUG_NOT_NULL(appState); |
| struct ChppTimesyncClientState *state = appState->timesyncClientContext; |
| CHPP_NOT_NULL(state); |
| |
| state->timesyncResult.error = CHPP_APP_ERROR_NONE; |
| state->timesyncResult.offsetNs = 0; |
| state->timesyncResult.rttNs = 0; |
| state->timesyncResult.measurementTimeNs = 0; |
| } |
| |
| bool chppDispatchTimesyncServiceResponse(struct ChppAppState *appState, |
| const uint8_t *buf, size_t len) { |
| CHPP_LOGD("Timesync client dispatch service response"); |
| CHPP_DEBUG_NOT_NULL(appState); |
| struct ChppTimesyncClientState *state = appState->timesyncClientContext; |
| CHPP_NOT_NULL(state); |
| CHPP_NOT_NULL(buf); |
| |
| if (len < sizeof(struct ChppTimesyncResponse)) { |
| CHPP_LOGE("Timesync resp short len=%" PRIuSIZE, len); |
| state->timesyncResult.error = CHPP_APP_ERROR_INVALID_LENGTH; |
| return false; |
| } |
| |
| const struct ChppTimesyncResponse *response = |
| (const struct ChppTimesyncResponse *)buf; |
| if (chppClientTimestampResponse(&state->client, &state->measureOffset, |
| &response->header)) { |
| state->timesyncResult.rttNs = state->measureOffset.responseTimeNs - |
| state->measureOffset.requestTimeNs; |
| int64_t offsetNs = |
| (int64_t)(response->timeNs - state->measureOffset.responseTimeNs); |
| int64_t offsetChangeNs = offsetNs - state->timesyncResult.offsetNs; |
| |
| int64_t clippedOffsetChangeNs = offsetChangeNs; |
| if (state->timesyncResult.offsetNs != 0) { |
| clippedOffsetChangeNs = MIN(clippedOffsetChangeNs, |
| (int64_t)CHPP_CLIENT_TIMESYNC_MAX_CHANGE_NS); |
| clippedOffsetChangeNs = MAX(clippedOffsetChangeNs, |
| -(int64_t)CHPP_CLIENT_TIMESYNC_MAX_CHANGE_NS); |
| } |
| |
| state->timesyncResult.offsetNs += clippedOffsetChangeNs; |
| |
| if (offsetChangeNs != clippedOffsetChangeNs) { |
| CHPP_LOGW("Drift=%" PRId64 " clipped to %" PRId64 " at t=%" PRIu64, |
| offsetChangeNs / (int64_t)CHPP_NSEC_PER_MSEC, |
| clippedOffsetChangeNs / (int64_t)CHPP_NSEC_PER_MSEC, |
| state->measureOffset.responseTimeNs / CHPP_NSEC_PER_MSEC); |
| } else { |
| state->timesyncResult.measurementTimeNs = |
| state->measureOffset.responseTimeNs; |
| } |
| |
| state->timesyncResult.error = CHPP_APP_ERROR_NONE; |
| |
| CHPP_LOGD("Timesync RTT=%" PRIu64 " correction=%" PRId64 " offset=%" PRId64 |
| " t=%" PRIu64, |
| state->timesyncResult.rttNs / CHPP_NSEC_PER_MSEC, |
| clippedOffsetChangeNs / (int64_t)CHPP_NSEC_PER_MSEC, |
| offsetNs / (int64_t)CHPP_NSEC_PER_MSEC, |
| state->timesyncResult.measurementTimeNs / CHPP_NSEC_PER_MSEC); |
| } |
| |
| return true; |
| } |
| |
| bool chppTimesyncMeasureOffset(struct ChppAppState *appState) { |
| bool result = false; |
| CHPP_LOGD("Measuring timesync t=%" PRIu64, |
| chppGetCurrentTimeNs() / CHPP_NSEC_PER_MSEC); |
| CHPP_DEBUG_NOT_NULL(appState); |
| struct ChppTimesyncClientState *state = appState->timesyncClientContext; |
| CHPP_NOT_NULL(state); |
| |
| state->timesyncResult.error = |
| CHPP_APP_ERROR_BUSY; // A measurement is in progress |
| |
| struct ChppAppHeader *request = chppAllocClientRequestCommand( |
| &state->client, CHPP_TIMESYNC_COMMAND_GETTIME); |
| size_t requestLen = sizeof(*request); |
| |
| if (request == NULL) { |
| state->timesyncResult.error = CHPP_APP_ERROR_OOM; |
| CHPP_LOG_OOM(); |
| |
| } else if (!chppSendTimestampedRequestOrFail( |
| &state->client, &state->measureOffset, request, requestLen, |
| CHPP_CLIENT_REQUEST_TIMEOUT_INFINITE)) { |
| state->timesyncResult.error = CHPP_APP_ERROR_UNSPECIFIED; |
| |
| } else { |
| result = true; |
| } |
| |
| return result; |
| } |
| |
| int64_t chppTimesyncGetOffset(struct ChppAppState *appState, |
| uint64_t maxTimesyncAgeNs) { |
| CHPP_DEBUG_NOT_NULL(appState); |
| struct ChppTimesyncClientState *state = appState->timesyncClientContext; |
| CHPP_DEBUG_NOT_NULL(state); |
| |
| bool timesyncNeverDone = state->timesyncResult.offsetNs == 0; |
| bool timesyncIsStale = |
| chppGetCurrentTimeNs() - state->timesyncResult.measurementTimeNs > |
| maxTimesyncAgeNs; |
| |
| if (timesyncNeverDone || timesyncIsStale) { |
| chppTimesyncMeasureOffset(appState); |
| } else { |
| CHPP_LOGD("No need to timesync at t~=%" PRIu64 "offset=%" PRId64, |
| chppGetCurrentTimeNs() / CHPP_NSEC_PER_MSEC, |
| state->timesyncResult.offsetNs / (int64_t)CHPP_NSEC_PER_MSEC); |
| } |
| |
| return state->timesyncResult.offsetNs; |
| } |
| |
| const struct ChppTimesyncResult *chppTimesyncGetResult( |
| struct ChppAppState *appState) { |
| CHPP_DEBUG_NOT_NULL(appState); |
| CHPP_DEBUG_NOT_NULL(appState->timesyncClientContext); |
| return &appState->timesyncClientContext->timesyncResult; |
| } |