blob: de0664b88cb9d3383cc77b8866ee0d906891e86c [file] [log] [blame]
/*
* 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;
}