pw_rpc: Some initial server code
This change starts adding basic RPC server code and request processing.
Change-Id: Ia59a3e65aef4117fc593db46450a6b428bf136f5
diff --git a/pw_protobuf_compiler/proto.gni b/pw_protobuf_compiler/proto.gni
index 2d1c1a5..97ac763 100644
--- a/pw_protobuf_compiler/proto.gni
+++ b/pw_protobuf_compiler/proto.gni
@@ -64,6 +64,7 @@
source_set(target_name) {
all_dependent_configs = [ ":$_include_config_target" ]
deps = [ ":$_gen_target" ] + invoker.deps
+ public_deps = [ dir_pw_protobuf ]
sources = get_target_outputs(":$_gen_target")
public = filter_include(sources, [ "*.pwpb.h" ])
}
diff --git a/pw_rpc/BUILD b/pw_rpc/BUILD
index d56573e..4d5936e 100644
--- a/pw_rpc/BUILD
+++ b/pw_rpc/BUILD
@@ -31,7 +31,19 @@
includes = ["public"],
deps = [
"//pw_assert",
+ "//pw_log",
"//pw_span",
+ "//pw_status",
+ ],
+ srcs = [
+ "channel.cc",
+ "packet.cc",
+ "public/pw_rpc/internal/packet.h",
+ "public/pw_rpc/internal/service.h",
+ "public/pw_rpc/internal/service_registry.h",
+ "server.cc",
+ "service.cc",
+ "service_registry.cc",
],
)
@@ -44,3 +56,13 @@
":pw_rpc",
],
)
+
+pw_cc_test(
+ name = "packet_test",
+ srcs = [
+ "packet_test.cc",
+ ],
+ deps = [
+ ":pw_rpc",
+ ],
+)
diff --git a/pw_rpc/BUILD.gn b/pw_rpc/BUILD.gn
index de98060..192a9bc 100644
--- a/pw_rpc/BUILD.gn
+++ b/pw_rpc/BUILD.gn
@@ -13,6 +13,7 @@
# the License.
import("$dir_pw_docgen/docs.gni")
+import("$dir_pw_protobuf_compiler/proto.gni")
import("$dir_pw_unit_test/test.gni")
config("default_config") {
@@ -20,15 +21,33 @@
}
source_set("pw_rpc") {
+ deps = [ ":protos_pwpb" ]
public_configs = [ ":default_config" ]
public_deps = [
dir_pw_assert,
+ dir_pw_log,
dir_pw_span,
+ dir_pw_status,
]
public = [
"public/pw_rpc/channel.h",
"public/pw_rpc/server.h",
]
+ sources = [
+ "channel.cc",
+ "packet.cc",
+ "public/pw_rpc/internal/packet.h",
+ "public/pw_rpc/internal/service.h",
+ "public/pw_rpc/internal/service_registry.h",
+ "server.cc",
+ "service.cc",
+ "service_registry.cc",
+ ]
+ friend = [ ":*" ]
+}
+
+pw_proto_library("protos") {
+ sources = [ "pw_rpc_protos/packet.proto" ]
}
pw_doc_group("docs") {
@@ -36,10 +55,21 @@
}
pw_test_group("tests") {
- tests = [ ":server_test" ]
+ tests = [
+ ":packet_test",
+ ":server_test",
+ ]
}
pw_test("server_test") {
deps = [ ":pw_rpc" ]
sources = [ "server_test.cc" ]
}
+
+pw_test("packet_test") {
+ deps = [
+ ":pw_rpc",
+ dir_pw_protobuf,
+ ]
+ sources = [ "packet_test.cc" ]
+}
diff --git a/pw_rpc/channel.cc b/pw_rpc/channel.cc
new file mode 100644
index 0000000..6095479
--- /dev/null
+++ b/pw_rpc/channel.cc
@@ -0,0 +1,17 @@
+// 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_rpc/channel.h"
+
+namespace pw::rpc {} // namespace pw::rpc
diff --git a/pw_rpc/packet.cc b/pw_rpc/packet.cc
new file mode 100644
index 0000000..e332c5e
--- /dev/null
+++ b/pw_rpc/packet.cc
@@ -0,0 +1,80 @@
+// 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_rpc/internal/packet.h"
+
+#include "pw_protobuf/decoder.h"
+
+namespace pw::rpc::internal {
+
+Packet Packet::FromBuffer(span<const std::byte> data) {
+ PacketType type = PacketType::kRpc;
+ uint32_t channel_id = 0;
+ uint32_t service_id = 0;
+ uint32_t method_id = 0;
+ span<const std::byte> payload;
+
+ protobuf::Decoder decoder(data);
+
+ while (decoder.Next().ok()) {
+ RpcPacket::Fields field =
+ static_cast<RpcPacket::Fields>(decoder.FieldNumber());
+ uint32_t proto_value = 0;
+
+ switch (field) {
+ case RpcPacket::Fields::kType:
+ decoder.ReadUint32(&proto_value);
+ type = static_cast<PacketType>(proto_value);
+ break;
+
+ case RpcPacket::Fields::kChannelId:
+ decoder.ReadUint32(&channel_id);
+ break;
+
+ case RpcPacket::Fields::kServiceId:
+ decoder.ReadUint32(&service_id);
+ break;
+
+ case RpcPacket::Fields::kMethodId:
+ decoder.ReadUint32(&method_id);
+ break;
+
+ case RpcPacket::Fields::kPayload:
+ decoder.ReadBytes(&payload);
+ break;
+ }
+ }
+
+ return Packet(type, channel_id, service_id, method_id, payload);
+}
+
+StatusWithSize Packet::Encode(span<std::byte> buffer) const {
+ pw::protobuf::NestedEncoder encoder(buffer);
+ RpcPacket::Encoder rpc_packet(&encoder);
+
+ rpc_packet.WriteType(type_);
+ rpc_packet.WriteChannelId(channel_id_);
+ rpc_packet.WriteServiceId(service_id_);
+ rpc_packet.WriteMethodId(method_id_);
+ rpc_packet.WritePayload(payload_);
+
+ span<const std::byte> proto;
+ if (Status status = encoder.Encode(&proto); !status.ok()) {
+ return StatusWithSize(status, 0);
+ }
+
+ return StatusWithSize(proto.size());
+}
+
+} // namespace pw::rpc::internal
diff --git a/pw_rpc/packet_test.cc b/pw_rpc/packet_test.cc
new file mode 100644
index 0000000..744b7e1
--- /dev/null
+++ b/pw_rpc/packet_test.cc
@@ -0,0 +1,53 @@
+// 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_rpc/internal/packet.h"
+
+#include "gtest/gtest.h"
+
+namespace pw::rpc::internal {
+namespace {
+
+using std::byte;
+
+TEST(Packet, EncodeDecode) {
+ constexpr byte payload[]{byte(0x00), byte(0x01), byte(0x02), byte(0x03)};
+
+ Packet packet = Packet::Empty();
+ packet.set_type(PacketType::kRpc);
+ packet.set_channel_id(12);
+ packet.set_service_id(0xdeadbeef);
+ packet.set_method_id(0x03a82921);
+ packet.set_payload(payload);
+
+ byte buffer[128];
+ StatusWithSize sws = packet.Encode(buffer);
+ ASSERT_EQ(sws.status(), Status::OK);
+
+ span<byte> packet_data(buffer, sws.size());
+ Packet decoded = Packet::FromBuffer(packet_data);
+
+ EXPECT_EQ(decoded.type(), packet.type());
+ EXPECT_EQ(decoded.channel_id(), packet.channel_id());
+ EXPECT_EQ(decoded.service_id(), packet.service_id());
+ EXPECT_EQ(decoded.method_id(), packet.method_id());
+ ASSERT_EQ(decoded.payload().size(), packet.payload().size());
+ EXPECT_EQ(std::memcmp(decoded.payload().data(),
+ packet.payload().data(),
+ packet.payload().size()),
+ 0);
+}
+
+} // namespace
+} // namespace pw::rpc::internal
diff --git a/pw_rpc/public/pw_rpc/channel.h b/pw_rpc/public/pw_rpc/channel.h
index 339a827..1ef6880 100644
--- a/pw_rpc/public/pw_rpc/channel.h
+++ b/pw_rpc/public/pw_rpc/channel.h
@@ -16,11 +16,14 @@
#include <cstdint>
#include "pw_span/span.h"
+#include "pw_status/status.h"
namespace pw::rpc {
class ChannelOutput {
public:
+ constexpr ChannelOutput(uint32_t id) : id_(id) {}
+
virtual ~ChannelOutput() = default;
// Acquire a buffer into which to write an outgoing RPC packet.
@@ -28,6 +31,11 @@
// Sends the contents of the buffer from AcquireBuffer().
virtual void SendAndReleaseBuffer(size_t size) = 0;
+
+ uint32_t id() const { return id_; }
+
+ private:
+ uint32_t id_;
};
class Channel {
@@ -43,7 +51,10 @@
constexpr uint32_t id() const { return id_; }
- void Write(span<std::byte> packet);
+ span<std::byte> AcquireBuffer() const { return output_->AcquireBuffer(); }
+ void SendAndReleaseBuffer(size_t size) const {
+ output_->SendAndReleaseBuffer(size);
+ }
private:
static constexpr uint32_t kUnassignedChannelId = 0;
diff --git a/pw_rpc/public/pw_rpc/internal/packet.h b/pw_rpc/public/pw_rpc/internal/packet.h
new file mode 100644
index 0000000..2733f2e
--- /dev/null
+++ b/pw_rpc/public/pw_rpc/internal/packet.h
@@ -0,0 +1,73 @@
+// 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 <cstddef>
+#include <cstdint>
+
+#include "pw_rpc_protos/packet.pwpb.h"
+#include "pw_span/span.h"
+#include "pw_status/status_with_size.h"
+
+namespace pw::rpc::internal {
+
+class Packet {
+ public:
+ // Parses a packet from a protobuf message. Missing or malformed fields take
+ // their default values.
+ static Packet FromBuffer(span<const std::byte> data);
+
+ // Returns an empty packet with default values set.
+ static constexpr Packet Empty() {
+ return Packet(PacketType::kRpc, 0, 0, 0, {});
+ }
+
+ // Encodes the packet into its wire format. Returns the encoded size.
+ StatusWithSize Encode(span<std::byte> buffer) const;
+
+ bool is_control() const { return !is_rpc(); }
+ bool is_rpc() const { return type_ == PacketType::kRpc; }
+
+ PacketType type() const { return type_; }
+ uint32_t channel_id() const { return channel_id_; }
+ uint32_t service_id() const { return service_id_; }
+ uint32_t method_id() const { return method_id_; }
+ span<const std::byte> payload() const { return payload_; }
+
+ void set_type(PacketType type) { type_ = type; }
+ void set_channel_id(uint32_t channel_id) { channel_id_ = channel_id; }
+ void set_service_id(uint32_t service_id) { service_id_ = service_id; }
+ void set_method_id(uint32_t method_id) { method_id_ = method_id; }
+ void set_payload(span<const std::byte> payload) { payload_ = payload; }
+
+ private:
+ constexpr Packet(PacketType type,
+ uint32_t channel_id,
+ uint32_t service_id,
+ uint32_t method_id,
+ span<const std::byte> payload)
+ : type_(type),
+ channel_id_(channel_id),
+ service_id_(service_id),
+ method_id_(method_id),
+ payload_(payload) {}
+
+ PacketType type_;
+ uint32_t channel_id_;
+ uint32_t service_id_;
+ uint32_t method_id_;
+ span<const std::byte> payload_;
+};
+
+} // namespace pw::rpc::internal
diff --git a/pw_rpc/public/pw_rpc/internal/service.h b/pw_rpc/public/pw_rpc/internal/service.h
new file mode 100644
index 0000000..e457285
--- /dev/null
+++ b/pw_rpc/public/pw_rpc/internal/service.h
@@ -0,0 +1,55 @@
+// 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 <cstdint>
+
+#include "pw_span/span.h"
+#include "pw_status/status.h"
+
+namespace pw::rpc::internal {
+
+// Forward-declare Packet instead of including it to avoid exposing the internal
+// dependency on pw_protobuf.
+class Packet;
+
+class ServiceRegistry;
+
+// Base class for all RPC services. This is not meant to be instantiated
+// directly; use a generated subclass instead.
+class Service {
+ public:
+ struct Method {
+ uint32_t id;
+ };
+
+ Service(uint32_t id, span<const Method> methods)
+ : id_(id), methods_(methods), next_(nullptr) {}
+
+ uint32_t id() const { return id_; }
+
+ // Handles an incoming packet and populates a response. Errors that occur
+ // should be set within the response packet.
+ void ProcessPacket(const internal::Packet& request,
+ internal::Packet& response);
+
+ private:
+ friend class internal::ServiceRegistry;
+
+ uint32_t id_;
+ span<const Method> methods_;
+ Service* next_;
+};
+
+} // namespace pw::rpc::internal
diff --git a/pw_rpc/public/pw_rpc/internal/service_registry.h b/pw_rpc/public/pw_rpc/internal/service_registry.h
new file mode 100644
index 0000000..283a36c
--- /dev/null
+++ b/pw_rpc/public/pw_rpc/internal/service_registry.h
@@ -0,0 +1,40 @@
+// 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_assert/assert.h"
+#include "pw_rpc/internal/service.h"
+
+namespace pw::rpc::internal {
+
+// Manages a list of registered RPC services.
+class ServiceRegistry {
+ public:
+ constexpr ServiceRegistry() : first_service_(nullptr) {}
+
+ ServiceRegistry(const ServiceRegistry& other) = delete;
+ ServiceRegistry& operator=(const ServiceRegistry& other) = delete;
+
+ void Register(Service& service) {
+ service.next_ = first_service_;
+ first_service_ = &service;
+ }
+
+ Service* Find(uint32_t id) const;
+
+ private:
+ Service* first_service_;
+};
+
+} // namespace pw::rpc::internal
diff --git a/pw_rpc/public/pw_rpc/server.h b/pw_rpc/public/pw_rpc/server.h
index c1ffd22..2faf1b8 100644
--- a/pw_rpc/public/pw_rpc/server.h
+++ b/pw_rpc/public/pw_rpc/server.h
@@ -16,17 +16,36 @@
#include <cstddef>
#include "pw_rpc/channel.h"
+#include "pw_rpc/internal/service_registry.h"
namespace pw::rpc {
class Server {
public:
- constexpr Server(span<const Channel> channels) : channels_(channels) {}
+ constexpr Server(span<Channel> channels) : channels_(channels) {}
+
+ Server(const Server& other) = delete;
+ Server& operator=(const Server& other) = delete;
+
+ // Registers a service with the server. This should not be called directly
+ // with an internal::Service; instead, use a generated class which inherits
+ // from it.
+ void RegisterService(internal::Service& service) {
+ services_.Register(service);
+ }
+
+ void ProcessPacket(span<const std::byte> packet, ChannelOutput& interface);
constexpr size_t channel_count() const { return channels_.size(); }
private:
- span<const Channel> channels_;
+ using Service = internal::Service;
+ using ServiceRegistry = internal::ServiceRegistry;
+
+ Channel* FindChannel(uint32_t id);
+
+ span<Channel> channels_;
+ ServiceRegistry services_;
};
} // namespace pw::rpc
diff --git a/pw_rpc/pw_rpc_protos/packet.proto b/pw_rpc/pw_rpc_protos/packet.proto
new file mode 100644
index 0000000..c8936e4
--- /dev/null
+++ b/pw_rpc/pw_rpc_protos/packet.proto
@@ -0,0 +1,37 @@
+// 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.
+syntax = "proto3";
+
+package pw.rpc.internal;
+
+enum PacketType { RPC = 0; };
+
+message RpcPacket {
+ // The type of packet. Either a general RPC packet or a specific control
+ // packet. Required.
+ PacketType type = 1;
+
+ // Channel through which the packet is sent. Required.
+ uint32 channel_id = 2;
+
+ // Tokenized fully-qualified name of the service with which this packet is
+ // associated. For RPC packets, this is the service that processes the packet.
+ uint32 service_id = 3;
+
+ // Tokenized name of the method which should process this packet.
+ uint32 method_id = 4;
+
+ // The packet's payload.
+ bytes payload = 5;
+};
diff --git a/pw_rpc/server.cc b/pw_rpc/server.cc
new file mode 100644
index 0000000..fb44de7
--- /dev/null
+++ b/pw_rpc/server.cc
@@ -0,0 +1,78 @@
+// 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_rpc/server.h"
+
+#include "pw_log/log.h"
+#include "pw_rpc/internal/packet.h"
+
+namespace pw::rpc {
+
+using internal::Packet;
+
+void Server::ProcessPacket(span<const std::byte> data,
+ ChannelOutput& interface) {
+ Packet packet = Packet::FromBuffer(data);
+ if (packet.is_control()) {
+ // TODO(frolv): Handle control packets.
+ return;
+ }
+
+ if (packet.service_id() == 0 || packet.method_id() == 0) {
+ // Malformed packet; don't even try to process it.
+ PW_LOG_ERROR("Received incomplete RPC packet on interface %u",
+ unsigned(interface.id()));
+ return;
+ }
+
+ Channel* channel = FindChannel(packet.channel_id());
+ if (channel == nullptr) {
+ // TODO(frolv): Dynamically assign channel.
+ return;
+ }
+
+ span<std::byte> response_buffer = channel->AcquireBuffer();
+
+ Service* service = services_.Find(packet.service_id());
+ if (service == nullptr) {
+ // TODO(frolv): Send back a NOT_FOUND response.
+ channel->SendAndReleaseBuffer(0);
+ return;
+ }
+
+ Packet response = Packet::Empty();
+ response.set_channel_id(channel->id());
+
+ service->ProcessPacket(packet, response);
+
+ StatusWithSize sws = response.Encode(response_buffer);
+ if (!sws.ok()) {
+ // TODO(frolv): What should be done here?
+ channel->SendAndReleaseBuffer(0);
+ return;
+ }
+
+ channel->SendAndReleaseBuffer(sws.size());
+}
+
+Channel* Server::FindChannel(uint32_t id) {
+ for (Channel& c : channels_) {
+ if (c.id() == id) {
+ return &c;
+ }
+ }
+ return nullptr;
+}
+
+} // namespace pw::rpc
diff --git a/pw_rpc/server_test.cc b/pw_rpc/server_test.cc
index 3079f00..aae410e 100644
--- a/pw_rpc/server_test.cc
+++ b/pw_rpc/server_test.cc
@@ -19,13 +19,55 @@
namespace pw::rpc {
namespace {
+using std::byte;
+
+template <size_t buffer_size>
+class TestOutput : public ChannelOutput {
+ public:
+ constexpr TestOutput(uint32_t id) : ChannelOutput(id), sent_packet_({}) {}
+
+ span<byte> AcquireBuffer() override { return buffer_; }
+
+ void SendAndReleaseBuffer(size_t size) override {
+ sent_packet_ = {buffer_, size};
+ }
+
+ span<const byte> sent_packet() const { return sent_packet_; }
+
+ private:
+ byte buffer_[buffer_size];
+ span<const byte> sent_packet_;
+};
+
+TestOutput<512> output(1);
+
+// clang-format off
+constexpr uint8_t encoded_packet[] = {
+ // type = PacketType::kRpc
+ 0x08, 0x00,
+ // channel_id = 1
+ 0x10, 0x01,
+ // service_id = 42
+ 0x18, 0x2a,
+ // method_id = 27
+ 0x20, 0x1b,
+ // payload
+ 0x82, 0x02, 0xff, 0xff,
+};
+// clang-format on
+
TEST(Server, DoesStuff) {
- constexpr Channel channels[] = {
- Channel(1, nullptr),
- Channel(2, nullptr),
+ Channel channels[] = {
+ Channel(1, &output),
+ Channel(2, &output),
};
Server server(channels);
- ASSERT_EQ(server.channel_count(), 2u);
+ internal::Service service(42, {});
+ server.RegisterService(service);
+
+ server.ProcessPacket(as_bytes(span(encoded_packet)), output);
+ auto packet = output.sent_packet();
+ EXPECT_GT(packet.size(), 0u);
}
} // namespace
diff --git a/pw_rpc/service.cc b/pw_rpc/service.cc
new file mode 100644
index 0000000..af82d19
--- /dev/null
+++ b/pw_rpc/service.cc
@@ -0,0 +1,33 @@
+// 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_rpc/internal/service.h"
+
+#include "pw_rpc/internal/packet.h"
+
+namespace pw::rpc::internal {
+
+void Service::ProcessPacket(const Packet& request, Packet& response) {
+ response.set_type(PacketType::kRpc);
+ response.set_service_id(id_);
+
+ for (const Method& method : methods_) {
+ if (method.id == request.method_id()) {
+ // TODO(frolv): call the method i guess
+ response.set_method_id(method.id);
+ }
+ }
+}
+
+} // namespace pw::rpc::internal
diff --git a/pw_rpc/service_registry.cc b/pw_rpc/service_registry.cc
new file mode 100644
index 0000000..d9182d7
--- /dev/null
+++ b/pw_rpc/service_registry.cc
@@ -0,0 +1,28 @@
+// 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_rpc/internal/service_registry.h"
+
+namespace pw::rpc::internal {
+
+Service* ServiceRegistry::Find(uint32_t id) const {
+ for (Service* s = first_service_; s != nullptr; s = s->next_) {
+ if (s->id() == id) {
+ return s;
+ }
+ }
+ return nullptr;
+}
+
+} // namespace pw::rpc::internal