Files
2017-04-12 02:28:37 +03:00

345 lines
9.6 KiB
Objective-C
Executable File

//
// GDUnixSocket.m
//
// Copyright © 2016 Alexey Gordiyenko. All rights reserved.
//
/*
The MIT License (MIT)
Copyright (c) 2016 A. Gordiyenko
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#import "GDUnixSocket_Private.h"
#import <sys/un.h>
#import <sys/socket.h>
#pragma mark - Constants
const int kGDBadSocketFD = -1;
NSString * const kGDUnixSocketErrDomain = @"com.coffellas.GDUnixSocket";
NSString * const kGDDummySocketPath = @"(dummy)";
#pragma mark - NSError
@implementation NSError (GDUnixSocket)
+ (NSError *)gduds_errorForCode:(GDUnixSocketError)code {
return [self gduds_errorForCode:code info:nil];
}
+ (NSError *)gduds_errorForCode:(GDUnixSocketError)code info:(NSString *)infoString {
NSString *localizedDescription;
switch (code) {
case GDUnixSocketErrorBadSocket:
localizedDescription = @"Bad socket";
break;
case GDUnixSocketErrorBind:
localizedDescription = @"Failed to bind socket";
break;
case GDUnixSocketErrorListen:
localizedDescription = @"Failed to listen on socket";
break;
case GDUnixSocketErrorAccept:
localizedDescription = @"Failed to accept connection, closing socket";
break;
case GDUnixSocketErrorUnlink:
localizedDescription = @"Failed to unlink socket";
break;
case GDUnixSocketErrorConnect:
localizedDescription = @"Failed to connect to socket";
break;
case GDUnixSocketErrorSocketWrite:
localizedDescription = @"Failed to write to socket";
break;
case GDUnixSocketErrorSocketRead:
localizedDescription = @"Failed to read from socket";
break;
case GDUnixSocketErrorClose:
localizedDescription = @"Failed to close socket";
break;
case GDUnixSocketErrorUnknownClient:
localizedDescription = @"Unknown client. It is either disconnected or never existed";
break;
default:
localizedDescription = @"Unknown Error";
break;
}
if (infoString.length) {
localizedDescription = [NSString stringWithFormat:@"%@. %@", localizedDescription, infoString];
}
GDUnixSocketLog(@"Error: %@", localizedDescription);
return [NSError errorWithDomain:kGDUnixSocketErrDomain code:code userInfo:@{NSLocalizedDescriptionKey: localizedDescription}];
}
@end
#pragma mark - GDUnixSocket
@interface GDUnixSocket () {
dispatch_fd_t _fd;
}
@end
@implementation GDUnixSocket
@synthesize uniqueID = _uniqueID;
#pragma mark - Accessors
- (NSString *)uniqueID {
@synchronized(self) {
if (!_uniqueID) {
_uniqueID = [NSUUID UUID].UUIDString;
}
}
return _uniqueID;
}
#pragma mark - Public Methods
- (ssize_t)writeData:(NSData *)data error:(NSError **)error {
return [self write:data toSocket:[self fd] error:error];
}
- (void)writeData:(NSData *)data completion:(void(^)(NSError *error, ssize_t size))completion {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSError *error = nil;
ssize_t size = [self writeData:data error:&error];
if (completion) {
completion(error, size);
}
});
}
- (NSData *)readWithError:(NSError **)error {
return [self readFromSocket:[self fd] error:error];
}
- (void)readWithCompletion:(void(^)(NSError *error, NSData *data))completion {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSError *error = nil;
NSData *data = [self readWithError:&error];
if (completion) {
completion(error, data);
}
});
}
- (BOOL)close {
return [self closeWithError:nil];
}
- (BOOL)closeWithError:(NSError **)error {
if (self.state == GDUnixSocketStateDisconnected) {
return YES;
}
BOOL retVal = NO;
NSError *retError = [self checkForBadSocket];
if (retError) {
NSString *tmpDescription = retError.localizedDescription;
retError = [NSError gduds_errorForCode:GDUnixSocketErrorClose info:tmpDescription];
} else {
retVal = close([self fd]) != -1;
if (!retVal) {
retError = [NSError gduds_errorForCode:GDUnixSocketErrorClose info:[self lastErrorInfo]];
} else {
GDUnixSocketLog(@"closed socket [%d]", [self fd]);
}
[self setFd:kGDBadSocketFD];
}
if (error) {
*error = retError;
}
if (retVal) {
self.state = GDUnixSocketStateDisconnected;
}
return retVal;
}
#pragma mark - Private Methods
- (NSData *)readFromSocket:(dispatch_fd_t)socket_fd error:(NSError **)error {
NSData *retVal = nil;
NSError *retError = nil;
size_t buffer_size = self.fragmentSize;
uint8_t *buffer = calloc(buffer_size, sizeof(uint8_t));
ssize_t bytes_read = read(socket_fd, buffer, buffer_size);
if (bytes_read == -1) {
retError = [NSError gduds_errorForCode:GDUnixSocketErrorSocketRead info:[self lastErrorInfoForSocket:socket_fd]];
} else {
GDUnixSocketLog(@"read %zd bytes from socket [%d]: %s", bytes_read, socket_fd, buffer);
}
if (bytes_read > 0) {
retVal = [NSData dataWithBytesNoCopy:buffer length:bytes_read freeWhenDone:YES];
} else {
free(buffer);
}
if (error) {
*error = retError;
}
return retVal;
}
- (ssize_t)write:(NSData *)data toSocket:(dispatch_fd_t)socket_fd error:(NSError **)error {
if (error) {
*error = nil;
}
if (!data || !data.length) {
return 0;
}
NSError *socketError = [self checkForBadSocket:socket_fd];
if (socketError) {
if (error) {
*error = socketError;
}
return -1;
}
const void *buffer = data.bytes;
size_t length = data.length;
ssize_t written = write(socket_fd, buffer, length);
if (-1 == written && error) {
*error = [NSError gduds_errorForCode:GDUnixSocketErrorSocketWrite info:[self lastErrorInfoForSocket:socket_fd]];
}
GDUnixSocketLog(@"written %zd bytes on socket [%d]: %s", written, socket_fd, buffer);
return written;
}
- (NSString *)lastErrorInfo {
return [self lastErrorInfoForSocket:[self fd]];
}
- (NSString *)lastErrorInfoForSocket:(dispatch_fd_t)socket_fd {
int error;
// TODO: Switch to `getsockopt`. The code commented out below is not accurate.
// socklen_t len = sizeof(error);
// if (-1 == getsockopt(socket_fd, SOL_SOCKET, SO_ERROR, &error, &len)) {
error = errno;
// }
return [NSString stringWithFormat:@"fd: %d. errno: %d. %s", socket_fd, error, strerror(error)];
}
- (NSError *)checkForBadSocket {
return [self checkForBadSocket:[self fd]];
}
- (NSError *)checkForBadSocket:(dispatch_fd_t)socket_fd {
if (socket_fd == kGDBadSocketFD) {
return [NSError gduds_errorForCode:GDUnixSocketErrorBadSocket];
}
return nil;
}
- (dispatch_fd_t)fd {
@synchronized(self) {
return _fd;
}
}
- (void)setFd:(dispatch_fd_t)fd {
@synchronized(self) {
_fd = fd;
}
}
- (NSString *)debugDescription {
return self.uniqueID;
}
- (NSString *)description {
return self.uniqueID;
}
#pragma mark - Life Cycle
- (instancetype)initWithSocketPath:(NSString *)socketPath {
return [self initWithSocketPath:socketPath andFragmentSize:256];
}
- (instancetype)initWithSocketPath:(NSString *)socketPath andFragmentSize:(size_t)fragmentSize {
NSParameterAssert(socketPath);
self = [super init];
if (self) {
_fd = kGDBadSocketFD;
if (!socketPath.length) {
return nil;
}
if (![socketPath isEqualToString:kGDDummySocketPath]) {
NSString *standardizedPath = [socketPath stringByStandardizingPath]; // Returns self if an error occurs.
if (![standardizedPath isEqualToString:socketPath]) {
return nil;
}
if (![standardizedPath rangeOfString:@"/"].length) {
return nil;
}
}
struct sockaddr_un address;
size_t allowed_size = sizeof(address.sun_path) - 1;
if (strlen([socketPath cStringUsingEncoding:NSUTF8StringEncoding]) > allowed_size) {
return nil;
}
_socketPath = [socketPath copy];
_fragmentSize = fragmentSize;
}
return self;
}
- (instancetype)init {
return [self initWithSocketPath:nil];
}
- (void)dealloc {
if (_fd != kGDBadSocketFD) {
[self close];
}
}
@end