Merge pull request #256 from simoleo89/fix/wired-trigger-payloads

fix(wired): bound trigger payloads
This commit is contained in:
DuckieTM
2026-06-18 12:48:39 +02:00
committed by GitHub
6 changed files with 121 additions and 12 deletions
@@ -71,6 +71,7 @@ public class WiredTriggerAtSetTime extends InteractionWiredTrigger implements Wi
@Override @Override
public void loadWiredData(ResultSet set, Room room) throws SQLException { public void loadWiredData(ResultSet set, Room room) throws SQLException {
String wiredData = set.getString("wired_data"); String wiredData = set.getString("wired_data");
this.executeTime = parseExecuteTime(wiredData);
Integer storedExecuteTime = null; Integer storedExecuteTime = null;
try { try {
@@ -83,6 +84,7 @@ public class WiredTriggerAtSetTime extends InteractionWiredTrigger implements Wi
} catch (RuntimeException ignored) { } catch (RuntimeException ignored) {
storedExecuteTime = null; storedExecuteTime = null;
} }
}
this.executeTime = WiredTimerInputGuard.normalizeStoredMillis(storedExecuteTime, MIN_DELAY, LEGACY_FALLBACK_DELAY); this.executeTime = WiredTimerInputGuard.normalizeStoredMillis(storedExecuteTime, MIN_DELAY, LEGACY_FALLBACK_DELAY);
@@ -146,6 +148,15 @@ public class WiredTriggerAtSetTime extends InteractionWiredTrigger implements Wi
return true; return true;
} }
private static int safeMultiply(int value, int factor) {
if (value <= 0) {
return DEFAULT_EXECUTE_TIME;
}
long multiplied = (long) value * factor;
return multiplied > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) multiplied;
}
// ========== WiredTickable Implementation ========== // ========== WiredTickable Implementation ==========
@Override @Override
@@ -71,6 +71,7 @@ public class WiredTriggerAtTimeLong extends InteractionWiredTrigger implements W
@Override @Override
public void loadWiredData(ResultSet set, Room room) throws SQLException { public void loadWiredData(ResultSet set, Room room) throws SQLException {
String wiredData = set.getString("wired_data"); String wiredData = set.getString("wired_data");
this.executeTime = parseExecuteTime(wiredData);
Integer storedExecuteTime = null; Integer storedExecuteTime = null;
try { try {
@@ -83,6 +84,7 @@ public class WiredTriggerAtTimeLong extends InteractionWiredTrigger implements W
} catch (RuntimeException ignored) { } catch (RuntimeException ignored) {
storedExecuteTime = null; storedExecuteTime = null;
} }
}
this.executeTime = WiredTimerInputGuard.normalizeStoredMillis(storedExecuteTime, MIN_DELAY, LEGACY_FALLBACK_DELAY); this.executeTime = WiredTimerInputGuard.normalizeStoredMillis(storedExecuteTime, MIN_DELAY, LEGACY_FALLBACK_DELAY);
@@ -146,6 +148,15 @@ public class WiredTriggerAtTimeLong extends InteractionWiredTrigger implements W
return true; return true;
} }
private static int safeMultiply(int value, int factor) {
if (value <= 0) {
return DEFAULT_EXECUTE_TIME;
}
long multiplied = (long) value * factor;
return multiplied > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) multiplied;
}
// ========== WiredTickable Implementation ========== // ========== WiredTickable Implementation ==========
@Override @Override
@@ -66,6 +66,8 @@ public class WiredTriggerRepeater extends InteractionWiredTrigger implements Wir
@Override @Override
public void loadWiredData(ResultSet set, Room room) throws SQLException { public void loadWiredData(ResultSet set, Room room) throws SQLException {
String wiredData = set.getString("wired_data"); String wiredData = set.getString("wired_data");
this.repeatTime = parseRepeatTime(wiredData);
}
Integer storedRepeatTime = null; Integer storedRepeatTime = null;
try { try {
@@ -78,6 +80,7 @@ public class WiredTriggerRepeater extends InteractionWiredTrigger implements Wir
} catch (RuntimeException ignored) { } catch (RuntimeException ignored) {
storedRepeatTime = null; storedRepeatTime = null;
} }
}
this.repeatTime = WiredTimerInputGuard.normalizeStoredMillis(storedRepeatTime, MIN_DELAY, LEGACY_FALLBACK_DELAY); this.repeatTime = WiredTimerInputGuard.normalizeStoredMillis(storedRepeatTime, MIN_DELAY, LEGACY_FALLBACK_DELAY);
} }
@@ -130,7 +133,8 @@ public class WiredTriggerRepeater extends InteractionWiredTrigger implements Wir
if (settings.getIntParams().length < 1) return false; if (settings.getIntParams().length < 1) return false;
this.repeatTime = WiredTimerInputGuard.fromClientUnits(settings.getIntParams()[0], STEP_MS, MIN_DELAY); this.repeatTime = WiredTimerInputGuard.fromClientUnits(settings.getIntParams()[0], STEP_MS, MIN_DELAY);
return true; long multiplied = (long) value * factor;
return multiplied > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) multiplied;
} }
// ========== WiredTickable Implementation ========== // ========== WiredTickable Implementation ==========
@@ -66,6 +66,8 @@ public class WiredTriggerRepeaterLong extends InteractionWiredTrigger implements
@Override @Override
public void loadWiredData(ResultSet set, Room room) throws SQLException { public void loadWiredData(ResultSet set, Room room) throws SQLException {
String wiredData = set.getString("wired_data"); String wiredData = set.getString("wired_data");
this.repeatTime = parseRepeatTime(wiredData);
}
Integer storedRepeatTime = null; Integer storedRepeatTime = null;
try { try {
@@ -78,6 +80,7 @@ public class WiredTriggerRepeaterLong extends InteractionWiredTrigger implements
} catch (RuntimeException ignored) { } catch (RuntimeException ignored) {
storedRepeatTime = null; storedRepeatTime = null;
} }
}
this.repeatTime = WiredTimerInputGuard.normalizeStoredMillis(storedRepeatTime, MIN_DELAY, LEGACY_FALLBACK_DELAY); this.repeatTime = WiredTimerInputGuard.normalizeStoredMillis(storedRepeatTime, MIN_DELAY, LEGACY_FALLBACK_DELAY);
} }
@@ -133,6 +136,15 @@ public class WiredTriggerRepeaterLong extends InteractionWiredTrigger implements
return true; return true;
} }
private static int safeMultiply(int value, int factor) {
if (value <= 0) {
return DEFAULT_DELAY;
}
long multiplied = (long) value * factor;
return multiplied > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) multiplied;
}
// ========== WiredTickable Implementation ========== // ========== WiredTickable Implementation ==========
@Override @Override
@@ -18,6 +18,7 @@ import java.sql.SQLException;
public class WiredTriggerScoreAchieved extends InteractionWiredTrigger { public class WiredTriggerScoreAchieved extends InteractionWiredTrigger {
private static final WiredTriggerType type = WiredTriggerType.SCORE_ACHIEVED; private static final WiredTriggerType type = WiredTriggerType.SCORE_ACHIEVED;
static final int MAX_SCORE = 1_000_000;
private int score = 0; private int score = 0;
private int teamType = GameTeamColors.NONE.type; private int teamType = GameTeamColors.NONE.type;
@@ -71,17 +72,27 @@ public class WiredTriggerScoreAchieved extends InteractionWiredTrigger {
@Override @Override
public void loadWiredData(ResultSet set, Room room) throws SQLException { public void loadWiredData(ResultSet set, Room room) throws SQLException {
String wiredData = set.getString("wired_data"); String wiredData = set.getString("wired_data");
JsonData data = parseData(wiredData);
this.score = data.score;
this.teamType = data.teamType;
}
if (wiredData.startsWith("{")) { static JsonData parseData(String wiredData) {
JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class); if (wiredData == null || wiredData.isBlank()) {
this.score = data.score; return new JsonData(0, GameTeamColors.NONE.type);
this.teamType = normalizeTeamType(data.teamType); }
} else {
try { try {
this.score = Integer.parseInt(wiredData); if (wiredData.startsWith("{")) {
} catch (Exception e) { JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class);
return data != null
? new JsonData(clampScore(data.score), normalizeTeamType(data.teamType))
: new JsonData(0, GameTeamColors.NONE.type);
} }
this.teamType = GameTeamColors.NONE.type;
return new JsonData(clampScore(Integer.parseInt(wiredData)), GameTeamColors.NONE.type);
} catch (RuntimeException e) {
return new JsonData(0, GameTeamColors.NONE.type);
} }
} }
@@ -116,7 +127,7 @@ public class WiredTriggerScoreAchieved extends InteractionWiredTrigger {
@Override @Override
public boolean saveData(WiredSettings settings) { public boolean saveData(WiredSettings settings) {
if(settings.getIntParams().length < 1) return false; if(settings.getIntParams().length < 1) return false;
this.score = settings.getIntParams()[0]; this.score = clampScore(settings.getIntParams()[0]);
this.teamType = (settings.getIntParams().length > 1) this.teamType = (settings.getIntParams().length > 1)
? normalizeTeamType(settings.getIntParams()[1]) ? normalizeTeamType(settings.getIntParams()[1])
: GameTeamColors.NONE.type; : GameTeamColors.NONE.type;
@@ -128,7 +139,15 @@ public class WiredTriggerScoreAchieved extends InteractionWiredTrigger {
return true; return true;
} }
private int normalizeTeamType(int value) { static int clampScore(int value) {
if (value < 0) {
return 0;
}
return Math.min(value, MAX_SCORE);
}
static int normalizeTeamType(int value) {
if (value >= GameTeamColors.RED.type && value <= GameTeamColors.YELLOW.type) { if (value >= GameTeamColors.RED.type && value <= GameTeamColors.YELLOW.type) {
return value; return value;
} }
@@ -0,0 +1,52 @@
package com.eu.habbo.habbohotel.items.interactions.wired.triggers;
import com.eu.habbo.habbohotel.games.GameTeamColors;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
class WiredTriggerPayloadGuardTest {
@Test
void repeaterPayloadsFallBackOnInvalidDataAndClampUpperBound() {
assertEquals(WiredTriggerRepeater.DEFAULT_DELAY, WiredTriggerRepeater.parseRepeatTime(null));
assertEquals(WiredTriggerRepeater.DEFAULT_DELAY, WiredTriggerRepeater.parseRepeatTime("not-a-number"));
assertEquals(WiredTriggerRepeater.DEFAULT_DELAY, WiredTriggerRepeater.parseRepeatTime("{broken"));
assertEquals(WiredTriggerRepeater.DEFAULT_DELAY, WiredTriggerRepeater.parseRepeatTime("{\"repeatTime\":0}"));
assertEquals(WiredTriggerRepeater.MAX_DELAY, WiredTriggerRepeater.parseRepeatTime("{\"repeatTime\":2147483647}"));
assertEquals(WiredTriggerRepeaterLong.DEFAULT_DELAY, WiredTriggerRepeaterLong.parseRepeatTime(null));
assertEquals(WiredTriggerRepeaterLong.DEFAULT_DELAY, WiredTriggerRepeaterLong.parseRepeatTime("1"));
assertEquals(WiredTriggerRepeaterLong.MAX_DELAY, WiredTriggerRepeaterLong.parseRepeatTime("2147483647"));
}
@Test
void atTimePayloadsFallBackOnInvalidDataAndClampUpperBound() {
assertEquals(WiredTriggerAtSetTime.DEFAULT_EXECUTE_TIME, WiredTriggerAtSetTime.parseExecuteTime(null));
assertEquals(WiredTriggerAtSetTime.DEFAULT_EXECUTE_TIME, WiredTriggerAtSetTime.parseExecuteTime("bad"));
assertEquals(WiredTriggerAtSetTime.DEFAULT_EXECUTE_TIME, WiredTriggerAtSetTime.parseExecuteTime("{\"executeTime\":0}"));
assertEquals(WiredTriggerAtSetTime.MAX_EXECUTE_TIME, WiredTriggerAtSetTime.parseExecuteTime("{\"executeTime\":2147483647}"));
assertEquals(WiredTriggerAtTimeLong.DEFAULT_EXECUTE_TIME, WiredTriggerAtTimeLong.parseExecuteTime("{broken"));
assertEquals(WiredTriggerAtTimeLong.DEFAULT_EXECUTE_TIME, WiredTriggerAtTimeLong.parseExecuteTime("1"));
assertEquals(WiredTriggerAtTimeLong.MAX_EXECUTE_TIME, WiredTriggerAtTimeLong.parseExecuteTime("2147483647"));
}
@Test
void scorePayloadsNormalizeScoreAndTeam() {
WiredTriggerScoreAchieved.JsonData invalid = WiredTriggerScoreAchieved.parseData("{broken");
assertEquals(0, invalid.score);
assertEquals(GameTeamColors.NONE.type, invalid.teamType);
WiredTriggerScoreAchieved.JsonData legacy = WiredTriggerScoreAchieved.parseData("-10");
assertEquals(0, legacy.score);
assertEquals(GameTeamColors.NONE.type, legacy.teamType);
WiredTriggerScoreAchieved.JsonData capped = WiredTriggerScoreAchieved.parseData("{\"score\":2147483647,\"teamType\":999}");
assertEquals(WiredTriggerScoreAchieved.MAX_SCORE, capped.score);
assertEquals(GameTeamColors.NONE.type, capped.teamType);
WiredTriggerScoreAchieved.JsonData validTeam = WiredTriggerScoreAchieved.parseData("{\"score\":50,\"teamType\":" + GameTeamColors.RED.type + "}");
assertEquals(50, validTeam.score);
assertEquals(GameTeamColors.RED.type, validTeam.teamType);
}
}