diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomTradeManager.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomTradeManager.java index 6b4790ec..b7c77efe 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomTradeManager.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomTradeManager.java @@ -19,8 +19,13 @@ public class RoomTradeManager { * Starts a trade between two users. */ public void startTrade(Habbo userOne, Habbo userTwo) { - RoomTrade trade = new RoomTrade(userOne, userTwo, this.room); + RoomTrade trade; synchronized (this.activeTrades) { + if (this.hasActiveTrade(userOne) || this.hasActiveTrade(userTwo)) { + return; + } + + trade = new RoomTrade(userOne, userTwo, this.room); this.activeTrades.add(trade); } @@ -58,4 +63,16 @@ public class RoomTradeManager { public THashSet getActiveTrades() { return this.activeTrades; } + + private boolean hasActiveTrade(Habbo user) { + for (RoomTrade trade : this.activeTrades) { + for (RoomTradeUser habbo : trade.getRoomTradeUsers()) { + if (habbo.getHabbo() == user) { + return true; + } + } + } + + return false; + } } diff --git a/Emulator/src/test/java/com/eu/habbo/habbohotel/rooms/RoomTradeManagerContractTest.java b/Emulator/src/test/java/com/eu/habbo/habbohotel/rooms/RoomTradeManagerContractTest.java new file mode 100644 index 00000000..07ec566d --- /dev/null +++ b/Emulator/src/test/java/com/eu/habbo/habbohotel/rooms/RoomTradeManagerContractTest.java @@ -0,0 +1,30 @@ +package com.eu.habbo.habbohotel.rooms; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.nio.file.Files; +import java.nio.file.Path; + +import org.junit.jupiter.api.Test; + +class RoomTradeManagerContractTest { + private static String roomTradeManagerSource() throws Exception { + return Files.readString(Path.of("src/main/java/com/eu/habbo/habbohotel/rooms/RoomTradeManager.java")); + } + + @Test + void startTradeRejectsParticipantsAlreadyInActiveTradeInsideLock() throws Exception { + String source = roomTradeManagerSource(); + int synchronizedBlock = source.indexOf("synchronized (this.activeTrades)"); + int activeGuard = source.indexOf("hasActiveTrade(userOne) || this.hasActiveTrade(userTwo)"); + int addTrade = source.indexOf("this.activeTrades.add(trade)"); + + assertTrue(synchronizedBlock > -1, "RoomTradeManager.startTrade must lock activeTrades before mutation"); + assertTrue(activeGuard > synchronizedBlock, + "startTrade must check both participants for an existing active trade while holding the activeTrades lock"); + assertTrue(activeGuard < addTrade, + "duplicate participant guard must run before a new RoomTrade is added"); + assertTrue(source.contains("private boolean hasActiveTrade(Habbo user)"), + "active trade lookup should be reusable under the same activeTrades lock"); + } +}