summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Narayan Kamath <narayan@google.com> 2020-10-06 09:15:34 +0100
committer Narayan Kamath <narayan@google.com> 2020-10-06 11:41:41 +0100
commit11700c0f4057ebc61f10d50384760e1156b91090 (patch)
treec99efaffb4ec4ee9cce8f8df3b83d93d0037f5b9
parent438eceec7e431cae18c2a1e2afe97a4a29a2965a (diff)
Fix Redaction calculation.
The redaction code in the FUSE daemon made mistaken assumptions about the ranges returned by ExifInterface, particularly that they were inclusive of the end index. This sometimes leads to extra bytes being redacted. The code has now been revamped to be clearer and better tested.. Test: atest RedactionInfoTest Test: atest ScopedStorageTest Bug: 169389401 Change-Id: I7ff2107cb62629b3ec7d9beba898d688a3aa1add
-rw-r--r--jni/FuseDaemon.cpp51
-rw-r--r--jni/RedactionInfo.cpp75
-rw-r--r--jni/RedactionInfoTest.cpp383
-rw-r--r--jni/include/libfuse_jni/RedactionInfo.h47
4 files changed, 300 insertions, 256 deletions
diff --git a/jni/FuseDaemon.cpp b/jni/FuseDaemon.cpp
index 1b9163c7e..83747b678 100644
--- a/jni/FuseDaemon.cpp
+++ b/jni/FuseDaemon.cpp
@@ -1066,10 +1066,6 @@ static void do_read(fuse_req_t req, size_t size, off_t off, struct fuse_file_inf
fuse_reply_data(req, &buf, (enum fuse_buf_copy_flags) 0);
}
-static bool range_contains(const RedactionRange& rr, off_t off) {
- return rr.first <= off && off <= rr.second;
-}
-
/**
* Sets the parameters for a fuse_buf that reads from memory, including flags.
* Makes buf->mem point to an already mapped region of zeroized memory.
@@ -1096,24 +1092,17 @@ static void create_file_fuse_buf(size_t size, off_t pos, int fd, fuse_buf* buf)
static void do_read_with_redaction(fuse_req_t req, size_t size, off_t off, fuse_file_info* fi) {
handle* h = reinterpret_cast<handle*>(fi->fh);
- auto overlapping_rr = h->ri->getOverlappingRedactionRanges(size, off);
- if (overlapping_rr->size() <= 0) {
- // no relevant redaction ranges for this request
+ std::vector<ReadRange> ranges;
+ h->ri->getReadRanges(off, size, &ranges);
+
+ // As an optimization, return early if there are no ranges to redact.
+ if (ranges.size() == 0) {
do_read(req, size, off, fi);
return;
}
- // the number of buffers we need, if the read doesn't start or end with
- // a redaction range.
- int num_bufs = overlapping_rr->size() * 2 + 1;
- if (overlapping_rr->front().first <= off) {
- // the beginning of the read request is redacted
- num_bufs--;
- }
- if (overlapping_rr->back().second >= off + size) {
- // the end of the read request is redacted
- num_bufs--;
- }
+
+ const size_t num_bufs = ranges.size();
auto bufvec_ptr = std::unique_ptr<fuse_bufvec, decltype(free)*>{
reinterpret_cast<fuse_bufvec*>(
malloc(sizeof(fuse_bufvec) + (num_bufs - 1) * sizeof(fuse_buf))),
@@ -1125,31 +1114,13 @@ static void do_read_with_redaction(fuse_req_t req, size_t size, off_t off, fuse_
bufvec.idx = 0;
bufvec.off = 0;
- int rr_idx = 0;
- off_t start = off;
- // Add a dummy redaction range to make sure we don't go out of vector
- // limits when computing the end of the last non-redacted range.
- // This ranges is invalid because its starting point is larger than it's ending point.
- overlapping_rr->push_back(RedactionRange(LLONG_MAX, LLONG_MAX - 1));
-
for (int i = 0; i < num_bufs; ++i) {
- off_t end;
- if (range_contains(overlapping_rr->at(rr_idx), start)) {
- // Handle a redacted range
- // end should be the end of the redacted range, but can't be out of
- // the read request bounds
- end = std::min(static_cast<off_t>(off + size - 1), overlapping_rr->at(rr_idx).second);
- create_mem_fuse_buf(/*size*/ end - start + 1, &(bufvec.buf[i]), get_fuse(req));
- ++rr_idx;
+ const ReadRange& range = ranges[i];
+ if (range.is_redaction) {
+ create_mem_fuse_buf(range.size, &(bufvec.buf[i]), get_fuse(req));
} else {
- // Handle a non-redacted range
- // end should be right before the next redaction range starts or
- // the end of the read request
- end = std::min(static_cast<off_t>(off + size - 1),
- overlapping_rr->at(rr_idx).first - 1);
- create_file_fuse_buf(/*size*/ end - start + 1, start, h->fd, &(bufvec.buf[i]));
+ create_file_fuse_buf(range.size, range.start, h->fd, &(bufvec.buf[i]));
}
- start = end + 1;
}
fuse_reply_data(req, &bufvec, static_cast<fuse_buf_copy_flags>(0));
diff --git a/jni/RedactionInfo.cpp b/jni/RedactionInfo.cpp
index 17de22e05..758fa4ccf 100644
--- a/jni/RedactionInfo.cpp
+++ b/jni/RedactionInfo.cpp
@@ -16,6 +16,8 @@
#include "include/libfuse_jni/RedactionInfo.h"
+#include <android-base/logging.h>
+
using std::unique_ptr;
using std::vector;
@@ -53,8 +55,8 @@ static void mergeOverlappingRedactionRanges(vector<RedactionRange>& ranges) {
* This function assumes redaction_ranges_ within RedactionInfo is sorted.
*/
bool RedactionInfo::hasOverlapWithReadRequest(size_t size, off64_t off) const {
- if (!isRedactionNeeded() || off > redaction_ranges_.back().second ||
- off + size < redaction_ranges_.front().first) {
+ if (!isRedactionNeeded() || off >= redaction_ranges_.back().second ||
+ off + size <= redaction_ranges_.front().first) {
return false;
}
return true;
@@ -91,26 +93,69 @@ RedactionInfo::RedactionInfo(int redaction_ranges_num, const off64_t* redaction_
unique_ptr<vector<RedactionRange>> RedactionInfo::getOverlappingRedactionRanges(size_t size,
off64_t off) const {
if (hasOverlapWithReadRequest(size, off)) {
+ const off64_t start = off;
+ const off64_t end = static_cast<off64_t>(off + size);
+
auto first_redaction = redaction_ranges_.end();
- auto last_redaction = redaction_ranges_.end();
+ auto last_redaction = redaction_ranges_.begin();
for (auto iter = redaction_ranges_.begin(); iter != redaction_ranges_.end(); ++iter) {
- const RedactionRange& rr = *iter;
- // Look for the first range that overlaps with the read request
- if (first_redaction == redaction_ranges_.end() && off <= rr.second &&
- off + size >= rr.first) {
- first_redaction = iter;
- } else if (first_redaction != redaction_ranges_.end() && off + size < rr.first) {
- // Once we're in the read request range, we start checking if
- // we're out of it so we can return the result to the caller
+ if (iter->second >= start && iter->first < end) {
+ if (iter < first_redaction) first_redaction = iter;
+ if (iter > last_redaction) last_redaction = iter;
+ }
+
+ if (iter->first >= end) {
break;
}
- last_redaction = iter;
- }
- if (first_redaction != redaction_ranges_.end()) {
- return std::make_unique<vector<RedactionRange>>(first_redaction, last_redaction + 1);
}
+
+ CHECK(first_redaction <= last_redaction);
+ return std::make_unique<vector<RedactionRange>>(first_redaction, last_redaction + 1);
}
return std::make_unique<vector<RedactionRange>>();
}
+
+void RedactionInfo::getReadRanges(off64_t off, size_t size, std::vector<ReadRange>* out) const {
+ auto rr = getOverlappingRedactionRanges(size, off);
+ if (rr->size() == 0) {
+ return;
+ }
+
+ // Add a sentinel redaction range to make sure we don't go out of vector
+ // limits when computing the end of the last non-redacted range.
+ // This ranges is invalid because its starting point is larger than it's ending point.
+ rr->push_back(RedactionRange(LLONG_MAX, LLONG_MAX - 1));
+
+ int rr_idx = 0;
+ off64_t start = off;
+ const off64_t read_end = static_cast<off64_t>(start + size);
+
+ while (true) {
+ const auto& current_redaction = rr->at(rr_idx);
+ off64_t end;
+ if (current_redaction.first <= start && start < current_redaction.second) {
+ // |start| is within a redaction range, so we must serve a redacted read.
+ end = std::min(read_end, current_redaction.second);
+ out->push_back(ReadRange(start, (end - start), true /* is_redaction */));
+ rr_idx++;
+ } else {
+ // |start| is either before the current redaction range, or beyond the end
+ // of the last redaction range, in which case redaction.first is LLONG_MAX.
+ end = std::min(read_end, current_redaction.first);
+ out->push_back(ReadRange(start, (end - start), false /* is_redaction */));
+ }
+
+ start = end;
+ // If we've done things correctly, start must point at |off + size| once we're
+ // through computing all of our redaction ranges.
+ if (start == read_end) {
+ break;
+ }
+ // If we're continuing iteration, the start of the next range must always be within
+ // the read bounds.
+ CHECK(start < read_end);
+ }
+}
+
} // namespace fuse
} // namespace mediaprovider
diff --git a/jni/RedactionInfoTest.cpp b/jni/RedactionInfoTest.cpp
index 9d98058e1..9662882f1 100644
--- a/jni/RedactionInfoTest.cpp
+++ b/jni/RedactionInfoTest.cpp
@@ -19,6 +19,7 @@
#include <gtest/gtest.h>
#include <memory>
+#include <ostream>
#include <vector>
#include "libfuse_jni/RedactionInfo.h"
@@ -28,249 +29,257 @@ using namespace mediaprovider::fuse;
using std::unique_ptr;
using std::vector;
-unique_ptr<vector<RedactionRange>> createRedactionRangeVector(int num_rr, off64_t* rr) {
- auto res = std::make_unique<vector<RedactionRange>>();
- for (int i = 0; i < num_rr; ++i) {
- res->push_back(RedactionRange(rr[2 * i], rr[2 * i + 1]));
- }
- return res;
+std::ostream& operator<<(std::ostream& os, const ReadRange& rr) {
+ os << "{ " << rr.start << ", " << rr.size << ", " << rr.is_redaction << " }";
+ return os;
}
-/**
- * Test the case where there are no redaction ranges.
- */
TEST(RedactionInfoTest, testNoRedactionRanges) {
RedactionInfo info(0, nullptr);
EXPECT_EQ(0, info.size());
EXPECT_EQ(false, info.isRedactionNeeded());
- auto overlapping_rr = info.getOverlappingRedactionRanges(/*size*/ 1000, /*off*/ 1000);
- EXPECT_EQ(0, overlapping_rr->size());
+ std::vector<ReadRange> out;
+ info.getReadRanges(0, std::numeric_limits<size_t>::max(), &out);
+ EXPECT_EQ(0, out.size());
}
-/**
- * Test the case where there is 1 redaction range.
- */
+// Test the case where there is 1 redaction range.
TEST(RedactionInfoTest, testSingleRedactionRange) {
off64_t ranges[2] = {
1,
10,
};
+
RedactionInfo info(1, ranges);
EXPECT_EQ(1, info.size());
EXPECT_EQ(true, info.isRedactionNeeded());
- // Overlapping ranges
- auto overlapping_rr = info.getOverlappingRedactionRanges(/*size*/ 1000, /*off*/ 0);
- EXPECT_EQ(*(createRedactionRangeVector(1, ranges)), *overlapping_rr);
-
- overlapping_rr = info.getOverlappingRedactionRanges(/*size*/ 5, /*off*/ 0);
- EXPECT_EQ(*(createRedactionRangeVector(1, ranges)), *overlapping_rr);
-
- overlapping_rr = info.getOverlappingRedactionRanges(/*size*/ 5, /*off*/ 5);
- EXPECT_EQ(*(createRedactionRangeVector(1, ranges)), *overlapping_rr);
-
- overlapping_rr = info.getOverlappingRedactionRanges(/*size*/ 10, /*off*/ 1);
- EXPECT_EQ(*(createRedactionRangeVector(1, ranges)), *overlapping_rr);
- overlapping_rr = info.getOverlappingRedactionRanges(/*size*/ 1, /*off*/ 1);
- EXPECT_EQ(*(createRedactionRangeVector(1, ranges)), *overlapping_rr);
-
- // Non-overlapping range
- overlapping_rr = info.getOverlappingRedactionRanges(/*size*/ 100, /*off*/ 11);
- EXPECT_EQ(*(createRedactionRangeVector(0, nullptr)), *overlapping_rr);
-
- overlapping_rr = info.getOverlappingRedactionRanges(/*size*/ 1, /*off*/ 11);
- EXPECT_EQ(*(createRedactionRangeVector(0, nullptr)), *overlapping_rr);
+ // Overlapping ranges
+ std::vector<ReadRange> out;
+ info.getReadRanges(0, 1000, &out); // read offsets [0, 1000)
+ EXPECT_EQ(3, out.size());
+ EXPECT_EQ(ReadRange(0, 1, false), out[0]);
+ EXPECT_EQ(ReadRange(1, 9, true), out[1]);
+ EXPECT_EQ(ReadRange(10, 990, false), out[2]);
+
+ out.clear();
+ info.getReadRanges(0, 5, &out); // read offsets [0, 5)
+ EXPECT_EQ(2, out.size());
+ EXPECT_EQ(ReadRange(0, 1, false), out[0]); // offsets: [0, 1) len = 1
+ EXPECT_EQ(ReadRange(1, 4, true), out[1]); // offsets: [1, 5) len = 4
+
+ out.clear();
+ info.getReadRanges(1, 10, &out); // read offsets [1, 11)
+ EXPECT_EQ(2, out.size());
+ EXPECT_EQ(ReadRange(1, 9, true), out[0]); // offsets: [1, 10) len = 9
+ EXPECT_EQ(ReadRange(10, 1, false), out[1]); // offsets: [10, 11) len = 1
+
+ // Read ranges that start or end with the boundary of the redacted area.
+ out.clear();
+ info.getReadRanges(5, 5, &out); // read offsets [5, 10)
+ EXPECT_EQ(1, out.size());
+ EXPECT_EQ(ReadRange(5, 5, true), out[0]); // offsets: [5, 10) len = 5
+
+ out.clear();
+ info.getReadRanges(1, 5, &out); // read offsets [1, 6)
+ EXPECT_EQ(1, out.size());
+ EXPECT_EQ(ReadRange(1, 5, true), out[0]); // offsets: [1, 6) len = 5
+
+ // Read ranges adjoining the redacted area.
+ out.clear();
+ info.getReadRanges(10, 10, &out); // read offsets [10, 20)
+ EXPECT_EQ(0, out.size());
+
+ out.clear();
+ info.getReadRanges(0, 1, &out); // read offsets [0, 1)
+ EXPECT_EQ(0, out.size());
+
+ // Read Range outside the redacted area.
+ out.clear();
+ info.getReadRanges(200, 10, &out); // read offsets [200, 210)
+ EXPECT_EQ(0, out.size());
}
-/**
- * Test the case where the redaction ranges don't require sorting or merging
- */
+// Multiple redaction ranges within a given area.
TEST(RedactionInfoTest, testSortedAndNonOverlappingRedactionRanges) {
- off64_t ranges[6] = {
- 1, 10, 15, 21, 32, 40,
- };
+ // [10, 20), [30, 40), [40, 50)
+ off64_t ranges[4] = {10, 20, 30, 40};
- RedactionInfo info = RedactionInfo(3, ranges);
- EXPECT_EQ(3, info.size());
+ RedactionInfo info = RedactionInfo(2, ranges);
+ EXPECT_EQ(2, info.size());
EXPECT_EQ(true, info.isRedactionNeeded());
- // Read request strictly contains all ranges: [0, 49]
- auto overlapping_rr = info.getOverlappingRedactionRanges(/*size*/ 50, /*off*/ 0);
- off64_t expected1[] = {
- 1, 10, 15, 21, 32, 40,
- };
- EXPECT_EQ(*(createRedactionRangeVector(3, expected1)), *overlapping_rr);
-
- // Read request strictly contains a subset of the ranges: [15, 40]
- overlapping_rr = info.getOverlappingRedactionRanges(/*size*/ 26, /*off*/ 15);
- off64_t expected2[] = {
- 15,
- 21,
- 32,
- 40,
- };
- EXPECT_EQ(*(createRedactionRangeVector(2, expected2)), *overlapping_rr);
-
- // Read request intersects with a subset of the ranges" [16, 32]
- overlapping_rr = info.getOverlappingRedactionRanges(/*size*/ 17, /*off*/ 16);
- EXPECT_EQ(*(createRedactionRangeVector(2, expected2)), *overlapping_rr);
+ std::vector<ReadRange> out;
+ info.getReadRanges(0, 40, &out); // read offsets [0, 40)
+ EXPECT_EQ(4, out.size());
+ EXPECT_EQ(ReadRange(0, 10, false), out[0]); // offsets: [0, 10) len = 10
+ EXPECT_EQ(ReadRange(10, 10, true), out[1]); // offsets: [10, 20) len = 10
+ EXPECT_EQ(ReadRange(20, 10, false), out[2]); // offsets: [20, 30) len = 10
+ EXPECT_EQ(ReadRange(30, 10, true), out[3]); // offsets [30, 40) len = 10
+
+ // Read request straddling two ranges.
+ out.clear();
+ info.getReadRanges(5, 30, &out); // read offsets [5, 35)
+ EXPECT_EQ(4, out.size());
+ EXPECT_EQ(ReadRange(5, 5, false), out[0]); // offsets: [5, 10) len = 5
+ EXPECT_EQ(ReadRange(10, 10, true), out[1]); // offsets: [10, 20) len = 10
+ EXPECT_EQ(ReadRange(20, 10, false), out[2]); // offsets: [20, 30) len = 10
+ EXPECT_EQ(ReadRange(30, 5, true), out[3]); // offsets [30, 35) len = 5
+
+ // Read request overlapping first range only.
+ out.clear();
+ info.getReadRanges(5, 10, &out); // read offsets [5, 15)
+ EXPECT_EQ(2, out.size());
+ EXPECT_EQ(ReadRange(5, 5, false), out[0]); // offsets: [5, 10) len = 5
+ EXPECT_EQ(ReadRange(10, 5, true), out[1]); // offsets: [10, 15) len = 5
+
+ // Read request overlapping last range only.
+ out.clear();
+ info.getReadRanges(35, 10, &out); // read offsets [35, 45)
+ EXPECT_EQ(2, out.size());
+ EXPECT_EQ(ReadRange(35, 5, true), out[0]); // offsets: [35, 40) len = 5
+ EXPECT_EQ(ReadRange(40, 5, false), out[1]); // offsets: [40, 45) len = 5
+
+ // Read request overlapping no ranges.
+ out.clear();
+ info.getReadRanges(0, 10, &out); // read offsets [0, 10)
+ EXPECT_EQ(0, out.size());
+ out.clear();
+ info.getReadRanges(40, 10, &out); // read offsets [40, 50)
+ EXPECT_EQ(0, out.size());
}
-/**
- * Test the case where the redaction ranges require sorting
- */
-TEST(RedactionInfoTest, testSortRedactionRanges) {
- off64_t ranges[6] = {
- 1, 10, 32, 40, 15, 21,
- };
+TEST(RedactionInfoTest, testRedactionRangesSorted) {
+ off64_t ranges[6] = {30, 40, 50, 60, 10, 20};
RedactionInfo info = RedactionInfo(3, ranges);
EXPECT_EQ(3, info.size());
EXPECT_EQ(true, info.isRedactionNeeded());
- // Read request strictly contains all ranges: [0, 49]
- auto overlapping_rr = info.getOverlappingRedactionRanges(/*size*/ 50, /*off*/ 0);
- off64_t expected1[] = {
- 1, 10, 15, 21, 32, 40,
- };
- EXPECT_EQ(*(createRedactionRangeVector(3, expected1)), *overlapping_rr);
-
- // Read request strictly contains a subset of the ranges: [15, 40]
- overlapping_rr = info.getOverlappingRedactionRanges(/*size*/ 26, /*off*/ 15);
- off64_t expected2[] = {
- 15,
- 21,
- 32,
- 40,
- };
- EXPECT_EQ(*(createRedactionRangeVector(2, expected2)), *overlapping_rr);
-
- // Read request intersects with a subset of the ranges" [16, 32]
- overlapping_rr = info.getOverlappingRedactionRanges(/*size*/ 17, /*off*/ 16);
- EXPECT_EQ(*(createRedactionRangeVector(2, expected2)), *overlapping_rr);
+ std::vector<ReadRange> out;
+ info.getReadRanges(0, 60, &out); // read offsets [0, 60)
+ EXPECT_EQ(6, out.size());
+ EXPECT_EQ(ReadRange(0, 10, false), out[0]); // offsets: [0, 10) len = 10
+ EXPECT_EQ(ReadRange(10, 10, true), out[1]); // offsets: [10, 20) len = 10
+ EXPECT_EQ(ReadRange(20, 10, false), out[2]); // offsets: [20, 30) len = 10
+ EXPECT_EQ(ReadRange(30, 10, true), out[3]); // offsets [30, 40) len = 10
+ EXPECT_EQ(ReadRange(40, 10, false), out[4]); // offsets [40, 50) len = 10
+ EXPECT_EQ(ReadRange(50, 10, true), out[5]); // offsets [50, 60) len = 10
+
+ // Read request overlapping first range only.
+ out.clear();
+ info.getReadRanges(5, 10, &out); // read offsets [5, 15)
+ EXPECT_EQ(2, out.size());
+ EXPECT_EQ(ReadRange(5, 5, false), out[0]); // offsets: [5, 10) len = 5
+ EXPECT_EQ(ReadRange(10, 5, true), out[1]); // offsets: [10, 15) len = 5
+
+ // Read request overlapping last range only.
+ out.clear();
+ info.getReadRanges(55, 10, &out); // read offsets [55, 65)
+ EXPECT_EQ(2, out.size());
+ EXPECT_EQ(ReadRange(55, 5, true), out[0]); // offsets: [55, 60) len = 5
+ EXPECT_EQ(ReadRange(60, 5, false), out[1]); // offsets: [60, 65) len = 5
+
+ // Read request overlapping no ranges.
+ out.clear();
+ info.getReadRanges(0, 10, &out); // read offsets [0, 10)
+ EXPECT_EQ(0, out.size());
+ out.clear();
+ info.getReadRanges(60, 10, &out); // read offsets [60, 70)
+ EXPECT_EQ(0, out.size());
}
-/**
- * Test the case where the redaction ranges require sorting or merging
- */
+// Test that the ranges are both sorted and merged
TEST(RedactionInfoTest, testSortAndMergeRedactionRanges) {
- off64_t ranges[8] = {
- 35, 40, 1, 10, 32, 35, 15, 21,
- };
+ // Ranges are: [10, 20), [25, 40), [50, 60)
+ off64_t ranges[8] = {30, 40, 10, 20, 25, 30, 50, 60};
RedactionInfo info = RedactionInfo(4, ranges);
EXPECT_EQ(3, info.size());
EXPECT_EQ(true, info.isRedactionNeeded());
- // Read request strictly contains all ranges: [0, 49]
- auto overlapping_rr = info.getOverlappingRedactionRanges(/*size*/ 50, /*off*/ 0);
- off64_t expected1[] = {
- 1, 10, 15, 21, 32, 40,
- };
- EXPECT_EQ(*(createRedactionRangeVector(3, expected1)), *overlapping_rr);
-
- // Read request strictly contains a subset of the ranges: [15, 40]
- overlapping_rr = info.getOverlappingRedactionRanges(/*size*/ 26, /*off*/ 15);
- off64_t expected2[] = {
- 15,
- 21,
- 32,
- 40,
- };
- EXPECT_EQ(*(createRedactionRangeVector(2, expected2)), *overlapping_rr);
-
- // Read request intersects with a subset of the ranges" [16, 32]
- overlapping_rr = info.getOverlappingRedactionRanges(/*size*/ 17, /*off*/ 16);
- EXPECT_EQ(*(createRedactionRangeVector(2, expected2)), *overlapping_rr);
+ std::vector<ReadRange> out;
+ info.getReadRanges(0, 60, &out); // read offsets [0, 60)
+ EXPECT_EQ(6, out.size());
+ EXPECT_EQ(ReadRange(0, 10, false), out[0]); // offsets: [0, 10) len = 10
+ EXPECT_EQ(ReadRange(10, 10, true), out[1]); // offsets: [10, 20) len = 10
+ EXPECT_EQ(ReadRange(20, 5, false), out[2]); // offsets: [20, 25) len = 5
+ EXPECT_EQ(ReadRange(25, 15, true), out[3]); // offsets [25, 40) len = 15
+ EXPECT_EQ(ReadRange(40, 10, false), out[4]); // offsets [40, 50) len = 10
+ EXPECT_EQ(ReadRange(50, 10, true), out[5]); // offsets [50, 60) len = 10
}
-/**
- * Test the case where the redaction ranges all merge into the first range
- */
-TEST(RedactionInfoTest, testMergeAllRangesIntoTheFirstRange) {
- off64_t ranges[10] = {
- 1, 100, 2, 99, 3, 98, 4, 97, 3, 15,
- };
+// Test that the ranges are both sorted and merged when there's an overlap.
+//
+// TODO: Can this ever happen ? Will we ever be in a state where we need to
+// redact exif attributes that have overlapping ranges ?
+TEST(RedactionInfoTest, testSortAndMergeRedactionRanges_overlap) {
+ // Ranges are: [10, 20), [25, 40), [50, 60)
+ off64_t ranges[8] = {30, 40, 10, 20, 25, 34, 50, 60};
- RedactionInfo info = RedactionInfo(5, ranges);
- EXPECT_EQ(1, info.size());
+ RedactionInfo info = RedactionInfo(4, ranges);
+ EXPECT_EQ(3, info.size());
EXPECT_EQ(true, info.isRedactionNeeded());
- // Read request equals the range: [1, 100]
- auto overlapping_rr = info.getOverlappingRedactionRanges(/*size*/ 100, /*off*/ 1);
- off64_t expected[] = {1, 100};
- EXPECT_EQ(*(createRedactionRangeVector(1, expected)), *overlapping_rr);
-
- // Read request is contained in the range: [15, 40]
- overlapping_rr = info.getOverlappingRedactionRanges(/*size*/ 26, /*off*/ 15);
- EXPECT_EQ(*(createRedactionRangeVector(1, expected)), *overlapping_rr);
-
- // Read request that strictly contains all of the redaction ranges
- overlapping_rr = info.getOverlappingRedactionRanges(/*size*/ 1000, /*off*/ 0);
- EXPECT_EQ(*(createRedactionRangeVector(1, expected)), *overlapping_rr);
+ std::vector<ReadRange> out;
+ info.getReadRanges(0, 60, &out); // read offsets [0, 60)
+ EXPECT_EQ(6, out.size());
+ EXPECT_EQ(ReadRange(0, 10, false), out[0]); // offsets: [0, 10) len = 10
+ EXPECT_EQ(ReadRange(10, 10, true), out[1]); // offsets: [10, 20) len = 10
+ EXPECT_EQ(ReadRange(20, 5, false), out[2]); // offsets: [20, 25) len = 5
+ EXPECT_EQ(ReadRange(25, 15, true), out[3]); // offsets [25, 40) len = 15
+ EXPECT_EQ(ReadRange(40, 10, false), out[4]); // offsets [40, 50) len = 10
+ EXPECT_EQ(ReadRange(50, 10, true), out[5]); // offsets [50, 60) len = 10
}
-/**
- * Test the case where the redaction ranges all merge into the last range
- */
-TEST(RedactionInfoTest, testMergeAllRangesIntoTheLastRange) {
- off64_t ranges[10] = {
- 4, 96, 3, 97, 2, 98, 1, 99, 0, 100,
- };
+// WARNING: The tests below assume that merging of ranges happen during
+// object construction (which is asserted by the check on |info.size()|.
+// Therefore, we don't write redundant tests for boundary conditions that
+// we've covered above. If this ever changes, these tests need to be expanded.
+TEST(RedactionInfoTest, testMergeAllRangesIntoSingleRange) {
+ // Ranges are: [8, 24)
+ off64_t ranges[8] = {10, 20, 8, 14, 14, 24, 12, 16};
- RedactionInfo info = RedactionInfo(5, ranges);
+ RedactionInfo info = RedactionInfo(4, ranges);
EXPECT_EQ(1, info.size());
EXPECT_EQ(true, info.isRedactionNeeded());
- // Read request equals the range: [0, 100]
- auto overlapping_rr = info.getOverlappingRedactionRanges(/*size*/ 100, /*off*/ 0);
- off64_t expected[] = {0, 100};
- EXPECT_EQ(*(createRedactionRangeVector(1, expected)), *overlapping_rr);
+ std::vector<ReadRange> out;
+ info.getReadRanges(0, 30, &out); // read offsets [0, 30)
+ EXPECT_EQ(3, out.size());
+ EXPECT_EQ(ReadRange(0, 8, false), out[0]); // offsets: [0, 8) len = 8
+ EXPECT_EQ(ReadRange(8, 16, true), out[1]); // offsets: [8, 24) len = 16
+ EXPECT_EQ(ReadRange(24, 6, false), out[2]); // offsets: [24, 30) len = 6
- // Read request is contained in the range: [15, 40]
- overlapping_rr = info.getOverlappingRedactionRanges(/*size*/ 26, /*off*/ 15);
- EXPECT_EQ(*(createRedactionRangeVector(1, expected)), *overlapping_rr);
-
- // Read request that strictly contains all of the redaction ranges
- overlapping_rr = info.getOverlappingRedactionRanges(/*size*/ 1000, /*off*/ 0);
- EXPECT_EQ(*(createRedactionRangeVector(1, expected)), *overlapping_rr);
-}
-
-/**
- * Test the case where the redaction ranges progressively merge
- */
-TEST(RedactionInfoTest, testMergeAllRangesProgressively) {
- off64_t ranges[10] = {
- 1, 11, 2, 12, 3, 13, 4, 14, 5, 15,
- };
-
- RedactionInfo info = RedactionInfo(5, ranges);
+ // Ranges are: [85, 100)
+ off64_t ranges2[10] = {90, 95, 95, 100, 85, 91, 92, 94, 99, 100};
+ info = RedactionInfo(5, ranges2);
EXPECT_EQ(1, info.size());
EXPECT_EQ(true, info.isRedactionNeeded());
- // Read request equals the range: [1, 15]
- auto overlapping_rr = info.getOverlappingRedactionRanges(/*size*/ 15, /*off*/ 1);
- off64_t expected[] = {1, 15};
- EXPECT_EQ(*(createRedactionRangeVector(1, expected)), *overlapping_rr);
-
- // Read request is contained in the range: [2, 12]
- overlapping_rr = info.getOverlappingRedactionRanges(/*size*/ 10, /*off*/ 2);
- EXPECT_EQ(*(createRedactionRangeVector(1, expected)), *overlapping_rr);
-
- // Read request that strictly contains all of the redaction ranges
- overlapping_rr = info.getOverlappingRedactionRanges(/*size*/ 100, /*off*/ 0);
- EXPECT_EQ(*(createRedactionRangeVector(1, expected)), *overlapping_rr);
+ out.clear();
+ info.getReadRanges(80, 30, &out); // read offsets [80, 110)
+ EXPECT_EQ(3, out.size());
+ EXPECT_EQ(ReadRange(80, 5, false), out[0]); // offsets: [80, 85) len = 5
+ EXPECT_EQ(ReadRange(85, 15, true), out[1]); // offsets: [85, 100) len = 15
+ EXPECT_EQ(ReadRange(100, 10, false), out[2]); // offsets: [100, 110) len = 10
+}
- off64_t reverse_rr[10] = {
- 5, 15, 4, 14, 3, 13, 2, 12, 1, 11,
- };
+TEST(RedactionInfoTest, testMergeMultipleRanges) {
+ // Ranges are: [10, 30), [60, 80)
+ off64_t ranges[8] = {20, 30, 10, 20, 70, 80, 60, 70};
- RedactionInfo reverse_info = RedactionInfo(5, reverse_rr);
- EXPECT_EQ(1, info.size());
+ RedactionInfo info = RedactionInfo(4, ranges);
+ EXPECT_EQ(2, info.size());
EXPECT_EQ(true, info.isRedactionNeeded());
- // Read request equals the range: [1, 15]
- overlapping_rr = info.getOverlappingRedactionRanges(/*size*/ 15, /*off*/ 1);
- EXPECT_EQ(*(createRedactionRangeVector(1, expected)), *overlapping_rr);
+ std::vector<ReadRange> out;
+ info.getReadRanges(0, 100, &out); // read offsets [0, 100)
+ EXPECT_EQ(5, out.size());
+ EXPECT_EQ(ReadRange(0, 10, false), out[0]); // offsets: [0, 10) len = 10
+ EXPECT_EQ(ReadRange(10, 20, true), out[1]); // offsets: [10, 30) len = 20
+ EXPECT_EQ(ReadRange(30, 30, false), out[2]); // offsets: [30, 60) len = 30
+ EXPECT_EQ(ReadRange(60, 20, true), out[3]); // offsets [60, 80) len = 20
+ EXPECT_EQ(ReadRange(80, 20, false), out[4]); // offsets [80, 100) len = 20
}
diff --git a/jni/include/libfuse_jni/RedactionInfo.h b/jni/include/libfuse_jni/RedactionInfo.h
index 5218e28a6..fe475cba4 100644
--- a/jni/include/libfuse_jni/RedactionInfo.h
+++ b/jni/include/libfuse_jni/RedactionInfo.h
@@ -17,19 +17,31 @@
#ifndef MEDIA_PROVIDER_FUSE_REDACTIONINFO_H_
#define MEDIA_PROVIDER_FUSE_REDACTIONINFO_H_
+#include <ostream>
#include <vector>
namespace mediaprovider {
namespace fuse {
/**
- * Type that represents a single redaction range within a file.
- * first is the offset of the first byte in the redaction range within the file
- * second is the offset of the last byte in the redaction range within the file
- * Ranges are inclusive.
+ * Type that represents a single redaction range within a file. Contains
+ * a pair of offsets in the file, [start, end).
*/
typedef std::pair<off64_t, off64_t> RedactionRange;
+class ReadRange {
+ public:
+ ReadRange(off64_t s, size_t l, bool r) : start(s), size(l), is_redaction(r) {}
+
+ const off64_t start;
+ const size_t size;
+ const bool is_redaction;
+
+ bool operator==(const ReadRange& rhs) const {
+ return start == rhs.start && size == rhs.size && is_redaction == rhs.is_redaction;
+ }
+};
+
class RedactionInfo {
public:
/**
@@ -55,6 +67,23 @@ class RedactionInfo {
* Calls d'tor for redactionRanges (vector).
*/
~RedactionInfo() = default;
+
+ /**
+ * Returns a set of ranges to fulfill a read request starting at |off| of size
+ * |size|.
+ */
+ void getReadRanges(off64_t off, size_t size, std::vector<ReadRange>* out) const;
+
+ /**
+ * Returns whether any ranges need to be redacted.
+ */
+ bool isRedactionNeeded() const;
+ /**
+ * Returns number of redaction ranges.
+ */
+ int size() const;
+
+ private:
/**
* Calculates the redaction ranges that overlap with a given read request.
* The read request is defined by its size and the offset of its first byte.
@@ -70,16 +99,6 @@ class RedactionInfo {
*/
std::unique_ptr<std::vector<RedactionRange>> getOverlappingRedactionRanges(size_t size,
off64_t off) const;
- /**
- * Returns whether any ranges need to be redacted.
- */
- bool isRedactionNeeded() const;
- /**
- * Returns number of redaction ranges.
- */
- int size() const;
-
- private:
std::vector<RedactionRange> redaction_ranges_;
void processRedactionRanges(int redaction_ranges_num, const off64_t* redaction_ranges);
bool hasOverlapWithReadRequest(size_t size, off64_t off) const;