summaryrefslogtreecommitdiffabout
path: root/pumpkin/SendXFer.m
blob: 9a1d85b5eee2acdc4aa1b5bbf73f811780cc5132 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
#import "SendXFer.h"
#import "StringsAttached.h"
#import "ConfirmRequest.h"

@implementation SendXFer

-(SendXFer*)initWithLocalFile:(NSString *)lf peerAddress:(const struct sockaddr_in *)pa remoteFile:(NSString *)rf xferType:(NSString *)xt blockSize:(uint16_t)bs andTimeout:(int)to {
    if(!(self = [super init])) return self;
    xferPrefix = @"⬆";
    retryTimeout = to;
    localFile = lf;
    memmove(&peer,pa,sizeof(peer));
    if(!(theFile = [[NSFileHandle fileHandleForReadingAtPath:localFile] retain])) {
	[pumpkin log:@"Failed to open '%@', transfer aborted.",localFile];
	return self;
    }
    
    long xb = ((xferSize=[theFile seekToEndOfFile])/blockSize)+1;
    if(xb > UINT16_MAX) {
	[pumpkin log:@"file is too big (%lld bytes) and will take %ld blocks to be sent with block size of %d bytes",xferSize,xb,blockSize];
	return self;
    }
    xferBlocks = xb;
    
    [self createSocket];
    NSMutableDictionary *o = [NSMutableDictionary dictionaryWithCapacity:4];
    [o setValue:[NSString stringWithFormat:@"%u",bs] forKey:@"blksize"];
    [o setValue:[NSString stringWithFormat:@"%llu",xferSize] forKey:@"tsize"];
    [o setValue:[NSString stringWithFormat:@"%d",(int)retryTimeout] forKey:@"timeout"];
    state = xferStateConnecting;
    [self queuePacket:[TFTPPacket packetWRQWithFile:xferFilename=rf xferType:xferType=xt andOptions:o]];
    [self appear];
    return self;
}

-(SendXFer*)initWithPeer:(struct sockaddr_in *)sin andPacket:(TFTPPacket*)p {
    if(!(self = [super initWithPeer:sin andPacket:p])) return self;
    xferPrefix = @"⬆";
    xferFilename = [p.rqFilename retain]; xferType = [p.rqType retain];
    [pumpkin log:@"'%@' of type '%@' is requested from %@",
		xferFilename, xferType, [NSString stringWithSocketAddress:&peer] ];

    [self createSocket];
    [self appear];

    if(![self makeLocalFileName:xferFilename])
	return self;

    switch([[pumpkin.theDefaults.values valueForKey:@"rrqBehavior"] intValue]) {
	case onRRQDeny: [self goOnWithVerdict:verdictDeny]; break;
	case onRRQGive: [self goOnWithVerdict:verdictAllow]; break;
	default: 
	    [ConfirmRequest confirmationWithXfer:self];
	    break;
    }
    return self;
}
-(void)goOnWithVerdict:(int)verdict {
    if(verdict!=verdictAllow) {
	[self queuePacket:[TFTPPacket packetErrorWithCode:tftpErrAccessViolation andMessage:@"Access denied"]];
	return;
    }
    if(!(theFile = [[NSFileHandle fileHandleForReadingAtPath:localFile] retain])) {
	[self queuePacket:[TFTPPacket packetErrorWithErrno:errno andFallback:@"couldn't open file"]];
	return;
    }
    xferSize = [theFile seekToEndOfFile];
    NSMutableDictionary *o = [NSMutableDictionary dictionaryWithCapacity:4];
    [[initialPacket rqOptions] enumerateKeysAndObjectsUsingBlock:^(NSString* k, NSString* v, BOOL *stop) {
	if([k isEqualToString:@"blksize"]) {
	    [o setValue:[NSString stringWithFormat:@"%u",blockSize=v.intValue] forKey:@"blksize"];
	}else if([k isEqualToString:@"tsize"]) {
	    [o setValue:[NSString stringWithFormat:@"%lld",xferSize] forKey:@"tsize"];
	}else if([k isEqualToString:@"timeout"]) {
	    [o setValue:[NSString stringWithFormat:@"%d",v.intValue] forKey:@"timeout"];
	    retryTimeout = v.intValue;
	}else
	    [pumpkin log:@"Unknown option '%@' with value '%@'. Ignoring.",k,v];
    }];
    long xb = (xferSize/blockSize)+1;
    if(xb > UINT16_MAX) {
	[self queuePacket:[TFTPPacket packetErrorWithCode:tftpErrUndefined andMessage:[NSString stringWithFormat:@"file is too big (%lld bytes) and will take %ld blocks to be sent with block size of %d bytes",xferSize,xb,blockSize]]];
	return;
    }
    xferBlocks = xb;
    state = xferStateXfer;
    if(o.count) {
	[self queuePacket:[TFTPPacket packetOACKWithOptions:o]];
    }else{
	[self xfer];
    }
}

- (void) xfer {
    NSAssert(theFile,@"no file!");
    [theFile seekToFileOffset:acked*blockSize];
    [self queuePacket:[TFTPPacket packetDataWithBlock:acked+1 andData:[theFile readDataOfLength:blockSize]]];
}

- (void) eatTFTPPacket:(TFTPPacket*)p from:(struct sockaddr_in*)sin{
    if(state==xferStateConnecting) {
	peer.sin_port = sin->sin_port;
	[self updateView];
    }else if(![self isPeer:sin]) {
	[pumpkin log:@"Packet from unexpected source (%@) recevied",[NSString stringWithSocketAddress:sin]];
	return;
    }
    switch(p.op) {
	case tftpOpACK:
	    if(state==xferStateShutdown || ( (acked=p.block)==xferBlocks && (state=xferStateShutdown) ) ) {
		CFSocketEnableCallBacks(sockie, kCFSocketWriteCallBack);
		return;
	    }
	    [self updateView];
	    [self xfer];
	    break;
	case tftpOpERROR:
	    [pumpkin log:@"Error %u:%@",p.rqCode, p.rqMessage];
	    [self updateView];
	    [self disappear];
	    return;
	case tftpOpOACK:
	    if(acked) {
		[pumpkin log:@"It's a bit too late to acknowledge options, ignoring OACK packet"];
		break;
	    }
	{
	    __block BOOL a=NO;
	    [p.rqOptions enumerateKeysAndObjectsUsingBlock:^(NSString *k,NSString *v,BOOL *s) {
		if([k isEqualToString:@"blksize"])
		    blockSize = v.intValue;
		else if([k isEqualToString:@"tsize"]) {
		}else if([k isEqualToString:@"timeout"])
		    retryTimeout = v.intValue;
		else{
		    [pumpkin log:@"Totally unknown option '%@' with value '%@' acknowledged by peer",k,v];
		    a=YES;
		}
	    }];
	    if(a) {
		[self abort];
		break;
	    }
	    state = xferStateXfer;
	    [self updateView];
	    [self xfer];
	}
	    break;
	default:
	    [pumpkin log:@"Totaly unexpected opcode %d received",p.op];
	    break;
    }
}


@end