/*
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.jpcsp;
import static jpcsp.network.adhoc.AdhocMessage.MAX_HEADER_SIZE;
import static jpcsp.network.jpcsp.JpcspAdhocPtpMessage.PTP_MESSAGE_TYPE_CONNECT;
import static jpcsp.network.jpcsp.JpcspAdhocPtpMessage.PTP_MESSAGE_TYPE_CONNECT_CONFIRM;
import static jpcsp.network.jpcsp.JpcspAdhocPtpMessage.PTP_MESSAGE_TYPE_DATA;
import java.io.IOException;
import java.net.InetAddress;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.util.HashSet;
import java.util.Set;
import jpcsp.Memory;
import jpcsp.HLE.Modules;
import jpcsp.HLE.kernel.types.SceKernelThreadInfo;
import jpcsp.HLE.kernel.types.pspNetMacAddress;
import jpcsp.network.INetworkAdapter;
import jpcsp.network.adhoc.AdhocDatagramSocket;
import jpcsp.network.adhoc.AdhocMessage;
import jpcsp.network.adhoc.AdhocSocket;
import jpcsp.network.adhoc.PtpObject;
/**
* @author gid15
*
*/
public class JpcspPtpObject extends PtpObject {
private JpcspAdhocPtpMessage connectRequest;
private int connectRequestPort;
private JpcspAdhocPtpMessage connectConfirm;
private boolean connected;
private Set<Integer> acceptedIds = new HashSet<Integer>();
public JpcspPtpObject(PtpObject ptpObject) {
super(ptpObject);
}
public JpcspPtpObject(INetworkAdapter networkAdapter) {
super(networkAdapter);
}
@Override
public boolean canAccept() {
return connectRequest != null;
}
@Override
public boolean canConnect() {
return connectConfirm != null;
}
@Override
public int connect(int timeout, int nonblock) {
int result = 0;
try {
JpcspAdhocPtpMessage adhocPtpMessage = new JpcspAdhocPtpMessage(PTP_MESSAGE_TYPE_CONNECT);
adhocPtpMessage.setDataInt32(getId());
send(adhocPtpMessage);
result = super.connect(timeout, nonblock);
} catch (SocketException e) {
log.error("connect", e);
} catch (IOException e) {
log.error("connect", e);
}
return result;
}
@Override
protected boolean pollAccept(int peerMacAddr, int peerPortAddr, SceKernelThreadInfo thread) {
boolean acceptCompleted = false;
Memory mem = Memory.getInstance();
try {
// Process a previously received connect message, if available
JpcspAdhocPtpMessage adhocPtpMessage = connectRequest;
int adhocPtpMessagePort = connectRequestPort;
if (adhocPtpMessage == null) {
byte[] bytes = new byte[getBufSize() + MAX_HEADER_SIZE];
int length = socket.receive(bytes, bytes.length);
if (length > 0) {
adhocPtpMessage = new JpcspAdhocPtpMessage(bytes, length);
adhocPtpMessagePort = socket.getReceivedPort();
if (log.isDebugEnabled()) {
log.debug(String.format("pollAccept: received message %s", adhocPtpMessage));
}
}
} else {
if (log.isDebugEnabled()) {
log.debug(String.format("pollAccept: processing pending message %s", adhocPtpMessage));
}
}
if (adhocPtpMessage != null && adhocPtpMessage.isForMe()) {
switch (adhocPtpMessage.getType()) {
case PTP_MESSAGE_TYPE_CONNECT:
int acceptedId = adhocPtpMessage.getDataInt32();
if (acceptedIds.contains(acceptedId)) {
if (log.isDebugEnabled()) {
log.debug(String.format("Connect message received for an id=%d already accepted. Dropping message.", acceptedId));
}
} else {
pspNetMacAddress peerMacAddress = new pspNetMacAddress();
peerMacAddress.setMacAddress(adhocPtpMessage.getFromMacAddress());
int peerPort = Modules.sceNetAdhocModule.getClientPortFromRealPort(adhocPtpMessage.getFromMacAddress(), adhocPtpMessagePort);
if (peerMacAddr != 0) {
peerMacAddress.write(mem, peerMacAddr);
}
if (peerPortAddr != 0) {
mem.write16(peerPortAddr, (short) peerPort);
}
// As a result of the "accept" call, create a new PTP Object
PtpObject ptpObject = new JpcspPtpObject(this);
ptpObject.setDestMacAddress(peerMacAddress);
ptpObject.setDestPort(peerPort);
ptpObject.setPort(0);
Modules.sceNetAdhocModule.hleAddPtpObject(ptpObject);
// Return the ID of the new PTP Object
setReturnValue(thread, ptpObject.getId());
// Remember that we have already accepted this Id.
acceptedIds.add(acceptedId);
// Get a new free port
ptpObject.setPort(0);
ptpObject.openSocket();
// Send a connect confirmation message including the new port
JpcspAdhocPtpMessage confirmMessage = new JpcspAdhocPtpMessage(PTP_MESSAGE_TYPE_CONNECT_CONFIRM);
confirmMessage.setDataInt32(ptpObject.getPort());
ptpObject.send(confirmMessage);
if (log.isDebugEnabled()) {
log.debug(String.format("accept completed, creating new Ptp object %s", ptpObject));
}
acceptCompleted = true;
connectRequest = null;
}
break;
}
} else {
if (log.isDebugEnabled()) {
log.debug(String.format("pollAccept: received a message not for me: %s", adhocPtpMessage));
}
}
} catch (SocketException e) {
log.error("pollAccept", e);
} catch (SocketTimeoutException e) {
// Ignore exception
} catch (IOException e) {
log.error("pollAccept", e);
}
return acceptCompleted;
}
@Override
protected boolean pollConnect(SceKernelThreadInfo thread) {
boolean connectCompleted = false;
try {
// Process a previously received confirm message, if available
JpcspAdhocPtpMessage adhocPtpMessage = connectConfirm;
if (adhocPtpMessage == null) {
byte[] bytes = new byte[getBufSize() + MAX_HEADER_SIZE];
int length = socket.receive(bytes, bytes.length);
if (length > 0) {
adhocPtpMessage = new JpcspAdhocPtpMessage(bytes, length);
if (log.isDebugEnabled()) {
log.debug(String.format("pollConnect: received message %s", adhocPtpMessage));
}
}
} else {
if (log.isDebugEnabled()) {
log.debug(String.format("pollConnect: processing pending message %s", adhocPtpMessage));
}
}
if (adhocPtpMessage != null && adhocPtpMessage.isForMe()) {
switch (adhocPtpMessage.getType()) {
case PTP_MESSAGE_TYPE_CONNECT_CONFIRM:
// Connect successfully completed, retrieve the new destination port
int port = Modules.sceNetAdhocModule.getClientPortFromRealPort(adhocPtpMessage.getFromMacAddress(), adhocPtpMessage.getDataInt32());
if (log.isDebugEnabled()) {
log.debug(String.format("Received connect confirmation, changing destination port from %d to %d", getDestPort(), port));
}
setDestPort(port);
setReturnValue(thread, 0);
connectConfirm = null;
connectCompleted = true;
break;
}
} else {
if (log.isDebugEnabled()) {
log.debug(String.format("pollConnect: received a message not for me: %s", adhocPtpMessage));
}
}
} catch (SocketException e) {
log.error("pollConnect", e);
} catch (SocketTimeoutException e) {
// Ignore exception
} catch (IOException e) {
log.error("pollConnect", e);
}
if (connectCompleted) {
connected = true;
}
return connectCompleted;
}
@Override
protected boolean isForMe(AdhocMessage adhocMessage, int port, InetAddress address) {
if (adhocMessage instanceof JpcspAdhocPtpMessage) {
JpcspAdhocPtpMessage adhocPtpMessage = (JpcspAdhocPtpMessage) adhocMessage;
int type = adhocPtpMessage.getType();
if (type == PTP_MESSAGE_TYPE_CONNECT_CONFIRM) {
if (connected) {
if (log.isDebugEnabled()) {
log.debug(String.format("Received connect confirmation but already connected, discarding"));
}
} else {
if (log.isDebugEnabled()) {
log.debug(String.format("Received connect confirmation, processing later"));
}
connectConfirm = adhocPtpMessage;
}
return false;
} else if (type == PTP_MESSAGE_TYPE_CONNECT) {
if (log.isDebugEnabled()) {
log.debug(String.format("Received connect request, processing later"));
}
connectRequest = adhocPtpMessage;
connectRequestPort = port;
return false;
} else if (type != PTP_MESSAGE_TYPE_DATA) {
return false;
}
}
return super.isForMe(adhocMessage, port, address);
}
@Override
protected void closeSocket() {
super.closeSocket();
connected = false;
}
@Override
protected AdhocSocket createSocket() {
return new AdhocDatagramSocket();
}
}