/*
 * Copyright (C) 2016 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 <general_test/timer_stress_test.h>

#include <cinttypes>
#include <cstddef>

#include <shared/send_message.h>

#include <chre.h>

using nanoapp_testing::sendFatalFailureToHost;
using nanoapp_testing::sendInternalFailureToHost;
using nanoapp_testing::sendSuccessToHost;

/*
 * We stress the system by setting more and more timers until the system
 * runs out.  We then cancel one (CT) and set a new timer post-cancel (NT).
 * We make sure all the timers we set fire.
 *
 * Our stages are:
 * Stage 0: Successfully cancel CT.
 * Stage 1: All of our "exhaustion" timers fire.
 * Stage 2: The new timer, NT, fires.
 *
 * After all of our stages have succeeded, we send success to the host.  Note
 * there is no system requirement that Stage 2 happens after Stage 1, so
 * we use markSuccess() to track this.
 */

// Allow 1000ms to create the large number of timers specified below. This
// equates to approximately 1ms per timer which should give ample time for
// timer creation to complete.
constexpr uint64_t kDuration = UINT64_C(1000000000);

// If the system keeps claiming it can set more timers, we don't let it
// continue forever.  Instead, we'll cut it off at this limit.  And then
// we'll call its bluff, and make sure that all of these timers legitimately
// fire.  While it won't be an actual exhaustion test (we never took the
// system down to no more timers available), it will still give us confidence
// that this CHRE can properly handle any semi-reasonable timer load properly.
// 1030 is an arbitrary number, slightly over 2^10.  The hope is this
// balances between catching incorrect behavior and the test taking too long.
constexpr int32_t kMaxTimersToSet = INT32_C(1030);

namespace general_test {

namespace {

const uint32_t kCookies[] = {0, 1, 2};

} // anonymous namespace

void TimerStressTest::startStages() {
  uint32_t cancelId = chreTimerSet(kDuration, &kCookies[0], true);
  if (cancelId == CHRE_TIMER_INVALID) {
    sendFatalFailureToHost("No timers available");
  }

  mStage1CallbacksLeft = 0;
  // We anticipate most CHREs will not reach kMaxTimersToSet.
  while (mStage1CallbacksLeft < kMaxTimersToSet) {
    if (chreTimerSet(kDuration, &kCookies[1], true) == CHRE_TIMER_INVALID) {
      break;
    }
    mStage1CallbacksLeft++;
  }
  if (mStage1CallbacksLeft == 0) {
    sendFatalFailureToHost("Insufficient timers available");
  }
  if (!chreTimerCancel(cancelId)) {
    sendFatalFailureToHost("Unable to cancel timer");
  }
  markSuccess(0);
  if (chreTimerSet(kDuration, &kCookies[2], true) == CHRE_TIMER_INVALID) {
    sendFatalFailureToHost("Unable to set new timer after successful "
                           "cancel.");
  }
}

TimerStressTest::TimerStressTest()
  : Test(CHRE_API_VERSION_1_0),
    mInMethod(false),
    mFinishedBitmask(0),
    mStage1CallbacksLeft(0) {
}

void TimerStressTest::setUp(uint32_t messageSize, const void * /* message */) {
  mInMethod = true;

  if (messageSize != 0) {
    sendFatalFailureToHost(
        "TimerStress message expects 0 additional bytes, got ",
        &messageSize);
  }

  startStages();

  mInMethod = false;
}

void TimerStressTest::handleStageEvent(uint32_t index) {
  switch (index) {
    case 0:
      sendFatalFailureToHost("Canceled timer fired:", &index);
      break;

    case 1:
      --mStage1CallbacksLeft;
      if (mStage1CallbacksLeft <= 0) {
        markSuccess(index);
      }
      break;

    case 2:
      markSuccess(index);
      break;

    default:
      sendFatalFailureToHost("Unexpected event stage:", &index);
  }
}

void TimerStressTest::handleEvent(uint32_t senderInstanceId,
                                  uint16_t eventType, const void* eventData) {
  if (mInMethod) {
    sendFatalFailureToHost("handleEvent invoked while another nanoapp "
                           "method is running");
  }
  mInMethod = true;
  if (senderInstanceId != CHRE_INSTANCE_ID) {
    sendFatalFailureToHost("handleEvent got event from unexpected sender:",
                           &senderInstanceId);
  }
  if (eventType != CHRE_EVENT_TIMER) {
    unexpectedEvent(eventType);
  }

  const uint32_t *data = static_cast<const uint32_t *>(eventData);
  handleStageEvent(*data);

  mInMethod = false;
}

void TimerStressTest::markSuccess(uint32_t stage) {
  chreLog(CHRE_LOG_DEBUG, "Stage %" PRIu32 " succeeded", stage);
  uint32_t finishedBit = (1 << stage);
  if ((kAllFinished & finishedBit) == 0) {
    sendFatalFailureToHost("markSuccess bad stage:", &stage);
  }
  if ((mFinishedBitmask & finishedBit) != 0) {
    sendFatalFailureToHost("timer over-triggered:", &stage);
  }
  mFinishedBitmask |= finishedBit;
  if (mFinishedBitmask == kAllFinished) {
    sendSuccessToHost();
  }
}


}  // namespace general_test