Created
July 19, 2015 03:24
-
-
Save BobBurns/25288e5e7d1b6bac74eb to your computer and use it in GitHub Desktop.
mac app to receive nfc data
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// AppDelegate.m | |
// nfc_socket_client | |
// | |
// Created by WozniBob on 6/19/15. | |
// Copyright (c) 2015 Bob_Burns. All rights reserved. | |
// | |
#import "AppDelegate.h" | |
#import "Student.h" | |
#import <errno.h> | |
#import <fcntl.h> | |
#import <stdbool.h> | |
#import <string.h> | |
#import <unistd.h> | |
#import <arpa/inet.h> // inet_ntop() | |
#import <netdb.h> //gethostbyname2() | |
#import <netinet/in.h> // struct sockaddr_in | |
#import <netinet6/in6.h> //struct sockaddr_in6 | |
#import <sys/socket.h> // socket(), AF_INET | |
#import <sys/types.h> | |
#define MAX_MESSAGE_SIZE (UINT8_MAX) | |
#define READ_BUFFER_SIZE (256) | |
static const in_port_t kPortNumber = 2342; | |
static const int kInvalidSocket = -1; | |
static int WriteMessage (int fd, const void *buffer, size_t length); | |
static int SocketConnectedToHostNamed (const char *hostname); | |
static bool GetAddressAtIndex (struct hostent *host, int addressIndex, | |
struct sockaddr_storage *outServerAddress); | |
#define debug 1 | |
@interface AppDelegate () <NSAlertDelegate> { | |
CFSocketNativeHandle _sockfd; | |
CFSocketRef _socketRef; | |
} | |
@property (weak) IBOutlet NSWindow *window; | |
@property (strong) NSInputStream *inputStream; | |
@property (strong) NSOutputStream *outStream; | |
@property (strong) NSMutableData *dataToRead; | |
@property (strong) NSMutableData *dataToWrite; | |
@property NSInteger bytesRead; | |
@property (nonatomic, readonly, getter=isConnected) BOOL connected; | |
- (void) updateUI; | |
- (void) runErrorMessage: (NSString *)message withError:(int)err; | |
// Connection | |
- (void)connectToHost: (NSString *)hostname asUser:(NSString *)pass; | |
- (void)closeConnection; | |
// Socket | |
- (void)startMonitoringSocket; | |
- (void)stopMonitoringSocket; | |
// RunLoop | |
static void ReceiveMessage (CFSocketRef, CFSocketCallBackType, CFDataRef, const void *, void *); | |
- (void)handleMessageData: (NSData *)data; | |
- (IBAction)scanButton:(id)sender; | |
- (IBAction)closeButton:(id)sender; | |
- (IBAction)connect:(id)sender; | |
@property (weak) IBOutlet NSTextField *connectedLabel; | |
@property (weak) IBOutlet NSTextField *creditLabel; | |
@property (weak) IBOutlet NSTextField *nameLable; | |
@property (weak) IBOutlet NSTextField *scanLabel; | |
@property (weak) IBOutlet NSProgressIndicator *pIndicate; | |
@property (weak) IBOutlet NSSecureTextField *passField; | |
@end | |
@implementation AppDelegate | |
@synthesize persistentStoreCoordinator = _persistentStoreCoordinator; | |
@synthesize managedObjectModel = _managedObjectModel; | |
@synthesize managedObjectContext = _managedObjectContext; | |
#pragma mark CoreData Stack | |
- (NSURL *)applicationFilesDirectory { | |
NSFileManager *fileManager = [NSFileManager defaultManager]; | |
NSURL *appSupportURL = [[fileManager URLsForDirectory:NSApplicationSupportDirectory inDomains:NSUserDomainMask] lastObject]; | |
return [appSupportURL URLByAppendingPathComponent:@"com.bobburnsapps.nfc-socket-client"]; | |
} | |
- (NSManagedObjectModel *)managedObjectModel { | |
if (_managedObjectModel) { | |
return _managedObjectModel; | |
} | |
NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"Person" withExtension:@"momd"]; | |
_managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL]; | |
return _managedObjectModel; | |
} | |
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator { | |
if (_persistentStoreCoordinator) { | |
return _persistentStoreCoordinator; | |
} | |
NSManagedObjectModel *mom = [self managedObjectModel]; | |
if (!mom) { | |
NSLog(@"%@:%@ No model to generate a store from", [self class], NSStringFromSelector(_cmd)); | |
return nil; | |
} | |
NSFileManager *fileManager = [NSFileManager defaultManager]; | |
NSURL *applicationFilesDirectory = [self applicationFilesDirectory]; | |
NSError *error = nil; | |
NSDictionary *properties = [applicationFilesDirectory resourceValuesForKeys:@[NSURLIsDirectoryKey] error:&error]; | |
if (!properties) { | |
BOOL ok = FALSE; | |
if ([error code] == NSFileReadNoSuchFileError) { | |
ok = [fileManager createDirectoryAtPath:[applicationFilesDirectory path] withIntermediateDirectories:YES attributes:nil error:&error]; | |
} | |
if (!ok) { | |
[[NSApplication sharedApplication] presentError:error]; | |
return nil; | |
} | |
} else { | |
if (![properties[NSURLIsDirectoryKey] boolValue]) { | |
NSString *failureDescription = [NSString stringWithFormat:@"Expected a folder to store application data, found a file (%@).", [applicationFilesDirectory path]]; | |
NSMutableDictionary *dict = [NSMutableDictionary dictionary]; | |
[dict setValue:failureDescription forKey:NSLocalizedDescriptionKey]; | |
error = [NSError errorWithDomain:@"ERROR_DOMAIN" code:101 userInfo:dict]; | |
[[NSApplication sharedApplication] presentError:error]; | |
return nil; | |
} | |
} | |
NSURL *url = [applicationFilesDirectory URLByAppendingPathComponent:@"Person.storedata"]; | |
NSPersistentStoreCoordinator *coordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:mom]; | |
if (![coordinator addPersistentStoreWithType:NSXMLStoreType configuration:nil URL:url options:nil error:&error]) { | |
[[NSApplication sharedApplication] presentError:error]; | |
return nil; | |
} | |
_persistentStoreCoordinator = coordinator; | |
return _persistentStoreCoordinator; | |
} | |
- (NSManagedObjectContext *)managedObjectContext { | |
if (_managedObjectContext) { | |
return _managedObjectContext; | |
} | |
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator]; | |
if (!coordinator) { | |
NSMutableDictionary *dict = [NSMutableDictionary dictionary]; | |
[dict setValue:@"Failed to initialize the store" forKey:NSLocalizedDescriptionKey]; | |
[dict setValue:@"There was an error building up the data file." forKey:NSLocalizedFailureReasonErrorKey]; | |
NSError *error = [NSError errorWithDomain:@"NFC_APP DOMAIN" code:9999 userInfo:dict]; | |
[[NSApplication sharedApplication] presentError:error]; | |
return nil; | |
} | |
_managedObjectContext = [[NSManagedObjectContext alloc] init]; | |
[_managedObjectContext setPersistentStoreCoordinator:coordinator]; | |
return _managedObjectContext; | |
} | |
- (NSUndoManager *)windowWillReturnUndoManager:(NSWindow *)window { | |
return [[self managedObjectContext] undoManager]; | |
} | |
- (IBAction)saveAction:(id)sender { | |
NSError *error = nil; | |
if (![[self managedObjectContext] commitEditing]) { | |
NSLog(@"%@:%@ unable to commit editing before saving", [self class], NSStringFromSelector(_cmd)); | |
} | |
if (![[self managedObjectContext] save:&error]) { | |
[[NSApplication sharedApplication] presentError:error]; | |
} | |
} | |
- (void)updateStudentTable { | |
NSManagedObjectContext *moc = [self managedObjectContext]; | |
NSEntityDescription *entityDescription = [NSEntityDescription entityForName:@"Student" inManagedObjectContext:moc]; | |
NSFetchRequest *request = [[NSFetchRequest alloc] init]; | |
[request setEntity:entityDescription]; | |
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"name" ascending:YES]; | |
[request setSortDescriptors:@[sortDescriptor]]; | |
NSError *error = nil; | |
NSArray *array = [moc executeFetchRequest:request error:&error]; | |
if (array == nil) { | |
NSLog(@"error fetching student"); | |
[[NSApplication sharedApplication] presentError:error]; | |
} else { | |
NSLog(@"looking at students..."); | |
BOOL found = NO; | |
for (Student *student in array) { | |
if ([student.name isEqualToString:[_nameLable stringValue]]) { | |
student.credits = [NSNumber numberWithInt:[_creditLabel intValue]]; | |
NSLog(@"found match"); | |
found = YES; | |
} | |
} | |
if (!found) { | |
NSLog(@"No one named %@ in array", [_nameLable stringValue]); | |
Student *newStudent = [NSEntityDescription insertNewObjectForEntityForName:@"Student" inManagedObjectContext:moc]; | |
newStudent.name = [_nameLable stringValue]; | |
newStudent.credits = [NSNumber numberWithInt:[_creditLabel intValue]]; | |
} | |
} | |
if ([moc hasChanges]) { | |
if (![moc save:&error]) | |
NSLog(@"Error saving changes: %@, %@", error, [error userInfo]); | |
} | |
} | |
- (id)init { | |
if ((self = [super init])) { | |
_sockfd = kInvalidSocket; | |
} | |
return self; | |
} | |
- (void)dealloc { | |
[self closeConnection]; | |
} | |
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification { | |
// Insert code here to initialize your application | |
[_pIndicate setStyle:NSProgressIndicatorSpinningStyle]; | |
[_scanLabel setHidden:YES]; | |
[self updateUI]; | |
if (![self isConnected]) { | |
NSAlert *alert = [[NSAlert alloc] init]; | |
[alert setMessageText:@"Please Enter Password"]; | |
[alert addButtonWithTitle:@"Join"]; | |
[alert addButtonWithTitle:@"Cancel"]; | |
[alert setDelegate:(id)self]; | |
NSSecureTextField *input = [[NSSecureTextField alloc] initWithFrame:NSMakeRect(0, 0, 200, 24)]; | |
[input setStringValue:@""]; | |
[alert setAccessoryView:input]; | |
NSInteger button = [alert runModal]; | |
if (button == NSAlertFirstButtonReturn) { | |
NSString *password = [input stringValue]; | |
[self connectToHost:@"10.0.0.6" asUser:password]; | |
[self updateUI]; | |
} | |
else if (button == NSAlertSecondButtonReturn) { | |
exit(0); | |
} | |
} | |
} | |
- (BOOL)isConnected { | |
BOOL connected = (_socketRef != NULL); | |
return connected; | |
} | |
- (void)startStream { | |
} | |
- (void)handleData:(NSMutableData *)data { | |
} | |
- (void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)eventCode { | |
} | |
- (void)applicationWillTerminate:(NSNotification *)aNotification { | |
// Insert code here to tear down your application | |
} | |
- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender { | |
if (!_managedObjectContext) { | |
return NSTerminateNow; | |
} | |
if (![[self managedObjectContext] commitEditing]) { | |
NSLog(@"%@:%@ unable to commit editing to terminate", [self class], NSStringFromSelector(_cmd)); | |
return NSTerminateCancel; | |
} | |
if (![[self managedObjectContext] hasChanges]) { | |
return NSTerminateNow; | |
} | |
NSError *error = nil; | |
if (![[self managedObjectContext] save:&error]) { | |
BOOL result = [sender presentError:error]; | |
if (result) { | |
return NSTerminateCancel; | |
} | |
NSString *question = NSLocalizedString(@"Could not save changes wile quitting. Quit anyway?", @"Quit without saves error question message"); | |
NSString *info = NSLocalizedString(@"Quitting now will lose any changes you've made since last successful save", @"Quit without saves error question info"); | |
NSString *quitButton = @"Quit anyway"; | |
NSString *cancelButton = @"Cancel"; | |
NSAlert *alert = [[NSAlert alloc] init]; | |
[alert setMessageText:question]; | |
[alert setInformativeText:info]; | |
[alert addButtonWithTitle:quitButton]; | |
[alert addButtonWithTitle:cancelButton]; | |
NSInteger answer = [alert runModal]; | |
if (answer == NSAlertAlternateReturn) { | |
return NSTerminateCancel; | |
} | |
} | |
return NSTerminateNow; | |
} | |
- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)sender { | |
return YES; | |
} | |
- (IBAction)scanButton:(id)sender { | |
if (_sockfd == kInvalidSocket) return; | |
[_pIndicate startAnimation:self]; | |
[_scanLabel setHidden:NO]; | |
uint8_t cmd_buf[3]; | |
cmd_buf[0] = 0x80; //opcode | |
cmd_buf[1] = 0x01; //length | |
cmd_buf[2] = 0x20; //cmd | |
int nwritten = WriteMessage(_sockfd, cmd_buf, 3); | |
if (nwritten == 3) printf("write scan successful!\n"); | |
} | |
- (IBAction)closeButton:(id)sender { | |
if (_sockfd == kInvalidSocket) return; | |
//tell server we are closing socket | |
uint8_t cmd_buf[3]; | |
cmd_buf[0] = 0x80; //opcode | |
cmd_buf[1] = 0x01; //length | |
cmd_buf[2] = 0x80; //cmd | |
int nwritten = WriteMessage(_sockfd, cmd_buf, 3); | |
if (nwritten == 3) printf("write close successful!\n"); | |
[self closeConnection]; | |
[self updateUI]; | |
} | |
- (IBAction)connect:(id)sender { | |
NSString *hostname = @"71.204.163.123"; | |
NSString *pass = [_passField stringValue]; | |
[self connectToHost:hostname asUser:pass]; | |
[self updateUI]; | |
if ([self isConnected]) { | |
NSLog(@"Connected!"); | |
} | |
} | |
- (void) updateUI { | |
const BOOL connected = [self isConnected]; | |
[_connectedLabel setStringValue:connected ? @"Connected" : @"Not Connected"]; | |
[_tableView reloadData]; | |
} | |
- (void)runErrorMessage:(NSString *)message withError:(int)err { | |
NSString *errorString = @""; | |
if (err != 0) { | |
errorString = ([NSString stringWithUTF8String:strerror(err)]); | |
} | |
NSAlert *alert = [[NSAlert alloc] init]; | |
[alert setMessageText:@"Error occured!"]; | |
[alert setInformativeText:errorString]; | |
[alert runModal]; | |
} | |
- (void)connectToHost:(NSString *)hostname asUser:(NSString *)pass { | |
NSString *errorMessage = nil; | |
int sysError = noErr; | |
if (_sockfd != kInvalidSocket) [self closeConnection]; | |
if (hostname.length < 1) { | |
errorMessage = @"Hostname must not be empty"; | |
goto bailout; | |
} | |
if (pass.length == 0 || pass.length > 8) { | |
errorMessage = @"Password must be between 1 and 8 characters long."; | |
goto bailout; | |
} | |
const char *hostnameCtr = [hostname UTF8String]; | |
_sockfd = SocketConnectedToHostNamed(hostnameCtr); | |
const char *passwrd = [pass UTF8String]; | |
NSUInteger passlen = strlen(passwrd); | |
int nwritten = WriteMessage(_sockfd, passwrd, passlen); | |
if (nwritten == -1) { | |
errorMessage = @"Failed to send username."; | |
sysError = errno; | |
goto bailout; | |
} | |
//Make the socket non-blocking | |
int err = fcntl(_sockfd, F_SETFL, O_NONBLOCK); | |
if (err == -1) { | |
errorMessage = @"Couldn't put socket into non-blocking mode"; | |
sysError = errno; | |
goto bailout; | |
} | |
[self startMonitoringSocket]; | |
bailout: | |
if (errorMessage != nil) { | |
[self runErrorMessage:errorMessage withError:sysError]; | |
[self closeConnection]; | |
} | |
return; | |
} | |
- (void)closeConnection { | |
[self stopMonitoringSocket]; | |
close(_sockfd); | |
_sockfd = kInvalidSocket; | |
} | |
- (void)startMonitoringSocket { | |
CFSocketContext context = {0, (__bridge void *)(self), NULL, NULL, NULL}; | |
_socketRef = CFSocketCreateWithNative(kCFAllocatorDefault, | |
_sockfd, | |
kCFSocketDataCallBack, | |
ReceiveMessage, | |
&context); | |
if (_socketRef == NULL) { | |
[self runErrorMessage:@"Unable to create CFSocketRef." withError:noErr]; | |
goto bailout; | |
} | |
CFRunLoopSourceRef rls = CFSocketCreateRunLoopSource(kCFAllocatorDefault, _socketRef, 0); | |
if (rls == NULL) { | |
[self runErrorMessage:@"Unable to create socket run loop source." withError:noErr]; | |
goto bailout; | |
} | |
CFRunLoopAddSource(CFRunLoopGetCurrent(), rls, kCFRunLoopDefaultMode); | |
CFRelease(rls); | |
bailout: | |
return; | |
} | |
- (void)stopMonitoringSocket { | |
if (_socketRef != NULL) { | |
CFSocketInvalidate(_socketRef); | |
CFRelease(_socketRef); | |
_socketRef = NULL; | |
} | |
} | |
static void ReceiveMessage (CFSocketRef socket, CFSocketCallBackType type, | |
CFDataRef address, const void *data, void *info) { | |
AppDelegate *self = (__bridge AppDelegate *)(info); | |
[self handleMessageData:(__bridge NSData *)(data)]; | |
} | |
- (void)handleMessageData:(NSData *)data { | |
// Closed connection? | |
if (data.length == 0) { | |
[self closeConnection]; | |
[self runErrorMessage:@"The server closed the connection." withError:noErr]; | |
return; | |
} | |
uint8_t byteArray[256]; | |
int credits; | |
const unsigned char* name; | |
[data getBytes:byteArray length:256]; | |
NSLog(@"byte array: %s", byteArray); | |
if (byteArray[0] == 0x82) { | |
switch (byteArray[1]) { | |
case 0xA1: | |
credits = byteArray[2] + (byteArray[3] * 256); | |
[_creditLabel setStringValue:[NSString stringWithFormat:@"%d",credits]]; | |
[self updateStudentTable]; | |
break; | |
case 0xF0: | |
[_nameLable setStringValue:@"Invalid Password"]; | |
break; | |
case 0xF1: | |
[_pIndicate stopAnimation:self]; | |
[_scanLabel setHidden:YES]; | |
default: | |
break; | |
} | |
} | |
if (byteArray[0] == 0x81) { | |
name = &byteArray[1]; // points to name string | |
[_nameLable setStringValue:[NSString stringWithUTF8String:(char *)name]]; | |
[_pIndicate stopAnimation:self]; | |
[_scanLabel setHidden:YES]; | |
} | |
} | |
static int SocketConnectedToHostNamed(const char *hostname) { | |
int sockfd = -1; | |
sa_family_t family[] = {AF_INET6, AF_INET }; | |
int family_count = sizeof(family) / sizeof(*family); | |
for (int i=0; sockfd == -1 && i < family_count; i++) { | |
printf("Looking at %s family:\n", family[i] == AF_INET6 ? "AF_INET6" : "AF_INET"); | |
// Get host address. | |
struct hostent *host = NULL; | |
host = gethostbyname2(hostname, family[i]); | |
if (host == NULL) { | |
herror("gethostbyname2"); | |
continue; | |
} | |
// Try to connect with each address. | |
struct sockaddr_storage server_addr; | |
for (int addressIndex = 0; sockfd == -1; addressIndex++) { | |
// grab the next address. Bail our if we've run out. | |
if (!GetAddressAtIndex(host, addressIndex, &server_addr)) break; | |
char buffer[INET6_ADDRSTRLEN]; | |
printf("Trying %s...\n", | |
inet_ntop(host->h_addrtype, host->h_addr_list[addressIndex], | |
buffer, sizeof(buffer))); | |
// Get a socket | |
sockfd = socket(server_addr.ss_family, SOCK_STREAM, 0); | |
if (sockfd == -1) { | |
perror(" socket"); | |
continue; | |
} | |
// connect | |
int err = connect(sockfd, (struct sockaddr *)&server_addr, server_addr.ss_len); | |
if (err == -1) { | |
perror(" connect"); | |
close(sockfd); | |
sockfd = -1; | |
} | |
//success | |
} | |
} | |
return sockfd; | |
} | |
static bool GetAddressAtIndex(struct hostent *host, int addressIndex, | |
struct sockaddr_storage *outServerAddress) { | |
if (outServerAddress == NULL || host == NULL) return false; | |
// Out of Addresses? | |
if (host->h_addr_list[addressIndex] == NULL) return false; | |
outServerAddress->ss_family = host->h_addrtype; | |
if (outServerAddress->ss_family == AF_INET6) { | |
struct sockaddr_in6 *addr = (struct sockaddr_in6 *)outServerAddress; | |
addr->sin6_len = sizeof(*addr); | |
addr->sin6_port = htons(kPortNumber); | |
addr->sin6_flowinfo = 0; | |
addr->sin6_addr = *(struct in6_addr *)host->h_addr_list[addressIndex]; | |
addr->sin6_scope_id = 0; | |
} else { | |
struct sockaddr_in *addr = (struct sockaddr_in *)outServerAddress; | |
addr->sin_len = sizeof(*addr); | |
addr->sin_port = htons(kPortNumber); | |
addr->sin_addr = *(struct in_addr *)host->h_addr_list[addressIndex]; | |
memset(&addr->sin_zero, 0, sizeof(addr->sin_zero)); | |
} | |
return true; | |
} | |
static int WriteMessage (int fd, const void*buffer, size_t length) { | |
// Message never longer than 256 bytes! | |
// Write opcode then length | |
uint8_t bytesLeft = (uint8_t)length; | |
uint8_t header[1]; | |
header[0] = bytesLeft; | |
ssize_t nwritten = write(fd, header, sizeof(header)); | |
if (nwritten <=0) goto bailout; | |
while (bytesLeft > 0) { | |
NSLog(@"%d %s",bytesLeft,buffer); | |
nwritten = write(fd, buffer, bytesLeft); | |
bytesLeft -= nwritten; | |
buffer += nwritten; //advance pointer | |
} | |
bailout: | |
if (nwritten == -1) perror("write"); | |
return (int)nwritten; | |
} | |
@end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment