-rw-r--r-- | pumpkin/XFer.m | 205 |
1 files changed, 205 insertions, 0 deletions
diff --git a/pumpkin/XFer.m b/pumpkin/XFer.m new file mode 100644 index 0000000..6803ade --- a/dev/null +++ b/pumpkin/XFer.m @@ -0,0 +1,205 @@ + +#import "XFer.h" +#import "TFTPPacket.h" +#import "StringsAttached.h" + +static void cbXfer(CFSocketRef sockie,CFSocketCallBackType cbt,CFDataRef cba, + const void *cbd,void *i) { + [(XFer*)i callbackWithType:cbt addr:cba data:cbd]; +} + +@implementation XFer +@synthesize initialPacket; +@synthesize xferFilename; +@synthesize localFile; +@synthesize xferPrefix; + +- (id) init { + if(!(self = [super init])) return self; + blockSize = 512; + sockie = NULL; + theFile = nil; + acked = 0; + xferSize = 0; xferBlocks = 0; + xferType = nil; xferFilename = nil; + state = xferStateNone; + pumpkin = NSApplication.sharedApplication.delegate; + queue = [[NSMutableArray alloc]initWithCapacity:4]; + localFile = nil; + retryTimeout = 3; + giveupTimeout = [[[[NSUserDefaultsController sharedUserDefaultsController] values] valueForKey:@"giveUpTimeout"] intValue]; + lastPacket = nil; retryTimer = nil; + giveupTimer = nil; + initialPacket = nil; + return self; + +} + +- (id) initWithPeer:(struct sockaddr_in *)sin andPacket:(TFTPPacket*)p { + if(!(self=[self init])) return self; + memmove(&peer,sin,sizeof(peer)); + initialPacket = [p retain]; + return self; +} + +- (struct sockaddr_in*)peer { return &peer; } + +- (BOOL) makeLocalFileName:(NSString *)xf { + NSString *fn = [xf stringByReplacingOccurrencesOfString:@"\\" withString:@"/"]; + if([fn hasPrefix:@"../"] || [fn hasSuffix:@"/.."] || [fn rangeOfString:@"/../"].location!=NSNotFound) { + [self queuePacket:[TFTPPacket packetErrorWithCode:tftpErrAccessViolation andMessage:@"bad path"]]; + return NO; + } + localFile = [[[pumpkin.theDefaults.values valueForKey:@"tftpRoot"] stringByAppendingPathComponent:fn] retain]; + return YES; +} + +- (void) retryTimeout { + [self queuePacket:lastPacket]; [lastPacket release]; lastPacket = nil; +} +- (void) giveUp { + [pumpkin log:@"Connection timeout for '%@'",xferFilename]; + [self abort]; +} +- (void) renewHope { + if(giveupTimer) { + [giveupTimer invalidate]; [giveupTimer release]; + } + giveupTimer = [[NSTimer scheduledTimerWithTimeInterval:giveupTimeout target:self selector:@selector(giveUp) userInfo:nil repeats:NO] retain]; +} + +- (void) callbackWithType:(CFSocketCallBackType)t addr:(CFDataRef)a data:(const void *)d { + if(!giveupTimer) [self renewHope]; + if(retryTimer) { + [retryTimer release]; [retryTimer invalidate]; retryTimer = nil; + } + switch (t) { + case kCFSocketWriteCallBack: + if(queue.count) { + TFTPPacket *p = queue[0]; + CFSocketError r = CFSocketSendData(sockie, (CFDataRef)[NSData dataWithBytesNoCopy:&peer length:sizeof(peer) freeWhenDone:NO], (CFDataRef)[NSData dataWithData:p.data], 0); + if(r!=kCFSocketSuccess) + [pumpkin log:@"Failed to send data, error %d",errno]; + if(!(p.op==tftpOpDATA || p.op==tftpOpERROR)) { + if(lastPacket) [lastPacket release]; + lastPacket = [p retain]; + if(retryTimer) { + [retryTimer invalidate]; [retryTimer release]; + } + retryTimer = [[NSTimer scheduledTimerWithTimeInterval:retryTimeout target:self selector:@selector(retryTimeout) userInfo:nil repeats:NO] retain]; + }else{ + [lastPacket release]; lastPacket = nil; + } + [queue removeObjectAtIndex:0]; + if([queue count] || state==xferStateShutdown) + CFSocketEnableCallBacks(sockie, kCFSocketWriteCallBack); + }else if(state==xferStateShutdown) { + [pumpkin log:@"%@ Transfer of '%@' finished.",xferPrefix,xferFilename]; + [self disappear]; + } + break; + case kCFSocketDataCallBack: + [self renewHope]; + [self eatTFTPPacket:[TFTPPacket packetWithData:(NSData*)d] from:(struct sockaddr_in*)CFDataGetBytePtr(a)]; + break; + default: + NSLog(@"unhandled %lu callback",t); + break; + } +} + +- (BOOL) createSocket { + CFSocketContext ctx; + ctx.version=0; ctx.info=self; ctx.retain=0; ctx.release=0; ctx.copyDescription=0; + sockie = CFSocketCreate(kCFAllocatorDefault, PF_INET, SOCK_DGRAM, IPPROTO_UDP, + kCFSocketReadCallBack|kCFSocketWriteCallBack|kCFSocketDataCallBack, + cbXfer, &ctx); + if(!sockie) return NO; + struct sockaddr_in a; memset(&a, 0, sizeof(a)); + a.sin_family = AF_INET; + if(CFSocketSetAddress(sockie, (CFDataRef)[NSData dataWithBytesNoCopy:&a length:sizeof(a) freeWhenDone:NO]) + !=kCFSocketSuccess) { + [pumpkin log:@"failed to set socket address"]; + return NO; + } + runloopSource = CFSocketCreateRunLoopSource(kCFAllocatorDefault, sockie, 0); + CFRunLoopAddSource(CFRunLoopGetCurrent(), runloopSource, kCFRunLoopDefaultMode); + return YES; +} + +- (void) queuePacket:(TFTPPacket*)p { + [queue addObject:p]; + CFSocketEnableCallBacks(sockie, kCFSocketWriteCallBack|kCFSocketReadCallBack); + if(p.op==tftpOpERROR) state = xferStateShutdown; +} + +- (void) goOnWithVerdict:(int)verdict { + NSAssert(false,@"unimplemented goOnWithVerdict"); +} + +- (void) eatTFTPPacket:(TFTPPacket*)p from:(struct sockaddr_in*)sin { + NSAssert(false,@"unimplemented eatTFTPPacket"); +} +-(void) abort { + [self queuePacket:[TFTPPacket packetErrorWithCode:tftpErrUndefined andMessage:@"transfer cancelled"]]; +} + +- (id) cellValueForColumn:(NSString*)ci { + if([ci isEqualToString:@"fileName"]) { + return [NSString stringWithFormat:@"%@ %@",xferPrefix,xferFilename]; + }else if([ci isEqualToString:@"xferType"]) { + return xferType; + }else if([ci isEqualToString:@"peerAddress"]) { + switch (state) { + case xferStateConnecting: return [NSString stringWithHostAddress:&peer]; + default: return [NSString stringWithSocketAddress:&peer]; + } + }else if([ci isEqualToString:@"ackBytes"]) { + return [NSString stringWithFormat:@"%u",acked*blockSize]; + }else if([ci isEqualToString:@"xferSize"]) { + return xferSize?[NSString stringWithFormat:@"%llu",xferSize]:nil; + } + return nil; +} + +- (void) updateView { + [pumpkin updateXfers]; +} +- (void) appear { + [pumpkin registerXfer:self]; +} +- (void) disappear { + if(retryTimer) { + [retryTimer invalidate]; [retryTimer release]; retryTimer = nil; + } + if(giveupTimer) { + [giveupTimer invalidate]; [giveupTimer release]; retryTimer = nil; + } + [pumpkin unregisterXfer:self]; +} + +- (BOOL) isPeer:(struct sockaddr_in*)sin { + return sin->sin_len==peer.sin_len && !memcmp(sin,&peer,sin->sin_len); +} + +-(void)dealloc { + if(runloopSource) { + CFRunLoopSourceInvalidate(runloopSource); + CFRelease(runloopSource); + } + if(sockie) { + CFSocketInvalidate(sockie); + CFRelease(sockie); + } + [queue release]; + if(theFile) [theFile release]; + if(xferFilename) [xferFilename release]; + if(xferType) [xferType release]; + if(lastPacket) [lastPacket release]; + if(initialPacket) [initialPacket release]; + if(localFile) [localFile release]; + [super dealloc]; +} + + +@end |