sceNetAdhoc

PDP (datagram) and PTP (stream) sockets, polling, socket alerts, and game mode's shared buffers. For the concepts, see PDP and PTP and Game mode. Tests: net/adhoc/pdp, ptp, poll, self, limits, gamemode, and the pair tests.

Common rules

  • Sockets can only be created once the console is in a group (or game mode). Before that, creating one gives INVALID_ADDR, even with the console's own MAC.
  • The local MAC must be the console's own (INVALID_ADDR otherwise, INVALID_ARG for NULL).
  • PDP and PTP sockets share one ID space and one limit: 255 sockets in all, then SOCKET_ID_NOT_AVAIL (0x8041070F).
  • Socket IDs always increase; a freed ID isn't reused. A create that fails on the address, the port or a port in use uses up an ID too; one that fails on NULL or a bad flag doesn't.
  • Local port 0 picks a free port, from 0x1000-0x7FFF.
  • See Conventions for timeout and flag.

PPSSPP: socket IDs are the lowest free slot; port 0 picks a port from 0x8000 up (the host OS's choice minus the port offset); hitting the socket limit gives 0x80410001 or 0x80410710.

sceNetAdhocInit / Term

int sceNetAdhocInit(void); // NID 0xE1D621D7
int sceNetAdhocTerm(void); // NID 0xA62C6F57

Init needs sceNetInit first; twice gives ALREADY_INITIALIZED (0x80410713). Term returns 0, also when called twice. Every other call before Init (or after Term) gives NOT_INITIALIZED (0x80410712).

PDP

typedef struct SceNetAdhocPdpStat {   // 20 bytes
	struct SceNetAdhocPdpStat *next;
	int id;
	SceNetEtherAddr laddr;
	u16 lport;
	u32 rcv_sb_cc;                     // bytes waiting in the receive buffer
} SceNetAdhocPdpStat;

sceNetAdhocPdpCreate

int sceNetAdhocPdpCreate(const SceNetEtherAddr *mac, u16 port, int bufsize, int flag); // NID 0x6F92741B

Creates a datagram socket on port. bufsize is the receive buffer in bytes: datagrams that don't fit are dropped. Any value is accepted, including 0 (which receives nothing) and negative.

CheckError
mac NULL, flag not 0INVALID_ARG
not initializedNOT_INITIALIZED
not in a group, mac not the console's ownINVALID_ADDR
port 65535INVALID_PORT
port in usePORT_IN_USE (0x8041070A)

PPSSPP: accepts a zero, broadcast or foreign MAC as the local address, port 65535 and any flag; rejects bufsize 0 or negative with INVALID_ARG. Outside a group it checks the address before the flag. The host buffer is 10 times bufsize, so a small buffer holds far more than on a PSP.

sceNetAdhocPdpSend

int sceNetAdhocPdpSend(int id, const SceNetEtherAddr *dst, u16 dport, const void *data, int len,
	u32 timeout, int flag); // NID 0xABED3790

Sends one datagram. Returns 0 on success, not the length.

Check, in orderError
dst NULL, flag not 0 or 1INVALID_ARG
not initializedNOT_INITIALIZED
bad idINVALID_SOCKET_ID
dst zero, or multicast other than FF:FF:FF:FF:FF:FFINVALID_ADDR
dport 0 or 65535INVALID_PORT
len above 65523, negative, or non-zero with NULL dataINVALID_DATALEN
  • len 0 is fine, even with NULL data.
  • Datagrams over 1444 bytes are fragmented on the air. Between two consoles, 16000 bytes arrive intact, 32000 don't arrive at all; a 65523-byte send sometimes fails with 0x80410001.
  • Sending to a MAC that isn't in the group, or to a port nobody has open, succeeds.
  • Sending to the console's own MAC is delivered locally. Its own broadcasts are not, to any of its sockets.
  • Datagrams can be lost: of 50 small ones sent back to back, a few usually are.
  • Send queue full: WOULD_BLOCK, or a blocking send waits.

PPSSPP: NULL dst gives INVALID_ADDR; a zero or multicast destination and port 65535 are accepted; NULL data with len 0 gives INVALID_ARG; the flag isn't checked; 65523 bytes give WOULD_BLOCK (the host's UDP limit). An unknown MAC returns 0 and sends nothing. In relay mode, datagrams over 10 KiB can't be sent.

sceNetAdhocPdpRecv

int sceNetAdhocPdpRecv(int id, SceNetEtherAddr *src, u16 *sport, void *buf, int *len,
	u32 timeout, int flag); // NID 0xDFE53E03

Receives one datagram. *len is the buffer size going in. Returns 0 on success, with *len the datagram's size and the sender in *src/*sport.

  • src and sport may be NULL. A NULL buf or len, or a bad flag, gives INVALID_ARG (before the ID is checked).
  • Nothing waiting: WOULD_BLOCK or TIMEOUT, with *len left alone.
  • Buffer too small: NOT_ENOUGH_SPACE (0x80400706), with *len set to the datagram's size and the sender filled in. Nothing is copied, and the datagram stays queued for the next call.

PPSSPP: receives its own broadcasts; copies what fits when the buffer is too small; checks the socket ID before NULL pointers; doesn't check the flag; a length of 0 or less with data waiting gives NOT_ENOUGH_SPACE.

sceNetAdhocPdpDelete

int sceNetAdhocPdpDelete(int id, int flag); // NID 0x7F27BB5E

Deletes the socket. flag must be 0 (INVALID_ARG). An unknown ID, or deleting twice: INVALID_SOCKET_ID. A thread blocked receiving on it wakes with SOCKET_DELETED (0x80410707).

PPSSPP: ignores the flag.

sceNetAdhocGetPdpStat

int sceNetAdhocGetPdpStat(int *size, SceNetAdhocPdpStat *buf); // NID 0xC7C1FC57

All PDP sockets, 20 bytes each. NULL buf: *size = bytes needed. NULL size: INVALID_ARG. A *size too small for one entry returns 0 with *size 0.

PTP

typedef struct SceNetAdhocPtpStat {   // 36 bytes
	struct SceNetAdhocPtpStat *next;
	int id;                            // -1 for a connection that hasn't been accepted yet
	SceNetEtherAddr laddr, paddr;
	u16 lport, pport;
	u32 snd_sb_cc;                     // bytes queued but not yet sent
	u32 rcv_sb_cc;                     // bytes waiting to be read
	int state;                         // 0 CLOSED, 1 LISTEN, 2 SYN_SENT, 3 SYN_RCVD, 4 ESTABLISHED
} SceNetAdhocPtpStat;

bufsize on Open and Listen is each direction's buffer. With 8 KB buffers, a sender whose peer doesn't read can queue 8-16 KB before sends return WOULD_BLOCK, and 16-32 KB in all is in flight.

sceNetAdhocPtpOpen

int sceNetAdhocPtpOpen(const SceNetEtherAddr *mac, u16 port, const SceNetEtherAddr *dst, u16 dport,
	int bufsize, u32 rexmt_int, int rexmt_cnt, int flag); // NID 0x877F6D66

Creates a socket and starts connecting at once, in the background. To a listener on the same console, it's ESTABLISHED by the time Open returns.

CheckError
mac or dst NULL, flag not 0INVALID_ARG
mac not the console's own, dst zeroINVALID_ADDR
dport 0 or 65535INVALID_PORT

bufsize, rexmt_int (µs) and rexmt_cnt aren't checked: 0 is accepted for each. The broadcast address is accepted as a destination.

PPSSPP: rejects 0 for the sizes and the broadcast destination, accepts a foreign source MAC, destination ports 0 and 65535, and any flag.

sceNetAdhocPtpConnect

int sceNetAdhocPtpConnect(int id, u32 timeout, int flag); // NID 0xFC6FC07B

Waits for (or, non-blocking, checks) the connection Open started. 0 once established, also when called again. A port nobody listens on: CONNECTION_REFUSED (0x80410718) at once, blocking or not, within a second across the air too.

PPSSPP: connecting to a port nobody listens on can report success (it did between two sockets of one instance); non-blocking calls otherwise return WOULD_BLOCK instead of CONNECTION_REFUSED; in relay mode a refused connect gives BUSY.

sceNetAdhocPtpListen

int sceNetAdhocPtpListen(const SceNetEtherAddr *mac, u16 port, int bufsize, u32 rexmt_int,
	int rexmt_cnt, int backlog, int flag); // NID 0xE08BDAC1

A listening socket. As for Open: the MAC must be the console's own, flag 0, and the sizes and backlog aren't checked. Until accepted, an incoming connection shows up in GetPtpStat with ID -1.

sceNetAdhocPtpAccept

int sceNetAdhocPtpAccept(int id, SceNetEtherAddr *mac, u16 *port, u32 timeout, int flag); // NID 0x9DF81198

Returns a new socket ID for the next incoming connection, with the peer's MAC and port (both pointers may be NULL). Nothing waiting: WOULD_BLOCK or TIMEOUT. On a socket that isn't listening: NOT_LISTENED (0x8040070E).

PPSSPP: in relay mode, the accepted socket reports the wrong peer port in GetPtpStat.

sceNetAdhocPtpSend / Recv

int sceNetAdhocPtpSend(int id, const void *data, int *len, u32 timeout, int flag); // NID 0x4DA4C788
int sceNetAdhocPtpRecv(int id, void *buf, int *len, u32 timeout, int flag);        // NID 0x8BEA2B3E

Both return 0 on success, with *len the bytes sent or received. Recv returns whatever is buffered, up to *len (100 bytes sent can be read as 30 then 70).

  • Send: NULL data or negative *len gives INVALID_DATALEN, NULL len INVALID_ARG; *len 0 is fine.
  • On a listener: NOT_CONNECTED (0x8041070B).
  • After the peer closes, or leaves the group: DISCONNECTED (0x8041070C), and the state becomes CLOSED.
  • Buffers full: WOULD_BLOCK, or TIMEOUT for a blocking call.

PPSSPP: NULL data, a length of 0 and a negative length all give INVALID_ARG; after the peer closed, send gives NOT_CONNECTED. Buffers are much larger (a full sender queues 64 KB or more), so WOULD_BLOCK comes far later. In relay mode, a peer leaving the group isn't noticed.

sceNetAdhocPtpFlush

int sceNetAdhocPtpFlush(int id, u32 timeout, int flag); // NID 0x9AC2EEAC

Waits until sent data has gone out. Returns 0.

sceNetAdhocPtpClose

int sceNetAdhocPtpClose(int id, int flag); // NID 0x157E6225

Closes. flag must be 0. Twice: INVALID_SOCKET_ID. The peer's next send or receive gives DISCONNECTED, and a socket closed this way disappears from the peer's GetPtpStat.

PPSSPP: ignores the flag.

sceNetAdhocGetPtpStat

int sceNetAdhocGetPtpStat(int *size, SceNetAdhocPtpStat *buf); // NID 0xB9685118

All PTP sockets, 36 bytes each, including connections not yet accepted (ID -1). Works like GetPdpStat.

PPSSPP: doesn't list unaccepted connections; a connecting socket shows SYN_SENT for a while where the PSP is already ESTABLISHED; snd_sb_cc is always 0.

Polling and alerts

sceNetAdhocPollSocket

typedef struct { int id; int events; int revents; } SceNetAdhocPollSd; // 12 bytes

int sceNetAdhocPollSocket(SceNetAdhocPollSd *sds, int count, u32 timeout, int flag); // NID 0x7A662D6B

Returns how many sockets have any of their requested events.

BitEvent
0x0001SEND
0x0002RECV
0x0004CONNECT
0x0008ACCEPT
0x0010FLUSH
0x0100INVALID (reported only)
0x0200DELETE (reported only)
0x0400ALERT (reported only)
0x0800DISCONNECT (reported only)

What sockets report, on a PSP:

Socketrevents (of all requested)
PDP, idleSEND
PDP, data waitingSEND, RECV
PTP listener, idleSEND, FLUSH
PTP listener, connection waitingSEND, FLUSH, ACCEPT
PTP, connectedSEND, CONNECT, FLUSH, plus RECV with data waiting
PTP, peer closed, or connect refusedDISCONNECT only, and the call returns EXCEPTION_EVENT (0x80410717), even with no events requested
unknown or deleted IDINVALID, and the call returns INVALID_SOCKET_ID
poll alert setALERT, and the call returns EXCEPTION_EVENT

revents holds only requested events, apart from the reported-only ones. count 0 or less, NULL sds, or a bad flag: INVALID_ARG. Blocking with nothing to report: TIMEOUT; timeout 0 waits forever.

PPSSPP: never reports FLUSH, CONNECT only while connecting, no DISCONNECT or EXCEPTION_EVENT for a closed peer or a refused connect (it reports SEND, RECV, CONNECT instead), no INVALID for bad IDs, no ALERT. A blocking poll that times out returns 0, a bad flag is accepted.

sceNetAdhocSetSocketAlert / GetSocketAlert

int sceNetAdhocSetSocketAlert(int id, int flags);  // NID 0x73BFD52D
int sceNetAdhocGetSocketAlert(int id, int *flags); // NID 0x4D2CE199
FlagMakes fail with SOCKET_ALERTED
0x0010send
0x0020receive
0x0040poll (as EXCEPTION_EVENT with ALERT)
0x0080connect
0x0100accept
0x0200flush
  • Only these bits are kept (flags & 0x3F0); setting 0 clears them.
  • Alerts persist: every matching call fails, blocking or not, until cleared.
  • Each flag only affects its own call: on a listening socket the receive alert doesn't stop PtpAccept, and on a connection the flush alert doesn't stop PtpSend or PtpRecv.
  • Setting an alert wakes a thread already blocked in that call at once, with SOCKET_ALERTED: the accept alert wakes a blocked PtpAccept, the receive alert a blocked PtpRecv. Other flags leave it blocked.
  • A call that can complete straight away isn't stopped: PtpConnect on a connected socket and PtpFlush with nothing left to send return 0 with their alerts set.
  • With the poll alert set, PollSocket returns EXCEPTION_EVENT (0x80410717) with ALERT (0x0400) in revents, for PTP sockets as for PDP.

PPSSPP: the connect and flush alerts stop calls that would complete at once, and the poll alert sets IN (0x0001) in revents instead of ALERT.

  • Unknown ID: INVALID_SOCKET_ID. GetSocketAlert with NULL: INVALID_ARG.

Game mode data

These only work in game mode (see sceNetAdhocctlCreateEnterGameMode); outside it, they fail with NOT_IN_GAMEMODE (0x8041071B) and the deletes with NOT_CREATED (0x8041071C).

int sceNetAdhocGameModeCreateMaster(const void *data, int size);                     // NID 0x7F75C338
int sceNetAdhocGameModeCreateReplica(const SceNetEtherAddr *mac, void *data, int size); // NID 0x3278AB0C
int sceNetAdhocGameModeUpdateMaster(void);                                           // NID 0x98C204C8
int sceNetAdhocGameModeUpdateReplica(int id, SceNetAdhocGameModeOptData *info);      // NID 0xFA324B4E
int sceNetAdhocGameModeDeleteMaster(void);                                           // NID 0xA0229362
int sceNetAdhocGameModeDeleteReplica(int id);                                        // NID 0x0B2228E9

typedef struct { u32 size; u32 flag; u64 last_recv; } SceNetAdhocGameModeOptData;    // 16 bytes
  • CreateMaster publishes this console's buffer. Size 0: INVALID_DATALEN; a second master: ALREADY_CREATED (0x8041071A).
  • CreateReplica returns the ID of a local copy of a member's buffer. The ID is that member's index in the game mode member list (0 for the master). A MAC that isn't a member: 0x80410D13; twice: ALREADY_CREATED.
  • UpdateMaster takes a new snapshot of the master buffer to send.
  • UpdateReplica copies the newest data received into the replica's buffer. info may be NULL; if given, size comes back as 16, flag non-zero if new data arrived since the last call, and last_recv increases. A bad ID: NOT_CREATED.
  • Between two consoles, polling every 50 ms, new data arrived on 40 or more polls of 60: the buffers are sent at least 20 times a second. Counters written into the master only ever increase on the other side.

PPSSPP: a master of size 0 is accepted; with two instances, the test hangs at buffer setup.