diff options
25 files changed, 1005 insertions, 229 deletions
diff --git a/cmds/incident_helper/IncidentHelper.cpp b/cmds/incident_helper/IncidentHelper.cpp index fba5e662b7c1..787d3a1557d6 100644 --- a/cmds/incident_helper/IncidentHelper.cpp +++ b/cmds/incident_helper/IncidentHelper.cpp @@ -61,6 +61,21 @@ SetTableField(::google::protobuf::Message* message, string field_name, string fi } // ================================================================================ +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 @@ status_t ProcrankParser::Parse(const int in, const int out) const { } 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 f319c419fcd6..f6579a2d3736 100644 --- a/cmds/incident_helper/IncidentHelper.h +++ b/cmds/incident_helper/IncidentHelper.h @@ -36,6 +36,17 @@ public: }; /** + * 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 333344b8ce86..296d3001b7bb 100644 --- a/cmds/incident_helper/main.cpp +++ b/cmds/incident_helper/main.cpp @@ -41,9 +41,11 @@ static TextParserBase* selectParser(int section) { 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 835a7b94507b..830bf9e66cde 100644 --- a/cmds/incidentd/Android.mk +++ b/cmds/incidentd/Android.mk @@ -23,10 +23,13 @@ include $(CLEAR_VARS) 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 @@ LOCAL_GENERATED_SOURCES += $(GEN) gen_src_dir:= GEN:= +ifeq ($(BUILD_WITH_INCIDENTD_RC), true) LOCAL_INIT_RC := incidentd.rc +endif include $(BUILD_EXECUTABLE) @@ -88,12 +93,16 @@ LOCAL_CFLAGS := -Werror -Wall -Wno-unused-variable -Wunused-parameter 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 000000000000..d1872f96b9df --- /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 000000000000..ea8603a585d7 --- /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 4d6a36cdba2e..23a611acd494 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 @@ FdBuffer::readProcessedDataInStream(int fd, int toFd, int fromFd, int64_t timeou } 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 @@ FdBuffer::iterator::operator+(size_t offset) } 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 e9a53ffe513a..4c4823e5a4d8 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 @@ public: /** * 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 @@ public: * 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 @@ public: 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 @@ public: 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 @@ private: 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 000000000000..a790efa2f611 --- /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 000000000000..53b0325b15f4 --- /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 4ffc11984224..722bd4fa8b87 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 @@ ReportRequest::~ReportRequest() ReportRequestSet::ReportRequestSet() :mRequests(), mSections(), - mWritableCount(0), mMainFd(-1) { } @@ -84,45 +70,12 @@ ReportRequestSet::add(const sp<ReportRequest>& request) { 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 509611c34d4b..b74cc4290306 100644 --- a/cmds/incidentd/src/Reporter.h +++ b/cmds/incidentd/src/Reporter.h @@ -52,21 +52,16 @@ public: 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 ac87fe3b1e40..a04804c8c770 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 @@ forkAndExecuteIncidentHelper(const int id, const char* name, Fpipe& p2cPipe, Fpi return pid; } +// ================================================================================ static status_t killChild(pid_t pid) { int status; kill(pid, SIGKILL); @@ -87,22 +94,79 @@ static status_t waitForChild(pid_t pid) { } // ================================================================================ -Section::Section(int i, const int64_t timeoutMs) - :id(i), timeoutMs(timeoutMs) +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; } -Section::~Section() +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; } -status_t -Section::WriteHeader(ReportRequestSet* requests, size_t size) const +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) +{ +} + +Section::~Section() { - 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); } // ================================================================================ @@ -113,7 +177,9 @@ FileSection::FileSection(int id, const char* filename, const int64_t timeoutMs) 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 @@ status_t FileSection::Execute(ReportRequestSet* requests) const { 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 @@ WorkerThreadSection::Execute(ReportRequestSet* requests) const // 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 @@ WorkerThreadSection::Execute(ReportRequestSet* requests) const } // ================================================================================ -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 @@ CommandSection::Execute(ReportRequestSet* requests) const 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 93b4848f5bd8..9cfd696c17f0 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 @@ public: 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 000000000000..f043d367d982 --- /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 000000000000..320dd6c386d2 --- /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 b865339a9b98..05de8314deb4 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); +} + +uint32_t read_field_id(uint32_t varint) +{ + return varint >> 3; +} -uint8_t* +uint8_t* write_raw_varint(uint8_t* buf, uint32_t val) { uint8_t* p = buf; @@ -32,7 +41,7 @@ write_raw_varint(uint8_t* buf, uint32_t val) } } -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 @@ write_length_delimited_tag_header(uint8_t* buf, uint32_t fieldId, size_t size) 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 f196ddc11967..fb0d69dbf755 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_raw_varint(uint8_t* buf, uint32_t val); */ 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 1abdb5284001..4d9efd760dee 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 000000000000..c51520b36a28 --- /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 403a2abf670a..d1436b2cc36f 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 @@ const std::string HEAD = "[OK]"; 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 @@ public: } 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 @@ TEST_F(FdBufferTest, IterateEmpty) { 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, ReadInStreamEmpty) { 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 a77474199d27..3c1b44b6a515 100644 --- a/cmds/incidentd/tests/Reporter_test.cpp +++ b/cmds/incidentd/tests/Reporter_test.cpp @@ -127,29 +127,7 @@ TEST_F(ReporterTest, IncidentReportArgs) { 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 93771ff30b64..ab0f05498232 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::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 - 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 @@ TEST(SectionTest, FileSection) { 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, CommandSectionConstructor) { } 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 f0053355bd24..3722c7244ed7 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/tools/incident_section_gen/main.cpp b/tools/incident_section_gen/main.cpp index 23aafb2626b8..7966d886d5a0 100644 --- a/tools/incident_section_gen/main.cpp +++ b/tools/incident_section_gen/main.cpp @@ -135,27 +135,24 @@ static bool generatePrivacyFlags(const Descriptor* descriptor, const char* alias 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 @@ static bool generateSectionListCpp(Descriptor const* descriptor) { 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)) { |