/* 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.network.adhoc.AdhocMessage.MAX_HEADER_SIZE; import java.io.IOException; import java.net.BindException; import java.net.InetAddress; import java.net.SocketException; import java.net.SocketTimeoutException; import java.net.UnknownHostException; import java.util.LinkedList; import jpcsp.Emulator; import jpcsp.Memory; import jpcsp.HLE.Modules; import jpcsp.HLE.TPointer; import jpcsp.HLE.TPointer16; import jpcsp.HLE.TPointer32; import jpcsp.HLE.kernel.types.IAction; 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 PdpObject extends AdhocObject { /** MAC address */ private pspNetMacAddress macAddress; /** Bytes received */ protected int rcvdData; private LinkedList<AdhocBufferMessage> rcvdMessages = new LinkedList<AdhocBufferMessage>(); // Polling period (micro seconds) for blocking operations protected static final int BLOCKED_OPERATION_POLLING_MICROS = 10000; protected static abstract class BlockedPdpAction implements IAction { protected final PdpObject pdpObject; protected final long timeoutMicros; protected final int threadUid; protected final SceKernelThreadInfo thread; public BlockedPdpAction(PdpObject pdpObject, long timeout) { this.pdpObject = pdpObject; timeoutMicros = Emulator.getClock().microTime() + timeout; threadUid = Modules.ThreadManForUserModule.getCurrentThreadID(); thread = Modules.ThreadManForUserModule.getThreadById(threadUid); if (log.isDebugEnabled()) { log.debug(String.format("BlockedPdpAction for thread %s", thread)); } } public void blockCurrentThread() { long schedule = Emulator.getClock().microTime() + BLOCKED_OPERATION_POLLING_MICROS; Emulator.getScheduler().addAction(schedule, this); Modules.ThreadManForUserModule.hleBlockCurrentThread(SceKernelThreadInfo.JPCSP_WAIT_NET); } @Override public void execute() { if (log.isDebugEnabled()) { log.debug(String.format("BlockedPdpAction: poll on %s, thread %s", pdpObject, thread)); } try { if (poll()) { if (log.isDebugEnabled()) { log.debug(String.format("BlockedPdpAction: unblocking thread %s", thread)); } Modules.ThreadManForUserModule.hleUnblockThread(threadUid); } else { long now = Emulator.getClock().microTime(); if (now >= timeoutMicros) { if (log.isDebugEnabled()) { log.debug(String.format("BlockedPdpAction: timeout for thread %s", thread)); } // Unblock thread and return timeout error setReturnValue(thread, SceKernelErrors.ERROR_NET_ADHOC_TIMEOUT); Modules.ThreadManForUserModule.hleUnblockThread(threadUid); } else { if (log.isDebugEnabled()) { log.debug(String.format("BlockedPdpAction: continue polling")); } long schedule = now + BLOCKED_OPERATION_POLLING_MICROS; Emulator.getScheduler().addAction(schedule, this); } } } catch (IOException e) { setReturnValue(thread, getExceptionResult(e)); log.error(getClass().getSimpleName(), e); } } protected abstract boolean poll() throws IOException; protected abstract int getExceptionResult(IOException e); } protected static class BlockedPdpRecv extends BlockedPdpAction { final protected TPointer srcMacAddr; final protected TPointer16 portAddr; final protected TPointer data; final protected TPointer32 dataLengthAddr; public BlockedPdpRecv(PdpObject pdpObject, TPointer srcMacAddr, TPointer16 portAddr, TPointer data, TPointer32 dataLengthAddr, long timeout) { super(pdpObject, timeout); this.srcMacAddr = srcMacAddr; this.portAddr = portAddr; this.data = data; this.dataLengthAddr = dataLengthAddr; } @Override protected boolean poll() throws IOException { return pdpObject.pollRecv(srcMacAddr, portAddr, data, dataLengthAddr, thread); } @Override protected int getExceptionResult(IOException e) { return SceKernelErrors.ERROR_NET_ADHOC_TIMEOUT; } } public PdpObject(INetworkAdapter networkAdapter) { super(networkAdapter); } public PdpObject(PdpObject pdpObject) { super(pdpObject); macAddress = pdpObject.macAddress; } public pspNetMacAddress getMacAddress() { return macAddress; } public void setMacAddress(pspNetMacAddress macAddress) { this.macAddress = macAddress; } public int getRcvdData() { return rcvdData; } public int create(pspNetMacAddress macAddress, int port, int bufSize) { int result = getId(); setMacAddress(macAddress); setPort(port); setBufSize(bufSize); try { openSocket(); } catch (BindException e) { if (log.isDebugEnabled()) { log.debug("create", e); } result = SceKernelErrors.ERROR_NET_ADHOC_PORT_IN_USE; } catch (SocketException e) { log.error("create", e); } catch (UnknownHostException e) { log.error("create", e); } catch (IOException e) { log.error("create", e); } return result; } public int send(pspNetMacAddress destMacAddress, int destPort, TPointer data, int length, int timeout, int nonblock) { int result = 0; try { openSocket(); setTimeout(timeout, nonblock); AdhocMessage adhocMessage = networkAdapter.createAdhocPdpMessage(data.getAddress(), length, destMacAddress.macAddress); send(adhocMessage, destPort); } catch (SocketException e) { log.error("send", e); } catch (UnknownHostException e) { result = SceKernelErrors.ERROR_NET_ADHOC_INVALID_ADDR; log.error("send", e); } catch (SocketTimeoutException e) { log.error("send", e); } catch (IOException e) { log.error("send", e); } return result; } // For Pdp sockets, data is stored in the internal buffer as a sequence of packets. // The organization in packets must be kept for reading. private void addReceivedMessage(AdhocMessage adhocMessage, int port) { AdhocBufferMessage bufferMessage = new AdhocBufferMessage(); bufferMessage.length = adhocMessage.getDataLength(); bufferMessage.macAddress.setMacAddress(adhocMessage.getFromMacAddress()); bufferMessage.port = Modules.sceNetAdhocModule.getClientPortFromRealPort(adhocMessage.getFromMacAddress(), port); bufferMessage.offset = rcvdData; adhocMessage.writeDataToMemory(buffer.addr + bufferMessage.offset); // Update the timestamp of the peer Modules.sceNetAdhocctlModule.hleNetAdhocctlPeerUpdateTimestamp(adhocMessage.getFromMacAddress()); if (log.isDebugEnabled()) { log.debug(String.format("Successfully received %d bytes from %s on port %d(%d)", bufferMessage.length, bufferMessage.macAddress, bufferMessage.port, port)); if (log.isTraceEnabled()) { log.trace(String.format("Message data: %s", Utilities.getMemoryDump(buffer.addr + bufferMessage.offset, bufferMessage.length))); } } rcvdData += bufferMessage.length; rcvdMessages.add(bufferMessage); } private void removeFirstReceivedMessage() { AdhocBufferMessage bufferMessage = rcvdMessages.removeFirst(); if (bufferMessage == null) { return; } if (rcvdData > bufferMessage.length) { // Move the remaining buffer data to the beginning of the buffer Memory.getInstance().memcpy(buffer.addr, buffer.addr + bufferMessage.length, rcvdData - bufferMessage.length); for (AdhocBufferMessage rcvdMessage : rcvdMessages) { rcvdMessage.offset -= bufferMessage.length; } } rcvdData -= bufferMessage.length; } // For Pdp sockets, data is read one packet at a time. // The caller has to provide enough space to fully read the available packet. public int recv(TPointer srcMacAddr, TPointer16 portAddr, TPointer data, TPointer32 dataLengthAddr, int timeout, int nonblock) { int result = 0; try { SceKernelThreadInfo thread = Modules.ThreadManForUserModule.getCurrentThread(); if (pollRecv(srcMacAddr, portAddr, 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 BlockedPdpRecv(this, srcMacAddr, portAddr, data, dataLengthAddr, timeout); blockedPdpAction.blockCurrentThread(); } } catch (IOException e) { result = SceKernelErrors.ERROR_NET_ADHOC_DISCONNECTED; log.error("recv", e); } return result; } public boolean pollRecv(TPointer srcMacAddr, TPointer16 portAddr, TPointer data, TPointer32 dataLengthAddr, SceKernelThreadInfo thread) throws IOException { int length = dataLengthAddr.getValue(); boolean completed = false; if (rcvdMessages.isEmpty()) { update(); } if (!rcvdMessages.isEmpty()) { AdhocBufferMessage bufferMessage = rcvdMessages.getFirst(); if (length < bufferMessage.length) { // Buffer is too small to contain all the available data. // Return the buffer size that would be required. dataLengthAddr.setValue(bufferMessage.length); setReturnValue(thread, SceKernelErrors.ERROR_NET_BUFFER_TOO_SMALL); } else { // Copy the data already received dataLengthAddr.setValue(bufferMessage.length); Memory.getInstance().memcpy(data.getAddress(), buffer.addr + bufferMessage.offset, bufferMessage.length); if (srcMacAddr != null && !srcMacAddr.isNull()) { bufferMessage.macAddress.write(srcMacAddr); } if (portAddr != null && portAddr.isNotNull()) { portAddr.setValue(bufferMessage.port); } removeFirstReceivedMessage(); if (log.isDebugEnabled()) { log.debug(String.format("Returned received data: %d bytes from %s on port %d", dataLengthAddr.getValue(), bufferMessage.macAddress, portAddr.getValue())); if (log.isTraceEnabled()) { log.trace(String.format("Returned data: %s", Utilities.getMemoryDump(data.getAddress(), dataLengthAddr.getValue()))); } } setReturnValue(thread, 0); } completed = true; } return completed; } public void update() throws IOException { // Receive all messages available while (rcvdData < getBufSize()) { try { openSocket(); socket.setTimeout(1); byte[] bytes = new byte[getBufSize() - rcvdData + MAX_HEADER_SIZE]; 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)) { if (getRcvdData() + adhocMessage.getDataLength() <= getBufSize()) { addReceivedMessage(adhocMessage, receivedPort); } else { if (log.isDebugEnabled()) { log.debug(String.format("Discarded message, receive buffer full (%d of %d): %s", getRcvdData(), getBufSize(), adhocMessage)); } } } 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; } } } protected AdhocMessage createAdhocMessage(byte[] message, int length) { return networkAdapter.createAdhocPdpMessage(message, length); } protected boolean isForMe(AdhocMessage adhocMessage, int port, InetAddress address) { return adhocMessage.isForMe(); } protected static void setReturnValue(SceKernelThreadInfo thread, int value) { thread.cpuContext._v0 = value; } @Override public String toString() { return String.format("PdpObject[id=0x%X, macAddress=%s, port=%d, bufSize=%d, rcvdData=%d]", getId(), macAddress, getPort(), getBufSize(), rcvdData); } }