/*
This file is part of jpcsp.
Jpcsp is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Jpcsp is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Jpcsp. If not, see <http://www.gnu.org/licenses/>.
*/
package jpcsp.network.adhoc;
import static jpcsp.HLE.modules.sceNetAdhocMatching.PSP_ADHOC_MATCHING_EVENT_ACCEPT;
import static jpcsp.HLE.modules.sceNetAdhocMatching.PSP_ADHOC_MATCHING_EVENT_CANCEL;
import static jpcsp.HLE.modules.sceNetAdhocMatching.PSP_ADHOC_MATCHING_EVENT_COMPLETE;
import static jpcsp.HLE.modules.sceNetAdhocMatching.PSP_ADHOC_MATCHING_EVENT_DATA;
import static jpcsp.HLE.modules.sceNetAdhocMatching.PSP_ADHOC_MATCHING_EVENT_DATA_CONFIRM;
import static jpcsp.HLE.modules.sceNetAdhocMatching.PSP_ADHOC_MATCHING_EVENT_DISCONNECT;
import static jpcsp.HLE.modules.sceNetAdhocMatching.PSP_ADHOC_MATCHING_EVENT_HELLO;
import static jpcsp.HLE.modules.sceNetAdhocMatching.PSP_ADHOC_MATCHING_EVENT_INTERNAL_PING;
import static jpcsp.HLE.modules.sceNetAdhocMatching.PSP_ADHOC_MATCHING_EVENT_JOIN;
import static jpcsp.HLE.modules.sceNetAdhocMatching.PSP_ADHOC_MATCHING_EVENT_LEFT;
import static jpcsp.HLE.modules.sceNetAdhocMatching.PSP_ADHOC_MATCHING_MODE_CLIENT;
import static jpcsp.network.adhoc.AdhocMessage.MAX_HEADER_SIZE;
import java.io.IOException;
import java.net.InetAddress;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
import java.util.LinkedList;
import java.util.List;
import jpcsp.Emulator;
import jpcsp.Memory;
import jpcsp.HLE.Modules;
import jpcsp.HLE.kernel.types.SceKernelThreadInfo;
import jpcsp.HLE.kernel.types.pspNetMacAddress;
import jpcsp.HLE.modules.SysMemUserForUser;
import jpcsp.HLE.modules.ThreadManForUser;
import jpcsp.HLE.modules.sceNetAdhoc;
import jpcsp.HLE.modules.sceNetAdhocMatching;
import jpcsp.hardware.Wlan;
import jpcsp.memory.IMemoryReader;
import jpcsp.memory.MemoryReader;
import jpcsp.network.INetworkAdapter;
import jpcsp.util.Utilities;
/**
* @author gid15
*
*/
public abstract class MatchingObject extends AdhocObject {
private int mode;
private int maxPeers;
private int helloDelay;
private int pingDelay;
private int initCount;
private int msgDelay;
private int callback;
private boolean started;
private long lastHelloMicros;
private long lastPingMicros;
private byte[] helloOptData;
private SceKernelThreadInfo eventThread;
private SceKernelThreadInfo inputThread;
private byte[] pendingJoinRequest;
private boolean inConnection;
private boolean connected;
private boolean pendingComplete;
private LinkedList<pspNetMacAddress> members = new LinkedList<pspNetMacAddress>();
private LinkedList<CallbackEvent> pendingCallbackEvents = new LinkedList<MatchingObject.CallbackEvent>();
private static class CallbackEvent {
private int event;
private pspNetMacAddress macAddress;
private int optLen;
private int optData;
public CallbackEvent(int event, int macAddr, int optLen, int optData) {
this.event = event;
macAddress = new pspNetMacAddress();
macAddress.read(Memory.getInstance(), macAddr);
this.optLen = optLen;
this.optData = optData;
}
public int getEvent() {
return event;
}
public pspNetMacAddress getMacAddress() {
return macAddress;
}
public int getOptLen() {
return optLen;
}
public int getOptData() {
return optData;
}
}
public MatchingObject(INetworkAdapter networkAdapter) {
super(networkAdapter);
}
public int getMode() {
return mode;
}
public void setMode(int mode) {
this.mode = mode;
}
public int getMaxPeers() {
return maxPeers;
}
public void setMaxPeers(int maxPeers) {
this.maxPeers = maxPeers;
}
public int getHelloDelay() {
return helloDelay;
}
public void setHelloDelay(int helloDelay) {
this.helloDelay = helloDelay;
}
public int getPingDelay() {
return pingDelay;
}
public void setPingDelay(int pingDelay) {
this.pingDelay = pingDelay;
}
public int getInitCount() {
return initCount;
}
public void setInitCount(int initCount) {
this.initCount = initCount;
}
public int getMsgDelay() {
return msgDelay;
}
public void setMsgDelay(int msgDelay) {
this.msgDelay = msgDelay;
}
public int getCallback() {
return callback;
}
public void setCallback(int callback) {
this.callback = callback;
}
public List<pspNetMacAddress> getMembers() {
return members;
}
public void create() {
}
public int start(int evthPri, int evthStack, int inthPri, int inthStack, int optLen, int optData) {
try {
setHelloOpt(optLen, optData);
openSocket();
ThreadManForUser threadMan = Modules.ThreadManForUserModule;
if (eventThread == null) {
eventThread = threadMan.hleKernelCreateThread("SceNetAdhocMatchingEvent",
ThreadManForUser.NET_ADHOC_MATCHING_EVENT_LOOP_ADDRESS,
evthPri, evthStack, threadMan.getCurrentThread().attr, 0, SysMemUserForUser.USER_PARTITION_ID);
threadMan.hleKernelStartThread(eventThread, 0, 0, eventThread.gpReg_addr);
eventThread.cpuContext.setRegister(sceNetAdhocMatching.loopThreadRegisterArgument, getId());
}
if (inputThread == null) {
inputThread = threadMan.hleKernelCreateThread("SceNetAdhocMatchingInput",
ThreadManForUser.NET_ADHOC_MATCHING_INPUT_LOOP_ADDRESS,
inthPri, inthStack, threadMan.getCurrentThread().attr, 0, SysMemUserForUser.USER_PARTITION_ID);
threadMan.hleKernelStartThread(inputThread, 0, 0, inputThread.gpReg_addr);
inputThread.cpuContext.setRegister(sceNetAdhocMatching.loopThreadRegisterArgument, getId());
}
// Add myself as the first member
addMember(Wlan.getMacAddress());
started = true;
} catch (SocketException e) {
log.error("start", e);
} catch (UnknownHostException e) {
log.error("start", e);
} catch (IOException e) {
log.error("start", e);
}
return 0;
}
public int stop() {
if (connected) {
if (log.isDebugEnabled()) {
log.debug(String.format("Sending disconnect to port %d", getPort()));
}
try {
AdhocMatchingEventMessage adhocMatchingEventMessage = createMessage(PSP_ADHOC_MATCHING_EVENT_DISCONNECT);
send(adhocMatchingEventMessage);
} catch (IOException e) {
log.error("stop", e);
}
}
closeSocket();
removeMember(Wlan.getMacAddress());
return 0;
}
private void sendHello() throws IOException {
if (log.isDebugEnabled()) {
log.debug(String.format("Sending hello to port %d", getPort()));
}
AdhocMatchingEventMessage adhocMatchingEventMessage = createMessage(PSP_ADHOC_MATCHING_EVENT_HELLO);
if (getHelloOptLen() > 0) {
adhocMatchingEventMessage.setData(helloOptData);
}
send(adhocMatchingEventMessage);
lastHelloMicros = Emulator.getClock().microTime();
}
private void sendPing() throws IOException {
if (log.isDebugEnabled()) {
log.debug(String.format("Sending ping to port %d", getPort()));
}
AdhocMatchingEventMessage adhocMatchingEventMessage = createMessage(PSP_ADHOC_MATCHING_EVENT_INTERNAL_PING);
send(adhocMatchingEventMessage);
lastPingMicros = Emulator.getClock().microTime();
}
@Override
protected void closeSocket() {
super.closeSocket();
started = false;
connected = false;
inConnection = false;
pendingComplete = false;
}
public int send(pspNetMacAddress macAddress, int dataLen, int data) {
int result = 0;
try {
AdhocMatchingEventMessage adhocMatchingEventMessage = createMessage(PSP_ADHOC_MATCHING_EVENT_DATA, data, dataLen, macAddress.macAddress);
send(adhocMatchingEventMessage, macAddress, dataLen, data);
result = dataLen;
} catch (SocketException e) {
log.error("send", e);
} catch (UnknownHostException e) {
log.error("send", e);
} catch (IOException e) {
log.error("send", e);
}
return result;
}
public int selectTarget(pspNetMacAddress macAddress, int optLen, int optData) {
int result = 0;
try {
int event;
if (pendingJoinRequest != null && sceNetAdhoc.isSameMacAddress(pendingJoinRequest, macAddress.macAddress)) {
event = PSP_ADHOC_MATCHING_EVENT_ACCEPT;
if (log.isDebugEnabled()) {
log.debug(String.format("Sending accept to port %d", getPort()));
}
if (getMode() == sceNetAdhocMatching.PSP_ADHOC_MATCHING_MODE_HOST) {
addMember(macAddress.macAddress);
connected = true;
inConnection = false;
}
} else {
event = PSP_ADHOC_MATCHING_EVENT_JOIN;
if (log.isDebugEnabled()) {
log.debug(String.format("Sending join to port %d", getPort()));
}
}
AdhocMatchingEventMessage adhocMatchingEventMessage = createMessage(event, optData, optLen, macAddress.macAddress);
send(adhocMatchingEventMessage, macAddress, optLen, optData);
inConnection = true;
} catch (SocketException e) {
log.error("selectTarget", e);
} catch (UnknownHostException e) {
log.error("selectTarget", e);
} catch (IOException e) {
log.error("selectTarget", e);
}
return result;
}
public int cancelTarget(pspNetMacAddress macAddress) {
return cancelTarget(macAddress, 0, 0);
}
public int cancelTarget(pspNetMacAddress macAddress, int optLen, int optData) {
int result = 0;
try {
int event;
if (connected) {
event = PSP_ADHOC_MATCHING_EVENT_LEFT;
if (log.isDebugEnabled()) {
log.debug(String.format("Sending leave to port %d", getPort()));
}
} else {
event = PSP_ADHOC_MATCHING_EVENT_CANCEL;
if (log.isDebugEnabled()) {
log.debug(String.format("Sending cancel to port %d", getPort()));
}
}
AdhocMatchingEventMessage adhocMatchingEventMessage = createMessage(event, optData, optLen, macAddress.macAddress);
send(adhocMatchingEventMessage, macAddress, optLen, optData);
} catch (SocketException e) {
log.error("cancelTarget", e);
} catch (UnknownHostException e) {
log.error("cancelTarget", e);
} catch (IOException e) {
log.error("cancelTarget", e);
}
removeMember(macAddress.macAddress);
connected = false;
inConnection = false;
return result;
}
public int getHelloOptLen() {
return helloOptData == null ? 0 : helloOptData.length;
}
public byte[] getHelloOptData() {
return helloOptData;
}
public void setHelloOpt(int optLen, int optData) {
if (optLen <= 0 || optData == 0) {
this.helloOptData = null;
return;
}
// Copy the HelloOpt into an internal buffer, the user memory can be overwritten
// after this call.
IMemoryReader memoryReader = MemoryReader.getMemoryReader(optData, optLen, 1);
helloOptData = new byte[optLen];
for (int i = 0; i < optLen; i++) {
helloOptData[i] = (byte) memoryReader.readNext();
}
}
public void notifyCallbackEvent(int event, int macAddr, int optLen, int optData) {
if (getCallback() == 0) {
return;
}
if (log.isDebugEnabled()) {
log.debug(String.format("Notify callback 0x%08X, event=%d, macAddr=0x%08X, optLen=%d, optData=0x%08X", getCallback(), event, macAddr, optLen, optData));
}
Modules.ThreadManForUserModule.executeCallback(eventThread, getCallback(), null, true, getId(), event, macAddr, optLen, optData);
}
public boolean eventLoop() {
if (socket == null || !started) {
return false;
}
if (Emulator.getClock().microTime() - lastPingMicros >= getPingDelay()) {
try {
sendPing();
} catch (IOException e) {
log.error("eventLoop ping", e);
}
}
if (!inConnection) {
if (!connected && getMode() != PSP_ADHOC_MATCHING_MODE_CLIENT) {
if (Emulator.getClock().microTime() - lastHelloMicros >= getHelloDelay()) {
try {
sendHello();
} catch (IOException e) {
log.error("eventLoop hello", e);
}
}
}
}
return true;
}
private void addMember(byte[] macAddr) {
for (pspNetMacAddress member : members) {
if (sceNetAdhoc.isSameMacAddress(macAddr, member.macAddress)) {
// Already in the members list
return;
}
}
pspNetMacAddress member = new pspNetMacAddress();
member.setMacAddress(macAddr);
members.add(member);
if (log.isDebugEnabled()) {
log.debug(String.format("Adding member %s", member));
}
}
private void removeMember(byte[] macAddr) {
for (pspNetMacAddress member : members) {
if (sceNetAdhoc.isSameMacAddress(macAddr, member.macAddress)) {
if (log.isDebugEnabled()) {
log.debug(String.format("Removing member %s", member));
}
members.remove(member);
break;
}
}
}
public void addCallbackEvent(int event, int macAddr, int optLen, int optData) {
CallbackEvent callbackEvent = new CallbackEvent(event, macAddr, optLen, optData);
pendingCallbackEvents.add(callbackEvent);
}
public boolean inputLoop() {
if (socket == null || !started) {
return false;
}
// Execute all the pending callback events
while (!pendingCallbackEvents.isEmpty()) {
CallbackEvent callbackEvent = pendingCallbackEvents.poll();
pspNetMacAddress macAddress = callbackEvent.getMacAddress();
macAddress.write(Memory.getInstance(), buffer.addr);
notifyCallbackEvent(callbackEvent.getEvent(), macAddress.getBaseAddress(), callbackEvent.getOptLen(), callbackEvent.getOptData());
}
try {
byte[] bytes = new byte[getBufSize() + MAX_HEADER_SIZE];
int length = socket.receive(bytes, bytes.length);
if (length > 0) {
int receivedPort = socket.getReceivedPort();
InetAddress receivedAddress = socket.getReceivedAddress();
AdhocMatchingEventMessage adhocMatchingEventMessage = createMessage(bytes, length);
if (isForMe(adhocMatchingEventMessage, receivedPort, receivedAddress)) {
int event = adhocMatchingEventMessage.getEvent();
int macAddr = buffer.addr;
int optData = buffer.addr + 8;
int optLen = adhocMatchingEventMessage.getDataLength();
adhocMatchingEventMessage.writeDataToMemory(optData);
pspNetMacAddress macAddress = new pspNetMacAddress();
macAddress.setMacAddress(adhocMatchingEventMessage.getFromMacAddress());
macAddress.write(Memory.getInstance(), macAddr);
if (log.isDebugEnabled()) {
log.debug(String.format("Received message length=%d, event=%d, fromMac=%s, port=%d: %s", adhocMatchingEventMessage.getDataLength(), event, macAddress, socket.getReceivedPort(), adhocMatchingEventMessage));
if (log.isTraceEnabled() && optLen > 0) {
log.trace(String.format("Message data: %s", Utilities.getMemoryDump(optData, optLen)));
}
}
// Keep track that we received a new message from this MAC address
Modules.sceNetAdhocctlModule.hleNetAdhocctlPeerUpdateTimestamp(adhocMatchingEventMessage.getFromMacAddress());
if (event == PSP_ADHOC_MATCHING_EVENT_JOIN) {
pendingJoinRequest = adhocMatchingEventMessage.getFromMacAddress();
inConnection = true;
}
adhocMatchingEventMessage.processOnReceive(macAddr, optData, optLen);
if (event == PSP_ADHOC_MATCHING_EVENT_ACCEPT) {
addMember(adhocMatchingEventMessage.getFromMacAddress());
if (log.isDebugEnabled()) {
log.debug(String.format("Sending complete to port %d", getPort()));
}
adhocMatchingEventMessage = createMessage(PSP_ADHOC_MATCHING_EVENT_COMPLETE, optData, optLen, macAddress.macAddress);
send(adhocMatchingEventMessage);
pendingComplete = true;
connected = true;
inConnection = false;
} else if (event == PSP_ADHOC_MATCHING_EVENT_COMPLETE) {
addMember(adhocMatchingEventMessage.getFromMacAddress());
if (!pendingComplete) {
if (log.isDebugEnabled()) {
log.debug(String.format("Sending complete to port %d", getPort()));
}
adhocMatchingEventMessage = createMessage(PSP_ADHOC_MATCHING_EVENT_COMPLETE, optData, optLen, macAddress.macAddress);
send(adhocMatchingEventMessage);
}
connected = true;
inConnection = false;
} else if (event == PSP_ADHOC_MATCHING_EVENT_DATA) {
if (log.isDebugEnabled()) {
log.debug(String.format("Sending data confirm to port %d", getPort()));
}
adhocMatchingEventMessage = createMessage(PSP_ADHOC_MATCHING_EVENT_DATA_CONFIRM, 0, 0, macAddress.macAddress);
send(adhocMatchingEventMessage);
} else if (event == PSP_ADHOC_MATCHING_EVENT_DISCONNECT || event == PSP_ADHOC_MATCHING_EVENT_LEFT) {
if (log.isDebugEnabled()) {
log.debug(String.format("Received disconnect/leave from %s", macAddress));
}
removeMember(adhocMatchingEventMessage.getFromMacAddress());
if (members.size() <= 1) {
connected = false;
inConnection = false;
}
}
} else {
if (log.isDebugEnabled()) {
log.debug(String.format("Received message not for me: %s", adhocMatchingEventMessage));
}
}
}
} catch (SocketTimeoutException e) {
// Nothing available
} catch (IOException e) {
log.error("inputLoop", e);
}
return true;
}
protected INetworkAdapter getNetworkAdapter() {
return Modules.sceNetModule.getNetworkAdapter();
}
protected AdhocMatchingEventMessage createMessage(int event) {
return getNetworkAdapter().createAdhocMatchingEventMessage(this, event);
}
protected AdhocMatchingEventMessage createMessage(int event, int data, int dataLength, byte[] macAddress) {
return getNetworkAdapter().createAdhocMatchingEventMessage(this, event, data, dataLength, macAddress);
}
protected AdhocMatchingEventMessage createMessage(byte[] message, int length) {
return getNetworkAdapter().createAdhocMatchingEventMessage(this, message, length);
}
protected boolean isForMe(AdhocMessage adhocMessage, int port, InetAddress address) {
return adhocMessage.isForMe();
}
@Override
public String toString() {
return String.format("MatchingObject[id=0x%X, mode=%d, maxPeers=%d, port=%d, callback=0x%08X]", getId(), mode, maxPeers, getPort(), callback);
}
protected void send(AdhocMatchingEventMessage adhocMatchingEventMessage, pspNetMacAddress macAddress, int dataLen, int data) throws IOException {
super.send(adhocMatchingEventMessage);
if (adhocMatchingEventMessage != null) {
adhocMatchingEventMessage.processOnSend(macAddress.getBaseAddress(), data, dataLen);
}
}
}