/* 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 java.io.IOException; import java.net.BindException; import java.net.ConnectException; import java.net.InetAddress; import java.net.SocketAddress; import java.net.SocketException; import java.net.SocketTimeoutException; import java.net.UnknownHostException; import jpcsp.Memory; import jpcsp.HLE.Modules; import jpcsp.HLE.TPointer; import jpcsp.HLE.TPointer32; import jpcsp.HLE.kernel.types.SceKernelErrors; import jpcsp.HLE.kernel.types.SceKernelThreadInfo; import jpcsp.HLE.kernel.types.pspNetMacAddress; import jpcsp.network.INetworkAdapter; import jpcsp.util.Utilities; /** * @author gid15 * */ public abstract class PtpObject extends PdpObject { /** Destination MAC address */ private pspNetMacAddress destMacAddress; /** Destination port */ private int destPort; /** Retry delay */ private int retryDelay; /** Retry count */ private int retryCount; /** Queue size */ private int queue; /** Bytes sent */ private int sentData; // Polling period (micro seconds) for blocking operations protected static final int BLOCKED_OPERATION_POLLING_MICROS = 10000; private AdhocMessage receivedMessage; private int receivedMessageOffset; protected static abstract class BlockedPtpAction extends BlockedPdpAction { protected final PtpObject ptpObject; protected BlockedPtpAction(PtpObject ptpObject, int timeout) { super(ptpObject, timeout); this.ptpObject = ptpObject; } } protected static class BlockedPtpAccept extends BlockedPtpAction { private final int peerMacAddr; private final int peerPortAddr; public BlockedPtpAccept(PtpObject ptpObject, int peerMacAddr, int peerPortAddr, int timeout) { super(ptpObject, timeout); this.peerMacAddr = peerMacAddr; this.peerPortAddr = peerPortAddr; } @Override protected boolean poll() throws IOException { return ptpObject.pollAccept(peerMacAddr, peerPortAddr, thread); } @Override protected int getExceptionResult(IOException e) { return SceKernelErrors.ERROR_NET_ADHOC_TIMEOUT; } } protected static class BlockedPtpConnect extends BlockedPtpAction { public BlockedPtpConnect(PtpObject ptpObject, int timeout) { super(ptpObject, timeout); } @Override protected boolean poll() throws IOException { return ptpObject.pollConnect(thread); } @Override protected int getExceptionResult(IOException e) { return SceKernelErrors.ERROR_NET_ADHOC_CONNECTION_REFUSED; } } protected static class BlockedPtpRecv extends BlockedPtpAction { final protected TPointer data; final protected TPointer32 dataLengthAddr; protected BlockedPtpRecv(PtpObject ptpObject, TPointer data, TPointer32 dataLengthAddr, int timeout) { super(ptpObject, timeout); this.data = data; this.dataLengthAddr = dataLengthAddr; } @Override protected boolean poll() throws IOException { return ptpObject.pollRecv(data, dataLengthAddr, thread); } @Override protected int getExceptionResult(IOException e) { return SceKernelErrors.ERROR_NET_ADHOC_TIMEOUT; } } public PtpObject(PtpObject ptpObject) { super(ptpObject); destMacAddress = ptpObject.destMacAddress; destPort = ptpObject.destPort; retryDelay = ptpObject.retryDelay; retryCount = ptpObject.retryCount; queue = ptpObject.queue; } public PtpObject(INetworkAdapter networkAdapter) { super(networkAdapter); } public pspNetMacAddress getDestMacAddress() { return destMacAddress; } public void setDestMacAddress(pspNetMacAddress destMacAddress) { this.destMacAddress = destMacAddress; } public int getDestPort() { return destPort; } public void setDestPort(int destPort) { this.destPort = destPort; } public int getRetryDelay() { return retryDelay; } public void setRetryDelay(int retryDelay) { this.retryDelay = retryDelay; } public int getRetryCount() { return retryCount; } public void setRetryCount(int retryCount) { this.retryCount = retryCount; } public int getQueue() { return queue; } public void setQueue(int queue) { this.queue = queue; } public int getSentData() { return sentData; } @Override public void openSocket() throws UnknownHostException, IOException { if (socket == null) { super.openSocket(); if (getDestMacAddress() != null) { int realDestPort = Modules.sceNetAdhocModule.getRealPortFromClientPort(getDestMacAddress().macAddress, getDestPort()); SocketAddress socketAddress = Modules.sceNetAdhocModule.getSocketAddress(getDestMacAddress().macAddress, realDestPort); if (log.isTraceEnabled()) { log.trace(String.format("Ptp openSocket address=%s, port=%d", socketAddress, realDestPort)); } socket.connect(socketAddress, realDestPort); } } } public int open() { int result = 0; try { openSocket(); } catch (BindException e) { if (log.isDebugEnabled()) { log.debug("open", e); } result = SceKernelErrors.ERROR_NET_ADHOC_PORT_IN_USE; } catch (ConnectException e) { if (log.isDebugEnabled()) { log.debug("open", e); } result = SceKernelErrors.ERROR_NET_ADHOC_INVALID_ARG; } catch (SocketException e) { log.error("open", e); result = SceKernelErrors.ERROR_NET_ADHOC_INVALID_ARG; } catch (UnknownHostException e) { log.error("open", e); result = SceKernelErrors.ERROR_NET_ADHOC_INVALID_ARG; } catch (IOException e) { log.error("open", e); result = SceKernelErrors.ERROR_NET_ADHOC_INVALID_ARG; } return result; } public int listen() { int result = 0; try { openSocket(); } catch (BindException e) { if (log.isDebugEnabled()) { log.debug("listen", e); } result = SceKernelErrors.ERROR_NET_ADHOC_PORT_IN_USE; } catch (SocketException e) { log.error("listen", e); } catch (UnknownHostException e) { log.error("listen", e); } catch (IOException e) { log.error("listen", e); } return result; } public int accept(int peerMacAddr, int peerPortAddr, int timeout, int nonblock) { int result = 0; SceKernelThreadInfo thread = Modules.ThreadManForUserModule.getCurrentThread(); if (pollAccept(peerMacAddr, peerPortAddr, thread)) { // Accept completed immediately result = thread.cpuContext._v0; } else if (nonblock != 0) { // Accept cannot be completed in non-blocking mode result = SceKernelErrors.ERROR_NET_ADHOC_NO_DATA_AVAILABLE; } else { // Block current thread BlockedPtpAction blockedPtpAction = new BlockedPtpAccept(this, peerMacAddr, peerPortAddr, timeout); blockedPtpAction.blockCurrentThread(); } return result; } public int connect(int timeout, int nonblock) { int result = 0; if (!pollConnect(Modules.ThreadManForUserModule.getCurrentThread())) { if (nonblock != 0) { result = SceKernelErrors.ERROR_NET_ADHOC_NO_DATA_AVAILABLE; } else { BlockedPtpAction blockedPtpAction = new BlockedPtpConnect(this, timeout); blockedPtpAction.blockCurrentThread(); } } return result; } @Override public void send(AdhocMessage adhocMessage) throws IOException { adhocMessage.setFromMacAddress(getMacAddress().macAddress); adhocMessage.setToMacAddress(getDestMacAddress().macAddress); send(adhocMessage, getDestPort()); } public int send(int data, TPointer32 dataSizeAddr, int timeout, int nonblock) { int result = 0; try { AdhocMessage adhocMessage = networkAdapter.createAdhocPtpMessage(data, dataSizeAddr.getValue()); send(adhocMessage); } catch (IOException e) { result = SceKernelErrors.ERROR_NET_ADHOC_DISCONNECTED; log.error("send returning ERROR_NET_ADHOC_DISCONNECTED", e); } return result; } // For Ptp sockets, data in read as a byte stream. Data is not organized in packets. // Read as much data as the provided buffer can contain. public int recv(TPointer data, TPointer32 dataLengthAddr, int timeout, int nonblock) { int result = 0; try { SceKernelThreadInfo thread = Modules.ThreadManForUserModule.getCurrentThread(); if (pollRecv(data, dataLengthAddr, thread)) { // Recv completed immediately result = thread.cpuContext._v0; } else if (nonblock != 0) { // Recv cannot be completed in non-blocking mode result = SceKernelErrors.ERROR_NET_ADHOC_NO_DATA_AVAILABLE; } else { // Block current thread BlockedPdpAction blockedPdpAction = new BlockedPtpRecv(this, data, dataLengthAddr, timeout); blockedPdpAction.blockCurrentThread(); } } catch (IOException e) { result = SceKernelErrors.ERROR_NET_ADHOC_DISCONNECTED; log.error("recv", e); } return result; } protected boolean pollRecv(TPointer data, TPointer32 dataLengthAddr, SceKernelThreadInfo thread) throws IOException { int length = dataLengthAddr.getValue(); boolean completed = false; if (length > 0) { if (getRcvdData() <= 0 || receivedMessage != null) { update(); } if (getRcvdData() > 0) { if (length > getRcvdData()) { length = getRcvdData(); } // Copy the data already received dataLengthAddr.setValue(length); Memory mem = Memory.getInstance(); mem.memcpy(data.getAddress(), buffer.addr, length); if (getRcvdData() > length) { // Shift the remaining buffer data to the beginning of the buffer mem.memmove(buffer.addr, buffer.addr + length, getRcvdData() - length); } rcvdData -= length; if (log.isDebugEnabled()) { log.debug(String.format("Returned received data: %d bytes", length)); if (log.isTraceEnabled()) { log.trace(String.format("Returned data: %s", Utilities.getMemoryDump(data.getAddress(), length))); } } setReturnValue(thread, 0); completed = true; } } return completed; } // For Ptp sockets, data is stored in the internal buffer as a continuous byte stream. // The organization in packets doesn't matter. private int addReceivedMessage(AdhocMessage adhocMessage, int offset) { int length = Math.min(adhocMessage.getDataLength() - offset, getBufSize() - getRcvdData()); int addr = buffer.addr + getRcvdData(); adhocMessage.writeDataToMemory(addr, offset, length); rcvdData += length; if (log.isDebugEnabled()) { if (offset == 0) { log.debug(String.format("Successfully received message (length=%d, rcvdData=%d) %s", length, getRcvdData(), adhocMessage)); } else { log.debug(String.format("Appending received message (offset=%d, length=%d, rcvdData=%d) %s", offset, length, getRcvdData(), adhocMessage)); } if (log.isTraceEnabled()) { log.trace(String.format("Message data: %s", Utilities.getMemoryDump(addr, length))); } } return length; } @Override public void update() throws IOException { // Receive all messages available while (getRcvdData() < getBufSize()) { if (receivedMessage != null) { receivedMessageOffset += addReceivedMessage(receivedMessage, receivedMessageOffset); if (receivedMessageOffset >= receivedMessage.getDataLength()) { receivedMessage = null; receivedMessageOffset = 0; } } else { try { openSocket(); socket.setTimeout(1); byte[] bytes = new byte[0x10000]; // 64K buffer int length = socket.receive(bytes, bytes.length); if (length <= 0) { break; } int receivedPort = socket.getReceivedPort(); InetAddress receivedAddress = socket.getReceivedAddress(); AdhocMessage adhocMessage = createAdhocMessage(bytes, length); if (isForMe(adhocMessage, receivedPort, receivedAddress)) { receivedMessage = adhocMessage; receivedMessageOffset = 0; } else { if (log.isDebugEnabled()) { log.debug(String.format("Received message not for me: %s", adhocMessage)); } } // } catch (SocketException e) { // log.error("update", e); // break; } catch (SocketTimeoutException e) { // Timeout break; } } } } @Override protected AdhocMessage createAdhocMessage(byte[] message, int length) { return networkAdapter.createAdhocPtpMessage(message, length); } protected abstract boolean pollAccept(int peerMacAddr, int peerPortAddr, SceKernelThreadInfo thread); protected abstract boolean pollConnect(SceKernelThreadInfo thread); public abstract boolean canAccept(); public abstract boolean canConnect(); @Override public String toString() { return String.format("PtpObject[id=0x%X, srcMacAddress=%s, srcPort=%d, destMacAddress=%s, destPort=%d, bufSize=%d, retryDelay=%d, retryCount=%d, queue=%d, rcvdData=%d]", getId(), getMacAddress(), getPort(), getDestMacAddress(), getDestPort(), getBufSize(), getRetryDelay(), getRetryCount(), getQueue(), getRcvdData()); } }