liblog: event log list logging handler

Based off an initial request and effort by williamluh@google.com

- Added the following functions:

* Composing and Writing:

android_log_context create_android_logger(uint32_t tag)

int android_log_write_list_begin(android_log_context ctx)
int android_log_write_list_end(android_log_context ctx)

int android_log_write_int32(android_log_context ctx, int32_t value)
int android_log_write_int64(android_log_context ctx, int64_t value)
int android_log_write_string8(android_log_context ctx, const char *value)
int android_log_write_float32(android_log_context ctx, float value)

int android_log_write_list(android_log_context ctx, log_id_t id)

* Reading and Interpreting:

android_log_context create_android_log_parser(const char *msg, size_t len)

android_log_list_element android_log_read_next(android_log_context ctx)
android_log_list_element android_log_peek_next(android_log_context ctx)

* Destroy context used above:

int android_log_destroy(android_log_context *ctx);

- Added unit gTests

We moved implemented android_log_buffer_to_string() to the test since
it is an alternate for already existing logprint functionality.
Please move into liblog should it be of some common use, otherwise
as is it is a good means of stessing the reading and interpreting
handlers.

Signed-off-by: Mark Salyzyn <salyzyn@google.com>
Bug: 19235719
Change-Id: I4aa1927e8e6a75f0a129d15a27c891cf1ccd4f5c
diff --git a/include/log/log.h b/include/log/log.h
index 1bd9165..6ad6f0a 100644
--- a/include/log/log.h
+++ b/include/log/log.h
@@ -484,15 +484,19 @@
  */
 
 /*
- * Event log entry types.  These must match up with the declarations in
- * java/android/android/util/EventLog.java.
+ * Event log entry types.
  */
 typedef enum {
-    EVENT_TYPE_INT      = 0,
-    EVENT_TYPE_LONG     = 1,
-    EVENT_TYPE_STRING   = 2,
-    EVENT_TYPE_LIST     = 3,
-    EVENT_TYPE_FLOAT    = 4,
+    /* Special markers for android_log_list_element type */
+    EVENT_TYPE_LIST_STOP = '\n', /* declare end of list  */
+    EVENT_TYPE_UNKNOWN   = '?',  /* protocol error       */
+
+    /* must match with declaration in java/android/android/util/EventLog.java */
+    EVENT_TYPE_INT       = 0,    /* uint32_t */
+    EVENT_TYPE_LONG      = 1,    /* uint64_t */
+    EVENT_TYPE_STRING    = 2,
+    EVENT_TYPE_LIST      = 3,
+    EVENT_TYPE_FLOAT     = 4,
 } AndroidEventLogType;
 #define sizeof_AndroidEventLogType sizeof(typeof_AndroidEventLogType)
 #define typeof_AndroidEventLogType unsigned char
@@ -522,7 +526,85 @@
 #define LOG_EVENT_STRING(_tag, _value)                                      \
         (void) __android_log_bswrite(_tag, _value);
 #endif
-/* TODO: something for LIST */
+
+typedef enum log_id {
+    LOG_ID_MIN = 0,
+
+#ifndef LINT_RLOG
+    LOG_ID_MAIN = 0,
+#endif
+    LOG_ID_RADIO = 1,
+#ifndef LINT_RLOG
+    LOG_ID_EVENTS = 2,
+    LOG_ID_SYSTEM = 3,
+    LOG_ID_CRASH = 4,
+    LOG_ID_SECURITY = 5,
+    LOG_ID_KERNEL = 6, /* place last, third-parties can not use it */
+#endif
+
+    LOG_ID_MAX
+} log_id_t;
+#define sizeof_log_id_t sizeof(typeof_log_id_t)
+#define typeof_log_id_t unsigned char
+
+/* For manipulating lists of events. */
+
+#define ANDROID_MAX_LIST_NEST_DEPTH 8
+
+/*
+ * The opaque context used to manipulate lists of events.
+ */
+typedef struct android_log_context_internal *android_log_context;
+
+/*
+ * Elements returned when reading a list of events.
+ */
+typedef struct {
+    AndroidEventLogType type;
+    uint16_t complete;
+    uint16_t len;
+    union {
+        int32_t int32;
+        int64_t int64;
+        char *string;
+        float float32;
+    } data;
+} android_log_list_element;
+
+/*
+ * Creates a context associated with an event tag to write elements to
+ * the list of events.
+ */
+android_log_context create_android_logger(uint32_t tag);
+
+/* All lists must be braced by a begin and end call */
+/*
+ * NB: If the first level braces are missing when specifying multiple
+ *     elements, we will manufacturer a list to embrace it for your API
+ *     convenience. For a single element, it will remain solitary.
+ */
+int android_log_write_list_begin(android_log_context ctx);
+int android_log_write_list_end(android_log_context ctx);
+
+int android_log_write_int32(android_log_context ctx, int32_t value);
+int android_log_write_int64(android_log_context ctx, int64_t value);
+int android_log_write_string8(android_log_context ctx, const char *value);
+int android_log_write_float32(android_log_context ctx, float value);
+
+/* Submit the composed list context to the specified logger id */
+/* NB: LOG_ID_EVENTS and LOG_ID_SECURITY only valid binary buffers */
+int android_log_write_list(android_log_context ctx, log_id_t id);
+
+/*
+ * Creates a context from a raw buffer representing a list of events to be read.
+ */
+android_log_context create_android_log_parser(const char *msg, size_t len);
+
+android_log_list_element android_log_read_next(android_log_context ctx);
+android_log_list_element android_log_peek_next(android_log_context ctx);
+
+/* Finished with reader or writer context */
+int android_log_destroy(android_log_context *ctx);
 
 /*
  * ===========================================================================
@@ -585,26 +667,6 @@
     (__android_log_is_loggable(prio, tag, ANDROID_LOG_VERBOSE) != 0)
 #endif
 
-typedef enum log_id {
-    LOG_ID_MIN = 0,
-
-#ifndef LINT_RLOG
-    LOG_ID_MAIN = 0,
-#endif
-    LOG_ID_RADIO = 1,
-#ifndef LINT_RLOG
-    LOG_ID_EVENTS = 2,
-    LOG_ID_SYSTEM = 3,
-    LOG_ID_CRASH = 4,
-    LOG_ID_SECURITY = 5,
-    LOG_ID_KERNEL = 6, /* place last, third-parties can not use it */
-#endif
-
-    LOG_ID_MAX
-} log_id_t;
-#define sizeof_log_id_t sizeof(typeof_log_id_t)
-#define typeof_log_id_t unsigned char
-
 /*
  * Use the per-tag properties "log.tag.<tagname>" to generate a runtime
  * result of non-zero to expose a log. prio is ANDROID_LOG_VERBOSE to
diff --git a/liblog/Android.mk b/liblog/Android.mk
index a183db8..c7b76d8 100644
--- a/liblog/Android.mk
+++ b/liblog/Android.mk
@@ -28,6 +28,7 @@
 liblog_target_sources := logd_write.c log_event_write.c event_tag_map.c log_time.cpp log_is_loggable.c
 liblog_target_sources += logprint.c
 liblog_target_sources += log_read.c
+liblog_target_sources += log_event_list.c
 
 # Shared and static library for host
 # ========================================================
diff --git a/liblog/log_event_list.c b/liblog/log_event_list.c
new file mode 100644
index 0000000..50a27c0
--- /dev/null
+++ b/liblog/log_event_list.c
@@ -0,0 +1,520 @@
+/*
+ * 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 <errno.h>
+#include <inttypes.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <log/log.h>
+#include <log/logger.h>
+
+#define MAX_EVENT_PAYLOAD (LOGGER_ENTRY_MAX_PAYLOAD - sizeof(int32_t))
+
+typedef struct {
+    uint32_t tag;
+    unsigned pos; /* Read/write position into buffer */
+    unsigned count[ANDROID_MAX_LIST_NEST_DEPTH + 1]; /* Number of elements   */
+    unsigned list[ANDROID_MAX_LIST_NEST_DEPTH + 1];  /* pos for list counter */
+    unsigned list_nest_depth;
+    unsigned len; /* Length or raw buffer. */
+    bool overflow;
+    bool list_stop; /* next call decrement list_nest_depth and issue a stop */
+    enum {
+        kAndroidLoggerRead = 1,
+        kAndroidLoggerWrite = 2,
+    } read_write_flag;
+    uint8_t storage[LOGGER_ENTRY_MAX_PAYLOAD];
+} android_log_context_internal;
+
+android_log_context create_android_logger(uint32_t tag) {
+    size_t needed, i;
+    android_log_context_internal *context;
+
+    context = calloc(1, sizeof(android_log_context_internal));
+    if (!context) {
+        return NULL;
+    }
+    context->tag = tag;
+    context->read_write_flag = kAndroidLoggerWrite;
+    needed = sizeof(uint8_t) + sizeof(uint8_t);
+    if ((context->pos + needed) > MAX_EVENT_PAYLOAD) {
+        context->overflow = true;
+    }
+    /* Everything is a list */
+    context->storage[context->pos + 0] = EVENT_TYPE_LIST;
+    context->list[0] = context->pos + 1;
+    context->pos += needed;
+
+    return (android_log_context)context;
+}
+
+android_log_context create_android_log_parser(const char *msg, size_t len) {
+    android_log_context_internal *context;
+    size_t i;
+
+    context = calloc(1, sizeof(android_log_context_internal));
+    if (!context) {
+        return NULL;
+    }
+    len = (len <= MAX_EVENT_PAYLOAD) ? len : MAX_EVENT_PAYLOAD;
+    context->len = len;
+    memcpy(context->storage, msg, len);
+    context->read_write_flag = kAndroidLoggerRead;
+
+    return (android_log_context)context;
+}
+
+int android_log_destroy(android_log_context *ctx) {
+    android_log_context_internal *context;
+
+    context = (android_log_context_internal *)*ctx;
+    if (!context) {
+        return -EBADF;
+    }
+    memset(context, 0, sizeof(*context));
+    free(context);
+    *ctx = NULL;
+    return 0;
+}
+
+int android_log_write_list_begin(android_log_context ctx) {
+    size_t needed;
+    android_log_context_internal *context;
+
+    context = (android_log_context_internal *)ctx;
+    if (!context ||
+            (kAndroidLoggerWrite != context->read_write_flag)) {
+        return -EBADF;
+    }
+    if (context->list_nest_depth > ANDROID_MAX_LIST_NEST_DEPTH) {
+        context->overflow = true;
+        return -EOVERFLOW;
+    }
+    needed = sizeof(uint8_t) + sizeof(uint8_t);
+    if ((context->pos + needed) > MAX_EVENT_PAYLOAD) {
+        context->overflow = true;
+        return -EIO;
+    }
+    context->count[context->list_nest_depth]++;
+    context->list_nest_depth++;
+    if (context->list_nest_depth > ANDROID_MAX_LIST_NEST_DEPTH) {
+        context->overflow = true;
+        return -EOVERFLOW;
+    }
+    if (context->overflow) {
+        return -EIO;
+    }
+    context->storage[context->pos + 0] = EVENT_TYPE_LIST;
+    context->storage[context->pos + 1] = 0;
+    context->list[context->list_nest_depth] = context->pos + 1;
+    context->count[context->list_nest_depth] = 0;
+    context->pos += needed;
+    return 0;
+}
+
+static inline void copy4LE(uint8_t *buf, uint32_t val)
+{
+    buf[0] = val & 0xFF;
+    buf[1] = (val >> 8) & 0xFF;
+    buf[2] = (val >> 16) & 0xFF;
+    buf[3] = (val >> 24) & 0xFF;
+}
+
+int android_log_write_int32(android_log_context ctx, int32_t value) {
+    size_t needed;
+    android_log_context_internal *context;
+
+    context = (android_log_context_internal *)ctx;
+    if (!context || (kAndroidLoggerWrite != context->read_write_flag)) {
+        return -EBADF;
+    }
+    if (context->overflow) {
+        return -EIO;
+    }
+    needed = sizeof(uint8_t) + sizeof(value);
+    if ((context->pos + needed) > MAX_EVENT_PAYLOAD) {
+        context->overflow = true;
+        return -EIO;
+    }
+    context->count[context->list_nest_depth]++;
+    context->storage[context->pos + 0] = EVENT_TYPE_INT;
+    copy4LE(&context->storage[context->pos + 1], value);
+    context->pos += needed;
+    return 0;
+}
+
+static inline void copy8LE(uint8_t *buf, uint64_t val)
+{
+    buf[0] = val & 0xFF;
+    buf[1] = (val >> 8) & 0xFF;
+    buf[2] = (val >> 16) & 0xFF;
+    buf[3] = (val >> 24) & 0xFF;
+    buf[4] = (val >> 32) & 0xFF;
+    buf[5] = (val >> 40) & 0xFF;
+    buf[6] = (val >> 48) & 0xFF;
+    buf[7] = (val >> 56) & 0xFF;
+}
+
+int android_log_write_int64(android_log_context ctx, int64_t value) {
+    size_t needed;
+    android_log_context_internal *context;
+
+    context = (android_log_context_internal *)ctx;
+    if (!context || (kAndroidLoggerWrite != context->read_write_flag)) {
+        return -EBADF;
+    }
+    if (context->overflow) {
+        return -EIO;
+    }
+    needed = sizeof(uint8_t) + sizeof(value);
+    if ((context->pos + needed) > MAX_EVENT_PAYLOAD) {
+        context->overflow = true;
+        return -EIO;
+    }
+    context->count[context->list_nest_depth]++;
+    context->storage[context->pos + 0] = EVENT_TYPE_LONG;
+    copy8LE(&context->storage[context->pos + 1], value);
+    context->pos += needed;
+    return 0;
+}
+
+int android_log_write_string8(android_log_context ctx, const char *value) {
+    size_t needed;
+    int32_t len;
+    android_log_context_internal *context;
+
+    context = (android_log_context_internal *)ctx;
+    if (!context || (kAndroidLoggerWrite != context->read_write_flag)) {
+        return -EBADF;
+    }
+    if (context->overflow) {
+        return -EIO;
+    }
+    if (!value) {
+        return -EINVAL;
+    }
+    len = strlen(value);
+    needed = sizeof(uint8_t) + sizeof(len) + len;
+    if ((context->pos + needed) > MAX_EVENT_PAYLOAD) {
+        /* Truncate string for delivery */
+        len = MAX_EVENT_PAYLOAD - context->pos - 1 - sizeof(len);
+        if (len <= 0) {
+            context->overflow = true;
+            return -EIO;
+        }
+    }
+    context->count[context->list_nest_depth]++;
+    context->storage[context->pos + 0] = EVENT_TYPE_STRING;
+    copy4LE(&context->storage[context->pos + 1], len);
+    memcpy(&context->storage[context->pos + 5], value, len);
+    context->pos += needed;
+    return 0;
+}
+
+int android_log_write_float32(android_log_context ctx, float value) {
+    size_t needed;
+    uint32_t ivalue;
+    android_log_context_internal *context;
+
+    context = (android_log_context_internal *)ctx;
+    if (!context || (kAndroidLoggerWrite != context->read_write_flag)) {
+        return -EBADF;
+    }
+    if (context->overflow) {
+        return -EIO;
+    }
+    needed = sizeof(uint8_t) + sizeof(ivalue);
+    if ((context->pos + needed) > MAX_EVENT_PAYLOAD) {
+        context->overflow = true;
+        return -EIO;
+    }
+    ivalue = *(uint32_t *)&value;
+    context->count[context->list_nest_depth]++;
+    context->storage[context->pos + 0] = EVENT_TYPE_FLOAT;
+    copy4LE(&context->storage[context->pos + 1], ivalue);
+    context->pos += needed;
+    return 0;
+}
+
+int android_log_write_list_end(android_log_context ctx) {
+    android_log_context_internal *context;
+
+    context = (android_log_context_internal *)ctx;
+    if (!context || (kAndroidLoggerWrite != context->read_write_flag)) {
+        return -EBADF;
+    }
+    if (context->list_nest_depth > ANDROID_MAX_LIST_NEST_DEPTH) {
+        context->overflow = true;
+        context->list_nest_depth--;
+        return -EOVERFLOW;
+    }
+    if (!context->list_nest_depth) {
+        context->overflow = true;
+        return -EOVERFLOW;
+    }
+    if (context->list[context->list_nest_depth] <= 0) {
+        context->list_nest_depth--;
+        context->overflow = true;
+        return -EOVERFLOW;
+    }
+    context->storage[context->list[context->list_nest_depth]] =
+        context->count[context->list_nest_depth];
+    context->list_nest_depth--;
+    return 0;
+}
+
+/*
+ * Logs the list of elements to the event log.
+ */
+int android_log_write_list(android_log_context ctx, log_id_t id) {
+    android_log_context_internal *context;
+    const char *msg;
+    ssize_t len;
+
+    if ((id != LOG_ID_EVENTS) && (id != LOG_ID_SECURITY)) {
+        return -EINVAL;
+    }
+
+    context = (android_log_context_internal *)ctx;
+    if (!context || (kAndroidLoggerWrite != context->read_write_flag)) {
+        return -EBADF;
+    }
+    if (context->list_nest_depth) {
+        return -EIO;
+    }
+    /* NB: if there was overflow, then log is truncated. Nothing reported */
+    context->storage[1] = context->count[0];
+    len = context->len = context->pos;
+    msg = (const char *)context->storage;
+    /* it'snot a list */
+    if (context->count[0] <= 1) {
+        len -= sizeof(uint8_t) + sizeof(uint8_t);
+        if (len < 0) {
+            len = 0;
+        }
+        msg += sizeof(uint8_t) + sizeof(uint8_t);
+    }
+    return (id == LOG_ID_EVENTS) ?
+        __android_log_bwrite(context->tag, msg, len) :
+        __android_log_security_bwrite(context->tag, msg, len);
+}
+
+/*
+ * Extract a 4-byte value from a byte stream.
+ */
+static inline uint32_t get4LE(const uint8_t* src)
+{
+    return src[0] | (src[1] << 8) | (src[2] << 16) | (src[3] << 24);
+}
+
+/*
+ * Extract an 8-byte value from a byte stream.
+ */
+static inline uint64_t get8LE(const uint8_t* src)
+{
+    uint32_t low = src[0] | (src[1] << 8) | (src[2] << 16) | (src[3] << 24);
+    uint32_t high = src[4] | (src[5] << 8) | (src[6] << 16) | (src[7] << 24);
+    return ((uint64_t) high << 32) | (uint64_t) low;
+}
+
+/*
+ * Gets the next element. Parsing errors result in an EVENT_TYPE_UNKNOWN type.
+ * If there is nothing to process, the complete field is set to non-zero. If
+ * an EVENT_TYPE_UNKNOWN type is returned once, and the caller does not check
+ * this and continues to call this function, the behavior is undefined
+ * (although it won't crash).
+ */
+static android_log_list_element android_log_read_next_internal(
+        android_log_context ctx, int peek) {
+    android_log_list_element elem;
+    unsigned pos;
+    android_log_context_internal *context;
+
+    context = (android_log_context_internal *)ctx;
+
+    memset(&elem, 0, sizeof(elem));
+
+    /* Nothing to parse from this context, so return complete. */
+    if (!context || (kAndroidLoggerRead != context->read_write_flag) ||
+            (context->list_nest_depth > ANDROID_MAX_LIST_NEST_DEPTH) ||
+            (context->count[context->list_nest_depth] >=
+                (MAX_EVENT_PAYLOAD / (sizeof(uint8_t) + sizeof(uint8_t))))) {
+        elem.type = EVENT_TYPE_UNKNOWN;
+        if (context &&
+                (context->list_stop ||
+                ((context->list_nest_depth <= ANDROID_MAX_LIST_NEST_DEPTH) &&
+                    !context->count[context->list_nest_depth]))) {
+            elem.type = EVENT_TYPE_LIST_STOP;
+        }
+        elem.complete = true;
+        return elem;
+    }
+
+    /*
+     * Use a different variable to update the position in case this
+     * operation is a "peek".
+     */
+    pos = context->pos;
+    if (context->list_stop) {
+        elem.type = EVENT_TYPE_LIST_STOP;
+        elem.complete = !context->count[0] && (!context->list_nest_depth ||
+            ((context->list_nest_depth == 1) && !context->count[1]));
+        if (!peek) {
+            /* Suck in superfluous stop */
+            if (context->storage[pos] == EVENT_TYPE_LIST_STOP) {
+                context->pos = pos + 1;
+            }
+            if (context->list_nest_depth) {
+                --context->list_nest_depth;
+                if (context->count[context->list_nest_depth]) {
+                    context->list_stop = false;
+                }
+            } else {
+                context->list_stop = false;
+            }
+        }
+        return elem;
+    }
+    if ((pos + 1) > context->len) {
+        elem.type = EVENT_TYPE_UNKNOWN;
+        elem.complete = true;
+        return elem;
+    }
+
+    elem.type = context->storage[pos++];
+    switch ((int)elem.type) {
+    case EVENT_TYPE_FLOAT:
+        /* Rely on union to translate elem.data.int32 into elem.data.float32 */
+        /* FALLTHRU */
+    case EVENT_TYPE_INT:
+        elem.len = sizeof(int32_t);
+        if ((pos + elem.len) > context->len) {
+            elem.type = EVENT_TYPE_UNKNOWN;
+            return elem;
+        }
+        elem.data.int32 = get4LE(&context->storage[pos]);
+        /* common tangeable object suffix */
+        pos += elem.len;
+        elem.complete = !context->list_nest_depth && !context->count[0];
+        if (!peek) {
+            if (!context->count[context->list_nest_depth] ||
+                    !--(context->count[context->list_nest_depth])) {
+                context->list_stop = true;
+            }
+            context->pos = pos;
+        }
+        return elem;
+
+    case EVENT_TYPE_LONG:
+        elem.len = sizeof(int64_t);
+        if ((pos + elem.len) > context->len) {
+            elem.type = EVENT_TYPE_UNKNOWN;
+            return elem;
+        }
+        elem.data.int64 = get8LE(&context->storage[pos]);
+        /* common tangeable object suffix */
+        pos += elem.len;
+        elem.complete = !context->list_nest_depth && !context->count[0];
+        if (!peek) {
+            if (!context->count[context->list_nest_depth] ||
+                    !--(context->count[context->list_nest_depth])) {
+                context->list_stop = true;
+            }
+            context->pos = pos;
+        }
+        return elem;
+
+    case EVENT_TYPE_STRING:
+        if ((pos + sizeof(int32_t)) > context->len) {
+            elem.type = EVENT_TYPE_UNKNOWN;
+            elem.complete = true;
+            return elem;
+        }
+        elem.len = get4LE(&context->storage[pos]);
+        pos += sizeof(int32_t);
+        if ((pos + elem.len) > context->len) {
+            elem.len = context->len - pos; /* truncate string */
+            elem.complete = true;
+            if (!elem.len) {
+                elem.type = EVENT_TYPE_UNKNOWN;
+                return elem;
+            }
+        }
+        elem.data.string = (char *)&context->storage[pos];
+        /* common tangeable object suffix */
+        pos += elem.len;
+        elem.complete = !context->list_nest_depth && !context->count[0];
+        if (!peek) {
+            if (!context->count[context->list_nest_depth] ||
+                    !--(context->count[context->list_nest_depth])) {
+                context->list_stop = true;
+            }
+            context->pos = pos;
+        }
+        return elem;
+
+    case EVENT_TYPE_LIST:
+        if ((pos + sizeof(uint8_t)) > context->len) {
+            elem.type = EVENT_TYPE_UNKNOWN;
+            elem.complete = true;
+            return elem;
+        }
+        elem.complete = context->list_nest_depth >= ANDROID_MAX_LIST_NEST_DEPTH;
+        if (peek) {
+            return elem;
+        }
+        if (context->count[context->list_nest_depth]) {
+            context->count[context->list_nest_depth]--;
+        }
+        context->list_stop = !context->storage[pos];
+        context->list_nest_depth++;
+        if (context->list_nest_depth <= ANDROID_MAX_LIST_NEST_DEPTH) {
+            context->count[context->list_nest_depth] = context->storage[pos];
+        }
+        context->pos = pos + sizeof(uint8_t);
+        return elem;
+
+    case EVENT_TYPE_LIST_STOP: /* Suprise Newline terminates lists. */
+        if (!peek) {
+            context->pos = pos;
+        }
+        elem.type = EVENT_TYPE_UNKNOWN;
+        elem.complete = !context->list_nest_depth;
+        if (context->list_nest_depth > 0) {
+            elem.type = EVENT_TYPE_LIST_STOP;
+            if (!peek) {
+                context->list_nest_depth--;
+            }
+        }
+        return elem;
+
+    default:
+        elem.type = EVENT_TYPE_UNKNOWN;
+        return elem;
+    }
+}
+
+android_log_list_element android_log_read_next(android_log_context ctx) {
+    return android_log_read_next_internal(ctx, 0);
+}
+
+android_log_list_element android_log_peek_next(android_log_context ctx) {
+    return android_log_read_next_internal(ctx, 1);
+}
diff --git a/liblog/tests/liblog_test.cpp b/liblog/tests/liblog_test.cpp
index f8d4c4c..1317853 100644
--- a/liblog/tests/liblog_test.cpp
+++ b/liblog/tests/liblog_test.cpp
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2013-2014 The Android Open Source Project
+ * Copyright (C) 2013-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.
@@ -1741,3 +1741,529 @@
 
     android_logger_list_close(logger_list);
 }
+
+static int is_real_element(int type) {
+    return ((type == EVENT_TYPE_INT) ||
+            (type == EVENT_TYPE_LONG) ||
+            (type == EVENT_TYPE_STRING) ||
+            (type == EVENT_TYPE_FLOAT));
+}
+
+int android_log_buffer_to_string(const char *msg, size_t len,
+                                 char *strOut, size_t strOutLen) {
+    android_log_context context = create_android_log_parser(msg, len);
+    android_log_list_element elem;
+    bool overflow = false;
+    /* Reserve 1 byte for null terminator. */
+    size_t origStrOutLen = strOutLen--;
+
+    if (!context) {
+        return -EBADF;
+    }
+
+    memset(&elem, 0, sizeof(elem));
+
+    size_t outCount;
+
+    do {
+        elem = android_log_read_next(context);
+        switch ((int)elem.type) {
+        case EVENT_TYPE_LIST:
+            if (strOutLen == 0) {
+                overflow = true;
+            } else {
+                *strOut++ = '[';
+                strOutLen--;
+            }
+            break;
+
+        case EVENT_TYPE_LIST_STOP:
+            if (strOutLen == 0) {
+                overflow = true;
+            } else {
+                *strOut++ = ']';
+                strOutLen--;
+            }
+            break;
+
+        case EVENT_TYPE_INT:
+            /*
+             * snprintf also requires room for the null terminator, which
+             * we don't care about  but we have allocated enough room for
+             * that
+             */
+            outCount = snprintf(strOut, strOutLen + 1,
+                                "%" PRId32, elem.data.int32);
+            if (outCount <= strOutLen) {
+                strOut += outCount;
+                strOutLen -= outCount;
+            } else {
+                overflow = true;
+            }
+            break;
+
+        case EVENT_TYPE_LONG:
+            /*
+             * snprintf also requires room for the null terminator, which
+             * we don't care about but we have allocated enough room for
+             * that
+             */
+            outCount = snprintf(strOut, strOutLen + 1,
+                                "%" PRId64, elem.data.int64);
+            if (outCount <= strOutLen) {
+                strOut += outCount;
+                strOutLen -= outCount;
+            } else {
+                overflow = true;
+            }
+            break;
+
+        case EVENT_TYPE_FLOAT:
+            /*
+             * snprintf also requires room for the null terminator, which
+             * we don't care about but we have allocated enough room for
+             * that
+             */
+            outCount = snprintf(strOut, strOutLen + 1, "%f", elem.data.float32);
+            if (outCount <= strOutLen) {
+                strOut += outCount;
+                strOutLen -= outCount;
+            } else {
+                overflow = true;
+            }
+            break;
+
+        default:
+            elem.complete = true;
+            break;
+
+        case EVENT_TYPE_UNKNOWN:
+#if 0 // Ideal purity in the test, we want to complain about UNKNOWN showing up
+            if (elem.complete) {
+                break;
+            }
+#endif
+            elem.data.string = const_cast<char *>("<unknown>");
+            elem.len = strlen(elem.data.string);
+            /* FALLTHRU */
+        case EVENT_TYPE_STRING:
+            if (elem.len <= strOutLen) {
+                memcpy(strOut, elem.data.string, elem.len);
+                strOut += elem.len;
+                strOutLen -= elem.len;
+            } else if (strOutLen > 0) {
+                /* copy what we can */
+                memcpy(strOut, elem.data.string, strOutLen);
+                strOut += strOutLen;
+                strOutLen = 0;
+                overflow = true;
+            }
+            break;
+        }
+
+        if (elem.complete) {
+            break;
+        }
+        /* Determine whether to put a comma or not. */
+        if (!overflow && (is_real_element(elem.type) ||
+                (elem.type == EVENT_TYPE_LIST_STOP))) {
+            android_log_list_element next = android_log_peek_next(context);
+            if (!next.complete && (is_real_element(next.type) ||
+                    (next.type == EVENT_TYPE_LIST))) {
+                if (strOutLen == 0) {
+                    overflow = true;
+                } else {
+                    *strOut++ = ',';
+                    strOutLen--;
+                }
+            }
+        }
+    } while ((elem.type != EVENT_TYPE_UNKNOWN) && !overflow && !elem.complete);
+
+    android_log_destroy(&context);
+
+    if (overflow) {
+        if (strOutLen < origStrOutLen) {
+            /* leave an indicator */
+            *(strOut-1) = '!';
+        } else {
+            /* nothing was written at all */
+            *strOut++ = '!';
+        }
+    }
+    *strOut++ = '\0';
+
+    if ((elem.type == EVENT_TYPE_UNKNOWN) && !elem.complete) {
+        fprintf(stderr, "Binary log entry conversion failed\n");
+        return -EINVAL;
+    }
+
+    return 0;
+}
+
+static const char *event_test_int32(uint32_t tag, size_t &expected_len) {
+    android_log_context ctx;
+
+    EXPECT_TRUE(NULL != (ctx = create_android_logger(tag)));
+    if (!ctx) {
+        return NULL;
+    }
+    EXPECT_LE(0, android_log_write_int32(ctx, 0x40302010));
+    EXPECT_LE(0, android_log_write_list(ctx, LOG_ID_EVENTS));
+    EXPECT_LE(0, android_log_destroy(&ctx));
+    EXPECT_TRUE(NULL == ctx);
+
+    expected_len = sizeof(uint32_t) +
+                   sizeof(uint8_t) + sizeof(uint32_t);
+
+    return "1076895760";
+}
+
+static const char *event_test_int64(uint32_t tag, size_t &expected_len) {
+    android_log_context ctx;
+
+    EXPECT_TRUE(NULL != (ctx = create_android_logger(tag)));
+    if (!ctx) {
+        return NULL;
+    }
+    EXPECT_LE(0, android_log_write_int64(ctx, 0x8070605040302010));
+    EXPECT_LE(0, android_log_write_list(ctx, LOG_ID_EVENTS));
+    EXPECT_LE(0, android_log_destroy(&ctx));
+    EXPECT_TRUE(NULL == ctx);
+
+    expected_len = sizeof(uint32_t) +
+                   sizeof(uint8_t) + sizeof(uint64_t);
+
+    return "-9191740941672636400";
+}
+
+static const char *event_test_list_int64(uint32_t tag, size_t &expected_len) {
+    android_log_context ctx;
+
+    EXPECT_TRUE(NULL != (ctx = create_android_logger(tag)));
+    if (!ctx) {
+        return NULL;
+    }
+    EXPECT_LE(0, android_log_write_list_begin(ctx));
+    EXPECT_LE(0, android_log_write_int64(ctx, 0x8070605040302010));
+    EXPECT_LE(0, android_log_write_list_end(ctx));
+    EXPECT_LE(0, android_log_write_list(ctx, LOG_ID_EVENTS));
+    EXPECT_LE(0, android_log_destroy(&ctx));
+    EXPECT_TRUE(NULL == ctx);
+
+    expected_len = sizeof(uint32_t) +
+                   sizeof(uint8_t) + sizeof(uint8_t) +
+                       sizeof(uint8_t) + sizeof(uint64_t);
+
+    return "[-9191740941672636400]";
+}
+
+static const char *event_test_simple_automagic_list(uint32_t tag, size_t &expected_len) {
+    android_log_context ctx;
+
+    EXPECT_TRUE(NULL != (ctx = create_android_logger(tag)));
+    if (!ctx) {
+        return NULL;
+    }
+    // The convenience API where we allow a simple list to be
+    // created without explicit begin or end calls.
+    EXPECT_LE(0, android_log_write_int32(ctx, 0x40302010));
+    EXPECT_LE(0, android_log_write_int64(ctx, 0x8070605040302010));
+    EXPECT_LE(0, android_log_write_list(ctx, LOG_ID_EVENTS));
+    EXPECT_LE(0, android_log_destroy(&ctx));
+    EXPECT_TRUE(NULL == ctx);
+
+    expected_len = sizeof(uint32_t) +
+                   sizeof(uint8_t) + sizeof(uint8_t) +
+                       sizeof(uint8_t) + sizeof(uint32_t) +
+                       sizeof(uint8_t) + sizeof(uint64_t);
+
+    return "[1076895760,-9191740941672636400]";
+}
+
+static const char *event_test_list_empty(uint32_t tag, size_t &expected_len) {
+    android_log_context ctx;
+
+    EXPECT_TRUE(NULL != (ctx = create_android_logger(tag)));
+    if (!ctx) {
+        return NULL;
+    }
+    EXPECT_LE(0, android_log_write_list_begin(ctx));
+    EXPECT_LE(0, android_log_write_list_end(ctx));
+    EXPECT_LE(0, android_log_write_list(ctx, LOG_ID_EVENTS));
+    EXPECT_LE(0, android_log_destroy(&ctx));
+    EXPECT_TRUE(NULL == ctx);
+
+    expected_len = sizeof(uint32_t) +
+                   sizeof(uint8_t) + sizeof(uint8_t);
+
+    return "[]";
+}
+
+static const char *event_test_complex_nested_list(uint32_t tag, size_t &expected_len) {
+    android_log_context ctx;
+
+    EXPECT_TRUE(NULL != (ctx = create_android_logger(tag)));
+    if (!ctx) {
+        return NULL;
+    }
+
+    EXPECT_LE(0, android_log_write_list_begin(ctx)); // [
+    EXPECT_LE(0, android_log_write_int32(ctx, 0x01020304));
+    EXPECT_LE(0, android_log_write_int64(ctx, 0x0102030405060708));
+    EXPECT_LE(0, android_log_write_string8(ctx, "Hello World"));
+    EXPECT_LE(0, android_log_write_list_begin(ctx)); // [
+    EXPECT_LE(0, android_log_write_int32(ctx, 1));
+    EXPECT_LE(0, android_log_write_int32(ctx, 2));
+    EXPECT_LE(0, android_log_write_int32(ctx, 3));
+    EXPECT_LE(0, android_log_write_int32(ctx, 4));
+    EXPECT_LE(0, android_log_write_list_end(ctx));   // ]
+    EXPECT_LE(0, android_log_write_float32(ctx, 1.0102030405060708));
+    EXPECT_LE(0, android_log_write_list_end(ctx));   // ]
+
+    //
+    // This one checks for the automagic list creation because a list
+    // begin and end was missing for it! This is actually an <oops> corner
+    // case, and not the behavior we morally support. The automagic API is to
+    // allow for a simple case of a series of objects in a single list. e.g.
+    //   int32,int32,int32,string -> [int32,int32,int32,string]
+    //
+    EXPECT_LE(0, android_log_write_string8(ctx, "dlroW olleH"));
+
+    EXPECT_LE(0, android_log_write_list(ctx, LOG_ID_EVENTS));
+    EXPECT_LE(0, android_log_destroy(&ctx));
+    EXPECT_TRUE(NULL == ctx);
+
+    expected_len = sizeof(uint32_t) +
+                   sizeof(uint8_t) + sizeof(uint8_t) +
+                       sizeof(uint8_t) + sizeof(uint8_t) +
+                           sizeof(uint8_t) + sizeof(uint32_t) +
+                           sizeof(uint8_t) + sizeof(uint64_t) +
+                           sizeof(uint8_t) + sizeof(uint32_t) +
+                                             sizeof("Hello World") - 1 +
+                           sizeof(uint8_t) + sizeof(uint8_t) +
+                               4 * (sizeof(uint8_t) + sizeof(uint32_t)) +
+                           sizeof(uint8_t) + sizeof(uint32_t) +
+                       sizeof(uint8_t) + sizeof(uint32_t) +
+                                         sizeof("dlroW olleH") - 1;
+
+    return "[[16909060,72623859790382856,Hello World,[1,2,3,4],1.010203],dlroW olleH]";
+}
+
+static const char *event_test_7_level_prefix(uint32_t tag, size_t &expected_len) {
+    android_log_context ctx;
+
+    EXPECT_TRUE(NULL != (ctx = create_android_logger(tag)));
+    if (!ctx) {
+        return NULL;
+    }
+    EXPECT_LE(0, android_log_write_list_begin(ctx));
+    EXPECT_LE(0, android_log_write_list_begin(ctx));
+    EXPECT_LE(0, android_log_write_list_begin(ctx));
+    EXPECT_LE(0, android_log_write_list_begin(ctx));
+    EXPECT_LE(0, android_log_write_list_begin(ctx));
+    EXPECT_LE(0, android_log_write_list_begin(ctx));
+    EXPECT_LE(0, android_log_write_list_begin(ctx));
+    EXPECT_LE(0, android_log_write_int32(ctx, 1));
+    EXPECT_LE(0, android_log_write_list_end(ctx));
+    EXPECT_LE(0, android_log_write_int32(ctx, 2));
+    EXPECT_LE(0, android_log_write_list_end(ctx));
+    EXPECT_LE(0, android_log_write_int32(ctx, 3));
+    EXPECT_LE(0, android_log_write_list_end(ctx));
+    EXPECT_LE(0, android_log_write_int32(ctx, 4));
+    EXPECT_LE(0, android_log_write_list_end(ctx));
+    EXPECT_LE(0, android_log_write_int32(ctx, 5));
+    EXPECT_LE(0, android_log_write_list_end(ctx));
+    EXPECT_LE(0, android_log_write_int32(ctx, 6));
+    EXPECT_LE(0, android_log_write_list_end(ctx));
+    EXPECT_LE(0, android_log_write_int32(ctx, 7));
+    EXPECT_LE(0, android_log_write_list_end(ctx));
+    EXPECT_LE(0, android_log_write_list(ctx, LOG_ID_EVENTS));
+    EXPECT_LE(0, android_log_destroy(&ctx));
+    EXPECT_TRUE(NULL == ctx);
+
+    expected_len = sizeof(uint32_t) + 7 *
+      (sizeof(uint8_t) + sizeof(uint8_t) + sizeof(uint8_t) + sizeof(uint32_t));
+
+    return "[[[[[[[1],2],3],4],5],6],7]";
+}
+
+static const char *event_test_7_level_suffix(uint32_t tag, size_t &expected_len) {
+    android_log_context ctx;
+
+    EXPECT_TRUE(NULL != (ctx = create_android_logger(tag)));
+    if (!ctx) {
+        return NULL;
+    }
+    EXPECT_LE(0, android_log_write_list_begin(ctx));
+    EXPECT_LE(0, android_log_write_int32(ctx, 1));
+    EXPECT_LE(0, android_log_write_list_begin(ctx));
+    EXPECT_LE(0, android_log_write_int32(ctx, 2));
+    EXPECT_LE(0, android_log_write_list_begin(ctx));
+    EXPECT_LE(0, android_log_write_int32(ctx, 3));
+    EXPECT_LE(0, android_log_write_list_begin(ctx));
+    EXPECT_LE(0, android_log_write_int32(ctx, 4));
+    EXPECT_LE(0, android_log_write_list_begin(ctx));
+    EXPECT_LE(0, android_log_write_int32(ctx, 5));
+    EXPECT_LE(0, android_log_write_list_begin(ctx));
+    EXPECT_LE(0, android_log_write_int32(ctx, 6));
+    EXPECT_LE(0, android_log_write_list_end(ctx));
+    EXPECT_LE(0, android_log_write_list_end(ctx));
+    EXPECT_LE(0, android_log_write_list_end(ctx));
+    EXPECT_LE(0, android_log_write_list_end(ctx));
+    EXPECT_LE(0, android_log_write_list_end(ctx));
+    EXPECT_LE(0, android_log_write_list_end(ctx));
+    EXPECT_LE(0, android_log_write_list(ctx, LOG_ID_EVENTS));
+    EXPECT_LE(0, android_log_destroy(&ctx));
+    EXPECT_TRUE(NULL == ctx);
+
+    expected_len = sizeof(uint32_t) + 6 *
+      (sizeof(uint8_t) + sizeof(uint8_t) + sizeof(uint8_t) + sizeof(uint32_t));
+
+    return "[1,[2,[3,[4,[5,[6]]]]]]";
+}
+
+// make sure all user buffers are flushed
+static void print_barrier() {
+    std::cout.flush();
+    fflush(stdout);
+    std::cerr.flush();
+    fflush(stderr); // everything else is paranoia ...
+}
+
+static void create_android_logger(const char *(*fn)(uint32_t tag, size_t &expected_len)) {
+    struct logger_list *logger_list;
+
+    pid_t pid = getpid();
+
+    ASSERT_TRUE(NULL != (logger_list = android_logger_list_open(
+        LOG_ID_EVENTS, ANDROID_LOG_RDONLY | ANDROID_LOG_NONBLOCK, 1000, pid)));
+
+    log_time ts(android_log_clockid());
+
+    size_t expected_len;
+    const char *expected_string = (*fn)(1005, expected_len);
+
+    if (!expected_string) {
+        android_logger_list_close(logger_list);
+        return;
+    }
+
+    usleep(1000000);
+
+    int count = 0;
+
+    for (;;) {
+        log_msg log_msg;
+        if (android_logger_list_read(logger_list, &log_msg) <= 0) {
+            break;
+        }
+
+        ASSERT_EQ(log_msg.entry.pid, pid);
+
+        if ((log_msg.entry.sec < (ts.tv_sec - 1))
+         || ((ts.tv_sec + 1) < log_msg.entry.sec)
+         || ((size_t)log_msg.entry.len != expected_len)
+         || (log_msg.id() != LOG_ID_EVENTS)) {
+            continue;
+        }
+
+        char *eventData = log_msg.msg();
+
+        ++count;
+
+        AndroidLogFormat *logformat = android_log_format_new();
+        EXPECT_TRUE(NULL != logformat);
+        AndroidLogEntry entry;
+        char msgBuf[1024];
+        int processBinaryLogBuffer = android_log_processBinaryLogBuffer(
+            &log_msg.entry_v1, &entry, NULL, msgBuf, sizeof(msgBuf));
+        EXPECT_EQ(0, processBinaryLogBuffer);
+        if (processBinaryLogBuffer == 0) {
+            print_barrier();
+            int printLogLine = android_log_printLogLine(
+                logformat, fileno(stderr), &entry);
+            print_barrier();
+            EXPECT_EQ(20 + (int)strlen(expected_string), printLogLine);
+        }
+        android_log_format_free(logformat);
+
+        // test buffer reading API
+        snprintf(msgBuf, sizeof(msgBuf), "I/[%d]", get4LE(eventData));
+        print_barrier();
+        fprintf(stderr, "%-10s(%5u): ", msgBuf, pid);
+        memset(msgBuf, 0, sizeof(msgBuf));
+        int buffer_to_string = android_log_buffer_to_string(
+            eventData + sizeof(uint32_t),
+            log_msg.entry.len - sizeof(uint32_t),
+            msgBuf, sizeof(msgBuf));
+        fprintf(stderr, "%s\n", msgBuf);
+        print_barrier();
+        EXPECT_EQ(0, buffer_to_string);
+        EXPECT_EQ(strlen(expected_string), strlen(msgBuf));
+        EXPECT_EQ(0, strcmp(expected_string, msgBuf));
+    }
+
+    EXPECT_EQ(1, count);
+
+    android_logger_list_close(logger_list);
+}
+
+TEST(liblog, create_android_logger_int32) {
+    create_android_logger(event_test_int32);
+}
+
+TEST(liblog, create_android_logger_int64) {
+    create_android_logger(event_test_int64);
+}
+
+TEST(liblog, create_android_logger_list_int64) {
+    create_android_logger(event_test_list_int64);
+}
+
+TEST(liblog, create_android_logger_simple_automagic_list) {
+    create_android_logger(event_test_simple_automagic_list);
+}
+
+TEST(liblog, create_android_logger_list_empty) {
+    create_android_logger(event_test_list_empty);
+}
+
+TEST(liblog, create_android_logger_complex_nested_list) {
+    create_android_logger(event_test_complex_nested_list);
+}
+
+TEST(liblog, create_android_logger_7_level_prefix) {
+    create_android_logger(event_test_7_level_prefix);
+}
+
+TEST(liblog, create_android_logger_7_level_suffix) {
+    create_android_logger(event_test_7_level_suffix);
+}
+
+TEST(liblog, create_android_logger_overflow) {
+    android_log_context ctx;
+
+    EXPECT_TRUE(NULL != (ctx = create_android_logger(1005)));
+    if (ctx) {
+        for (size_t i = 0; i < ANDROID_MAX_LIST_NEST_DEPTH; ++i) {
+            EXPECT_LE(0, android_log_write_list_begin(ctx));
+        }
+        EXPECT_GT(0, android_log_write_list_begin(ctx));
+        /* One more for good measure, must be permanently unhappy */
+        EXPECT_GT(0, android_log_write_list_begin(ctx));
+        EXPECT_LE(0, android_log_destroy(&ctx));
+        EXPECT_TRUE(NULL == ctx);
+    }
+
+    ASSERT_TRUE(NULL != (ctx = create_android_logger(1005)));
+    for (size_t i = 0; i < ANDROID_MAX_LIST_NEST_DEPTH; ++i) {
+        EXPECT_LE(0, android_log_write_list_begin(ctx));
+        EXPECT_LE(0, android_log_write_int32(ctx, i));
+    }
+    EXPECT_GT(0, android_log_write_list_begin(ctx));
+    /* One more for good measure, must be permanently unhappy */
+    EXPECT_GT(0, android_log_write_list_begin(ctx));
+    EXPECT_LE(0, android_log_destroy(&ctx));
+    ASSERT_TRUE(NULL == ctx);
+}