Implement log buffer essential methods

Also add unit tests for the log buffer implementation.

Bug: 146164384
Test: run_tests.sh --gtest_filter='LogBuffer.*'

Change-Id: I99a516940e24b3be97a60cd341529fefbed5fee4
diff --git a/platform/platform.mk b/platform/platform.mk
index 70fb58c..aaf7c21 100644
--- a/platform/platform.mk
+++ b/platform/platform.mk
@@ -436,3 +436,5 @@
 GOOGLETEST_COMMON_SRCS += platform/linux/assert.cc
 GOOGLETEST_COMMON_SRCS += platform/linux/audio_source.cc
 GOOGLETEST_COMMON_SRCS += platform/linux/platform_audio.cc
+GOOGLETEST_COMMON_SRCS += platform/test/log_buffer_test.cc
+GOOGLETEST_COMMON_SRCS += platform/shared/log_buffer.cc
diff --git a/platform/shared/include/chre/platform/shared/log_buffer.h b/platform/shared/include/chre/platform/shared/log_buffer.h
index 0bc8eb6..e30872c 100644
--- a/platform/shared/include/chre/platform/shared/log_buffer.h
+++ b/platform/shared/include/chre/platform/shared/log_buffer.h
@@ -19,7 +19,9 @@
 
 #include <cinttypes>
 #include <cstdarg>
+#include <cstring>
 
+#include "chre/platform/mutex.h"
 #include "chre/util/fixed_size_vector.h"
 #include "chre/util/singleton.h"
 
@@ -36,12 +38,12 @@
  * THRESHOLD - The LogBuffer should notify the platform when a certain thresold
  *             of memory has been allocated for logs in the buffer.
  */
-enum LogBufferNotificationSetting { ALWAYS, NEVER, THRESHOLD };
+enum class LogBufferNotificationSetting : uint8_t { ALWAYS, NEVER, THRESHOLD };
 
 /**
  * The log level options for logs stored in a log buffer.
  */
-enum LogBufferLogLevel { ERROR, WARN, INFO, DEBUG, VERBOSE };
+enum class LogBufferLogLevel : uint8_t { ERROR, WARN, INFO, DEBUG, VERBOSE };
 
 // Forward declaration for LogBufferCallbackInterface.
 class LogBuffer;
@@ -52,6 +54,7 @@
  * through this callback interface.
  */
 class LogBufferCallbackInterface {
+ public:
   /**
    * Notify the platform code that is using the buffer manager that it should
    * call copyLogs because the buffer internal state has changed to suit the
@@ -75,7 +78,8 @@
    *                 the state of the log buffer.
    * @param buffer The buffer location that will store log data.
    *                    message.
-   * @param bufferSize The number of bytes in the buffer.
+   * @param bufferSize The number of bytes in the buffer. This value must be >
+   *                   kBufferMinSize
    */
   LogBuffer(LogBufferCallbackInterface *callback, void *buffer,
             size_t bufferSize);
@@ -133,6 +137,95 @@
    */
   void updateNotificationSetting(LogBufferNotificationSetting setting,
                                  size_t thresholdBytes = 0);
+
+ private:
+  /**
+   * @return The current buffer size.
+   */
+  size_t getBufferSize() const;
+
+  /**
+   * Increment the value and take the modulus of the max size of the buffer.
+   *
+   * @param originalVal The original value to increment and mod.
+   * @param incrementBy The amount to increment by.
+   * @return The final value after incrementing and modulus.
+   */
+  size_t incrementAndModByBufferMaxSize(size_t originalVal,
+                                        size_t incrementBy) const;
+
+  /**
+   * Copy from the source memory location to the buffer data ensuring that
+   * the copy wraps around the buffer data if needed.
+   *
+   * @param size The number of bytes to copy into the buffer.
+   * @param source The memory location to copy from.
+   */
+  void copyToBuffer(size_t size, const void *source);
+
+  /**
+   * Copy from the buffer data to a destination memory location ensuring that
+   * the copy wraps around the buffer data if needed.
+   *
+   * @param size The number of bytes to copy into the buffer.
+   * @param destination The memory location to copy to.
+   */
+  void copyFromBuffer(size_t size, void *destination);
+
+  /**
+   * Get next index indicating the start of a log entry from the starting
+   * index of a previous log entry.
+   *
+   * @param startingIndex The starting index given.
+   * @param logSize Non-null pointer that will be set to the size of the current
+   *        log message.
+   * @return The next starting log index.
+   */
+  size_t getNextLogIndex(size_t startingIndex, size_t *logSize);
+
+  //! The number of bytes in a log entry of the buffer before the log size data
+  //! is encountered.
+  static constexpr size_t kLogSizeOffset = 5;
+  //! The number of bytes that the log size data takes up in a log entry.
+  static constexpr size_t kLogSizeBytes = 1;
+  //! The max size of a single log entry which must fit in a single byte.
+  static constexpr size_t kLogMaxSize = 255;
+
+  /**
+   * The buffer data is stored in the format
+   *
+   * [ logLevel (1B) , timestamp (4B), dataLength (1B), data (dataLenB) ]
+   *
+   * Since dataLength cannot be greater than uint8_t the max size of the data
+   * portion can be max 255.
+   */
+  uint8_t *mBufferData;
+
+  // TODO(b/170870354): Create a cirular buffer class to reuse this concept in
+  // other parts of CHRE
+  //! The buffer data head index
+  size_t mBufferDataHeadIndex = 0;
+  //! The buffer data tail index
+  size_t mBufferDataTailIndex = 0;
+  //! The current size of the data buffer
+  size_t mBufferDataSize = 0;
+  //! The buffer max size
+  size_t mBufferMaxSize;
+  //! The buffer min size
+  // TODO(b/170870354): Setup a more appropriate min size
+  static constexpr size_t kBufferMinSize = 1024;  // 1KB
+
+  //! The callback object
+  LogBufferCallbackInterface *mCallback;
+  //! The notification setting object
+  LogBufferNotificationSetting mNotificationSetting =
+      LogBufferNotificationSetting::NEVER;
+  //! The number of bytes that will trigger the threshold notification
+  size_t mNotificationThresholdBytes = 0;
+
+  // TODO(srok): Optimize the locking scheme
+  //! The mutex guarding the buffer data bytes array
+  Mutex mBufferDataLock;
 };
 
 }  // namespace chre
diff --git a/platform/shared/log_buffer.cc b/platform/shared/log_buffer.cc
index 246d513..16be3e5 100644
--- a/platform/shared/log_buffer.cc
+++ b/platform/shared/log_buffer.cc
@@ -15,6 +15,7 @@
  */
 
 #include "chre/platform/shared/log_buffer.h"
+#include "chre/util/lock_guard.h"
 
 #include <cstdarg>
 #include <cstdio>
@@ -22,27 +23,134 @@
 namespace chre {
 
 LogBuffer::LogBuffer(LogBufferCallbackInterface *callback, void *buffer,
-                     size_t bufferSize) {
-  // TODO(b/146164384): Implement
+                     size_t bufferSize)
+    : mBufferData(static_cast<uint8_t *>(buffer)),
+      mBufferMaxSize(bufferSize),
+      mCallback(callback) {
+  CHRE_ASSERT(bufferSize >= kBufferMinSize);
 }
 
 void LogBuffer::handleLog(LogBufferLogLevel logLevel, uint32_t timestampMs,
                           const char *log) {
-  // TODO(b/146164384): Implement
+  uint8_t logLen = static_cast<uint8_t>(
+      strnlen(log, kLogMaxSize - kLogSizeOffset - kLogSizeBytes));
+  size_t totalLogSize = kLogSizeOffset + kLogSizeBytes + logLen;
+
+  if (totalLogSize > mBufferMaxSize) {
+    return;
+  }
+
+  {
+    LockGuard<Mutex> lockGuard(mBufferDataLock);
+    // Invalidate memory allocated for log at head while the buffer is greater
+    // than max size
+    while (getBufferSize() + totalLogSize > mBufferMaxSize) {
+      size_t logSize;
+      mBufferDataHeadIndex = getNextLogIndex(mBufferDataHeadIndex, &logSize);
+      mBufferDataSize -= logSize;
+    }
+    copyToBuffer(sizeof(logLevel), &logLevel);
+    copyToBuffer(sizeof(timestampMs), &timestampMs);
+    copyToBuffer(sizeof(logLen), &logLen);
+    copyToBuffer(logLen, reinterpret_cast<const void *>(log));
+  }
+
+  switch (mNotificationSetting) {
+    case LogBufferNotificationSetting::ALWAYS: {
+      mCallback->onLogsReady(this);
+      break;
+    }
+    case LogBufferNotificationSetting::NEVER: {
+      break;
+    }
+    case LogBufferNotificationSetting::THRESHOLD: {
+      if (getBufferSize() > mNotificationThresholdBytes) {
+        mCallback->onLogsReady(this);
+      }
+      break;
+    }
+  }
 }
 
 size_t LogBuffer::copyLogs(void *destination, size_t size) {
-  // TODO(b/146164384): Implement
-  return 0;
+  LockGuard<Mutex> lock(mBufferDataLock);
+
+  size_t copySize = 0;
+
+  if (size != 0 && destination != nullptr && getBufferSize() != 0) {
+    size_t logStartIndex = mBufferDataHeadIndex;
+    size_t logDataSize = mBufferData[incrementAndModByBufferMaxSize(
+        logStartIndex, kLogSizeOffset)];
+    size_t logSize = kLogSizeOffset + kLogSizeBytes + logDataSize;
+    size_t sizeAfterAddingLog = logSize;
+    while (sizeAfterAddingLog <= size &&
+           sizeAfterAddingLog <= getBufferSize()) {
+      logStartIndex = getNextLogIndex(logStartIndex, &logSize);
+      sizeAfterAddingLog += logSize;
+    }
+    copySize = sizeAfterAddingLog - logSize;
+    copyFromBuffer(copySize, destination);
+    mBufferDataHeadIndex = logStartIndex;
+  }
+
+  return copySize;
 }
 
 void LogBuffer::transferTo(LogBuffer &buffer) {
   // TODO(b/146164384): Implement
 }
 
-void updateNotificationSetting(LogBufferNotificationSetting setting,
-                               size_t thresholdBytes) {
-  // TODO(b/146164384): Implement
+void LogBuffer::updateNotificationSetting(LogBufferNotificationSetting setting,
+                                          size_t thresholdBytes) {
+  LockGuard<Mutex> lock(mBufferDataLock);
+
+  mNotificationSetting = setting;
+  mNotificationThresholdBytes = thresholdBytes;
+}
+
+size_t LogBuffer::getBufferSize() const {
+  return mBufferDataSize;
+}
+
+size_t LogBuffer::incrementAndModByBufferMaxSize(size_t originalVal,
+                                                 size_t incrementBy) const {
+  return (originalVal + incrementBy) % mBufferMaxSize;
+}
+
+void LogBuffer::copyToBuffer(size_t size, const void *source) {
+  const uint8_t *sourceBytes = static_cast<const uint8_t *>(source);
+  if (mBufferDataTailIndex + size > mBufferMaxSize) {
+    size_t firstSize = mBufferMaxSize - mBufferDataTailIndex;
+    size_t secondSize = size - firstSize;
+    memcpy(&mBufferData[mBufferDataTailIndex], sourceBytes, firstSize);
+    memcpy(mBufferData, &sourceBytes[firstSize], secondSize);
+  } else {
+    memcpy(&mBufferData[mBufferDataTailIndex], sourceBytes, size);
+  }
+  mBufferDataSize += size;
+  mBufferDataTailIndex =
+      incrementAndModByBufferMaxSize(mBufferDataTailIndex, size);
+}
+
+void LogBuffer::copyFromBuffer(size_t size, void *destination) {
+  uint8_t *destinationBytes = static_cast<uint8_t *>(destination);
+  if (mBufferDataHeadIndex + size > mBufferMaxSize) {
+    size_t firstSize = mBufferMaxSize - mBufferDataHeadIndex;
+    size_t secondSize = size - firstSize;
+    memcpy(destinationBytes, &mBufferData[mBufferDataHeadIndex], firstSize);
+    memcpy(&destinationBytes[firstSize], mBufferData, secondSize);
+  } else {
+    memcpy(destinationBytes, &mBufferData[mBufferDataHeadIndex], size);
+  }
+  mBufferDataSize -= size;
+  mBufferDataHeadIndex =
+      incrementAndModByBufferMaxSize(mBufferDataHeadIndex, size);
+}
+
+size_t LogBuffer::getNextLogIndex(size_t startingIndex, size_t *logSize) {
+  size_t logDataSize = mBufferData[startingIndex + kLogSizeOffset];
+  *logSize = kLogSizeOffset + kLogSizeBytes + logDataSize;
+  return incrementAndModByBufferMaxSize(startingIndex, *logSize);
 }
 
 }  // namespace chre
diff --git a/platform/test/log_buffer_test.cc b/platform/test/log_buffer_test.cc
new file mode 100644
index 0000000..41b3b95
--- /dev/null
+++ b/platform/test/log_buffer_test.cc
@@ -0,0 +1,139 @@
+/*
+ * 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 <gtest/gtest.h>
+#include <string>
+
+#include "chre/platform/atomic.h"
+#include "chre/platform/condition_variable.h"
+#include "chre/platform/mutex.h"
+#include "chre/platform/shared/log_buffer.h"
+
+namespace chre {
+
+// TODO(b/146164384): Test that the onLogsReady callback is called
+//                    asynchronously
+
+class TestLogBufferCallback : public LogBufferCallbackInterface {
+ public:
+  void onLogsReady(LogBuffer *logBuffer) {
+    // Do nothing
+  }
+};
+
+static constexpr size_t kDefaultBufferSize = 1024;
+static constexpr size_t kBytesBeforeLogData = 6;
+
+TEST(LogBuffer, HandleOneLogAndCopy) {
+  char buffer[kDefaultBufferSize];
+  constexpr size_t kOutBufferSize = 20;
+  char outBuffer[kOutBufferSize];
+  const char *testLogStr = "test";
+  char testedBuffer[kOutBufferSize];
+  TestLogBufferCallback callback;
+
+  LogBuffer logBuffer(&callback, buffer, kDefaultBufferSize);
+  logBuffer.handleLog(LogBufferLogLevel::INFO, 0, testLogStr);
+  size_t bytesCopied = logBuffer.copyLogs(outBuffer, kOutBufferSize);
+
+  EXPECT_EQ(bytesCopied,
+            strlen(testLogStr) +
+                kBytesBeforeLogData /*loglevel, timestamp, and loglen*/);
+  memcpy(testedBuffer, outBuffer + kBytesBeforeLogData, strlen(testLogStr));
+  testedBuffer[strlen(testLogStr)] = '\0';
+  EXPECT_TRUE(strcmp(testedBuffer, testLogStr) == 0);
+}
+
+TEST(LogBuffer, FailOnMoreCopyThanHandle) {
+  char buffer[kDefaultBufferSize];
+  constexpr size_t kOutBufferSize = 20;
+  char outBuffer[kOutBufferSize];
+  constexpr size_t kEmptyBufferSize = 10;
+  char emptyBuffer[kEmptyBufferSize];
+  emptyBuffer[0] = '\0';
+  const char *testLogStr = "test";
+  TestLogBufferCallback callback;
+  char testedBuffer[kOutBufferSize];
+
+  LogBuffer logBuffer(&callback, buffer, kDefaultBufferSize);
+  logBuffer.handleLog(LogBufferLogLevel::INFO, 0, testLogStr);
+  logBuffer.copyLogs(outBuffer, kOutBufferSize);
+  memcpy(testedBuffer, outBuffer + kBytesBeforeLogData, strlen(testLogStr));
+  testedBuffer[strlen(testLogStr)] = '\0';
+  size_t bytesCopied = logBuffer.copyLogs(outBuffer, kOutBufferSize);
+
+  EXPECT_EQ(bytesCopied, 0);
+  EXPECT_EQ(strlen(emptyBuffer), 0);
+}
+
+TEST(LogBuffer, FailOnHandleLargerLogThanBufferSize) {
+  char buffer[kDefaultBufferSize];
+  constexpr size_t kOutBufferSize = 20;
+  char outBuffer[kOutBufferSize];
+  // Note the size of this log is too big to fit in the buffer that we are
+  // using for the LogBuffer object
+  std::string testLogStrStr(1025, 'a');
+  TestLogBufferCallback callback;
+
+  LogBuffer logBuffer(&callback, buffer, kDefaultBufferSize);
+  logBuffer.handleLog(LogBufferLogLevel::INFO, 0, testLogStrStr.c_str());
+  size_t bytesCopied = logBuffer.copyLogs(outBuffer, kOutBufferSize);
+
+  // Should not be able to read this log out because there should be no log in
+  // the first place
+  EXPECT_EQ(bytesCopied, 0);
+}
+
+TEST(LogBuffer, LogOverwritten) {
+  char buffer[kDefaultBufferSize];
+  constexpr size_t kOutBufferSize = 200;
+  char outBuffer[kOutBufferSize];
+  char testedBuffer[kOutBufferSize];
+  TestLogBufferCallback callback;
+  LogBuffer logBuffer(&callback, buffer, kDefaultBufferSize);
+  // This for loop adds 1060 bytes of data through the buffer which is > than
+  // 1024
+  for (size_t i = 0; i < 10; i++) {
+    std::string testLogStrStr(100, 'a' + i);
+    const char *testLogStr = testLogStrStr.c_str();
+    logBuffer.handleLog(LogBufferLogLevel::INFO, 0, testLogStr);
+  }
+  size_t bytesCopied = logBuffer.copyLogs(outBuffer, kBytesBeforeLogData + 100);
+  memcpy(testedBuffer, outBuffer + kBytesBeforeLogData, 100);
+  testedBuffer[100] = '\0';
+
+  // Should have read out the second from front test log string which is 'a' + 1
+  // = 'b'
+  EXPECT_TRUE(strcmp(testedBuffer, std::string(100, 'b').c_str()) == 0);
+  EXPECT_EQ(bytesCopied, kBytesBeforeLogData + 100);
+}
+
+TEST(LogBuffer, CopyIntoEmptyBuffer) {
+  char buffer[kDefaultBufferSize];
+  constexpr size_t kOutBufferSize = 0;
+  char outBuffer[kOutBufferSize];
+  TestLogBufferCallback callback;
+  LogBuffer logBuffer(&callback, buffer, kDefaultBufferSize);
+
+  logBuffer.handleLog(LogBufferLogLevel::INFO, 0, "test");
+  size_t bytesCopied = logBuffer.copyLogs(outBuffer, kOutBufferSize);
+
+  EXPECT_EQ(bytesCopied, 0);
+}
+
+// TODO(srok): Add multithreaded tests
+
+}  // namespace chre