Skip to content

Commit d34ede5

Browse files
[all-devices-app] Make the Pigweed RPC server port configurable (project-chip#74139)
* [all-devices-app] Make the Pigweed RPC server port configurable The POSIX all-devices-app hardcoded the Pigweed RPC listen port to 33000 (with a "TODO: Add an arg for Pw port"), so two instances of the app could not run concurrently and the in-tree PwRPC test was pinned to that port. Add a --rpc-server-port option, named to match the equivalent option in examples/platform/linux so that existing tooling and docs carry over. It defaults to 33000, so behavior is unchanged when the flag is not passed. Note this is distinct from --port, which sets the Matter operational port. The option and the AppConfig field are parsed unconditionally rather than being guarded by PW_RPC_ENABLED, for two reasons: - the app_options unit test target does not define PW_RPC_ENABLED, so a guarded option would compile away and could not be tested; - all-devices-app defines PW_RPC_ENABLED as 0/1 while examples/platform/linux/Options.h tests it with #if defined(), so spreading the define to more targets risks changing the visible layout of LinuxDeviceOptions between translation units. Only the consumption of the value in main.cpp is guarded; builds without Pigweed RPC log a warning if the flag is passed rather than silently ignoring it. While here, the duplicated inline port parsing is factored into a shared AppOptions::ParsePortNumber() used by both --port and --rpc-server-port. This adds an `endptr == value` check the previous --port code lacked, so an empty value is now rejected instead of silently parsing as 0. The boolean-state-sensor PwRPC test no longer hardcodes 127.0.0.1:33000 and accepts "--int-arg rpc_server_port:<port>". * [all-devices-app] Address review feedback on --rpc-server-port Two fixes from review: Reject port 0 for --rpc-server-port. Binding port 0 makes the OS pick an ephemeral port, and because the pw_rpc port is not advertised anywhere and the app does not report the port it actually bound, no client could reach the server. The check is applied at the option site rather than in the shared ParsePortNumber(), so --port 0 keeps working: the Matter operational port is advertised over DNS-SD, so an ephemeral port is still resolvable there. Track whether --rpc-server-port was actually supplied, by making the AppConfig field a std::optional<uint16_t> (matching the neighbouring `port` field). Previously the non-PW_RPC build compared the value against the default, so "--rpc-server-port 33000" was silently ignored instead of warning. main.cpp now uses value_or(kDefaultRpcServerPort) when RPC is enabled and has_value() for the warning when it is not. * [all-devices-app] Document --rpc-server-port in the boolean-state-sensor test Wire the new option through both halves of the example invocation in the test's docstring: --rpc-server-port on the app side and the matching --int-arg rpc_server_port on the script side, so the two stay in sync. Addresses review feedback from @sxb427.
1 parent bc64de7 commit d34ede5

5 files changed

Lines changed: 156 additions & 11 deletions

File tree

examples/all-devices-app/all-devices-common/device/types/boolean-state-sensor/test.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,15 @@
3030
--app out/linux-x64-all-devices-ipv6only-no-ble-no-wifi-rpc-asan-clang-test/all-devices-app \
3131
--app-args "\
3232
--device contact-sensor:1 \
33-
--device water-leak-detector:2" \
33+
--device water-leak-detector:2 \
34+
--rpc-server-port 33000" \
3435
--factory-reset \
3536
--script examples/all-devices-app/all-devices-common/device/types/boolean-state-sensor/test.py \
3637
--script-args "\
3738
--commissioning-method on-network \
3839
--discriminator 3840 \
39-
--passcode 20202021\
40+
--passcode 20202021 \
41+
--int-arg rpc_server_port:33000\
4042
" \
4143
--app-stdin-pipe /tmp/app_stdin.txt'
4244
@@ -103,12 +105,15 @@ async def test_TC_BOOL_1_1(self):
103105
self.step(3, "Toggle and assert state values on Endpoint 1 and Endpoint 2 independently via PwRPC")
104106

105107
# Establish PwRPC connection
106-
logger.info("Establishing Pigweed RPC connection...")
108+
# Defaults to the app's own default port; override with
109+
# "--int-arg rpc_server_port:<port>" when the app is started with --rpc-server-port.
110+
rpc_server_port = self.user_params.get("rpc_server_port", 33000)
111+
logger.info("Establishing Pigweed RPC connection on port %d...", rpc_server_port)
107112
device_connection = create_device_serial_or_socket_connection(
108113
device="",
109114
baudrate=115200,
110115
token_databases=[],
111-
socket_addr="127.0.0.1:33000",
116+
socket_addr=f"127.0.0.1:{rpc_server_port}",
112117
compiled_protos=[attributes_service_pb2],
113118
rpc_logging=True,
114119
channel_id=rpc.DEFAULT_CHANNEL_ID,

examples/all-devices-app/posix/app_options/AppOptions.cpp

Lines changed: 51 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ constexpr uint16_t kOptionEnableKey = 0xffde;
6363
#if CHIP_DEVICE_CONFIG_ENABLE_WIFIPAF
6464
constexpr uint16_t kOptionWiFiPAF = 0xffdf;
6565
#endif
66+
constexpr uint16_t kOptionRpcServerPort = 0xffe0;
6667

6768
DeviceTypeParser AppOptions::sParser;
6869
AppOptions::AppConfig AppOptions::mConfig;
@@ -112,6 +113,27 @@ std::vector<uint16_t> AppOptions::ParseWiFiPafFreqList(const std::string & extCm
112113
}
113114
#endif // CHIP_DEVICE_CONFIG_ENABLE_WIFIPAF
114115

116+
bool AppOptions::ParsePortNumber(const char * value, uint16_t & port)
117+
{
118+
if (value == nullptr || *value == '\0')
119+
{
120+
return false;
121+
}
122+
123+
char * endptr = nullptr;
124+
const unsigned long val = strtoul(value, &endptr, 0);
125+
126+
// `endptr == value` catches input with no digits at all; strtoul would otherwise
127+
// report success with a value of 0.
128+
if (endptr == value || *endptr != '\0' || val > UINT16_MAX)
129+
{
130+
return false;
131+
}
132+
133+
port = static_cast<uint16_t>(val);
134+
return true;
135+
}
136+
115137
const AppOptions::AppConfig & AppOptions::GetConfig()
116138
{
117139
VerifyOrDie(sIsConfigValidated);
@@ -196,15 +218,33 @@ bool AppOptions::AllDevicesAppOptionHandler(const char * program, OptionSet * op
196218
mConfig.productId = static_cast<uint16_t>(strtoul(value, nullptr, 0));
197219
return true;
198220
case kOptionPort: {
199-
char * endptr;
200-
unsigned long val = strtoul(value, &endptr, 0);
201-
if (*endptr != '\0' || val > 0xFFFF)
221+
uint16_t port = 0;
222+
if (!ParsePortNumber(value, port))
202223
{
203224
ChipLogError(Support, "Invalid port: %s", value);
204225
return false;
205226
}
206-
mConfig.port = static_cast<uint16_t>(val);
207-
ChipLogProgress(AppServer, "Port option set to %u", static_cast<uint16_t>(val));
227+
mConfig.port = port;
228+
ChipLogProgress(AppServer, "Port option set to %u", port);
229+
return true;
230+
}
231+
case kOptionRpcServerPort: {
232+
uint16_t port = 0;
233+
if (!ParsePortNumber(value, port))
234+
{
235+
ChipLogError(Support, "Invalid RPC server port: %s", value);
236+
return false;
237+
}
238+
// Port 0 would make the OS pick an ephemeral port. Unlike the Matter operational port,
239+
// which is advertised over DNS-SD, the pw_rpc port is not discoverable and the app does
240+
// not report the port it actually bound, so no client could ever reach the server.
241+
if (port == 0)
242+
{
243+
ChipLogError(Support, "Invalid RPC server port: 0 is not a usable listen port");
244+
return false;
245+
}
246+
mConfig.rpcServerPort = port;
247+
ChipLogProgress(AppServer, "RPC server port option set to %u", port);
208248
return true;
209249
}
210250
case kOptionInterfaceId:
@@ -273,6 +313,7 @@ OptionSet * AppOptions::GetOptions()
273313
{ "trace-to", kArgumentRequired, kOptionTraceTo },
274314
{ "dac_provider", kArgumentRequired, kOptionDacProvider },
275315
{ "enable-key", kArgumentRequired, kOptionEnableKey },
316+
{ "rpc-server-port", kArgumentRequired, kOptionRpcServerPort },
276317
#if CHIP_DEVICE_CONFIG_ENABLE_WIFIPAF
277318
{ "wifipaf", kArgumentRequired, kOptionWiFiPAF },
278319
#endif
@@ -344,6 +385,11 @@ OptionSet * AppOptions::GetOptions()
344385
result += " --enable-key <key>\n";
345386
result += " A 16-byte, hex-encoded key, used to validate TestEventTrigger command of General Diagnostics cluster\n\n";
346387

388+
result += " --rpc-server-port <number>\n";
389+
result += " Listen port for the Pigweed RPC server, 1-65535 (default: 33000). This is\n";
390+
result += " separate from --port, which sets the Matter operational port. Only has an\n";
391+
result += " effect in builds compiled with Pigweed RPC support (chip_enable_pw_rpc).\n\n";
392+
347393
#if CHIP_DEVICE_CONFIG_ENABLE_WIFIPAF
348394
result += " --wifipaf freq_list=<freq_1>,<freq_2>...\n";
349395
result += " Enable Wi-Fi PAF via wpa_supplicant, on these NAN frequencies in MHz.\n";

examples/all-devices-app/posix/app_options/AppOptions.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,11 @@
3030
class AppOptions
3131
{
3232
public:
33+
/// Default listen port for the Pigweed RPC server. Matches the default used by the
34+
/// other Linux examples (see LinuxDeviceOptions::rpcServerPort) so that existing
35+
/// tooling keeps working when no port is given on the command line.
36+
static constexpr uint16_t kDefaultRpcServerPort = 33000;
37+
3338
struct AppConfig
3439
{
3540
std::vector<DeviceTypeParser::Entry> deviceTypeEntries;
@@ -47,6 +52,12 @@ class AppOptions
4752
uint8_t testEventTriggerEnableKey[16] = { 0 };
4853
bool enableWiFi = false;
4954
uint32_t bleController = 0;
55+
/// Listen port for the Pigweed RPC server, unset when --rpc-server-port was not given.
56+
/// This is unconditionally parsed, but only consumed by builds compiled with Pigweed RPC
57+
/// support (chip_enable_pw_rpc), so that the option stays unit-testable and the struct
58+
/// layout does not vary per build flavor. Callers should fall back to
59+
/// kDefaultRpcServerPort.
60+
std::optional<uint16_t> rpcServerPort;
5061
#if CHIP_DEVICE_CONFIG_ENABLE_WIFIPAF
5162
std::string wifipafExtCmds;
5263
// Frequencies in MHz parsed out of "--wifipaf freq_list=", in the order given.
@@ -58,6 +69,11 @@ class AppOptions
5869

5970
static const AppConfig & GetConfig();
6071

72+
/// Parse a TCP/UDP port number given on the command line. Accepts decimal, octal and
73+
/// hexadecimal notation (strtoul with base 0). Returns false, leaving `port` untouched,
74+
/// when the value is empty, is not entirely numeric, or does not fit in a uint16_t.
75+
static bool ParsePortNumber(const char * value, uint16_t & port);
76+
6177
#if CHIP_DEVICE_CONFIG_ENABLE_WIFIPAF
6278
/// Parse the frequencies out of a "--wifipaf" argument of the form
6379
/// "freq_list=<freq_1>,<freq_2>...". Returns an empty list when the key is absent

examples/all-devices-app/posix/app_options/tests/TestAppOptions.cpp

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,77 @@
2424
#include <string>
2525
#include <vector>
2626

27+
namespace {
28+
29+
TEST(TestAppOptionsPortNumber, AcceptsPortsAcrossTheWholeRange)
30+
{
31+
uint16_t port = 0;
32+
33+
EXPECT_TRUE(AppOptions::ParsePortNumber("5540", port));
34+
EXPECT_EQ(port, static_cast<uint16_t>(5540));
35+
36+
EXPECT_TRUE(AppOptions::ParsePortNumber("33000", port));
37+
EXPECT_EQ(port, static_cast<uint16_t>(33000));
38+
39+
// 0 stays acceptable: --port already allowed it before the parsing was shared.
40+
EXPECT_TRUE(AppOptions::ParsePortNumber("0", port));
41+
EXPECT_EQ(port, static_cast<uint16_t>(0));
42+
43+
EXPECT_TRUE(AppOptions::ParsePortNumber("65535", port));
44+
EXPECT_EQ(port, static_cast<uint16_t>(65535));
45+
}
46+
47+
TEST(TestAppOptionsPortNumber, AcceptsHexadecimalAndOctalNotation)
48+
{
49+
uint16_t port = 0;
50+
51+
EXPECT_TRUE(AppOptions::ParsePortNumber("0x80e8", port));
52+
EXPECT_EQ(port, static_cast<uint16_t>(33000));
53+
54+
EXPECT_TRUE(AppOptions::ParsePortNumber("010", port));
55+
EXPECT_EQ(port, static_cast<uint16_t>(8));
56+
}
57+
58+
TEST(TestAppOptionsPortNumber, RejectsValuesOutsideAUint16)
59+
{
60+
uint16_t port = 1;
61+
62+
EXPECT_FALSE(AppOptions::ParsePortNumber("65536", port));
63+
EXPECT_FALSE(AppOptions::ParsePortNumber("70000", port));
64+
EXPECT_FALSE(AppOptions::ParsePortNumber("-1", port));
65+
66+
// Rejected input must leave the caller's value untouched.
67+
EXPECT_EQ(port, static_cast<uint16_t>(1));
68+
}
69+
70+
TEST(TestAppOptionsPortNumber, RejectsInputThatIsNotEntirelyNumeric)
71+
{
72+
uint16_t port = 1;
73+
74+
EXPECT_FALSE(AppOptions::ParsePortNumber(nullptr, port));
75+
EXPECT_FALSE(AppOptions::ParsePortNumber("", port));
76+
EXPECT_FALSE(AppOptions::ParsePortNumber("abc", port));
77+
EXPECT_FALSE(AppOptions::ParsePortNumber("5540x", port));
78+
EXPECT_FALSE(AppOptions::ParsePortNumber("5540 ", port));
79+
80+
EXPECT_EQ(port, static_cast<uint16_t>(1));
81+
}
82+
83+
TEST(TestAppOptionsRpcServerPort, IsUnsetUntilTheOptionIsGiven)
84+
{
85+
// Left unset so that a non-PW_RPC build can tell "--rpc-server-port 33000" apart from the
86+
// option not being passed at all, and warn in the former case.
87+
const AppOptions::AppConfig defaults;
88+
EXPECT_FALSE(defaults.rpcServerPort.has_value());
89+
90+
// Keep in sync with LinuxDeviceOptions::rpcServerPort so that tooling which does not pass
91+
// --rpc-server-port keeps reaching the app.
92+
EXPECT_EQ(AppOptions::kDefaultRpcServerPort, static_cast<uint16_t>(33000));
93+
EXPECT_EQ(defaults.rpcServerPort.value_or(AppOptions::kDefaultRpcServerPort), AppOptions::kDefaultRpcServerPort);
94+
}
95+
96+
} // namespace
97+
2798
#if CHIP_DEVICE_CONFIG_ENABLE_WIFIPAF
2899

29100
namespace {

examples/all-devices-app/posix/main.cpp

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -398,8 +398,15 @@ void RunApplication(AppMainLoopImplementation * mainLoop = nullptr)
398398
static chip::app::PigweedAttributeAccessor sPwOobAccessor;
399399
chip::rpc::PigweedDebugAccessInterceptorRegistry::Instance().Register(&sPwOobAccessor);
400400

401-
chip::rpc::Init(33000); // TODO: Add an arg for Pw port.
402-
ChipLogProgress(AppServer, "PW_RPC initialized.");
401+
const uint16_t rpcServerPort = AppOptions::GetConfig().rpcServerPort.value_or(AppOptions::kDefaultRpcServerPort);
402+
chip::rpc::Init(rpcServerPort);
403+
ChipLogProgress(AppServer, "PW_RPC initialized on port %u.", rpcServerPort);
404+
#else
405+
if (AppOptions::GetConfig().rpcServerPort.has_value())
406+
{
407+
ChipLogError(AppServer,
408+
"--rpc-server-port was specified, but this binary was built without Pigweed RPC support. Ignoring it.");
409+
}
403410
#endif // PW_RPC_ENABLED
404411

405412
// Init ZCL Data Model and CHIP App Server

0 commit comments

Comments
 (0)