pw_protobuf: Add FindDecodeHandler
This change defines a protobuf DecodeHandler which searches for a single
field within a proto message and cancels the decode if it is found.
Change-Id: I2ec18a15e7f23f664fb22384a2eacb280c9dece7
diff --git a/pw_protobuf/BUILD b/pw_protobuf/BUILD
index 08b43d5..b0d0902 100644
--- a/pw_protobuf/BUILD
+++ b/pw_protobuf/BUILD
@@ -1,4 +1,4 @@
-# Copyright 2019 The Pigweed Authors
+# Copyright 2020 The Pigweed Authors
#
# 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
@@ -27,11 +27,13 @@
srcs = [
"decoder.cc",
"encoder.cc",
+ "find.cc",
],
hdrs = [
"public/pw_protobuf/codegen.h",
"public/pw_protobuf/decoder.h",
"public/pw_protobuf/encoder.h",
+ "public/pw_protobuf/find.h",
"public/pw_protobuf/wire_format.h",
],
includes = ["public"],
@@ -54,6 +56,12 @@
deps = ["//pw_protobuf"],
)
+pw_cc_test(
+ name = "find_test",
+ srcs = ["find_test.cc"],
+ deps = ["//pw_protobuf"],
+)
+
# TODO(frolv): Figure out how to integrate pw_protobuf codegen into Bazel.
filegroup(
name = "codegen_test",
diff --git a/pw_protobuf/BUILD.gn b/pw_protobuf/BUILD.gn
index 2b152cc..62c28e7 100644
--- a/pw_protobuf/BUILD.gn
+++ b/pw_protobuf/BUILD.gn
@@ -32,11 +32,13 @@
"public/pw_protobuf/codegen.h",
"public/pw_protobuf/decoder.h",
"public/pw_protobuf/encoder.h",
+ "public/pw_protobuf/find.h",
"public/pw_protobuf/wire_format.h",
]
sources = [
"decoder.cc",
"encoder.cc",
+ "find.cc",
]
sources += public
}
@@ -66,6 +68,7 @@
":codegen_test",
":decoder_test",
":encoder_test",
+ ":find_test",
]
}
@@ -79,6 +82,11 @@
sources = [ "encoder_test.cc" ]
}
+pw_test("find_test") {
+ deps = [ ":pw_protobuf" ]
+ sources = [ "find_test.cc" ]
+}
+
pw_test("codegen_test") {
deps = [
":codegen_test_protos_cc",
diff --git a/pw_protobuf/decoder.cc b/pw_protobuf/decoder.cc
index 220c608..54d4675 100644
--- a/pw_protobuf/decoder.cc
+++ b/pw_protobuf/decoder.cc
@@ -43,6 +43,7 @@
uint32_t field_number = key >> kFieldNumberShift;
Status status = handler_->ProcessField(this, field_number);
if (!status.ok()) {
+ state_ = status == Status::CANCELLED ? kDecodeCancelled : kDecodeFailed;
return status;
}
diff --git a/pw_protobuf/find.cc b/pw_protobuf/find.cc
new file mode 100644
index 0000000..b98c4b9
--- /dev/null
+++ b/pw_protobuf/find.cc
@@ -0,0 +1,42 @@
+// Copyright 2020 The Pigweed Authors
+//
+// 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
+//
+// https://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 "pw_protobuf/find.h"
+
+namespace pw::protobuf {
+
+Status FindDecodeHandler::ProcessField(Decoder* decoder,
+ uint32_t field_number) {
+ if (field_number != field_number_) {
+ // Continue to the next field.
+ return Status::OK;
+ }
+
+ found_ = true;
+ if (nested_handler_ == nullptr) {
+ return Status::CANCELLED;
+ }
+
+ span<const std::byte> submessage;
+ if (Status status = decoder->ReadBytes(field_number, &submessage);
+ !status.ok()) {
+ return status;
+ }
+
+ Decoder subdecoder;
+ subdecoder.set_handler(nested_handler_);
+ return subdecoder.Decode(submessage);
+}
+
+} // namespace pw::protobuf
diff --git a/pw_protobuf/find_test.cc b/pw_protobuf/find_test.cc
new file mode 100644
index 0000000..860e338
--- /dev/null
+++ b/pw_protobuf/find_test.cc
@@ -0,0 +1,92 @@
+// Copyright 2020 The Pigweed Authors
+//
+// 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
+//
+// https://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 "pw_protobuf/find.h"
+
+#include "gtest/gtest.h"
+
+namespace pw::protobuf {
+namespace {
+
+// clang-format off
+constexpr uint8_t encoded_proto[] = {
+ // type=int32, k=1, v=42
+ 0x08, 0x2a,
+ // type=sint32, k=2, v=-13
+ 0x10, 0x19,
+ // type=bool, k=3, v=false
+ 0x18, 0x00,
+ // type=double, k=4, v=3.14159
+ 0x21, 0x6e, 0x86, 0x1b, 0xf0, 0xf9, 0x21, 0x09, 0x40,
+ // type=fixed32, k=5, v=0xdeadbeef
+ 0x2d, 0xef, 0xbe, 0xad, 0xde,
+ // type=string, k=6, v="Hello world"
+ 0x32, 0x0b, 'H', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd',
+
+ // type=message, k=7, len=2
+ 0x3a, 0x02,
+ // (nested) type=uint32, k=1, v=3
+ 0x08, 0x03
+};
+
+TEST(FindDecodeHandler, SingleLevel_FindsExistingField) {
+ Decoder decoder;
+ FindDecodeHandler finder(3);
+
+ decoder.set_handler(&finder);
+ decoder.Decode(as_bytes(span(encoded_proto)));
+
+ EXPECT_TRUE(finder.found());
+ EXPECT_TRUE(decoder.cancelled());
+}
+
+TEST(FindDecodeHandler, SingleLevel_DoesntFindNonExistingField) {
+ Decoder decoder;
+ FindDecodeHandler finder(8);
+
+ decoder.set_handler(&finder);
+ decoder.Decode(as_bytes(span(encoded_proto)));
+
+ EXPECT_FALSE(finder.found());
+ EXPECT_FALSE(decoder.cancelled());
+}
+
+TEST(FindDecodeHandler, MultiLevel_FindsExistingNestedField) {
+ Decoder decoder;
+ FindDecodeHandler nested_finder(1);
+ FindDecodeHandler finder(7, &nested_finder);
+
+ decoder.set_handler(&finder);
+ decoder.Decode(as_bytes(span(encoded_proto)));
+
+ EXPECT_TRUE(finder.found());
+ EXPECT_TRUE(nested_finder.found());
+ EXPECT_TRUE(decoder.cancelled());
+}
+
+TEST(FindDecodeHandler, MultiLevel_DoesntFindNonExistingNestedField) {
+ Decoder decoder;
+ FindDecodeHandler nested_finder(3);
+ FindDecodeHandler finder(7, &nested_finder);
+
+ decoder.set_handler(&finder);
+ decoder.Decode(as_bytes(span(encoded_proto)));
+
+ EXPECT_TRUE(finder.found());
+ EXPECT_FALSE(nested_finder.found());
+ EXPECT_FALSE(decoder.cancelled());
+}
+
+} // namespace
+} // namespace pw::protobuf
diff --git a/pw_protobuf/public/pw_protobuf/decoder.h b/pw_protobuf/public/pw_protobuf/decoder.h
index 7e5feed..a3aca75 100644
--- a/pw_protobuf/public/pw_protobuf/decoder.h
+++ b/pw_protobuf/public/pw_protobuf/decoder.h
@@ -208,10 +208,13 @@
return ReadDelimited(field_number, out);
}
+ bool cancelled() const { return state_ == kDecodeCancelled; };
+
private:
enum State {
kReady,
kDecodeInProgress,
+ kDecodeCancelled,
kDecodeFailed,
};
diff --git a/pw_protobuf/public/pw_protobuf/find.h b/pw_protobuf/public/pw_protobuf/find.h
new file mode 100644
index 0000000..1e2f3e8
--- /dev/null
+++ b/pw_protobuf/public/pw_protobuf/find.h
@@ -0,0 +1,46 @@
+// Copyright 2020 The Pigweed Authors
+//
+// 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
+//
+// https://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.
+#pragma once
+
+#include "pw_protobuf/decoder.h"
+
+namespace pw::protobuf {
+
+// DecodeHandler that searches for a specific field in a proto message. If the
+// field is found, it cancels the decode operation. Supports searching for
+// nested fields by passing in another instance of a FindDecodeHandler for the
+// nested message.
+class FindDecodeHandler final : public DecodeHandler {
+ public:
+ constexpr FindDecodeHandler(uint32_t field_number)
+ : FindDecodeHandler(field_number, nullptr) {}
+
+ constexpr FindDecodeHandler(uint32_t field_number, FindDecodeHandler* nested)
+ : field_number_(field_number), found_(false), nested_handler_(nested) {}
+
+ Status ProcessField(Decoder* decoder, uint32_t field_number) override;
+
+ bool found() const { return found_; }
+
+ void set_nested_handler(FindDecodeHandler* nested) {
+ nested_handler_ = nested;
+ }
+
+ private:
+ uint32_t field_number_;
+ bool found_;
+ FindDecodeHandler* nested_handler_;
+};
+
+} // namespace pw::protobuf