pw_rpc: Release acquired buffer in RawServerWriter destructor
This fixes a bug in the RawServerWriter where it would crash if the user
acquired a payload buffer but didn't release it.
Change-Id: I684f3c62a6f24d9a827e3d5258502f0bbfd0ddd4
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/23380
Commit-Queue: Alexei Frolov <frolv@google.com>
Pigweed-Auto-Submit: Alexei Frolov <frolv@google.com>
Reviewed-by: Wyatt Hepler <hepler@google.com>
diff --git a/pw_rpc/base_server_writer.cc b/pw_rpc/base_server_writer.cc
index 942cf32..8c57ee1 100644
--- a/pw_rpc/base_server_writer.cc
+++ b/pw_rpc/base_server_writer.cc
@@ -78,6 +78,15 @@
return call_.channel().Send(response_, ResponsePacket(payload));
}
+Status BaseServerWriter::ReleasePayloadBuffer() {
+ if (!open()) {
+ return Status::FailedPrecondition();
+ }
+
+ call_.channel().Release(response_);
+ return Status::Ok();
+}
+
void BaseServerWriter::Close() {
if (!open()) {
return;
diff --git a/pw_rpc/nanopb/public/pw_rpc/internal/nanopb_method.h b/pw_rpc/nanopb/public/pw_rpc/internal/nanopb_method.h
index 8f8c4c5..7c5dd9a 100644
--- a/pw_rpc/nanopb/public/pw_rpc/internal/nanopb_method.h
+++ b/pw_rpc/nanopb/public/pw_rpc/internal/nanopb_method.h
@@ -299,7 +299,7 @@
return ReleasePayloadBuffer(buffer.first(result.size()));
}
- ReleasePayloadBuffer({});
+ ReleasePayloadBuffer();
return Status::Internal();
}
diff --git a/pw_rpc/public/pw_rpc/internal/base_server_writer.h b/pw_rpc/public/pw_rpc/internal/base_server_writer.h
index ef36d38..4b9dbad 100644
--- a/pw_rpc/public/pw_rpc/internal/base_server_writer.h
+++ b/pw_rpc/public/pw_rpc/internal/base_server_writer.h
@@ -70,8 +70,12 @@
std::span<std::byte> AcquirePayloadBuffer();
+ // Releases the buffer, sending a packet with the specified payload.
Status ReleasePayloadBuffer(std::span<const std::byte> payload);
+ // Releases the buffer without sending a packet.
+ Status ReleasePayloadBuffer();
+
private:
friend class rpc::Server;
diff --git a/pw_rpc/public/pw_rpc/internal/channel.h b/pw_rpc/public/pw_rpc/internal/channel.h
index 870e425..6533eda 100644
--- a/pw_rpc/public/pw_rpc/internal/channel.h
+++ b/pw_rpc/public/pw_rpc/internal/channel.h
@@ -56,6 +56,8 @@
buffer.data() + buffer.size() <= buffer_.data() + buffer_.size();
}
+ bool empty() const { return buffer_.empty(); }
+
private:
friend class Channel;
@@ -76,6 +78,11 @@
}
Status Send(OutputBuffer& output, const internal::Packet& packet);
+
+ void Release(OutputBuffer& buffer) {
+ buffer.buffer_ = {};
+ output().SendAndReleaseBuffer(0);
+ }
};
} // namespace pw::rpc::internal
diff --git a/pw_rpc/raw/public/pw_rpc/internal/raw_method.h b/pw_rpc/raw/public/pw_rpc/internal/raw_method.h
index e0a514f..a1e3b66 100644
--- a/pw_rpc/raw/public/pw_rpc/internal/raw_method.h
+++ b/pw_rpc/raw/public/pw_rpc/internal/raw_method.h
@@ -24,6 +24,10 @@
class RawServerWriter : public internal::BaseServerWriter {
public:
RawServerWriter() = default;
+ RawServerWriter(RawServerWriter&&) = default;
+ RawServerWriter& operator=(RawServerWriter&&) = default;
+
+ ~RawServerWriter();
// Returns a buffer in which a response payload can be built.
ByteSpan PayloadBuffer() { return AcquirePayloadBuffer(); }
diff --git a/pw_rpc/raw/raw_method.cc b/pw_rpc/raw/raw_method.cc
index 1b2075f..b23ff5b 100644
--- a/pw_rpc/raw/raw_method.cc
+++ b/pw_rpc/raw/raw_method.cc
@@ -21,6 +21,12 @@
namespace pw::rpc {
+RawServerWriter::~RawServerWriter() {
+ if (!buffer().empty()) {
+ ReleasePayloadBuffer();
+ }
+}
+
Status RawServerWriter::Write(ConstByteSpan response) {
if (buffer().Contains(response)) {
return ReleasePayloadBuffer(response);
@@ -29,7 +35,7 @@
std::span<std::byte> buffer = AcquirePayloadBuffer();
if (response.size() > buffer.size()) {
- ReleasePayloadBuffer({});
+ ReleasePayloadBuffer();
return Status::OutOfRange();
}
diff --git a/pw_rpc/raw/raw_method_test.cc b/pw_rpc/raw/raw_method_test.cc
index 25b6e95..9b03402 100644
--- a/pw_rpc/raw/raw_method_test.cc
+++ b/pw_rpc/raw/raw_method_test.cc
@@ -174,5 +174,24 @@
EXPECT_EQ(last_writer.Write(data), Status::OutOfRange());
}
+TEST(RawServerWriter,
+ Destructor_ReleasesAcquiredBufferWithoutSendingAndCloses) {
+ const RawMethod& method = std::get<1>(FakeService::kMethods).raw_method();
+ ServerContextForTest<FakeService> context(method);
+
+ method.Invoke(context.get(), context.packet({}));
+
+ {
+ RawServerWriter writer = std::move(last_writer);
+ auto buffer = writer.PayloadBuffer();
+ buffer[0] = std::byte{'!'};
+ // Don't release the buffer.
+ }
+
+ auto output = context.output();
+ EXPECT_EQ(output.packet_count(), 1u);
+ EXPECT_EQ(output.sent_packet().type(), PacketType::SERVER_STREAM_END);
+}
+
} // namespace
} // namespace pw::rpc::internal