Merge "Tron/proto: Add ACTION and FIELD for logging the boot reason string value through metrics."
diff --git a/cmds/incident/main.cpp b/cmds/incident/main.cpp
index 47f1db8..519852d 100644
--- a/cmds/incident/main.cpp
+++ b/cmds/incident/main.cpp
@@ -25,6 +25,7 @@
#include <binder/IServiceManager.h>
#include <utils/Looper.h>
+#include <cstring>
#include <fcntl.h>
#include <getopt.h>
#include <stdio.h>
@@ -144,6 +145,16 @@
}
// ================================================================================
+static int
+get_dest(const char* arg)
+{
+ if (strcmp(arg, "LOCAL") == 0) return 0;
+ if (strcmp(arg, "EXPLICIT") == 0) return 1;
+ if (strcmp(arg, "AUTOMATIC") == 0) return 2;
+ return -1; // return the default value
+}
+
+// ================================================================================
static void
usage(FILE* out)
{
@@ -155,6 +166,7 @@
fprintf(out, " -b (default) print the report to stdout (in proto format)\n");
fprintf(out, " -d send the report into dropbox\n");
fprintf(out, " -l list available sections\n");
+ fprintf(out, " -p privacy spec, LOCAL, EXPLICIT or AUTOMATIC\n");
fprintf(out, "\n");
fprintf(out, " SECTION the field numbers of the incident report fields to include\n");
fprintf(out, "\n");
@@ -166,10 +178,11 @@
Status status;
IncidentReportArgs args;
enum { DEST_DROPBOX, DEST_STDOUT } destination = DEST_STDOUT;
+ int dest = -1; // default
// Parse the args
int opt;
- while ((opt = getopt(argc, argv, "bhdl")) != -1) {
+ while ((opt = getopt(argc, argv, "bhdlp:")) != -1) {
switch (opt) {
case 'h':
usage(stdout);
@@ -183,6 +196,9 @@
case 'd':
destination = DEST_DROPBOX;
break;
+ case 'p':
+ dest = get_dest(optarg);
+ break;
default:
usage(stderr);
return 1;
@@ -210,8 +226,7 @@
}
}
}
-
-
+ args.setDest(dest);
// Start the thread pool.
sp<ProcessState> ps(ProcessState::self());
diff --git a/cmds/incident_helper/IncidentHelper.cpp b/cmds/incident_helper/IncidentHelper.cpp
index fba5e66..787d3a1 100644
--- a/cmds/incident_helper/IncidentHelper.cpp
+++ b/cmds/incident_helper/IncidentHelper.cpp
@@ -61,6 +61,21 @@
}
// ================================================================================
+status_t NoopParser::Parse(const int in, const int out) const
+{
+ string content;
+ if (!ReadFdToString(in, &content)) {
+ fprintf(stderr, "[%s]Failed to read data from incidentd\n", this->name.string());
+ return -1;
+ }
+ if (!WriteStringToFd(content, out)) {
+ fprintf(stderr, "[%s]Failed to write data to incidentd\n", this->name.string());
+ return -1;
+ }
+ return NO_ERROR;
+}
+
+// ================================================================================
status_t ReverseParser::Parse(const int in, const int out) const
{
string content;
@@ -189,4 +204,4 @@
}
fprintf(stderr, "[%s]Proto size: %d bytes\n", this->name.string(), proto.ByteSize());
return NO_ERROR;
-}
\ No newline at end of file
+}
diff --git a/cmds/incident_helper/IncidentHelper.h b/cmds/incident_helper/IncidentHelper.h
index f319c41..f6579a2 100644
--- a/cmds/incident_helper/IncidentHelper.h
+++ b/cmds/incident_helper/IncidentHelper.h
@@ -36,6 +36,17 @@
};
/**
+ * No op parser returns what it reads
+ */
+class NoopParser : public TextParserBase {
+public:
+ NoopParser() : TextParserBase(String8("NoopParser")) {};
+ ~NoopParser() {};
+
+ virtual status_t Parse(const int in, const int out) const;
+};
+
+/**
* This parser is used for testing only, results in timeout.
*/
class TimeoutParser : public TextParserBase {
diff --git a/cmds/incident_helper/main.cpp b/cmds/incident_helper/main.cpp
index 333344b..296d300 100644
--- a/cmds/incident_helper/main.cpp
+++ b/cmds/incident_helper/main.cpp
@@ -41,9 +41,11 @@
case -1:
return new TimeoutParser();
case 0:
+ return new NoopParser();
+ case 1: // 1 is reserved for incident header so it won't be section id
return new ReverseParser();
/* ========================================================================= */
- // IDs larger than 0 are reserved in incident.proto
+ // IDs larger than 1 are section ids reserved in incident.proto
case 2000:
return new ProcrankParser();
case 2002:
diff --git a/cmds/incidentd/Android.mk b/cmds/incidentd/Android.mk
index 835a7b9..830bf9e 100644
--- a/cmds/incidentd/Android.mk
+++ b/cmds/incidentd/Android.mk
@@ -23,10 +23,13 @@
LOCAL_MODULE := incidentd
LOCAL_SRC_FILES := \
+ src/EncodedBuffer.cpp \
src/FdBuffer.cpp \
src/IncidentService.cpp \
+ src/Privacy.cpp \
src/Reporter.cpp \
src/Section.cpp \
+ src/io_util.cpp \
src/main.cpp \
src/protobuf.cpp \
src/report_directory.cpp
@@ -69,7 +72,9 @@
gen_src_dir:=
GEN:=
+ifeq ($(BUILD_WITH_INCIDENTD_RC), true)
LOCAL_INIT_RC := incidentd.rc
+endif
include $(BUILD_EXECUTABLE)
@@ -88,12 +93,16 @@
LOCAL_C_INCLUDES += $(LOCAL_PATH)/src
LOCAL_SRC_FILES := \
+ src/EncodedBuffer.cpp \
src/FdBuffer.cpp \
+ src/Privacy.cpp \
src/Reporter.cpp \
src/Section.cpp \
+ src/io_util.cpp \
src/protobuf.cpp \
src/report_directory.cpp \
tests/section_list.cpp \
+ tests/EncodedBuffer_test.cpp \
tests/FdBuffer_test.cpp \
tests/Reporter_test.cpp \
tests/Section_test.cpp \
diff --git a/cmds/incidentd/src/EncodedBuffer.cpp b/cmds/incidentd/src/EncodedBuffer.cpp
new file mode 100644
index 0000000..3d20548
--- /dev/null
+++ b/cmds/incidentd/src/EncodedBuffer.cpp
@@ -0,0 +1,195 @@
+/*
+ * Copyright (C) 2017 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 "EncodedBuffer.h"
+#include "io_util.h"
+#include "protobuf.h"
+
+#include <deque>
+
+const size_t BUFFER_SIZE = 4 * 1024; // 4 KB
+
+/**
+ * Read varint from iterator, the iterator will point to next available byte.
+ * Return the number of bytes of the varint.
+ */
+static uint32_t
+read_raw_varint(FdBuffer::iterator& it)
+{
+ uint32_t val = 0;
+ int i = 0;
+ bool hasNext = true;
+ while (hasNext) {
+ hasNext = ((*it & 0x80) != 0);
+ val += (*it & 0x7F) << (7*i);
+ it++;
+ i++;
+ }
+ return val;
+}
+
+/**
+ * Write the field to buf based on the wire type, iterator will point to next field.
+ * If skip is set to true, no data will be written to buf. Return number of bytes written.
+ */
+static size_t
+write_field_or_skip(FdBuffer::iterator &iterator, vector<uint8_t> &buf, uint8_t wireType, bool skip)
+{
+ FdBuffer::iterator snapshot = iterator.snapshot();
+ size_t bytesToWrite = 0;
+ uint32_t varint = 0;
+ switch (wireType) {
+ case WIRE_TYPE_VARINT:
+ varint = read_raw_varint(iterator);
+ if(!skip) return write_raw_varint(buf, varint);
+ break;
+ case WIRE_TYPE_FIXED64:
+ bytesToWrite = 8;
+ break;
+ case WIRE_TYPE_LENGTH_DELIMITED:
+ bytesToWrite = read_raw_varint(iterator);
+ if(!skip) write_raw_varint(buf, bytesToWrite);
+ break;
+ case WIRE_TYPE_FIXED32:
+ bytesToWrite = 4;
+ break;
+ }
+ if (skip) {
+ iterator += bytesToWrite;
+ } else {
+ for (size_t i=0; i<bytesToWrite; i++) {
+ buf.push_back(*iterator);
+ iterator++;
+ }
+ }
+ return skip ? 0 : iterator - snapshot;
+}
+
+/**
+ * Strip next field based on its private policy and request spec, then stores data in buf.
+ * Return NO_ERROR if succeeds, otherwise BAD_VALUE is returned to indicate bad data in FdBuffer.
+ *
+ * The iterator must point to the head of a protobuf formatted field for successful operation.
+ * After exit with NO_ERROR, iterator points to the next protobuf field's head.
+ */
+static status_t
+stripField(FdBuffer::iterator &iterator, vector<uint8_t> &buf, const Privacy* parentPolicy, const PrivacySpec& spec)
+{
+ if (iterator.outOfBound() || parentPolicy == NULL) return BAD_VALUE;
+
+ uint32_t varint = read_raw_varint(iterator);
+ uint8_t wireType = read_wire_type(varint);
+ uint32_t fieldId = read_field_id(varint);
+ const Privacy* policy = parentPolicy->lookup(fieldId);
+
+ if (policy == NULL || !policy->IsMessageType() || !policy->HasChildren()) {
+ bool skip = !spec.CheckPremission(policy);
+ size_t amt = buf.size();
+ if (!skip) amt += write_header(buf, fieldId, wireType);
+ amt += write_field_or_skip(iterator, buf, wireType, skip); // point to head of next field
+ return buf.size() != amt ? BAD_VALUE : NO_ERROR;
+ }
+ // current field is message type and its sub-fields have extra privacy policies
+ deque<vector<uint8_t>> q;
+ uint32_t msgSize = read_raw_varint(iterator);
+ size_t finalSize = 0;
+ FdBuffer::iterator start = iterator.snapshot();
+ while ((iterator - start) != (int)msgSize) {
+ vector<uint8_t> v;
+ status_t err = stripField(iterator, v, policy, spec);
+ if (err != NO_ERROR) return err;
+ if (v.empty()) continue;
+ q.push_back(v);
+ finalSize += v.size();
+ }
+
+ write_header(buf, fieldId, wireType);
+ write_raw_varint(buf, finalSize);
+ buf.reserve(finalSize);
+ while (!q.empty()) {
+ vector<uint8_t> subField = q.front();
+ for (vector<uint8_t>::iterator it = subField.begin(); it != subField.end(); it++) {
+ buf.push_back(*it);
+ }
+ q.pop_front();
+ }
+ return NO_ERROR;
+}
+
+// ================================================================================
+EncodedBuffer::EncodedBuffer(const FdBuffer& buffer, const Privacy* policy)
+ : mFdBuffer(buffer),
+ mPolicy(policy),
+ mBuffers(),
+ mSize(0)
+{
+}
+
+EncodedBuffer::~EncodedBuffer()
+{
+}
+
+status_t
+EncodedBuffer::strip(const PrivacySpec& spec)
+{
+ // optimization when no strip happens
+ if (mPolicy == NULL || !mPolicy->HasChildren() || spec.RequireAll()) {
+ if (spec.CheckPremission(mPolicy)) mSize = mFdBuffer.size();
+ return NO_ERROR;
+ }
+
+ FdBuffer::iterator it = mFdBuffer.begin();
+ vector<uint8_t> field;
+ field.reserve(BUFFER_SIZE);
+
+ while (it != mFdBuffer.end()) {
+ status_t err = stripField(it, field, mPolicy, spec);
+ if (err != NO_ERROR) return err;
+ if (field.size() > BUFFER_SIZE) { // rotate to another chunk if buffer size exceeds
+ mBuffers.push_back(field);
+ mSize += field.size();
+ field.clear();
+ }
+ }
+ if (!field.empty()) {
+ mBuffers.push_back(field);
+ mSize += field.size();
+ }
+ return NO_ERROR;
+}
+
+void
+EncodedBuffer::clear()
+{
+ mSize = 0;
+ mBuffers.clear();
+}
+
+size_t
+EncodedBuffer::size() const { return mSize; }
+
+status_t
+EncodedBuffer::flush(int fd)
+{
+ if (size() == mFdBuffer.size()) return mFdBuffer.flush(fd);
+
+ for (vector<vector<uint8_t>>::iterator it = mBuffers.begin(); it != mBuffers.end(); it++) {
+ status_t err = write_all(fd, it->data(), it->size());
+ if (err != NO_ERROR) return err;
+ }
+ return NO_ERROR;
+}
+
diff --git a/cmds/incidentd/src/EncodedBuffer.h b/cmds/incidentd/src/EncodedBuffer.h
new file mode 100644
index 0000000..ea8603a
--- /dev/null
+++ b/cmds/incidentd/src/EncodedBuffer.h
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#ifndef ENCODED_BUFFER_H
+#define ENCODED_BUFFER_H
+
+#include "FdBuffer.h"
+#include "Privacy.h"
+
+#include <stdint.h>
+#include <vector>
+
+/**
+ * EncodedBuffer is constructed from FdBuffer which holds original protobuf formatted data and
+ * its privacy policy in its tagged proto message. The class strips PII-sensitive fields
+ * based on the request and holds stripped data in its buffer for output.
+ */
+class EncodedBuffer
+{
+public:
+ EncodedBuffer(const FdBuffer& buffer, const Privacy* policy);
+ ~EncodedBuffer();
+
+ /**
+ * Strip based on the request and hold data in its own buffer. Return NO_ERROR if strip succeeds.
+ */
+ status_t strip(const PrivacySpec& spec);
+
+ /**
+ * Clear encoded buffer so it can be reused by another request.
+ */
+ void clear();
+
+ /**
+ * Return the size of the stripped data.
+ */
+ size_t size() const;
+
+ /**
+ * Flush buffer to the given fd. NO_ERROR is returned if the flush succeeds.
+ */
+ status_t flush(int fd);
+
+private:
+ const FdBuffer& mFdBuffer;
+ const Privacy* mPolicy;
+ vector<vector<uint8_t>> mBuffers;
+ size_t mSize;
+};
+
+#endif // ENCODED_BUFFER_H
\ No newline at end of file
diff --git a/cmds/incidentd/src/FdBuffer.cpp b/cmds/incidentd/src/FdBuffer.cpp
index 4d6a36c..bb399b5 100644
--- a/cmds/incidentd/src/FdBuffer.cpp
+++ b/cmds/incidentd/src/FdBuffer.cpp
@@ -17,6 +17,7 @@
#define LOG_TAG "incidentd"
#include "FdBuffer.h"
+#include "io_util.h"
#include <cutils/log.h>
#include <utils/SystemClock.h>
@@ -239,25 +240,32 @@
}
size_t
-FdBuffer::size()
+FdBuffer::size() const
{
if (mBuffers.empty()) return 0;
return ((mBuffers.size() - 1) * BUFFER_SIZE) + mCurrentWritten;
}
status_t
-FdBuffer::write(ReportRequestSet* reporter)
+FdBuffer::flush(int fd) const
{
- const int N = mBuffers.size() - 1;
- for (int i=0; i<N; i++) {
- reporter->write(mBuffers[i], BUFFER_SIZE);
+ size_t i=0;
+ status_t err = NO_ERROR;
+ for (i=0; i<mBuffers.size()-1; i++) {
+ err = write_all(fd, mBuffers[i], BUFFER_SIZE);
+ if (err != NO_ERROR) return err;
}
- reporter->write(mBuffers[N], mCurrentWritten);
- return NO_ERROR;
+ return write_all(fd, mBuffers[i], mCurrentWritten);
}
FdBuffer::iterator
-FdBuffer::end()
+FdBuffer::begin() const
+{
+ return iterator(*this, 0, 0);
+}
+
+FdBuffer::iterator
+FdBuffer::end() const
{
if (mBuffers.empty() || mCurrentWritten < 0) return begin();
if (mCurrentWritten == BUFFER_SIZE)
@@ -266,6 +274,17 @@
return FdBuffer::iterator(*this, mBuffers.size() - 1, mCurrentWritten);
}
+// ===============================================================================
+FdBuffer::iterator::iterator(const FdBuffer& buffer, ssize_t index, ssize_t offset)
+ : mFdBuffer(buffer),
+ mIndex(index),
+ mOffset(offset)
+{
+}
+
+FdBuffer::iterator&
+FdBuffer::iterator::operator=(iterator& other) const { return other; }
+
FdBuffer::iterator&
FdBuffer::iterator::operator+(size_t offset)
{
@@ -278,8 +297,50 @@
return *this;
}
+FdBuffer::iterator&
+FdBuffer::iterator::operator+=(size_t offset) { return *this + offset; }
+
+FdBuffer::iterator&
+FdBuffer::iterator::operator++() { return *this + 1; }
+
+FdBuffer::iterator
+FdBuffer::iterator::operator++(int) { return *this + 1; }
+
+bool
+FdBuffer::iterator::operator==(iterator other) const
+{
+ return mIndex == other.mIndex && mOffset == other.mOffset;
+}
+
+bool
+FdBuffer::iterator::operator!=(iterator other) const { return !(*this == other); }
+
+int
+FdBuffer::iterator::operator-(iterator other) const
+{
+ return (int)bytesRead() - (int)other.bytesRead();
+}
+
+FdBuffer::iterator::reference
+FdBuffer::iterator::operator*() const
+{
+ return mFdBuffer.mBuffers[mIndex][mOffset];
+}
+
+FdBuffer::iterator
+FdBuffer::iterator::snapshot() const
+{
+ return FdBuffer::iterator(mFdBuffer, mIndex, mOffset);
+}
+
size_t
-FdBuffer::iterator::bytesRead()
+FdBuffer::iterator::bytesRead() const
{
return mIndex * BUFFER_SIZE + mOffset;
}
+
+bool
+FdBuffer::iterator::outOfBound() const
+{
+ return bytesRead() > mFdBuffer.size();
+}
diff --git a/cmds/incidentd/src/FdBuffer.h b/cmds/incidentd/src/FdBuffer.h
index e9a53ff..dfe39c6 100644
--- a/cmds/incidentd/src/FdBuffer.h
+++ b/cmds/incidentd/src/FdBuffer.h
@@ -17,8 +17,6 @@
#ifndef FD_BUFFER_H
#define FD_BUFFER_H
-#include "Reporter.h"
-
#include <utils/Errors.h>
#include <vector>
@@ -55,7 +53,7 @@
/**
* Whether we timed out.
*/
- bool timedOut() { return mTimedOut; }
+ bool timedOut() const { return mTimedOut; }
/**
* If more than 4 MB is read, we truncate the data and return success.
@@ -65,23 +63,22 @@
* happens, truncated() will return true so it can be marked. If the data is
* exactly 4 MB, truncated is still set. Sorry.
*/
- bool truncated() { return mTruncated; }
+ bool truncated() const { return mTruncated; }
/**
* How much data was read.
*/
- size_t size();
+ size_t size() const;
/**
- * [Deprecated] Write the data that we recorded to the fd given.
- * TODO: remove it once the iterator api is working
+ * Flush all the data to given file descriptor;
*/
- status_t write(ReportRequestSet* requests);
+ status_t flush(int fd) const;
/**
* How long the read took in milliseconds.
*/
- int64_t durationMs() { return mFinishTime - mStartTime; }
+ int64_t durationMs() const { return mFinishTime - mStartTime; }
/**
* Read data stored in FdBuffer
@@ -89,30 +86,31 @@
class iterator;
friend class iterator;
class iterator : public std::iterator<std::random_access_iterator_tag, uint8_t> {
+ public:
+ iterator(const FdBuffer& buffer, ssize_t index, ssize_t offset);
+ iterator& operator=(iterator& other) const;
+ iterator& operator+(size_t offset);
+ iterator& operator+=(size_t offset);
+ iterator& operator++();
+ iterator operator++(int);
+ bool operator==(iterator other) const;
+ bool operator!=(iterator other) const;
+ int operator-(iterator other) const;
+ reference operator*() const;
+
+ // return the snapshot of the current iterator
+ iterator snapshot() const;
+ // how many bytes are read
+ size_t bytesRead() const;
+ // random access could make the iterator out of bound
+ bool outOfBound() const;
private:
- FdBuffer& mFdBuffer;
+ const FdBuffer& mFdBuffer;
size_t mIndex;
size_t mOffset;
- public:
- explicit iterator(FdBuffer& buffer, ssize_t index, ssize_t offset)
- : mFdBuffer(buffer), mIndex(index), mOffset(offset) {}
- iterator& operator=(iterator& other) { return other; }
- iterator& operator+(size_t offset); // this is implemented in .cpp
- iterator& operator+=(size_t offset) { return *this + offset; }
- iterator& operator++() { return *this + 1; }
- iterator operator++(int) { return *this + 1; }
- bool operator==(iterator other) const {
- return mIndex == other.mIndex && mOffset == other.mOffset;
- }
- bool operator!=(iterator other) const { return !(*this == other); }
- reference operator*() const { return mFdBuffer.mBuffers[mIndex][mOffset]; }
-
- // random access could make the iterator out of bound
- size_t bytesRead();
- bool outOfBound() { return bytesRead() > mFdBuffer.size(); };
};
- iterator begin() { return iterator(*this, 0, 0); }
- iterator end();
+ iterator begin() const;
+ iterator end() const;
private:
vector<uint8_t*> mBuffers;
@@ -123,19 +121,4 @@
bool mTruncated;
};
-class Fpipe {
-public:
- Fpipe() {}
- bool close() { return !(::close(mFds[0]) || ::close(mFds[1])); }
- ~Fpipe() { close(); }
-
- inline bool init() { return pipe(mFds) != -1; }
- inline int readFd() const { return mFds[0]; }
- inline int writeFd() const { return mFds[1]; }
-
-private:
- int mFds[2];
-};
-
-
#endif // FD_BUFFER_H
diff --git a/cmds/incidentd/src/Privacy.cpp b/cmds/incidentd/src/Privacy.cpp
new file mode 100644
index 0000000..dbab548
--- /dev/null
+++ b/cmds/incidentd/src/Privacy.cpp
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2017 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 "Privacy.h"
+
+// DESTINATION enum value
+const uint8_t DEST_LOCAL = 0;
+const uint8_t DEST_EXPLICIT = 1;
+const uint8_t DEST_AUTOMATIC = 2;
+
+// type of the field, identitical to protobuf definition
+const uint8_t TYPE_STRING = 9;
+const uint8_t TYPE_MESSAGE = 11;
+
+Privacy::Privacy(uint32_t field_id, uint8_t type, uint8_t dest)
+ : field_id(field_id),
+ type(type),
+ children(NULL),
+ dest(dest),
+ patterns(NULL)
+{
+}
+
+Privacy::Privacy(uint32_t field_id, const Privacy** children)
+ : field_id(field_id),
+ type(TYPE_MESSAGE),
+ children(children),
+ dest(DEST_DEFAULT_VALUE), // this will be ignored
+ patterns(NULL)
+{
+}
+
+Privacy::Privacy(uint32_t field_id, uint8_t dest, const char** patterns)
+ : field_id(field_id),
+ type(TYPE_STRING),
+ children(NULL),
+ dest(dest),
+ patterns(patterns)
+{
+}
+
+bool
+Privacy::IsMessageType() const { return type == TYPE_MESSAGE; }
+
+bool
+Privacy::IsStringType() const { return type == TYPE_STRING; }
+
+bool
+Privacy::HasChildren() const { return children != NULL && children[0] != NULL; }
+
+const Privacy*
+Privacy::lookup(uint32_t fieldId) const
+{
+ if (children == NULL) return NULL;
+ for (int i=0; children[i] != NULL; i++) {
+ if (children[i]->field_id == fieldId) return children[i];
+ // This assumes the list's field id is in ascending order and must be true.
+ if (children[i]->field_id > fieldId) return NULL;
+ }
+ return NULL;
+}
+
+static bool allowDest(const uint8_t dest, const uint8_t policy)
+{
+ switch (policy) {
+ case DEST_LOCAL:
+ return dest == DEST_LOCAL;
+ case DEST_EXPLICIT:
+ return dest == DEST_LOCAL || dest == DEST_EXPLICIT;
+ case DEST_AUTOMATIC:
+ return true;
+ default:
+ return false;
+ }
+}
+
+bool
+PrivacySpec::operator<(const PrivacySpec& other) const
+{
+ return dest < other.dest;
+}
+
+bool
+PrivacySpec::CheckPremission(const Privacy* privacy) const
+{
+ uint8_t policy = privacy == NULL ? DEST_DEFAULT_VALUE : privacy->dest;
+ return allowDest(dest, policy);
+}
+
+bool
+PrivacySpec::RequireAll() const { return dest == DEST_LOCAL; }
+
+PrivacySpec new_spec_from_args(int dest) {
+ if (dest < 0) return PrivacySpec();
+ return PrivacySpec(dest);
+}
+
+PrivacySpec get_default_dropbox_spec() { return PrivacySpec(DEST_AUTOMATIC); }
\ No newline at end of file
diff --git a/cmds/incidentd/src/Privacy.h b/cmds/incidentd/src/Privacy.h
new file mode 100644
index 0000000..c56ba9b8
--- /dev/null
+++ b/cmds/incidentd/src/Privacy.h
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#ifndef PRIVACY_H
+#define PRIVACY_H
+
+#include <stdint.h>
+
+// This is the default value of DEST enum
+const uint8_t DEST_DEFAULT_VALUE = 1;
+
+/*
+ * In order not to depend on libprotobuf-cpp-full nor libplatformprotos in incidentd,
+ * privacy options's data structure are explicitly redefined in this file.
+ */
+struct Privacy {
+ uint32_t field_id;
+ uint8_t type;
+ // ignore parent's privacy flags if children are set, NULL-terminated
+ const Privacy** children;
+
+ // the following fields are identitical to
+ // frameworks/base/libs/incident/proto/android/privacy.proto
+ uint8_t dest;
+ const char** patterns; // only set when type is string
+
+ Privacy(uint32_t field_id, uint8_t type, uint8_t dest); // generic constructor
+ Privacy(uint32_t field_id, const Privacy** children); // used for message type
+ Privacy(uint32_t field_id, uint8_t dest, const char** patterns); // used for string type
+
+ bool IsMessageType() const;
+ bool IsStringType() const;
+ bool HasChildren() const;
+ const Privacy* lookup(uint32_t fieldId) const;
+};
+
+/**
+ * PrivacySpec defines the request has what level of privacy authorization.
+ * For example, a device without user consent should only be able to upload AUTOMATIC fields.
+ */
+class PrivacySpec {
+public:
+ const uint8_t dest;
+
+ PrivacySpec() : dest(DEST_DEFAULT_VALUE) {}
+ PrivacySpec(uint8_t dest) : dest(dest) {}
+
+ bool operator<(const PrivacySpec& other) const;
+
+ bool CheckPremission(const Privacy* privacy) const;
+ bool RequireAll() const;
+};
+
+PrivacySpec new_spec_from_args(int dest);
+PrivacySpec get_default_dropbox_spec();
+
+#endif // PRIVACY_H
diff --git a/cmds/incidentd/src/Reporter.cpp b/cmds/incidentd/src/Reporter.cpp
index 4ffc119..11347e2 100644
--- a/cmds/incidentd/src/Reporter.cpp
+++ b/cmds/incidentd/src/Reporter.cpp
@@ -17,7 +17,6 @@
#define LOG_TAG "incidentd"
#include "Reporter.h"
-#include "protobuf.h"
#include "report_directory.h"
#include "section_list.h"
@@ -38,20 +37,6 @@
static const char* INCIDENT_DIRECTORY = "/data/misc/incidents/";
// ================================================================================
-static status_t write_all(int fd, uint8_t const* buf, size_t size)
-{
- while (size > 0) {
- ssize_t amt = ::write(fd, buf, size);
- if (amt < 0) {
- return -errno;
- }
- size -= amt;
- buf += amt;
- }
- return NO_ERROR;
-}
-
-// ================================================================================
ReportRequest::ReportRequest(const IncidentReportArgs& a,
const sp<IIncidentReportStatusListener> &l, int f)
:args(a),
@@ -65,11 +50,16 @@
{
}
+bool
+ReportRequest::ok()
+{
+ return fd >= 0 && err == NO_ERROR;
+}
+
// ================================================================================
ReportRequestSet::ReportRequestSet()
:mRequests(),
mSections(),
- mWritableCount(0),
mMainFd(-1)
{
}
@@ -84,45 +74,12 @@
{
mRequests.push_back(request);
mSections.merge(request->args);
- mWritableCount++;
}
void
ReportRequestSet::setMainFd(int fd)
{
mMainFd = fd;
- mWritableCount++;
-}
-
-status_t
-ReportRequestSet::write(uint8_t const* buf, size_t size)
-{
- status_t err = EBADF;
-
- // The streaming ones
- int const N = mRequests.size();
- for (int i=N-1; i>=0; i--) {
- sp<ReportRequest> request = mRequests[i];
- if (request->fd >= 0 && request->err == NO_ERROR) {
- err = write_all(request->fd, buf, size);
- if (err != NO_ERROR) {
- request->err = err;
- mWritableCount--;
- }
- }
- }
-
- // The dropbox file
- if (mMainFd >= 0) {
- err = write_all(mMainFd, buf, size);
- if (err != NO_ERROR) {
- mMainFd = -1;
- mWritableCount--;
- }
- }
-
- // Return an error only when there are no FDs to write.
- return mWritableCount > 0 ? NO_ERROR : err;
}
bool
@@ -164,6 +121,7 @@
status_t err = NO_ERROR;
bool needMainFd = false;
int mainFd = -1;
+ HeaderSection headers;
// See if we need the main file
for (ReportRequestSet::iterator it=batch.begin(); it!=batch.end(); it++) {
@@ -176,7 +134,7 @@
// Create the directory
if (!isTest) err = create_directory(mIncidentDirectory);
if (err != NO_ERROR) {
- goto done;
+ goto DONE;
}
// If there are too many files in the directory (for whatever reason),
@@ -187,7 +145,7 @@
// Open the file.
err = create_file(&mainFd);
if (err != NO_ERROR) {
- goto done;
+ goto DONE;
}
// Add to the set
@@ -202,24 +160,7 @@
}
// Write the incident headers
- for (ReportRequestSet::iterator it=batch.begin(); it!=batch.end(); it++) {
- const sp<ReportRequest> request = (*it);
- const vector<vector<int8_t>>& headers = request->args.headers();
-
- for (vector<vector<int8_t>>::const_iterator buf=headers.begin(); buf!=headers.end();
- buf++) {
- int fd = request->fd >= 0 ? request->fd : mainFd;
-
- uint8_t buffer[20];
- uint8_t* p = write_length_delimited_tag_header(buffer, FIELD_ID_INCIDENT_HEADER,
- buf->size());
- write_all(fd, buffer, p-buffer);
-
- write_all(fd, (uint8_t const*)buf->data(), buf->size());
- // If there was an error now, there will be an error later and we will remove
- // it from the list then.
- }
- }
+ headers.Execute(&batch);
// For each of the report fields, see if we need it, and if so, execute the command
// and report to those that care that we're doing it.
@@ -240,7 +181,7 @@
if (err != NO_ERROR) {
ALOGW("Incident section %s (%d) failed. Stopping report.",
(*section)->name.string(), id);
- goto done;
+ goto DONE;
}
// Notify listener of starting
@@ -254,7 +195,7 @@
}
}
-done:
+DONE:
// Close the file.
if (mainFd >= 0) {
close(mainFd);
diff --git a/cmds/incidentd/src/Reporter.h b/cmds/incidentd/src/Reporter.h
index 509611c..2615c62 100644
--- a/cmds/incidentd/src/Reporter.h
+++ b/cmds/incidentd/src/Reporter.h
@@ -40,6 +40,8 @@
ReportRequest(const IncidentReportArgs& args,
const sp<IIncidentReportStatusListener> &listener, int fd);
virtual ~ReportRequest();
+
+ bool ok(); // returns true if the request is ok for write.
};
// ================================================================================
@@ -52,21 +54,16 @@
void add(const sp<ReportRequest>& request);
void setMainFd(int fd);
- // Write to all of the fds for the requests. If a write fails, it stops
- // writing to that fd and returns NO_ERROR. When we are out of fds to write
- // to it returns an error.
- status_t write(uint8_t const* buf, size_t size);
-
typedef vector<sp<ReportRequest>>::iterator iterator;
iterator begin() { return mRequests.begin(); }
iterator end() { return mRequests.end(); }
+ int mainFd() { return mMainFd; }
bool containsSection(int id);
private:
vector<sp<ReportRequest>> mRequests;
IncidentReportArgs mSections;
- int mWritableCount;
int mMainFd;
};
diff --git a/cmds/incidentd/src/Section.cpp b/cmds/incidentd/src/Section.cpp
index ac87fe3..6f052de 100644
--- a/cmds/incidentd/src/Section.cpp
+++ b/cmds/incidentd/src/Section.cpp
@@ -16,11 +16,18 @@
#define LOG_TAG "incidentd"
+#include "EncodedBuffer.h"
+#include "FdBuffer.h"
+#include "Privacy.h"
#include "Section.h"
+
+#include "io_util.h"
#include "protobuf.h"
+#include "section_list.h"
#include <private/android_filesystem_config.h>
#include <binder/IServiceManager.h>
+#include <map>
#include <mutex>
#include <wait.h>
#include <unistd.h>
@@ -32,7 +39,7 @@
const char* INCIDENT_HELPER = "/system/bin/incident_helper";
static pid_t
-forkAndExecuteIncidentHelper(const int id, const char* name, Fpipe& p2cPipe, Fpipe& c2pPipe)
+fork_execute_incident_helper(const int id, const char* name, Fpipe& p2cPipe, Fpipe& c2pPipe)
{
const char* ihArgs[] { INCIDENT_HELPER, "-s", String8::format("%d", id).string(), NULL };
@@ -66,14 +73,15 @@
return pid;
}
-static status_t killChild(pid_t pid) {
+// ================================================================================
+static status_t kill_child(pid_t pid) {
int status;
kill(pid, SIGKILL);
if (waitpid(pid, &status, 0) == -1) return -1;
return WIFEXITED(status) == 0 ? NO_ERROR : -WEXITSTATUS(status);
}
-static status_t waitForChild(pid_t pid) {
+static status_t wait_child(pid_t pid) {
int status;
bool died = false;
// wait for child to report status up to 1 seconds
@@ -82,13 +90,92 @@
// sleep for 0.2 second
nanosleep(&WAIT_INTERVAL_NS, NULL);
}
- if (!died) return killChild(pid);
+ if (!died) return kill_child(pid);
return WIFEXITED(status) == 0 ? NO_ERROR : -WEXITSTATUS(status);
}
+// ================================================================================
+static const Privacy*
+get_privacy_of_section(int id)
+{
+ if (id < 0) return NULL;
+ int i=0;
+ while (PRIVACY_POLICY_LIST[i] != NULL) {
+ const Privacy* p = PRIVACY_POLICY_LIST[i];
+ if (p->field_id == (uint32_t)id) return p;
+ if (p->field_id > (uint32_t)id) return NULL;
+ i++;
+ }
+ return NULL;
+}
+
+// ================================================================================
+static status_t
+write_section_header(int fd, int sectionId, size_t size)
+{
+ uint8_t buf[20];
+ uint8_t *p = write_length_delimited_tag_header(buf, sectionId, size);
+ return write_all(fd, buf, p-buf);
+}
+
+static status_t
+write_report_requests(const int id, const FdBuffer& buffer, ReportRequestSet* requests)
+{
+ status_t err = -EBADF;
+ EncodedBuffer encodedBuffer(buffer, get_privacy_of_section(id));
+ int writeable = 0;
+
+ // The streaming ones, group requests by spec in order to save unnecessary strip operations
+ map<PrivacySpec, vector<sp<ReportRequest>>> requestsBySpec;
+ for (ReportRequestSet::iterator it = requests->begin(); it != requests->end(); it++) {
+ sp<ReportRequest> request = *it;
+ if (!request->ok() || !request->args.containsSection(id)) {
+ continue; // skip invalid request
+ }
+ PrivacySpec spec = new_spec_from_args(request->args.dest());
+ requestsBySpec[spec].push_back(request);
+ }
+
+ for (map<PrivacySpec, vector<sp<ReportRequest>>>::iterator mit = requestsBySpec.begin(); mit != requestsBySpec.end(); mit++) {
+ PrivacySpec spec = mit->first;
+ err = encodedBuffer.strip(spec);
+ if (err != NO_ERROR) return err; // it means the encodedBuffer data is corrupted.
+ if (encodedBuffer.size() == 0) continue;
+
+ for (vector<sp<ReportRequest>>::iterator it = mit->second.begin(); it != mit->second.end(); it++) {
+ sp<ReportRequest> request = *it;
+ err = write_section_header(request->fd, id, encodedBuffer.size());
+ if (err != NO_ERROR) { request->err = err; continue; }
+ err = encodedBuffer.flush(request->fd);
+ if (err != NO_ERROR) { request->err = err; continue; }
+ writeable++;
+ ALOGD("Section %d flushed %zu bytes to fd %d with spec %d", id, encodedBuffer.size(), request->fd, spec.dest);
+ }
+ encodedBuffer.clear();
+ }
+
+ // The dropbox file
+ if (requests->mainFd() >= 0) {
+ err = encodedBuffer.strip(get_default_dropbox_spec());
+ if (err != NO_ERROR) return err; // the buffer data is corrupted.
+ if (encodedBuffer.size() == 0) goto DONE;
+
+ err = write_section_header(requests->mainFd(), id, encodedBuffer.size());
+ if (err != NO_ERROR) { requests->setMainFd(-1); goto DONE; }
+ err = encodedBuffer.flush(requests->mainFd());
+ if (err != NO_ERROR) { requests->setMainFd(-1); goto DONE; }
+ writeable++;
+ ALOGD("Section %d flushed %zu bytes to dropbox %d", id, encodedBuffer.size(), requests->mainFd());
+ }
+
+DONE:
+ // only returns error if there is no fd to write to.
+ return writeable > 0 ? NO_ERROR : err;
+}
// ================================================================================
Section::Section(int i, const int64_t timeoutMs)
- :id(i), timeoutMs(timeoutMs)
+ :id(i),
+ timeoutMs(timeoutMs)
{
}
@@ -96,24 +183,50 @@
{
}
-status_t
-Section::WriteHeader(ReportRequestSet* requests, size_t size) const
+// ================================================================================
+HeaderSection::HeaderSection()
+ :Section(FIELD_ID_INCIDENT_HEADER, 0)
{
- ssize_t amt;
- uint8_t buf[20];
- uint8_t* p = write_length_delimited_tag_header(buf, this->id, size);
- return requests->write(buf, p-buf);
+}
+
+HeaderSection::~HeaderSection()
+{
+}
+
+status_t
+HeaderSection::Execute(ReportRequestSet* requests) const
+{
+ for (ReportRequestSet::iterator it=requests->begin(); it!=requests->end(); it++) {
+ const sp<ReportRequest> request = *it;
+ const vector<vector<int8_t>>& headers = request->args.headers();
+
+ for (vector<vector<int8_t>>::const_iterator buf=headers.begin(); buf!=headers.end(); buf++) {
+ if (buf->empty()) continue;
+
+ // So the idea is only requests with negative fd are written to dropbox file.
+ int fd = request->fd >= 0 ? request->fd : requests->mainFd();
+ write_section_header(fd, FIELD_ID_INCIDENT_HEADER, buf->size());
+ write_all(fd, (uint8_t const*)buf->data(), buf->size());
+ // If there was an error now, there will be an error later and we will remove
+ // it from the list then.
+ }
+ }
+ return NO_ERROR;
}
// ================================================================================
FileSection::FileSection(int id, const char* filename, const int64_t timeoutMs)
- : Section(id, timeoutMs), mFilename(filename) {
+ :Section(id, timeoutMs),
+ mFilename(filename)
+{
name = filename;
}
FileSection::~FileSection() {}
-status_t FileSection::Execute(ReportRequestSet* requests) const {
+status_t
+FileSection::Execute(ReportRequestSet* requests) const
+{
// read from mFilename first, make sure the file is available
// add O_CLOEXEC to make sure it is closed when exec incident helper
int fd = open(mFilename, O_RDONLY | O_CLOEXEC);
@@ -131,7 +244,7 @@
return -errno;
}
- pid_t pid = forkAndExecuteIncidentHelper(this->id, this->name.string(), p2cPipe, c2pPipe);
+ pid_t pid = fork_execute_incident_helper(this->id, this->name.string(), p2cPipe, c2pPipe);
if (pid == -1) {
ALOGW("FileSection '%s' failed to fork", this->name.string());
return -errno;
@@ -143,11 +256,11 @@
if (readStatus != NO_ERROR || buffer.timedOut()) {
ALOGW("FileSection '%s' failed to read data from incident helper: %s, timedout: %s, kill: %s",
this->name.string(), strerror(-readStatus), buffer.timedOut() ? "true" : "false",
- strerror(-killChild(pid)));
+ strerror(-kill_child(pid)));
return readStatus;
}
- status_t ihStatus = waitForChild(pid);
+ status_t ihStatus = wait_child(pid);
if (ihStatus != NO_ERROR) {
ALOGW("FileSection '%s' abnormal child process: %s", this->name.string(), strerror(-ihStatus));
return ihStatus;
@@ -155,8 +268,7 @@
ALOGD("FileSection '%s' wrote %zd bytes in %d ms", this->name.string(), buffer.size(),
(int)buffer.durationMs());
- WriteHeader(requests, buffer.size());
- status_t err = buffer.write(requests);
+ status_t err = write_report_requests(this->id, buffer, requests);
if (err != NO_ERROR) {
ALOGW("FileSection '%s' failed writing: %s", this->name.string(), strerror(-err));
return err;
@@ -313,8 +425,7 @@
// Write the data that was collected
ALOGD("WorkerThreadSection '%s' wrote %zd bytes in %d ms", name.string(), buffer.size(),
(int)buffer.durationMs());
- WriteHeader(requests, buffer.size());
- err = buffer.write(requests);
+ err = write_report_requests(this->id, buffer, requests);
if (err != NO_ERROR) {
ALOGW("WorkerThreadSection '%s' failed writing: '%s'", this->name.string(), strerror(-err));
return err;
@@ -324,7 +435,8 @@
}
// ================================================================================
-void CommandSection::init(const char* command, va_list args)
+void
+CommandSection::init(const char* command, va_list args)
{
va_list copied_args;
int numOfArgs = 0;
@@ -350,7 +462,7 @@
}
CommandSection::CommandSection(int id, const int64_t timeoutMs, const char* command, ...)
- : Section(id, timeoutMs)
+ :Section(id, timeoutMs)
{
va_list args;
va_start(args, command);
@@ -359,7 +471,7 @@
}
CommandSection::CommandSection(int id, const char* command, ...)
- : Section(id)
+ :Section(id)
{
va_list args;
va_start(args, command);
@@ -401,7 +513,7 @@
ALOGW("CommandSection '%s' failed in executing command: %s", this->name.string(), strerror(errno));
_exit(err); // exit with command error code
}
- pid_t ihPid = forkAndExecuteIncidentHelper(this->id, this->name.string(), cmdPipe, ihPipe);
+ pid_t ihPid = fork_execute_incident_helper(this->id, this->name.string(), cmdPipe, ihPipe);
if (ihPid == -1) {
ALOGW("CommandSection '%s' failed to fork", this->name.string());
return -errno;
@@ -413,14 +525,14 @@
ALOGW("CommandSection '%s' failed to read data from incident helper: %s, "
"timedout: %s, kill command: %s, kill incident helper: %s",
this->name.string(), strerror(-readStatus), buffer.timedOut() ? "true" : "false",
- strerror(-killChild(cmdPid)), strerror(-killChild(ihPid)));
+ strerror(-kill_child(cmdPid)), strerror(-kill_child(ihPid)));
return readStatus;
}
// TODO: wait for command here has one trade-off: the failed status of command won't be detected until
// buffer timeout, but it has advatage on starting the data stream earlier.
- status_t cmdStatus = waitForChild(cmdPid);
- status_t ihStatus = waitForChild(ihPid);
+ status_t cmdStatus = wait_child(cmdPid);
+ status_t ihStatus = wait_child(ihPid);
if (cmdStatus != NO_ERROR || ihStatus != NO_ERROR) {
ALOGW("CommandSection '%s' abnormal child processes, return status: command: %s, incident helper: %s",
this->name.string(), strerror(-cmdStatus), strerror(-ihStatus));
@@ -429,8 +541,7 @@
ALOGD("CommandSection '%s' wrote %zd bytes in %d ms", this->name.string(), buffer.size(),
(int)buffer.durationMs());
- WriteHeader(requests, buffer.size());
- status_t err = buffer.write(requests);
+ status_t err = write_report_requests(this->id, buffer, requests);
if (err != NO_ERROR) {
ALOGW("CommandSection '%s' failed writing: %s", this->name.string(), strerror(-err));
return err;
diff --git a/cmds/incidentd/src/Section.h b/cmds/incidentd/src/Section.h
index 93b4848..0a1e03e 100644
--- a/cmds/incidentd/src/Section.h
+++ b/cmds/incidentd/src/Section.h
@@ -17,7 +17,7 @@
#ifndef SECTIONS_H
#define SECTIONS_H
-#include "FdBuffer.h"
+#include "Reporter.h"
#include <stdarg.h>
#include <utils/String8.h>
@@ -42,8 +42,18 @@
virtual ~Section();
virtual status_t Execute(ReportRequestSet* requests) const = 0;
+};
- status_t WriteHeader(ReportRequestSet* requests, size_t size) const;
+/**
+ * Section that generates incident headers.
+ */
+class HeaderSection : public Section
+{
+public:
+ HeaderSection();
+ virtual ~HeaderSection();
+
+ virtual status_t Execute(ReportRequestSet* requests) const;
};
/**
diff --git a/cmds/incidentd/src/io_util.cpp b/cmds/incidentd/src/io_util.cpp
new file mode 100644
index 0000000..f043d36
--- /dev/null
+++ b/cmds/incidentd/src/io_util.cpp
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2017 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 "io_util.h"
+
+#include <unistd.h>
+
+status_t write_all(int fd, uint8_t const* buf, size_t size)
+{
+ while (size > 0) {
+ ssize_t amt = ::write(fd, buf, size);
+ if (amt < 0) {
+ return -errno;
+ }
+ size -= amt;
+ buf += amt;
+ }
+ return NO_ERROR;
+}
+
+Fpipe::Fpipe() {}
+
+Fpipe::~Fpipe() { close(); }
+
+bool Fpipe::close() { return !(::close(mFds[0]) || ::close(mFds[1])); }
+
+bool Fpipe::init() { return pipe(mFds) != -1; }
+
+int Fpipe::readFd() const { return mFds[0]; }
+
+int Fpipe::writeFd() const { return mFds[1]; }
diff --git a/cmds/incidentd/src/io_util.h b/cmds/incidentd/src/io_util.h
new file mode 100644
index 0000000..320dd6c
--- /dev/null
+++ b/cmds/incidentd/src/io_util.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#ifndef IO_UTIL_H
+#define IO_UTIL_H
+
+#include <stdint.h>
+#include <utils/Errors.h>
+
+using namespace android;
+
+status_t write_all(int fd, uint8_t const* buf, size_t size);
+
+class Fpipe {
+public:
+ Fpipe();
+ ~Fpipe();
+
+ bool init();
+ bool close();
+ int readFd() const;
+ int writeFd() const;
+
+private:
+ int mFds[2];
+};
+
+#endif // IO_UTIL_H
\ No newline at end of file
diff --git a/cmds/incidentd/src/protobuf.cpp b/cmds/incidentd/src/protobuf.cpp
index b865339..05de831 100644
--- a/cmds/incidentd/src/protobuf.cpp
+++ b/cmds/incidentd/src/protobuf.cpp
@@ -16,8 +16,17 @@
#include "protobuf.h"
+uint8_t read_wire_type(uint32_t varint)
+{
+ return (uint8_t) (varint & 0x07);
+}
-uint8_t*
+uint32_t read_field_id(uint32_t varint)
+{
+ return varint >> 3;
+}
+
+uint8_t*
write_raw_varint(uint8_t* buf, uint32_t val)
{
uint8_t* p = buf;
@@ -32,7 +41,7 @@
}
}
-uint8_t*
+uint8_t*
write_length_delimited_tag_header(uint8_t* buf, uint32_t fieldId, size_t size)
{
buf = write_raw_varint(buf, (fieldId << 3) | 2);
@@ -40,3 +49,24 @@
return buf;
}
+size_t
+write_raw_varint(vector<uint8_t> &buf, uint32_t val)
+{
+ size_t size = 0;
+ while (true) {
+ size++;
+ if ((val & ~0x7F) == 0) {
+ buf.push_back((uint8_t) val);
+ return size;
+ } else {
+ buf.push_back((uint8_t)((val & 0x7F) | 0x80));
+ val >>= 7;
+ }
+ }
+}
+
+size_t
+write_header(vector<uint8_t> &buf, uint32_t fieldId, uint8_t wireType)
+{
+ return write_raw_varint(buf, (fieldId << 3) | wireType);
+}
\ No newline at end of file
diff --git a/cmds/incidentd/src/protobuf.h b/cmds/incidentd/src/protobuf.h
index f196ddc..fb0d69d 100644
--- a/cmds/incidentd/src/protobuf.h
+++ b/cmds/incidentd/src/protobuf.h
@@ -18,6 +18,24 @@
#define PROTOBUF_H
#include <stdint.h>
+#include <vector>
+
+using namespace std;
+
+const uint8_t WIRE_TYPE_VARINT = 0;
+const uint8_t WIRE_TYPE_FIXED64 = 1;
+const uint8_t WIRE_TYPE_LENGTH_DELIMITED = 2;
+const uint8_t WIRE_TYPE_FIXED32 = 5;
+
+/**
+ * Read the wire type from varint, it is the smallest 3 bits.
+ */
+uint8_t read_wire_type(uint32_t varint);
+
+/**
+ * read field id from varint, it is varint >> 3;
+ */
+uint32_t read_field_id(uint32_t varint);
/**
* Write a varint into the buffer. Return the next position to write at.
@@ -32,6 +50,16 @@
*/
uint8_t* write_length_delimited_tag_header(uint8_t* buf, uint32_t fieldId, size_t size);
+/**
+ * Write a varint into a vector. Return the size of the varint.
+ */
+size_t write_raw_varint(vector<uint8_t> &buf, uint32_t val);
+
+/**
+ * Write a protobuf header. Return the size of the header.
+ */
+size_t write_header(vector<uint8_t> &buf, uint32_t fieldId, uint8_t wireType);
+
enum {
// IncidentProto.header
FIELD_ID_INCIDENT_HEADER = 1
diff --git a/cmds/incidentd/src/section_list.h b/cmds/incidentd/src/section_list.h
index 1abdb52..4d9efd7 100644
--- a/cmds/incidentd/src/section_list.h
+++ b/cmds/incidentd/src/section_list.h
@@ -17,6 +17,7 @@
#ifndef SECTION_LIST_H
#define SECTION_LIST_H
+#include "Privacy.h"
#include "Section.h"
/**
@@ -25,37 +26,6 @@
*/
extern const Section* SECTION_LIST[];
-/*
- * In order not to use libprotobuf-cpp-full nor libplatformprotos in incidentd
- * privacy options's data structure are explicityly redefined in this file.
- */
-
-// DESTINATION enum
-extern const uint8_t DEST_LOCAL;
-extern const uint8_t DEST_EXPLICIT;
-extern const uint8_t DEST_AUTOMATIC;
-
-// This is the default value of DEST enum
-// field with this value doesn't generate Privacy to save too much generated code
-extern const uint8_t DEST_DEFAULT_VALUE;
-
-// type of the field, identitical to protobuf definition
-extern const uint8_t TYPE_STRING;
-extern const uint8_t TYPE_MESSAGE;
-
-struct Privacy {
- int field_id;
- uint8_t type;
-
- // the following two fields are identitical to
- // frameworks/base/libs/incident/proto/android/privacy.proto
- uint8_t dest;
- const char** patterns;
-
- // ignore parent's privacy flags if children are set, NULL-terminated
- const Privacy** children;
-};
-
/**
* This is the mapping of section IDs to each section's privacy policy.
* The section IDs are guaranteed in ascending order
diff --git a/cmds/incidentd/tests/EncodedBuffer_test.cpp b/cmds/incidentd/tests/EncodedBuffer_test.cpp
new file mode 100644
index 0000000..c51520b
--- /dev/null
+++ b/cmds/incidentd/tests/EncodedBuffer_test.cpp
@@ -0,0 +1,207 @@
+// Copyright (C) 2017 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 "EncodedBuffer.h"
+
+#include <android-base/file.h>
+#include <android-base/test_utils.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <string.h>
+
+using namespace android;
+using namespace android::base;
+using namespace std;
+using ::testing::StrEq;
+using ::testing::Test;
+using ::testing::internal::CaptureStdout;
+using ::testing::internal::GetCapturedStdout;
+
+const uint8_t LOCAL = 0;
+const uint8_t EXPLICIT = 1;
+const uint8_t AUTOMATIC = 2;
+
+const uint8_t OTHER_TYPE = 1;
+const uint8_t STRING_TYPE = 9;
+const uint8_t MESSAGE_TYPE = 11;
+const string STRING_FIELD_0 = "\x02\viamtestdata";
+const string VARINT_FIELD_1 = "\x08\x96\x01"; // 150
+const string STRING_FIELD_2 = "\x12\vwhatthefuck";
+const string FIX64_FIELD_3 = "\x19\xff\xff\xff\xff\xff\xff\xff\xff"; // -1
+const string FIX32_FIELD_4 = "\x25\xff\xff\xff\xff"; // -1
+const string MESSAGE_FIELD_5 = "\x2a\x10" + VARINT_FIELD_1 + STRING_FIELD_2;
+
+class EncodedBufferTest : public Test {
+public:
+ virtual void SetUp() override {
+ ASSERT_NE(tf.fd, -1);
+ }
+
+ void writeToFdBuffer(string str) {
+ ASSERT_TRUE(WriteStringToFile(str, tf.path, false));
+ ASSERT_EQ(NO_ERROR, buffer.read(tf.fd, 10000));
+ }
+
+ void assertBuffer(EncodedBuffer& buf, string expected) {
+ ASSERT_EQ(buf.size(), expected.size());
+ CaptureStdout();
+ ASSERT_EQ(buf.flush(STDOUT_FILENO), NO_ERROR);
+ ASSERT_THAT(GetCapturedStdout(), StrEq(expected));
+ }
+
+ void assertStrip(uint8_t dest, string expected, Privacy* policy) {
+ PrivacySpec spec(dest);
+ EncodedBuffer encodedBuf(buffer, policy);
+ ASSERT_EQ(encodedBuf.strip(spec), NO_ERROR);
+ assertBuffer(encodedBuf, expected);
+ }
+
+ void assertStripByFields(uint8_t dest, string expected, int size, Privacy* privacy, ...) {
+ Privacy* list[size+1];
+ list[0] = privacy;
+ va_list args;
+ va_start(args, privacy);
+ for (int i=1; i<size; i++) {
+ Privacy* p = va_arg(args, Privacy*);
+ list[i] = p;
+ }
+ va_end(args);
+ list[size] = NULL;
+ assertStrip(dest, expected, new Privacy(300, const_cast<const Privacy**>(list)));
+ }
+
+ FdBuffer buffer;
+private:
+ TemporaryFile tf;
+};
+
+TEST_F(EncodedBufferTest, NullFieldPolicy) {
+ writeToFdBuffer(STRING_FIELD_0);
+ assertStrip(EXPLICIT, STRING_FIELD_0, new Privacy(300, NULL));
+}
+
+TEST_F(EncodedBufferTest, StripSpecNotAllowed) {
+ writeToFdBuffer(STRING_FIELD_0);
+ assertStripByFields(AUTOMATIC, "", 1, new Privacy(0, STRING_TYPE, EXPLICIT));
+}
+
+TEST_F(EncodedBufferTest, StripVarintField) {
+ writeToFdBuffer(VARINT_FIELD_1);
+ assertStripByFields(EXPLICIT, "", 1, new Privacy(1, OTHER_TYPE, LOCAL));
+}
+
+TEST_F(EncodedBufferTest, StripLengthDelimitedField_String) {
+ writeToFdBuffer(STRING_FIELD_2);
+ assertStripByFields(EXPLICIT, "", 1, new Privacy(2, STRING_TYPE, LOCAL));
+}
+
+TEST_F(EncodedBufferTest, StripFixed64Field) {
+ writeToFdBuffer(FIX64_FIELD_3);
+ assertStripByFields(EXPLICIT, "", 1, new Privacy(3, OTHER_TYPE, LOCAL));
+}
+
+TEST_F(EncodedBufferTest, StripFixed32Field) {
+ writeToFdBuffer(FIX32_FIELD_4);
+ assertStripByFields(EXPLICIT, "", 1, new Privacy(4, OTHER_TYPE, LOCAL));
+}
+
+TEST_F(EncodedBufferTest, StripLengthDelimitedField_Message) {
+ writeToFdBuffer(MESSAGE_FIELD_5);
+ assertStripByFields(EXPLICIT, "", 1, new Privacy(5, MESSAGE_TYPE, LOCAL));
+}
+
+TEST_F(EncodedBufferTest, NoStripVarintField) {
+ writeToFdBuffer(VARINT_FIELD_1);
+ assertStripByFields(EXPLICIT, VARINT_FIELD_1, 1, new Privacy(1, OTHER_TYPE, AUTOMATIC));
+}
+
+TEST_F(EncodedBufferTest, NoStripLengthDelimitedField_String) {
+ writeToFdBuffer(STRING_FIELD_2);
+ assertStripByFields(EXPLICIT, STRING_FIELD_2, 1, new Privacy(2, STRING_TYPE, AUTOMATIC));
+}
+
+TEST_F(EncodedBufferTest, NoStripFixed64Field) {
+ writeToFdBuffer(FIX64_FIELD_3);
+ assertStripByFields(EXPLICIT, FIX64_FIELD_3, 1, new Privacy(3, OTHER_TYPE, AUTOMATIC));
+}
+
+TEST_F(EncodedBufferTest, NoStripFixed32Field) {
+ writeToFdBuffer(FIX32_FIELD_4);
+ assertStripByFields(EXPLICIT, FIX32_FIELD_4, 1, new Privacy(4, OTHER_TYPE, AUTOMATIC));
+}
+
+TEST_F(EncodedBufferTest, NoStripLengthDelimitedField_Message) {
+ writeToFdBuffer(MESSAGE_FIELD_5);
+ assertStripByFields(EXPLICIT, MESSAGE_FIELD_5, 1, new Privacy(5, MESSAGE_TYPE, AUTOMATIC));
+}
+
+TEST_F(EncodedBufferTest, StripVarintAndString) {
+ writeToFdBuffer(STRING_FIELD_0 + VARINT_FIELD_1 + STRING_FIELD_2
+ + FIX64_FIELD_3 + FIX32_FIELD_4);
+ string expected = STRING_FIELD_0 + FIX64_FIELD_3 + FIX32_FIELD_4;
+ assertStripByFields(EXPLICIT, expected, 2,
+ new Privacy(1, OTHER_TYPE, LOCAL), new Privacy(2, STRING_TYPE, LOCAL));
+}
+
+TEST_F(EncodedBufferTest, StripVarintAndFixed64) {
+ writeToFdBuffer(STRING_FIELD_0 + VARINT_FIELD_1 + STRING_FIELD_2
+ + FIX64_FIELD_3 + FIX32_FIELD_4);
+ string expected = STRING_FIELD_0 + STRING_FIELD_2 + FIX32_FIELD_4;
+ assertStripByFields(EXPLICIT, expected, 2,
+ new Privacy(1, OTHER_TYPE, LOCAL), new Privacy(3, OTHER_TYPE, LOCAL));
+}
+
+TEST_F(EncodedBufferTest, StripVarintInNestedMessage) {
+ writeToFdBuffer(STRING_FIELD_0 + MESSAGE_FIELD_5);
+ const Privacy* list[] = { new Privacy(1, OTHER_TYPE, LOCAL), NULL };
+ string expected = STRING_FIELD_0 + "\x2a\xd" + STRING_FIELD_2;
+ assertStripByFields(EXPLICIT, expected, 1, new Privacy(5, list));
+}
+
+TEST_F(EncodedBufferTest, StripFix64AndVarintInNestedMessage) {
+ writeToFdBuffer(STRING_FIELD_0 + FIX64_FIELD_3 + MESSAGE_FIELD_5);
+ const Privacy* list[] = { new Privacy(1, OTHER_TYPE, LOCAL), NULL };
+ string expected = STRING_FIELD_0 + "\x2a\xd" + STRING_FIELD_2;
+ assertStripByFields(EXPLICIT, expected, 2, new Privacy(3, OTHER_TYPE, LOCAL), new Privacy(5, list));
+}
+
+TEST_F(EncodedBufferTest, ClearAndStrip) {
+ string data = STRING_FIELD_0 + VARINT_FIELD_1;
+ writeToFdBuffer(data);
+ const Privacy* list[] = { new Privacy(1, OTHER_TYPE, LOCAL), NULL };
+ EncodedBuffer encodedBuf(buffer, new Privacy(300, list));
+ PrivacySpec spec1(EXPLICIT), spec2(LOCAL);
+
+ ASSERT_EQ(encodedBuf.strip(spec1), NO_ERROR);
+ assertBuffer(encodedBuf, STRING_FIELD_0);
+ ASSERT_EQ(encodedBuf.strip(spec2), NO_ERROR);
+ assertBuffer(encodedBuf, data);
+}
+
+TEST_F(EncodedBufferTest, BadDataInFdBuffer) {
+ writeToFdBuffer("iambaddata");
+ const Privacy* list[] = { new Privacy(4, OTHER_TYPE, AUTOMATIC), NULL };
+ EncodedBuffer encodedBuf(buffer, new Privacy(300, list));
+ PrivacySpec spec;
+ ASSERT_EQ(encodedBuf.strip(spec), BAD_VALUE);
+}
+
+TEST_F(EncodedBufferTest, BadDataInNestedMessage) {
+ writeToFdBuffer(STRING_FIELD_0 + MESSAGE_FIELD_5 + "aoeoe");
+ const Privacy* list[] = { new Privacy(1, OTHER_TYPE, LOCAL), NULL };
+ const Privacy* field5[] = { new Privacy(5, list), NULL };
+ EncodedBuffer encodedBuf(buffer, new Privacy(300, field5));
+ PrivacySpec spec;
+ ASSERT_EQ(encodedBuf.strip(spec), BAD_VALUE);
+}
diff --git a/cmds/incidentd/tests/FdBuffer_test.cpp b/cmds/incidentd/tests/FdBuffer_test.cpp
index 403a2ab..d1436b2 100644
--- a/cmds/incidentd/tests/FdBuffer_test.cpp
+++ b/cmds/incidentd/tests/FdBuffer_test.cpp
@@ -15,10 +15,11 @@
#define LOG_TAG "incidentd"
#include "FdBuffer.h"
+#include "io_util.h"
#include <android-base/file.h>
#include <android-base/test_utils.h>
-#include <gmock/gmock.h>
+#include <fcntl.h>
#include <gtest/gtest.h>
#include <signal.h>
#include <string.h>
@@ -30,10 +31,7 @@
using namespace android;
using namespace android::base;
-using ::testing::StrEq;
using ::testing::Test;
-using ::testing::internal::CaptureStdout;
-using ::testing::internal::GetCapturedStdout;
class FdBufferTest : public Test {
public:
@@ -50,12 +48,13 @@
}
void AssertBufferContent(const char* expected) {
- ReportRequestSet requests;
- requests.setMainFd(STDOUT_FILENO);
-
- CaptureStdout();
- ASSERT_EQ(NO_ERROR, buffer.write(&requests));
- EXPECT_THAT(GetCapturedStdout(), StrEq(expected));
+ int i=0;
+ FdBuffer::iterator it = buffer.begin();
+ while (expected[i] != '\0') {
+ ASSERT_EQ(*it, expected[i++]);
+ it++;
+ }
+ ASSERT_EQ(it, buffer.end());
}
bool DoDataStream(int rFd, int wFd) {
@@ -99,6 +98,16 @@
EXPECT_TRUE(it.outOfBound());
}
+TEST_F(FdBufferTest, IteratorSnapshot) {
+ FdBuffer::iterator it = buffer.begin();
+ it += 4;
+ FdBuffer::iterator snapshot = it.snapshot();
+ it += 5;
+ EXPECT_TRUE(snapshot != it);
+ EXPECT_EQ(it - snapshot, 5);
+ EXPECT_EQ(snapshot - it, -5);
+}
+
TEST_F(FdBufferTest, ReadAndIterate) {
std::string testdata = "FdBuffer test string";
ASSERT_TRUE(WriteStringToFile(testdata, tf.path, false));
@@ -227,7 +236,7 @@
TEST_F(FdBufferTest, ReadInStreamMoreThan4MB) {
const std::string testFile = kTestDataPath + "morethan4MB.txt";
size_t fourMB = (size_t) 4 * 1024 * 1024;
- int fd = open(testFile.c_str(), O_RDONLY);
+ int fd = open(testFile.c_str(), O_RDONLY | O_CLOEXEC);
ASSERT_NE(fd, -1);
int pid = fork();
ASSERT_TRUE(pid != -1);
diff --git a/cmds/incidentd/tests/Reporter_test.cpp b/cmds/incidentd/tests/Reporter_test.cpp
index a774741..5d074bc 100644
--- a/cmds/incidentd/tests/Reporter_test.cpp
+++ b/cmds/incidentd/tests/Reporter_test.cpp
@@ -76,8 +76,7 @@
};
protected:
- IBinder* onAsBinder() override { return nullptr; };
-
+ virtual IBinder* onAsBinder() override { return nullptr; };
};
class ReporterTest : public Test {
@@ -127,29 +126,7 @@
TEST_F(ReporterTest, ReportRequestSetEmpty) {
requests.setMainFd(STDOUT_FILENO);
-
- CaptureStdout();
- requests.write((uint8_t *) "abcdef", 6);
- EXPECT_THAT(GetCapturedStdout(), StrEq("abcdef"));
-}
-
-TEST_F(ReporterTest, WriteToStreamFdAndMainFd) {
- TemporaryFile tf;
- IncidentReportArgs args;
- sp<ReportRequest> r = new ReportRequest(args, l, tf.fd);
-
- requests.add(r);
- requests.setMainFd(STDOUT_FILENO);
-
- const char* data = "abcdef";
-
- CaptureStdout();
- requests.write((uint8_t *) data, 6);
- EXPECT_THAT(GetCapturedStdout(), StrEq(data));
-
- string content;
- ASSERT_TRUE(ReadFileToString(tf.path, &content));
- EXPECT_THAT(content, StrEq(data));
+ ASSERT_EQ(requests.mainFd(), STDOUT_FILENO);
}
TEST_F(ReporterTest, RunReportEmpty) {
diff --git a/cmds/incidentd/tests/Section_test.cpp b/cmds/incidentd/tests/Section_test.cpp
index 93771ff..25b05b2 100644
--- a/cmds/incidentd/tests/Section_test.cpp
+++ b/cmds/incidentd/tests/Section_test.cpp
@@ -22,31 +22,80 @@
#include <gtest/gtest.h>
#include <string.h>
+const int TIMEOUT_PARSER = -1;
+const int NOOP_PARSER = 0;
+const int REVERSE_PARSER = 1;
+
const int QUICK_TIMEOUT_MS = 100;
+const string VARINT_FIELD_1 = "\x08\x96\x01"; // 150
+const string STRING_FIELD_2 = "\x12\vwhatthefuck";
+const string FIX64_FIELD_3 = "\x19\xff\xff\xff\xff\xff\xff\xff\xff"; // -1
+
using namespace android::base;
+using namespace android::binder;
using namespace std;
using ::testing::StrEq;
using ::testing::internal::CaptureStdout;
using ::testing::internal::GetCapturedStdout;
// NOTICE: this test requires /system/bin/incident_helper is installed.
-TEST(SectionTest, WriteHeader) {
- int id = 13; // expect output is 13 << 3 & 2 = 106 --> \x6a in ASCII
- FileSection s(id, ""); // ignore the path, just used to test the header
+
+class SimpleListener : public IIncidentReportStatusListener
+{
+public:
+ SimpleListener() {};
+ virtual ~SimpleListener() {};
+
+ virtual Status onReportStarted() { return Status::ok(); };
+ virtual Status onReportSectionStatus(int /*section*/, int /*status*/) { return Status::ok(); };
+ virtual Status onReportFinished() { return Status::ok(); };
+ virtual Status onReportFailed() { return Status::ok(); };
+
+protected:
+ virtual IBinder* onAsBinder() override { return nullptr; };
+};
+
+TEST(SectionTest, HeaderSection) {
+ TemporaryFile output2;
+ HeaderSection hs;
ReportRequestSet requests;
+ IncidentReportArgs args1, args2;
+ args1.addSection(1);
+ args1.addSection(2);
+ args2.setAll(true);
+
+ vector<int8_t> head1;
+ head1.push_back('a');
+ head1.push_back('x');
+ head1.push_back('e');
+
+ vector<int8_t> head2;
+ head2.push_back('p');
+ head2.push_back('u');
+ head2.push_back('p');
+
+ args1.addHeader(head1);
+ args1.addHeader(head2);
+ args2.addHeader(head2);
+
+ requests.add(new ReportRequest(args1, new SimpleListener(), -1));
+ requests.add(new ReportRequest(args2, new SimpleListener(), output2.fd));
requests.setMainFd(STDOUT_FILENO);
+ string content;
CaptureStdout();
- ASSERT_EQ(NO_ERROR, s.WriteHeader(&requests, 300));
- // According to protobuf encoding, 300 is "1010 1100 0000 0010" -> \xac \x02
- EXPECT_THAT(GetCapturedStdout(), StrEq("\x6a\xac\x02"));
+ ASSERT_EQ(NO_ERROR, hs.Execute(&requests));
+ EXPECT_THAT(GetCapturedStdout(), StrEq("\n\x3" "axe\n\x03pup"));
+
+ EXPECT_TRUE(ReadFileToString(output2.path, &content));
+ EXPECT_THAT(content, StrEq("\n\x03pup"));
}
TEST(SectionTest, FileSection) {
TemporaryFile tf;
- FileSection fs(0, tf.path);
+ FileSection fs(REVERSE_PARSER, tf.path);
ReportRequestSet requests;
ASSERT_TRUE(tf.fd != -1);
@@ -58,13 +107,13 @@
ASSERT_EQ(NO_ERROR, fs.Execute(&requests));
// The input string is reversed in incident helper
// The length is 11, in 128Varint it is "0000 1011" -> \v
- EXPECT_THAT(GetCapturedStdout(), StrEq("\x02\vatadtsetmai"));
+ EXPECT_THAT(GetCapturedStdout(), StrEq("\xa\vatadtsetmai"));
}
TEST(SectionTest, FileSectionTimeout) {
TemporaryFile tf;
// id -1 is timeout parser
- FileSection fs(-1, tf.path, QUICK_TIMEOUT_MS);
+ FileSection fs(TIMEOUT_PARSER, tf.path, QUICK_TIMEOUT_MS);
ReportRequestSet requests;
ASSERT_EQ(NO_ERROR, fs.Execute(&requests));
}
@@ -84,36 +133,165 @@
}
TEST(SectionTest, CommandSectionEcho) {
- CommandSection cs(0, "/system/bin/echo", "about", NULL);
+ CommandSection cs(REVERSE_PARSER, "/system/bin/echo", "about", NULL);
ReportRequestSet requests;
requests.setMainFd(STDOUT_FILENO);
CaptureStdout();
ASSERT_EQ(NO_ERROR, cs.Execute(&requests));
- EXPECT_THAT(GetCapturedStdout(), StrEq("\x02\x06\ntuoba"));
+ EXPECT_THAT(GetCapturedStdout(), StrEq("\xa\x06\ntuoba"));
}
TEST(SectionTest, CommandSectionCommandTimeout) {
- CommandSection cs(0, QUICK_TIMEOUT_MS, "/system/bin/yes", NULL);
+ CommandSection cs(NOOP_PARSER, QUICK_TIMEOUT_MS, "/system/bin/yes", NULL);
ReportRequestSet requests;
ASSERT_EQ(NO_ERROR, cs.Execute(&requests));
}
TEST(SectionTest, CommandSectionIncidentHelperTimeout) {
- CommandSection cs(-1, QUICK_TIMEOUT_MS, "/system/bin/echo", "about", NULL);
+ CommandSection cs(TIMEOUT_PARSER, QUICK_TIMEOUT_MS, "/system/bin/echo", "about", NULL);
ReportRequestSet requests;
requests.setMainFd(STDOUT_FILENO);
ASSERT_EQ(NO_ERROR, cs.Execute(&requests));
}
TEST(SectionTest, CommandSectionBadCommand) {
- CommandSection cs(0, "echo", "about", NULL);
+ CommandSection cs(NOOP_PARSER, "echo", "about", NULL);
ReportRequestSet requests;
ASSERT_EQ(NAME_NOT_FOUND, cs.Execute(&requests));
}
TEST(SectionTest, CommandSectionBadCommandAndTimeout) {
- CommandSection cs(-1, QUICK_TIMEOUT_MS, "nonexistcommand", "-opt", NULL);
+ CommandSection cs(TIMEOUT_PARSER, QUICK_TIMEOUT_MS, "nonexistcommand", "-opt", NULL);
ReportRequestSet requests;
// timeout will return first
ASSERT_EQ(NO_ERROR, cs.Execute(&requests));
+}
+
+TEST(SectionTest, TestFilterPiiTaggedFields) {
+ TemporaryFile tf;
+ FileSection fs(NOOP_PARSER, tf.path);
+ ReportRequestSet requests;
+
+ ASSERT_TRUE(tf.fd != -1);
+ ASSERT_TRUE(WriteStringToFile(VARINT_FIELD_1 + STRING_FIELD_2 + FIX64_FIELD_3, tf.path, false));
+
+ requests.setMainFd(STDOUT_FILENO);
+
+ CaptureStdout();
+ ASSERT_EQ(NO_ERROR, fs.Execute(&requests));
+ EXPECT_THAT(GetCapturedStdout(), StrEq("\x02\r" + STRING_FIELD_2));
+}
+
+TEST(SectionTest, TestBadFdRequest) {
+ TemporaryFile input;
+ FileSection fs(NOOP_PARSER, input.path);
+ ReportRequestSet requests;
+ ASSERT_TRUE(WriteStringToFile(VARINT_FIELD_1 + STRING_FIELD_2 + FIX64_FIELD_3, input.path, false));
+
+ IncidentReportArgs args;
+ args.setAll(true);
+ args.setDest(0);
+ sp<ReportRequest> badFdRequest = new ReportRequest(args, new SimpleListener(), 1234567);
+ requests.add(badFdRequest);
+ requests.setMainFd(STDOUT_FILENO);
+
+ CaptureStdout();
+ ASSERT_EQ(NO_ERROR, fs.Execute(&requests));
+ EXPECT_THAT(GetCapturedStdout(), StrEq("\x02\r" + STRING_FIELD_2));
+ EXPECT_EQ(badFdRequest->err, -EBADF);
+}
+
+TEST(SectionTest, TestBadRequests) {
+ TemporaryFile input;
+ FileSection fs(NOOP_PARSER, input.path);
+ ReportRequestSet requests;
+ ASSERT_TRUE(WriteStringToFile(VARINT_FIELD_1 + STRING_FIELD_2 + FIX64_FIELD_3, input.path, false));
+
+ IncidentReportArgs args;
+ args.setAll(true);
+ args.setDest(0);
+ requests.add(new ReportRequest(args, new SimpleListener(), -1));
+ EXPECT_EQ(fs.Execute(&requests), -EBADF);
+}
+
+TEST(SectionTest, TestMultipleRequests) {
+ TemporaryFile input, output1, output2, output3;
+ FileSection fs(NOOP_PARSER, input.path);
+ ReportRequestSet requests;
+
+ ASSERT_TRUE(input.fd != -1);
+ ASSERT_TRUE(output1.fd != -1);
+ ASSERT_TRUE(output2.fd != -1);
+ ASSERT_TRUE(output3.fd != -1);
+ ASSERT_TRUE(WriteStringToFile(VARINT_FIELD_1 + STRING_FIELD_2 + FIX64_FIELD_3, input.path, false));
+
+ IncidentReportArgs args1, args2, args3;
+ args1.setAll(true);
+ args1.setDest(0); // LOCAL
+ args2.setAll(true); // default to explicit
+ sp<SimpleListener> l = new SimpleListener();
+ requests.add(new ReportRequest(args1, l, output1.fd));
+ requests.add(new ReportRequest(args2, l, output2.fd));
+ requests.add(new ReportRequest(args3, l, output3.fd));
+ requests.setMainFd(STDOUT_FILENO);
+
+ CaptureStdout();
+ ASSERT_EQ(NO_ERROR, fs.Execute(&requests));
+ EXPECT_THAT(GetCapturedStdout(), StrEq("\x02\r" + STRING_FIELD_2));
+
+ string content, expect;
+ expect = VARINT_FIELD_1 + STRING_FIELD_2 + FIX64_FIELD_3;
+ char c = (char) expect.size();
+ EXPECT_TRUE(ReadFileToString(output1.path, &content));
+ EXPECT_THAT(content, StrEq(string("\x02") + c + expect));
+
+ expect = STRING_FIELD_2 + FIX64_FIELD_3;
+ c = (char) expect.size();
+ EXPECT_TRUE(ReadFileToString(output2.path, &content));
+ EXPECT_THAT(content, StrEq(string("\x02") + c + expect));
+
+ // because args3 doesn't set section, so it should receive nothing
+ EXPECT_TRUE(ReadFileToString(output3.path, &content));
+ EXPECT_THAT(content, StrEq(""));
+}
+
+TEST(SectionTest, TestMultipleRequestsBySpec) {
+ TemporaryFile input, output1, output2, output3;
+ FileSection fs(NOOP_PARSER, input.path);
+ ReportRequestSet requests;
+
+ ASSERT_TRUE(input.fd != -1);
+ ASSERT_TRUE(output1.fd != -1);
+ ASSERT_TRUE(output2.fd != -1);
+ ASSERT_TRUE(output3.fd != -1);
+
+ ASSERT_TRUE(WriteStringToFile(VARINT_FIELD_1 + STRING_FIELD_2 + FIX64_FIELD_3, input.path, false));
+
+ IncidentReportArgs args1, args2, args3, args4;
+ args1.setAll(true);
+ args2.setAll(true);
+ args4.setAll(true);
+ sp<SimpleListener> l = new SimpleListener();
+ requests.add(new ReportRequest(args1, l, output1.fd));
+ requests.add(new ReportRequest(args2, l, output2.fd));
+ requests.add(new ReportRequest(args3, l, output3.fd));
+ requests.setMainFd(STDOUT_FILENO);
+
+ CaptureStdout();
+ ASSERT_EQ(NO_ERROR, fs.Execute(&requests));
+ EXPECT_THAT(GetCapturedStdout(), StrEq("\x02\r" + STRING_FIELD_2));
+
+ string content, expect;
+ expect = STRING_FIELD_2 + FIX64_FIELD_3;
+ char c = (char) expect.size();
+
+ // output1 and output2 are the same
+ EXPECT_TRUE(ReadFileToString(output1.path, &content));
+ EXPECT_THAT(content, StrEq(string("\x02") + c + expect));
+ EXPECT_TRUE(ReadFileToString(output2.path, &content));
+ EXPECT_THAT(content, StrEq(string("\x02") + c + expect));
+
+ // because args3 doesn't set section, so it should receive nothing
+ EXPECT_TRUE(ReadFileToString(output3.path, &content));
+ EXPECT_THAT(content, StrEq(""));
}
\ No newline at end of file
diff --git a/cmds/incidentd/tests/section_list.cpp b/cmds/incidentd/tests/section_list.cpp
index f005335..3722c72 100644
--- a/cmds/incidentd/tests/section_list.cpp
+++ b/cmds/incidentd/tests/section_list.cpp
@@ -4,3 +4,18 @@
const Section* SECTION_LIST[] = {
NULL
};
+
+const uint8_t LOCAL = 0;
+const uint8_t EXPLICIT = 1;
+const uint8_t AUTOMATIC = 2;
+
+const Privacy* list[] = {
+ new Privacy(1, 1, LOCAL),
+ new Privacy(2, AUTOMATIC, (const char**)NULL),
+ NULL };
+
+const Privacy* PRIVACY_POLICY_LIST[] = {
+ new Privacy(0, list),
+ new Privacy(1, 9, AUTOMATIC),
+ NULL
+};
\ No newline at end of file
diff --git a/core/java/android/os/IncidentReportArgs.java b/core/java/android/os/IncidentReportArgs.java
index abb3161..fd0ebcf 100644
--- a/core/java/android/os/IncidentReportArgs.java
+++ b/core/java/android/os/IncidentReportArgs.java
@@ -35,6 +35,7 @@
private final IntArray mSections = new IntArray();
private final ArrayList<byte[]> mHeaders = new ArrayList<byte[]>();
private boolean mAll;
+ private int mDest;
/**
* Construct an incident report args with no fields.
@@ -69,6 +70,8 @@
for (int i=0; i<N; i++) {
out.writeByteArray(mHeaders.get(i));
}
+
+ out.writeInt(mDest);
}
public void readFromParcel(Parcel in) {
@@ -85,6 +88,8 @@
for (int i=0; i<N; i++) {
mHeaders.add(in.createByteArray());
}
+
+ mDest = in.readInt();
}
public static final Parcelable.Creator<IncidentReportArgs> CREATOR
@@ -118,7 +123,8 @@
}
sb.append(", ");
sb.append(mHeaders.size());
- sb.append(" headers)");
+ sb.append(" headers), ");
+ sb.append("Dest enum value: ").append(mDest);
return sb.toString();
}
@@ -133,6 +139,14 @@
}
/**
+ * Set this incident report privacy policy spec.
+ * @hide
+ */
+ public void setPrivacyPolicy(int dest) {
+ mDest = dest;
+ }
+
+ /**
* Add this section to the incident report. Skip if the input is smaller than 2 since section
* id are only valid for positive integer as Protobuf field id. Here 1 is reserved for Header.
*/
diff --git a/core/proto/android/os/incident.proto b/core/proto/android/os/incident.proto
index 5dfcd2a..aab4142 100644
--- a/core/proto/android/os/incident.proto
+++ b/core/proto/android/os/incident.proto
@@ -31,22 +31,12 @@
import "frameworks/base/core/proto/android/service/power.proto";
import "frameworks/base/core/proto/android/service/print.proto";
import "frameworks/base/core/proto/android/providers/settings.proto";
+import "frameworks/base/core/proto/android/os/incidentheader.proto";
import "frameworks/base/core/proto/android/os/kernelwake.proto";
import "frameworks/base/core/proto/android/os/procrank.proto";
package android.os;
-message IncidentHeaderProto {
- enum Cause {
- CAUSE_UNKNOWN = 0;
- CAUSE_USER = 1;
- CAUSE_ANR = 2;
- CAUSE_CRASH = 3;
- }
-
- Cause cause = 1;
-}
-
// privacy field options must not be set at this level because all
// the sections are able to be controlled and configured by section ids.
// Instead privacy field options need to be configured in each section proto message.
diff --git a/core/proto/android/os/incidentheader.proto b/core/proto/android/os/incidentheader.proto
new file mode 100644
index 0000000..55a0616
--- /dev/null
+++ b/core/proto/android/os/incidentheader.proto
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+syntax = "proto3";
+
+option java_multiple_files = true;
+option java_outer_classname = "IncidentHeaderProtoMetadata";
+
+package android.os;
+
+message IncidentHeaderProto {
+ enum Cause {
+ CAUSE_UNKNOWN = 0;
+ CAUSE_USER = 1;
+ CAUSE_ANR = 2;
+ CAUSE_CRASH = 3;
+ }
+
+ Cause cause = 1;
+}
+
diff --git a/libs/incident/include/android/os/IncidentReportArgs.h b/libs/incident/include/android/os/IncidentReportArgs.h
index 956ef6c..da80989 100644
--- a/libs/incident/include/android/os/IncidentReportArgs.h
+++ b/libs/incident/include/android/os/IncidentReportArgs.h
@@ -39,12 +39,13 @@
virtual status_t readFromParcel(const Parcel* in);
void setAll(bool all);
+ void setDest(int dest);
void addSection(int section);
void addHeader(const vector<int8_t>& header);
- inline bool all() const { return mAll; };
+ inline bool all() const { return mAll; }
bool containsSection(int section) const;
-
+ inline int dest() const { return mDest; }
inline const set<int>& sections() const { return mSections; }
inline const vector<vector<int8_t>>& headers() const { return mHeaders; }
@@ -54,6 +55,7 @@
set<int> mSections;
vector<vector<int8_t>> mHeaders;
bool mAll;
+ int mDest;
};
}
diff --git a/libs/incident/src/IncidentReportArgs.cpp b/libs/incident/src/IncidentReportArgs.cpp
index f604909..e628722 100644
--- a/libs/incident/src/IncidentReportArgs.cpp
+++ b/libs/incident/src/IncidentReportArgs.cpp
@@ -25,14 +25,16 @@
IncidentReportArgs::IncidentReportArgs()
:mSections(),
- mAll(false)
+ mAll(false),
+ mDest(-1)
{
}
IncidentReportArgs::IncidentReportArgs(const IncidentReportArgs& that)
:mSections(that.mSections),
mHeaders(that.mHeaders),
- mAll(that.mAll)
+ mAll(that.mAll),
+ mDest(that.mDest)
{
}
@@ -74,6 +76,11 @@
}
}
+ err = out->writeInt32(mDest);
+ if (err != NO_ERROR) {
+ return err;
+ }
+
return NO_ERROR;
}
@@ -120,6 +127,13 @@
}
}
+ int32_t dest;
+ err = in->readInt32(&dest);
+ if (err != NO_ERROR) {
+ return err;
+ }
+ mDest = dest;
+
return OK;
}
@@ -133,6 +147,12 @@
}
void
+IncidentReportArgs::setDest(int dest)
+{
+ mDest = dest;
+}
+
+void
IncidentReportArgs::addSection(int section)
{
if (!mAll) {
diff --git a/tools/incident_section_gen/main.cpp b/tools/incident_section_gen/main.cpp
index 23aafb2..7966d88 100644
--- a/tools/incident_section_gen/main.cpp
+++ b/tools/incident_section_gen/main.cpp
@@ -135,27 +135,24 @@
if (generatePrivacyFlags(field->message_type(), field_name, msgNames) &&
isDefaultDest(field)) break;
- printf("static Privacy %s = { %d, %d, %d, NULL, %s_LIST };\n", field_name, field->number(),
- (int) field->type(), p.dest(), field_name);
+ printf("Privacy %s(%d, %s_LIST);\n", field_name, field->number(), field_name);
hasDefaultFlags[i] = false;
break;
case FieldDescriptor::TYPE_STRING:
if (isDefaultDest(field) && p.patterns_size() == 0) break;
- printf("static const char* %s_patterns[] = {\n", field_name);
+ printf("const char* %s_patterns[] = {\n", field_name);
for (int i=0; i<p.patterns_size(); i++) {
// the generated string need to escape backslash as well, need to dup it here
printf(" \"%s\",\n", replaceAll(p.patterns(i), '\\', "\\\\").c_str());
}
printf(" NULL };\n");
- printf("static Privacy %s = { %d, %d, %d, %s_patterns };\n", field_name, field->number(),
- (int) field->type(), p.dest(), field_name);
+ printf("Privacy %s(%d, %d, %s_patterns);\n", field_name, field->number(), p.dest(), field_name);
hasDefaultFlags[i] = false;
break;
default:
if (isDefaultDest(field)) break;
- printf("static Privacy %s = { %d, %d, %d };\n", field_name, field->number(),
- (int) field->type(), p.dest());
+ printf("Privacy %s(%d, %d, %d);\n", field_name, field->number(), (int) field->type(), p.dest());
hasDefaultFlags[i] = false;
}
// add the field name to message map, true means it has default flags
@@ -213,20 +210,6 @@
printf(" NULL };\n");
emptyline();
- // generates DESTINATION enum values
- EnumDescriptor const* destination = Destination_descriptor();
- for (int i=0; i<destination->value_count(); i++) {
- EnumValueDescriptor const* val = destination->value(i);
- printf("const uint8_t %s = %d;\n", val->name().c_str(), val->number());
- }
- emptyline();
- printf("const uint8_t DEST_DEFAULT_VALUE = %d;\n", PrivacyFlags::default_instance().dest());
- emptyline();
- // populates string type and message type values
- printf("const uint8_t TYPE_STRING = %d;\n", (int) FieldDescriptor::TYPE_STRING);
- printf("const uint8_t TYPE_MESSAGE = %d;\n", (int) FieldDescriptor::TYPE_MESSAGE);
- emptyline();
-
// generates PRIVACY_POLICY
map<string, bool> messageNames;
if (generatePrivacyFlags(descriptor, "PRIVACY_POLICY", messageNames)) {