Implement PII Stripper, part 2
Implement EncodedBuffer that strip pii based on given privacy request.
The reason to implement another buffer is the length-delimited field's
size could change when its submessage gets stripped. It also intends to
keep the orignal data around for other requests to consume it.
In addition, the section implementation has adapted EncodedBuffer so
write out to each request's fd could be request-specific. The next step
is allow requests to set its privacy spec.
Notice the current design set the privacy spec of dropbox to AUTOMATIC,
this behavior might change in the future.
Bug: 64687253
Test: unit tests are writtern, see README.md for how to run unit tests.
Change-Id: I7ac236b8265ba9289dc6e17a8a5bf7f67ffb6bf5
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..d1872f9
--- /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 {
+ buf.reserve(bytesToWrite);
+ 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;
+}
\ No newline at end of file
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..23a611a 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,26 @@
}
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::end() const
{
if (mBuffers.empty() || mCurrentWritten < 0) return begin();
if (mCurrentWritten == BUFFER_SIZE)
@@ -279,7 +281,7 @@
}
size_t
-FdBuffer::iterator::bytesRead()
+FdBuffer::iterator::bytesRead() const
{
return mIndex * BUFFER_SIZE + mOffset;
}
diff --git a/cmds/incidentd/src/FdBuffer.h b/cmds/incidentd/src/FdBuffer.h
index e9a53ff..4c4823e 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,14 +86,10 @@
class iterator;
friend class iterator;
class iterator : public std::iterator<std::random_access_iterator_tag, uint8_t> {
- private:
- FdBuffer& mFdBuffer;
- size_t mIndex;
- size_t mOffset;
public:
- explicit iterator(FdBuffer& buffer, ssize_t index, ssize_t offset)
+ iterator(const FdBuffer& buffer, ssize_t index, ssize_t offset)
: mFdBuffer(buffer), mIndex(index), mOffset(offset) {}
- iterator& operator=(iterator& other) { return other; }
+ iterator& operator=(iterator& other) const { 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; }
@@ -105,14 +98,22 @@
return mIndex == other.mIndex && mOffset == other.mOffset;
}
bool operator!=(iterator other) const { return !(*this == other); }
+ int operator-(iterator other) const { return (int)bytesRead() - (int)other.bytesRead(); }
reference operator*() const { return mFdBuffer.mBuffers[mIndex][mOffset]; }
+ // return the snapshot of the current iterator
+ iterator snapshot() const { return iterator(mFdBuffer, mIndex, mOffset); }
+ // how many bytes are read
+ size_t bytesRead() const;
// random access could make the iterator out of bound
- size_t bytesRead();
- bool outOfBound() { return bytesRead() > mFdBuffer.size(); };
+ bool outOfBound() const { return bytesRead() > mFdBuffer.size(); }
+ private:
+ const FdBuffer& mFdBuffer;
+ size_t mIndex;
+ size_t mOffset;
};
- iterator begin() { return iterator(*this, 0, 0); }
- iterator end();
+ iterator begin() const { return iterator(*this, 0, 0); }
+ iterator end() const;
private:
vector<uint8_t*> mBuffers;
@@ -123,19 +124,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..a790efa
--- /dev/null
+++ b/cmds/incidentd/src/Privacy.cpp
@@ -0,0 +1,100 @@
+/*
+ * 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::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 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..53b0325
--- /dev/null
+++ b/cmds/incidentd/src/Privacy.h
@@ -0,0 +1,67 @@
+/*
+ * 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 CheckPremission(const Privacy* privacy) const;
+ bool RequireAll() const;
+};
+
+PrivacySpec get_default_dropbox_spec();
+
+#endif // PRIVACY_H
diff --git a/cmds/incidentd/src/Reporter.cpp b/cmds/incidentd/src/Reporter.cpp
index 4ffc119..722bd4f 100644
--- a/cmds/incidentd/src/Reporter.cpp
+++ b/cmds/incidentd/src/Reporter.cpp
@@ -17,6 +17,7 @@
#define LOG_TAG "incidentd"
#include "Reporter.h"
+#include "io_util.h"
#include "protobuf.h"
#include "report_directory.h"
@@ -38,20 +39,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),
@@ -69,7 +56,6 @@
ReportRequestSet::ReportRequestSet()
:mRequests(),
mSections(),
- mWritableCount(0),
mMainFd(-1)
{
}
@@ -84,45 +70,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
diff --git a/cmds/incidentd/src/Reporter.h b/cmds/incidentd/src/Reporter.h
index 509611c..b74cc42 100644
--- a/cmds/incidentd/src/Reporter.h
+++ b/cmds/incidentd/src/Reporter.h
@@ -52,21 +52,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..a04804c 100644
--- a/cmds/incidentd/src/Section.cpp
+++ b/cmds/incidentd/src/Section.cpp
@@ -16,8 +16,14 @@
#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>
@@ -66,6 +72,7 @@
return pid;
}
+// ================================================================================
static status_t killChild(pid_t pid) {
int status;
kill(pid, SIGKILL);
@@ -87,6 +94,72 @@
}
// ================================================================================
+static const Privacy*
+GetPrivacyOfSection(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
+WriteToRequest(const int id, const int fd, EncodedBuffer& buffer, const PrivacySpec& spec)
+{
+ if (fd < 0) return EBADF;
+ status_t err = NO_ERROR;
+ uint8_t buf[20];
+
+ buffer.clear(); // clear before strip
+ err = buffer.strip(spec); // TODO: don't have to strip again if the spec is the same.
+ if (err != NO_ERROR || buffer.size() == 0) return err;
+ uint8_t *p = write_length_delimited_tag_header(buf, id, buffer.size());
+ err = write_all(fd, buf, p-buf);
+ if (err == NO_ERROR) {
+ err = buffer.flush(fd);
+ ALOGD("Section %d flushed %zu bytes to fd %d with spec %d", id, buffer.size(), fd, spec.dest);
+ }
+ return err;
+}
+
+static status_t
+WriteToReportRequests(const int id, const FdBuffer& buffer, ReportRequestSet* requests)
+{
+ status_t err = EBADF;
+ EncodedBuffer encodedBuffer(buffer, GetPrivacyOfSection(id));
+ int writeable = 0;
+
+ // The streaming ones
+ for (ReportRequestSet::iterator it = requests->begin(); it != requests->end(); it++) {
+ sp<ReportRequest> request = *it;
+ PrivacySpec spec; // TODO: this should be derived from each request.
+ err = WriteToRequest(id, request->fd, encodedBuffer, spec);
+ if (err != NO_ERROR) {
+ request->err = err;
+ } else {
+ writeable++;
+ }
+ }
+
+ // The dropbox file
+ if (requests->mainFd() >= 0) {
+ err = WriteToRequest(id, requests->mainFd(), encodedBuffer, get_default_dropbox_spec());
+ if (err != NO_ERROR) {
+ requests->setMainFd(-1);
+ } else {
+ writeable++;
+ }
+ }
+ // 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)
{
@@ -96,15 +169,6 @@
{
}
-status_t
-Section::WriteHeader(ReportRequestSet* requests, size_t size) const
-{
- 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);
-}
-
// ================================================================================
FileSection::FileSection(int id, const char* filename, const int64_t timeoutMs)
: Section(id, timeoutMs), mFilename(filename) {
@@ -113,7 +177,9 @@
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);
@@ -155,8 +221,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 = WriteToReportRequests(this->id, buffer, requests);
if (err != NO_ERROR) {
ALOGW("FileSection '%s' failed writing: %s", this->name.string(), strerror(-err));
return err;
@@ -313,8 +378,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 = WriteToReportRequests(this->id, buffer, requests);
if (err != NO_ERROR) {
ALOGW("WorkerThreadSection '%s' failed writing: '%s'", this->name.string(), strerror(-err));
return err;
@@ -324,7 +388,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;
@@ -429,8 +494,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 = WriteToReportRequests(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..9cfd696 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,6 @@
virtual ~Section();
virtual status_t Execute(ReportRequestSet* requests) const = 0;
-
- status_t WriteHeader(ReportRequestSet* requests, size_t size) 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 f196ddc1..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..3c1b44b 100644
--- a/cmds/incidentd/tests/Reporter_test.cpp
+++ b/cmds/incidentd/tests/Reporter_test.cpp
@@ -127,29 +127,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..ab0f054 100644
--- a/cmds/incidentd/tests/Section_test.cpp
+++ b/cmds/incidentd/tests/Section_test.cpp
@@ -22,8 +22,16 @@
#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 std;
using ::testing::StrEq;
@@ -31,22 +39,9 @@
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
- ReportRequestSet requests;
-
- requests.setMainFd(STDOUT_FILENO);
-
- 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"));
-}
-
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 +53,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 +79,51 @@
}
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));
}
\ 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