/*
* Copyright (c) 2008-2024 Jonathan Schleifer <js@nil.im>
*
* All rights reserved.
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License version 3.0 only,
* as published by the Free Software Foundation.
*
* This program 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 Lesser General Public License
* version 3.0 for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* version 3.0 along with this program. If not, see
* <https://www.gnu.org/licenses/>.
*/
#include "config.h"
#define _XPG4_2
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifdef HAVE_FCNTL_H
# include <fcntl.h>
#endif
#import "OFSCTPSocket.h"
#import "OFAsyncIPSocketConnector.h"
#import "OFDNSResolver.h"
#import "OFData.h"
#import "OFDate.h"
#import "OFDictionary.h"
#import "OFNumber.h"
#import "OFRunLoop.h"
#import "OFRunLoop+Private.h"
#import "OFSocket.h"
#import "OFSocket+Private.h"
#import "OFString.h"
#import "OFThread.h"
#import "OFAcceptSocketFailedException.h"
#import "OFAlreadyOpenException.h"
#import "OFBindIPSocketFailedException.h"
#import "OFGetOptionFailedException.h"
#import "OFNotOpenException.h"
#import "OFOutOfRangeException.h"
#import "OFReadFailedException.h"
#import "OFSetOptionFailedException.h"
#import "OFWriteFailedException.h"
#ifdef OF_SOLARIS
# define SCTP_UNORDERED MSG_UNORDERED
#endif
const OFSCTPMessageInfoKey OFSCTPStreamID = @"OFSCTPStreamID";
const OFSCTPMessageInfoKey OFSCTPPPID = @"OFSCTPPPID";
const OFSCTPMessageInfoKey OFSCTPUnordered = @"OFSCTPUnordered";
static const OFRunLoopMode connectRunLoopMode =
@"OFSCTPSocketConnectRunLoopMode";
@interface OFSCTPSocket () <OFAsyncIPSocketConnecting>
@end
@interface OFSCTPSocketConnectDelegate: OFObject <OFSCTPSocketDelegate>
{
@public
bool _done;
id _exception;
}
@end
@implementation OFSCTPSocketConnectDelegate
- (void)dealloc
{
[_exception release];
[super dealloc];
}
- (void)socket: (OFSCTPSocket *)sock
didConnectToHost: (OFString *)host
port: (uint16_t)port
exception: (id)exception
{
_done = true;
_exception = [exception retain];
}
@end
@implementation OFSCTPSocket
@dynamic delegate;
- (bool)of_createSocketForAddress: (const OFSocketAddress *)address
errNo: (int *)errNo
{
const struct sctp_event_subscribe events = {
.sctp_data_io_event = 1
};
#if SOCK_CLOEXEC == 0 && defined(HAVE_FCNTL) && defined(FD_CLOEXEC)
int flags;
#endif
if (_socket != OFInvalidSocketHandle)
@throw [OFAlreadyOpenException exceptionWithObject: self];
if ((_socket = socket(
((struct sockaddr *)&address->sockaddr)->sa_family,
SOCK_STREAM | SOCK_CLOEXEC, IPPROTO_SCTP)) ==
OFInvalidSocketHandle) {
*errNo = _OFSocketErrNo();
return false;
}
#if SOCK_CLOEXEC == 0 && defined(HAVE_FCNTL) && defined(FD_CLOEXEC)
if ((flags = fcntl(_socket, F_GETFD, 0)) != -1)
fcntl(_socket, F_SETFD, flags | FD_CLOEXEC);
#endif
if (setsockopt(_socket, IPPROTO_SCTP, SCTP_EVENTS, &events,
sizeof(events)) != 0) {
*errNo = _OFSocketErrNo();
closesocket(_socket);
_socket = OFInvalidSocketHandle;
return false;
}
return true;
}
- (bool)of_connectSocketToAddress: (const OFSocketAddress *)address
errNo: (int *)errNo
{
if (_socket == OFInvalidSocketHandle)
@throw [OFNotOpenException exceptionWithObject: self];
if (connect(_socket, (struct sockaddr *)&address->sockaddr,
address->length) != 0) {
*errNo = _OFSocketErrNo();
return false;
}
return true;
}
- (void)of_closeSocket
{
closesocket(_socket);
_socket = OFInvalidSocketHandle;
}
- (void)connectToHost: (OFString *)host port: (uint16_t)port
{
void *pool = objc_autoreleasePoolPush();
id <OFSCTPSocketDelegate> delegate = _delegate;
OFSCTPSocketConnectDelegate *connectDelegate =
[[[OFSCTPSocketConnectDelegate alloc] init] autorelease];
OFRunLoop *runLoop = [OFRunLoop currentRunLoop];
_delegate = connectDelegate;
[self asyncConnectToHost: host
port: port
runLoopMode: connectRunLoopMode];
while (!connectDelegate->_done)
[runLoop runMode: connectRunLoopMode beforeDate: nil];
/* Cleanup */
[runLoop runMode: connectRunLoopMode beforeDate: [OFDate date]];
_delegate = delegate;
if (connectDelegate->_exception != nil)
@throw connectDelegate->_exception;
objc_autoreleasePoolPop(pool);
}
- (void)asyncConnectToHost: (OFString *)host port: (uint16_t)port
{
[self asyncConnectToHost: host
port: port
runLoopMode: OFDefaultRunLoopMode];
}
- (void)asyncConnectToHost: (OFString *)host
port: (uint16_t)port
runLoopMode: (OFRunLoopMode)runLoopMode
{
void *pool = objc_autoreleasePoolPush();
if (_socket != OFInvalidSocketHandle)
@throw [OFAlreadyOpenException exceptionWithObject: self];
[[[[OFAsyncIPSocketConnector alloc]
initWithSocket: self
host: host
port: port
delegate: _delegate
handler: NULL
] autorelease] startWithRunLoopMode: runLoopMode];
objc_autoreleasePoolPop(pool);
}
#ifdef OF_HAVE_BLOCKS
- (void)asyncConnectToHost: (OFString *)host
port: (uint16_t)port
handler: (OFSCTPSocketConnectedHandler)handler
{
[self asyncConnectToHost: host
port: port
runLoopMode: OFDefaultRunLoopMode
handler: handler];
}
- (void)asyncConnectToHost: (OFString *)host
port: (uint16_t)port
runLoopMode: (OFRunLoopMode)runLoopMode
handler: (OFSCTPSocketConnectedHandler)handler
{
void *pool = objc_autoreleasePoolPush();
if (_socket != OFInvalidSocketHandle)
@throw [OFAlreadyOpenException exceptionWithObject: self];
[[[[OFAsyncIPSocketConnector alloc]
initWithSocket: self
host: host
port: port
delegate: nil
handler: handler] autorelease]
startWithRunLoopMode: runLoopMode];
objc_autoreleasePoolPop(pool);
}
#endif
- (OFSocketAddress)bindToHost: (OFString *)host port: (uint16_t)port
{
const int one = 1;
void *pool = objc_autoreleasePoolPush();
OFData *socketAddresses;
OFSocketAddress address;
#if SOCK_CLOEXEC == 0 && defined(HAVE_FCNTL) && defined(FD_CLOEXEC)
int flags;
#endif
if (_socket != OFInvalidSocketHandle)
@throw [OFAlreadyOpenException exceptionWithObject: self];
socketAddresses = [[OFThread DNSResolver]
resolveAddressesForHost: host
addressFamily: OFSocketAddressFamilyAny];
address = *(OFSocketAddress *)[socketAddresses itemAtIndex: 0];
OFSocketAddressSetIPPort(&address, port);
if ((_socket = socket(
((struct sockaddr *)&address.sockaddr)->sa_family,
SOCK_STREAM | SOCK_CLOEXEC, IPPROTO_SCTP)) == OFInvalidSocketHandle)
@throw [OFBindIPSocketFailedException
exceptionWithHost: host
port: port
socket: self
errNo: _OFSocketErrNo()];
_canBlock = true;
#if SOCK_CLOEXEC == 0 && defined(HAVE_FCNTL) && defined(FD_CLOEXEC)
if ((flags = fcntl(_socket, F_GETFD, 0)) != -1)
fcntl(_socket, F_SETFD, flags | FD_CLOEXEC);
#endif
setsockopt(_socket, SOL_SOCKET, SO_REUSEADDR,
(char *)&one, (socklen_t)sizeof(one));
if (bind(_socket, (struct sockaddr *)&address.sockaddr,
address.length) != 0) {
int errNo = _OFSocketErrNo();
closesocket(_socket);
_socket = OFInvalidSocketHandle;
@throw [OFBindIPSocketFailedException exceptionWithHost: host
port: port
socket: self
errNo: errNo];
}
memset(&address, 0, sizeof(address));
address.length = (socklen_t)sizeof(address.sockaddr);
if (_OFGetSockName(_socket, (struct sockaddr *)&address.sockaddr,
&address.length) != 0) {
int errNo = _OFSocketErrNo();
closesocket(_socket);
_socket = OFInvalidSocketHandle;
@throw [OFBindIPSocketFailedException exceptionWithHost: host
port: port
socket: self
errNo: errNo];
}
switch (((struct sockaddr *)&address.sockaddr)->sa_family) {
case AF_INET:
address.family = OFSocketAddressFamilyIPv4;
break;
# ifdef OF_HAVE_IPV6
case AF_INET6:
address.family = OFSocketAddressFamilyIPv6;
break;
# endif
default:
closesocket(_socket);
_socket = OFInvalidSocketHandle;
@throw [OFBindIPSocketFailedException
exceptionWithHost: host
port: port
socket: self
errNo: EAFNOSUPPORT];
}
objc_autoreleasePoolPop(pool);
return address;
}
- (instancetype)accept
{
const struct sctp_event_subscribe events = {
.sctp_data_io_event = 1
};
OFSCTPSocket *accepted = [super accept];
if (setsockopt(accepted->_socket, IPPROTO_SCTP, SCTP_EVENTS, &events,
sizeof(events)) != 0)
@throw [OFAcceptSocketFailedException
exceptionWithSocket: self
errNo: _OFSocketErrNo()];
return accepted;
}
- (size_t)receiveIntoBuffer: (void *)buffer length: (size_t)length
{
return [self receiveIntoBuffer: buffer length: length info: NULL];
}
- (size_t)receiveIntoBuffer: (void *)buffer
length: (size_t)length
info: (OFSCTPMessageInfo *)info
{
ssize_t ret;
struct iovec iov = {
.iov_base = buffer,
.iov_len = length
};
char cmsgBuffer[CMSG_SPACE(sizeof(struct sctp_sndrcvinfo))];
struct msghdr msg = {
.msg_iov = &iov,
.msg_iovlen = 1,
.msg_control = cmsgBuffer,
.msg_controllen = sizeof(cmsgBuffer)
};
struct cmsghdr *cmsg;
if (_socket == OFInvalidSocketHandle)
@throw [OFNotOpenException exceptionWithObject: self];
if ((ret = recvmsg(_socket, &msg, 0)) < 0)
@throw [OFReadFailedException
exceptionWithObject: self
requestedLength: length
errNo: _OFSocketErrNo()];
if (info == NULL)
return ret;
*info = nil;
for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL;
cmsg = CMSG_NXTHDR(&msg, cmsg)) {
if (cmsg->cmsg_level != IPPROTO_SCTP)
continue;
if (cmsg->cmsg_type == SCTP_SNDRCV) {
struct sctp_sndrcvinfo sndrcv;
memcpy(&sndrcv, CMSG_DATA(cmsg), sizeof(sndrcv));
OFNumber *streamID = [OFNumber numberWithUnsignedShort:
sndrcv.sinfo_stream];
OFNumber *PPID = [OFNumber numberWithUnsignedLong:
OFFromBigEndian32(sndrcv.sinfo_ppid)];
OFNumber *unordered = [OFNumber numberWithBool:
(sndrcv.sinfo_flags & SCTP_UNORDERED)];
*info = [OFDictionary dictionaryWithKeysAndObjects:
OFSCTPStreamID, streamID,
OFSCTPPPID, PPID,
OFSCTPUnordered, unordered, nil];
break;
}
}
return ret;
}
- (void)asyncReceiveWithInfoIntoBuffer: (void *)buffer
length: (size_t)length
{
[self asyncReceiveWithInfoIntoBuffer: buffer
length: length
runLoopMode: OFDefaultRunLoopMode];
}
- (void)asyncReceiveWithInfoIntoBuffer: (void *)buffer
length: (size_t)length
runLoopMode: (OFRunLoopMode)runLoopMode
{
[OFRunLoop of_addAsyncReceiveForSCTPSocket: self
buffer: buffer
length: length
mode: runLoopMode
# ifdef OF_HAVE_BLOCKS
handler: NULL
# endif
delegate: _delegate];
}
#ifdef OF_HAVE_BLOCKS
- (void)
asyncReceiveWithInfoIntoBuffer: (void *)buffer
length: (size_t)length
handler: (OFSCTPSocketMessageReceivedHandler)handler
{
[self asyncReceiveWithInfoIntoBuffer: buffer
length: length
runLoopMode: OFDefaultRunLoopMode
handler: handler];
}
- (void)
asyncReceiveWithInfoIntoBuffer: (void *)buffer
length: (size_t)length
runLoopMode: (OFRunLoopMode)runLoopMode
handler: (OFSCTPSocketMessageReceivedHandler)handler
{
[OFRunLoop of_addAsyncReceiveForSCTPSocket: self
buffer: buffer
length: length
mode: runLoopMode
handler: handler
delegate: nil];
}
#endif
- (void)sendBuffer: (const void *)buffer length: (size_t)length
{
[self sendBuffer: buffer length: length info: nil];
}
- (void)sendBuffer: (const void *)buffer
length: (size_t)length
info: (OFSCTPMessageInfo)info
{
ssize_t bytesWritten;
struct iovec iov = {
.iov_base = (void *)buffer,
.iov_len = length
};
struct sctp_sndrcvinfo sndrcv = {
.sinfo_stream = (uint16_t)
[[info objectForKey: OFSCTPStreamID] unsignedShortValue],
.sinfo_ppid = OFToBigEndian32((uint32_t)
[[info objectForKey: OFSCTPPPID] unsignedLongValue]),
.sinfo_flags = ([[info objectForKey: OFSCTPUnordered] boolValue]
? SCTP_UNORDERED : 0)
};
char cmsgBuffer[CMSG_SPACE(sizeof(sndrcv))];
struct cmsghdr *cmsg = (struct cmsghdr *)(void *)&cmsgBuffer;
struct msghdr msg = {
.msg_iov = &iov,
.msg_iovlen = 1,
.msg_control = &cmsgBuffer,
.msg_controllen = sizeof(cmsgBuffer)
};
if (_socket == OFInvalidSocketHandle)
@throw [OFNotOpenException exceptionWithObject: self];
if (length > SSIZE_MAX)
@throw [OFOutOfRangeException exception];
cmsg->cmsg_level = IPPROTO_SCTP;
cmsg->cmsg_type = SCTP_SNDRCV;
cmsg->cmsg_len = CMSG_LEN(sizeof(sndrcv));
memcpy(CMSG_DATA(cmsg), &sndrcv, sizeof(sndrcv));
if ((bytesWritten = sendmsg(_socket, &msg, 0)) < 0)
@throw [OFWriteFailedException
exceptionWithObject: self
requestedLength: length
bytesWritten: 0
errNo: _OFSocketErrNo()];
#ifndef OF_SOLARIS
/* Solaris seems to just return 0. */
if ((size_t)bytesWritten != length)
@throw [OFWriteFailedException exceptionWithObject: self
requestedLength: length
bytesWritten: bytesWritten
errNo: 0];
#endif
}
- (void)asyncSendData: (OFData *)data info: (OFSCTPMessageInfo)info
{
[self asyncSendData: data info: nil runLoopMode: OFDefaultRunLoopMode];
}
- (void)asyncSendData: (OFData *)data
info: (OFSCTPMessageInfo)info
runLoopMode: (OFRunLoopMode)runLoopMode
{
[OFRunLoop of_addAsyncSendForSCTPSocket: self
data: data
info: info
mode: runLoopMode
# ifdef OF_HAVE_BLOCKS
handler: NULL
# endif
delegate: _delegate];
}
#ifdef OF_HAVE_BLOCKS
- (void)asyncSendData: (OFData *)data
info: (OFSCTPMessageInfo)info
handler: (OFSCTPSocketDataSentHandler)handler
{
[self asyncSendData: data
info: info
runLoopMode: OFDefaultRunLoopMode
handler: handler];
}
- (void)asyncSendData: (OFData *)data
info: (OFSCTPMessageInfo)info
runLoopMode: (OFRunLoopMode)runLoopMode
handler: (OFSCTPSocketDataSentHandler)handler
{
[OFRunLoop of_addAsyncSendForSCTPSocket: self
data: data
info: info
mode: runLoopMode
handler: handler
delegate: nil];
}
#endif
- (void)setCanDelaySendingMessages: (bool)canDelaySendingMessages
{
int v = !canDelaySendingMessages;
if (setsockopt(_socket, IPPROTO_SCTP, SCTP_NODELAY,
(char *)&v, (socklen_t)sizeof(v)) != 0)
@throw [OFSetOptionFailedException
exceptionWithObject: self
errNo: _OFSocketErrNo()];
}
- (bool)canDelaySendingMessages
{
int v;
socklen_t len = sizeof(v);
if (getsockopt(_socket, IPPROTO_SCTP, SCTP_NODELAY,
(char *)&v, &len) != 0 || len != sizeof(v))
@throw [OFGetOptionFailedException
exceptionWithObject: self
errNo: _OFSocketErrNo()];
return !v;
}
@end