Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 14 additions & 2 deletions lib/src/main/cpp/pixiewps/pixiewps_wrapper.c
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
#include <errno.h>
#include <android/log.h>

#define LOG_TAG "PixiewpsWrapper"
Expand Down Expand Up @@ -107,15 +108,26 @@ int pixiewps_compute(const char *pke, const char *pkr,

// Wait for child
int status;
waitpid(pid, &status, 0);
if (waitpid(pid, &status, 0) < 0) {
LOGE("pixiewps_compute: waitpid failed: %s", strerror(errno));
pin_out[0] = '\0';
return -1;
}

if (WIFSIGNALED(status)) {
LOGE("pixiewps_compute: killed by signal %d, path=%s", WTERMSIG(status), pixiewps_exec_path);
pin_out[0] = '\0';
return -1;
}
Comment on lines 110 to +121

Copilot AI Mar 12, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

waitpid(pid, &status, 0) return value isn’t checked. If waitpid fails, status is undefined and the subsequent WIFSIGNALED/WIFEXITED handling and logging can read garbage. Handle waitpid returning -1 (log errno, clear pin_out, close resources) before interpreting status.

Copilot uses AI. Check for mistakes.

if (WIFEXITED(status) && WEXITSTATUS(status) == 127) {
LOGE("pixiewps_compute: execl failed (exit 127), path=%s", pixiewps_exec_path);
pin_out[0] = '\0';
return -1;
}

LOGI("pixiewps_compute: exit=%d, output len=%d", WEXITSTATUS(status), total);
LOGI("pixiewps_compute: exit=%d, output len=%d",
WIFEXITED(status) ? WEXITSTATUS(status) : -1, total);

// Parse output for "WPS pin:" line
char *pin_line = strstr(buf, "WPS pin:");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,12 @@ private WpsResult executeNativeWps(String bssid, String pin) {
SystemClock.sleep(2000);

// Send WPS_REG command
wpsNative.wpsReg(bssid, pin);
String regResponse = wpsNative.wpsReg(bssid, pin);
String trimmed = regResponse != null ? regResponse.trim() : null;
if (trimmed == null || trimmed.isEmpty() || "FAIL".equalsIgnoreCase(trimmed)) {
return createErrorResult(bssid, pin,
"WPS_REG command failed (response: " + regResponse + ")");
}

// Read WPS result with timeout
sangiorgi.wps.lib.ndk.WpsResult nativeResult =
Expand Down
15 changes: 11 additions & 4 deletions lib/src/main/java/sangiorgi/wps/lib/services/WpsResult.java
Original file line number Diff line number Diff line change
Expand Up @@ -115,14 +115,21 @@ public boolean isLocked() {
* @return true if first 4 digits are correct (M6 failure or later)
*/
public boolean isFirstHalfCorrect() {
// Scan all results before concluding — an M6 in any position wins over an earlier M4,
// which matters when WpsResult aggregates multiple CommandResults (e.g. Pixie Dust).
for (CommandResult result : commandResults) {
String output = result.getOutputAsString().toLowerCase(Locale.ROOT);
// M6 failure indicates first half was correct
if (output.contains("m6") || output.contains("wsc_nack after m6")) {
// M6 failure (msg=10) indicates first half was correct, second half wrong
if (output.contains("m6") || output.contains("wsc_nack after m6")
|| output.contains("msg=10")) {
return true;
}
// M4 failure indicates first half was wrong
if (output.contains("m4") || output.contains("wsc_nack after m4")) {
}
for (CommandResult result : commandResults) {
String output = result.getOutputAsString().toLowerCase(Locale.ROOT);
// M4 failure (msg=8) indicates first half was wrong
if (output.contains("m4") || output.contains("wsc_nack after m4")
|| output.contains("msg=8")) {
return false;
Comment on lines 117 to 133

Copilot AI Mar 12, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isFirstHalfCorrect() returns immediately when it finds an M4/msg=8 indicator in an earlier CommandResult, which can incorrectly report false if a later aggregated result contains an M6/msg=10 indicator (first half correct). Since WpsResult can aggregate multiple command outputs, consider scanning all results first (e.g., remember whether any M6/msg=10 was seen, then only fall back to M4/msg=8 if no M6 was found) rather than returning on the first match.

Copilot uses AI. Check for mistakes.
}
}
Expand Down
115 changes: 115 additions & 0 deletions lib/src/test/java/sangiorgi/wps/lib/services/WpsResultTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,121 @@ public void testFirstHalfCorrectDefault() {
assertFalse(result.isFirstHalfCorrect());
}

// ============ Native Status Mapping (convertNativeResult output) ============
// These tests verify that WpsResult correctly interprets the synthetic output
// produced by WpsExecutor.convertNativeResult() for each native status code.

@Test
public void testNativeSuccessWithPassword() {
// Simulates convertNativeResult for ndk.WpsResult.Status.SUCCESS with networkKey
CommandResult cmdResult = new CommandResult(
true,
Arrays.asList("WPS-SUCCESS", "Network Key: MyPassword123"),
null, WpsCommand.CommandType.WPA_SUPPLICANT);
WpsResult result = new WpsResult(TEST_BSSID, TEST_PIN, Collections.singletonList(cmdResult));
result.setPassword("MyPassword123");

assertTrue(result.isSuccess());
assertEquals("MyPassword123", result.getPassword());
assertFalse(result.isTimeout());
assertFalse(result.isLocked());
}

@Test
public void testNativeSuccessWithoutPassword() {
// Simulates convertNativeResult for SUCCESS when networkKey is null
CommandResult cmdResult = new CommandResult(
true,
Arrays.asList("WPS-SUCCESS"),
null, WpsCommand.CommandType.WPA_SUPPLICANT);
WpsResult result = new WpsResult(TEST_BSSID, TEST_PIN, Collections.singletonList(cmdResult));

assertTrue(result.isSuccess());
assertNull(result.getPassword());
}

@Test
public void testNativeFourFailIsWrongPin() {
// Simulates convertNativeResult for FOUR_FAIL (msg=8 = M4 failure)
CommandResult cmdResult = new CommandResult(
false,
Arrays.asList("WPS-FAIL msg=8 config_error=18"),
null, WpsCommand.CommandType.WPA_SUPPLICANT);
WpsResult result = new WpsResult(TEST_BSSID, TEST_PIN, Collections.singletonList(cmdResult));

assertFalse(result.isSuccess());
assertTrue("FOUR_FAIL should be detected as wrong PIN", result.isWrongPin());
assertTrue("FOUR_FAIL should be detected as PIN rejected", result.isPinRejected());
assertFalse("FOUR_FAIL first half should NOT be correct", result.isFirstHalfCorrect());
}

@Test
public void testNativeThreeFailIsFirstHalfCorrect() {
// Simulates convertNativeResult for THREE_FAIL (msg=10 = M6 failure)
CommandResult cmdResult = new CommandResult(
false,
Arrays.asList("WPS-FAIL msg=10 config_error=18"),
null, WpsCommand.CommandType.WPA_SUPPLICANT);
WpsResult result = new WpsResult(TEST_BSSID, TEST_PIN, Collections.singletonList(cmdResult));

assertFalse(result.isSuccess());
assertTrue("THREE_FAIL should be detected as PIN rejected", result.isPinRejected());
assertTrue("THREE_FAIL first half should be correct", result.isFirstHalfCorrect());
}

@Test
public void testNativeLockedStatus() {
// Simulates convertNativeResult for LOCKED
CommandResult cmdResult = new CommandResult(
false,
Arrays.asList("WPS-FAIL config_error=15", "setup locked"),
null, WpsCommand.CommandType.WPA_SUPPLICANT);
WpsResult result = new WpsResult(TEST_BSSID, TEST_PIN, Collections.singletonList(cmdResult));

assertFalse(result.isSuccess());
assertTrue("LOCKED should be detected", result.isLocked());
assertEquals("WPS is locked on this router", result.getFailureReason());
}

@Test
public void testNativeTimeoutStatus() {
// Simulates convertNativeResult for TIMEOUT
CommandResult cmdResult = new CommandResult(
false,
Arrays.asList("WPS-TIMEOUT"),
null, WpsCommand.CommandType.WPA_SUPPLICANT);
WpsResult result = new WpsResult(TEST_BSSID, TEST_PIN, Collections.singletonList(cmdResult));

assertFalse(result.isSuccess());
assertTrue("TIMEOUT should be detected", result.isTimeout());
}

@Test
public void testNativeSelinuxStatus() {
// Simulates convertNativeResult for SELINUX
CommandResult cmdResult = new CommandResult(
false,
Arrays.asList("SELinux denied"),
null, WpsCommand.CommandType.WPA_SUPPLICANT);
WpsResult result = new WpsResult(TEST_BSSID, TEST_PIN, Collections.singletonList(cmdResult));

assertFalse(result.isSuccess());
assertFalse(result.isTimeout());
}

@Test
public void testNativeDefaultFailStatus() {
// Simulates convertNativeResult for ERROR/CRC_FAIL/unknown
CommandResult cmdResult = new CommandResult(
false,
Arrays.asList("WPS-FAIL"),
null, WpsCommand.CommandType.WPA_SUPPLICANT);
WpsResult result = new WpsResult(TEST_BSSID, TEST_PIN, Collections.singletonList(cmdResult));

assertFalse(result.isSuccess());
assertTrue("Generic WPS-FAIL should be detected as PIN rejected", result.isPinRejected());
}

// ============ Wrong PIN ============

@Test
Expand Down
Loading