pw_stream: Add client-side SocketStream support

Adds the SocketStream::Connect member function, allowing a SocketStream
to be established from the client side of a socket rather than only the
serving side as before.

Additionally, this change renames the existing Init member function to
Serve instead to make it clear that it is not required before calling
Connect and is instead an alternative way of establishing a SocketStream
connection.

Change-Id: Ib3fdb9f5405eee92ae1a166c64e990cb9c05fca7
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/31261
Pigweed-Auto-Submit: Jason Graffius <jgraff@google.com>
Commit-Queue: Auto-Submit <auto-submit@pigweed.google.com.iam.gserviceaccount.com>
Reviewed-by: Alexei Frolov <frolv@google.com>
Reviewed-by: Keir Mierle <keir@google.com>
diff --git a/pw_stream/public/pw_stream/socket_stream.h b/pw_stream/public/pw_stream/socket_stream.h
index c9d3e0f..1ab1161 100644
--- a/pw_stream/public/pw_stream/socket_stream.h
+++ b/pw_stream/public/pw_stream/socket_stream.h
@@ -37,7 +37,11 @@
   ~SocketStream();
 
   // Listen to the port and return after a client is connected
-  Status Init(uint16_t port);
+  Status Serve(uint16_t port);
+
+  // Connect to a local or remote endpoint. Host must be an IPv4 address. If
+  // host is nullptr then the locahost address is used instead.
+  Status Connect(const char* host, uint16_t port);
 
   // Close the socket stream and release all resources
   void Close();
diff --git a/pw_stream/socket_stream.cc b/pw_stream/socket_stream.cc
index f81ef05..034a3fb 100644
--- a/pw_stream/socket_stream.cc
+++ b/pw_stream/socket_stream.cc
@@ -16,11 +16,12 @@
 namespace pw::stream {
 
 static constexpr uint32_t kMaxConcurrentUser = 1;
+static constexpr char kLocalhostAddress[] = "127.0.0.1";
 
 SocketStream::~SocketStream() { Close(); }
 
 // Listen to the port and return after a client is connected
-Status SocketStream::Init(uint16_t port) {
+Status SocketStream::Serve(uint16_t port) {
   listen_port_ = port;
   socket_fd_ = socket(AF_INET, SOCK_STREAM, 0);
   if (socket_fd_ == kInvalidFd) {
@@ -53,6 +54,30 @@
   return OkStatus();
 }
 
+Status SocketStream::SocketStream::Connect(const char* host, uint16_t port) {
+  conn_fd_ = socket(AF_INET, SOCK_STREAM, 0);
+
+  sockaddr_in addr;
+  addr.sin_family = AF_INET;
+  addr.sin_port = htons(port);
+
+  if (host == nullptr) {
+    host = kLocalhostAddress;
+  }
+
+  if (inet_pton(AF_INET, host, &addr.sin_addr) <= 0) {
+    return Status::Unknown();
+  }
+
+  int result = connect(
+      conn_fd_, reinterpret_cast<struct sockaddr*>(&addr), sizeof(addr));
+  if (result < 0) {
+    return Status::Unknown();
+  }
+
+  return OkStatus();
+}
+
 void SocketStream::Close() {
   if (socket_fd_ != kInvalidFd) {
     close(socket_fd_);
diff --git a/targets/host/system_rpc_server.cc b/targets/host/system_rpc_server.cc
index 2e2c260..fd38732 100644
--- a/targets/host/system_rpc_server.cc
+++ b/targets/host/system_rpc_server.cc
@@ -46,7 +46,7 @@
     hdlc::WriteUIFrame(1, std::as_bytes(std::span(log)), socket_stream);
   });
 
-  socket_stream.Init(kSocketPort);
+  socket_stream.Serve(kSocketPort);
 }
 
 rpc::Server& Server() { return server; }