1//
2// TORCircuit.m
3// Tor
4//
5// Created by Benjamin Erhart on 11.12.19.
6//
7
8#import "TORCircuit.h"
9#import "NSCharacterSet+PredefinedSets.h"
10
11@implementation TORCircuit
12
13
14// MARK: Class Properties
15
16static NSRegularExpression *_mainInfoRegex;
17static NSMutableDictionary<NSString *, NSRegularExpression *> *_optionsRegexes;
18static NSDateFormatter *_timestampFormatter;
19
20+ (NSRegularExpression *)mainInfoRegex
21{
22 if (!_mainInfoRegex)
23 {
24 _mainInfoRegex =
25 [NSRegularExpression
26 regularExpressionWithPattern:
27 @"(\\w+)\\s+(LAUNCHED|BUILT|GUARD_WAIT|EXTENDED|FAILED|CLOSED)\\s+((?:\\$[\\da-f]+[=~]\\w+(?:,|\\s|\\Z))+)?"
28 options:NSRegularExpressionCaseInsensitive error:nil];
29 }
30
31 return _mainInfoRegex;
32}
33
34+ (NSString *)statusLaunched
35{
36 return @"LAUNCHED";
37}
38
39+ (NSString *)statusBuilt
40{
41 return @"BUILT";
42}
43
44+ (NSString *)statusGuardWait
45{
46 return @"GUARD_WAIT";
47}
48
49+ (NSString *)statusExtended
50{
51 return @"EXTENDED";
52}
53
54+ (NSString *)statusFailed
55{
56 return @"FAILED";
57}
58
59+ (NSString *)statusClosed
60{
61 return @"CLOSED";
62}
63
64+ (NSString *)buildFlagOneHopTunnel
65{
66 return @"ONEHOP_TUNNEL";
67}
68
69+ (NSString *)buildFlagIsInternal
70{
71 return @"IS_INTERNAL";
72}
73
74+ (NSString *)buildFlagNeedCapacity
75{
76 return @"NEED_CAPACITY";
77}
78
79+ (NSString *)buildFlagNeedUptime
80{
81 return @"NEED_UPTIME";
82}
83
84+ (NSString *)purposeGeneral
85{
86 return @"GENERAL";
87}
88
89+ (NSString *)purposeHsClientIntro
90{
91 return @"HS_CLIENT_INTRO";
92}
93
94+ (NSString *)purposeHsClientRend
95{
96 return @"HS_CLIENT_REND";
97}
98
99+ (NSString *)purposeHsServiceIntro
100{
101 return @"HS_SERVICE_INTRO";
102}
103
104+ (NSString *)purposeHsServiceRend
105{
106 return @"HS_SERVICE_REND";
107}
108
109+ (NSString *)purposeHsVanguards
110{
111 return @"HS_VANGUARDS";
112}
113
114+ (NSString *)purposeTesting
115{
116 return @"TESTING";
117}
118
119+ (NSString *)purposeController
120{
121 return @"CONTROLLER";
122}
123
124+ (NSString *)purposeMeasureTimeout
125{
126 return @"MEASURE_TIMEOUT";
127}
128
129+ (NSString *)purposeConfluxLinked
130{
131 return @"CONFLUX_LINKED";
132}
133
134+ (NSString *)purposeConfluxUnlinked
135{
136 return @"CONFLUX_UNLINKED";
137}
138
139+ (NSString *)hsStateHsciConnecting
140{
141 return @"HSCI_CONNECTING";
142}
143
144+ (NSString *)hsStateHsciIntroSent
145{
146 return @"HSCI_INTRO_SENT";
147}
148
149+ (NSString *)hsStateHsciDone
150{
151 return @"HSCI_DONE";
152}
153
154+ (NSString *)hsStateHscrConnecting
155{
156 return @"HSCR_CONNECTING";
157}
158
159+ (NSString *)hsStateHscrEstablishedIdle
160{
161 return @"HSCR_ESTABLISHED_IDLE";
162}
163
164+ (NSString *)hsStateHscrEstablishedWaiting
165{
166 return @"HSCR_ESTABLISHED_WAITING";
167}
168
169+ (NSString *)hsStateHscrJoined
170{
171 return @"HSCR_JOINED";
172}
173
174+ (NSString *)hsStateHssiConnecting
175{
176 return @"HSSI_CONNECTING";
177}
178
179+ (NSString *)hsStateHssiEstablished
180{
181 return @"HSSI_ESTABLISHED";
182}
183
184+ (NSString *)hsStateHssrConnecting
185{
186 return @"HSSR_CONNECTING";
187}
188
189+ (NSString *)hsStateHssrJoined
190{
191 return @"HSSR_JOINED";
192}
193
194+ (NSString *)reasonNone
195{
196 return @"NONE";
197}
198
199+ (NSString *)reasonTorProtocol
200{
201 return @"TORPROTOCOL";
202}
203
204+ (NSString *)reasonInternal
205{
206 return @"INTERNAL";
207}
208
209+ (NSString *)reasonRequested
210{
211 return @"REQUESTED";
212}
213
214+ (NSString *)reasonHibernating
215{
216 return @"HIBERNATING";
217}
218
219+ (NSString *)reasonResourceLimit
220{
221 return @"RESOURCELIMIT";
222}
223
224+ (NSString *)reasonConnectFailed
225{
226 return @"CONNECTFAILED";
227}
228
229+ (NSString *)reasonOrIdentity
230{
231 return @"OR_IDENTITY";
232}
233
234+ (NSString *)reasonOrConnClosed
235{
236 return @"OR_CONN_CLOSED";
237}
238
239+ (NSString *)reasonTimeout
240{
241 return @"TIMEOUT";
242}
243
244+ (NSString *)reasonFinished
245{
246 return @"FINISHED";
247}
248
249+ (NSString *)reasonDestroyed
250{
251 return @"DESTROYED";
252}
253
254+ (NSString *)reasonNoPath
255{
256 return @"NOPATH";
257}
258
259+ (NSString *)reasonNoSuchService
260{
261 return @"NOSUCHSERVICE";
262}
263
264+ (NSString *)reasonMeasurementExpired
265{
266 return @"MEASUREMENT_EXPIRED";
267}
268
269
270// MARK: Class Methods
271
272+ (NSRegularExpression *)regexForOption:(NSString *)option
273{
274 if (!_optionsRegexes)
275 {
276 _optionsRegexes = [NSMutableDictionary new];
277 }
278
279 if (!_optionsRegexes[option])
280 {
281 _optionsRegexes[option] =
282 [NSRegularExpression
283 regularExpressionWithPattern:[NSString stringWithFormat:@"(?:%@=(.+?)(?:\\s|\\Z))", option]
284 options:NSRegularExpressionCaseInsensitive error:nil];
285 }
286
287 return _optionsRegexes[option];
288}
289
290+ (NSDateFormatter *)timestampFormatter
291{
292 if (!_timestampFormatter)
293 {
294 _timestampFormatter = [NSDateFormatter new];
295 _timestampFormatter.dateFormat = @"yyyy-MM-dd'T'HH:mm:ss.SSSSSS";
296 _timestampFormatter.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];
297 _timestampFormatter.timeZone = [NSTimeZone timeZoneWithName:@"UTC"];
298 }
299
300 return _timestampFormatter;
301}
302
303+ (NSArray<TORCircuit *> *)circuitsFromString:(NSString *)circuitsString
304{
305 NSMutableArray<TORCircuit *> *circuits = [NSMutableArray new];
306
307 for (NSString *circuitString in [circuitsString componentsSeparatedByString:@"\r\n"]) {
308 if (circuitString.length > 0)
309 {
310 [circuits addObject:
311 [[TORCircuit alloc] initFromString:circuitString]];
312 }
313 }
314
315 return circuits;
316}
317
318
319// MARK: Initializers
320
321- (instancetype)initFromString:(NSString *)circuitString
322{
323 self = [super init];
324
325 if (self)
326 {
327 _raw = circuitString;
328
329 NSRange range = NSMakeRange(0, circuitString.length);
330
331 NSTextCheckingResult *match = [TORCircuit.mainInfoRegex
332 matchesInString:circuitString options:0
333 range:range].firstObject;
334
335 if (match && [match rangeAtIndex:1].location != NSNotFound)
336 {
337 _circuitId = [circuitString substringWithRange:[match rangeAtIndex:1]];
338 }
339
340 if (match && [match rangeAtIndex:2].location != NSNotFound)
341 {
342 _status = [circuitString substringWithRange:[match rangeAtIndex:2]];
343 }
344
345 if (match && [match rangeAtIndex:3].location != NSNotFound)
346 {
347 NSMutableArray<TORNode *> *nodes = [NSMutableArray new];
348
349 NSString *path = [circuitString substringWithRange:[match rangeAtIndex:3]];
350
351 NSArray<NSString *> *nodesStrings = [path componentsSeparatedByString:@","];
352
353 for (NSString *nodeString in nodesStrings)
354 {
355 [nodes addObject:
356 [[TORNode alloc] initFromString:
357 [nodeString stringByTrimmingCharactersInSet:NSCharacterSet.whitespaceCharacterSet]]];
358 }
359
360 _nodes = nodes;
361 }
362
363 match = [[TORCircuit regexForOption:@"BUILD_FLAGS"]
364 matchesInString:circuitString options:0 range:range].firstObject;
365
366 if (match && [match rangeAtIndex:1].location != NSNotFound)
367 {
368 _buildFlags = [[circuitString substringWithRange:[match rangeAtIndex:1]]
369 componentsSeparatedByString:@","];
370 }
371
372 match = [[TORCircuit regexForOption:@"PURPOSE"]
373 matchesInString:circuitString options:0 range:range].firstObject;
374
375 if (match && [match rangeAtIndex:1].location != NSNotFound)
376 {
377 _purpose = [circuitString substringWithRange:[match rangeAtIndex:1]];
378 }
379
380 match = [[TORCircuit regexForOption:@"HS_STATE"]
381 matchesInString:circuitString options:0 range:range].firstObject;
382
383 if (match && [match rangeAtIndex:1].location != NSNotFound)
384 {
385 _hsState = [circuitString substringWithRange:[match rangeAtIndex:1]];
386 }
387
388 match = [[TORCircuit regexForOption:@"REND_QUERY"]
389 matchesInString:circuitString options:0 range:range].firstObject;
390
391 if (match && [match rangeAtIndex:1].location != NSNotFound)
392 {
393 _rendQuery = [circuitString substringWithRange:[match rangeAtIndex:1]];
394 }
395
396 match = [[TORCircuit regexForOption:@"TIME_CREATED"]
397 matchesInString:circuitString options:0 range:range].firstObject;
398
399 if (match && [match rangeAtIndex:1].location != NSNotFound)
400 {
401 _timeCreated = [TORCircuit.timestampFormatter dateFromString:
402 [circuitString substringWithRange:[match rangeAtIndex:1]]];
403 }
404
405 match = [[TORCircuit regexForOption:@"REASON"]
406 matchesInString:circuitString options:0 range:range].firstObject;
407
408 if (match && [match rangeAtIndex:1].location != NSNotFound)
409 {
410 _reason = [circuitString substringWithRange:[match rangeAtIndex:1]];
411 }
412
413 match = [[TORCircuit regexForOption:@"REMOTE_REASON"]
414 matchesInString:circuitString options:0 range:range].firstObject;
415
416 if (match && [match rangeAtIndex:1].location != NSNotFound)
417 {
418 _remoteReason = [circuitString substringWithRange:[match rangeAtIndex:1]];
419 }
420
421 match = [[TORCircuit regexForOption:@"SOCKS_USERNAME"]
422 matchesInString:circuitString options:0 range:range].firstObject;
423
424 if (match && [match rangeAtIndex:1].location != NSNotFound)
425 {
426 _socksUsername = [[circuitString substringWithRange:[match rangeAtIndex:1]]
427 stringByTrimmingCharactersInSet:NSCharacterSet.doubleQuote];
428 }
429
430 match = [[TORCircuit regexForOption:@"SOCKS_PASSWORD"]
431 matchesInString:circuitString options:0 range:range].firstObject;
432
433 if (match && [match rangeAtIndex:1].location != NSNotFound)
434 {
435 _socksPassword = [[circuitString substringWithRange:[match rangeAtIndex:1]]
436 stringByTrimmingCharactersInSet:NSCharacterSet.doubleQuote];
437 }
438 }
439
440 return self;
441}
442
443
444// MARK: NSSecureCoding
445
446+ (BOOL)supportsSecureCoding
447{
448 return YES;
449}
450
451- (id)initWithCoder:(NSCoder *)coder
452{
453 if ((self = [super init]))
454 {
455 _raw = [coder decodeObjectOfClass:NSString.class forKey:@"raw"];
456 _circuitId = [coder decodeObjectOfClass:NSString.class forKey:@"circuitId"];
457 _status = [coder decodeObjectOfClass:NSString.class forKey:@"status"];
458 _nodes = [coder decodeObjectOfClasses:[NSSet setWithArray:@[NSArray.class, TORNode.class]] forKey:@"nodes"];
459 _buildFlags = [coder decodeObjectOfClasses:[NSSet setWithArray:@[NSArray.class, NSString.class]] forKey:@"buildFlags"];
460 _purpose = [coder decodeObjectOfClass:NSString.class forKey:@"purpose"];
461 _hsState = [coder decodeObjectOfClass:NSString.class forKey:@"hsState"];
462 _rendQuery = [coder decodeObjectOfClass:NSString.class forKey:@"rendQuery"];
463 _timeCreated = [coder decodeObjectOfClass:NSDate.class forKey:@"timeCreated"];
464 _reason = [coder decodeObjectOfClass:NSString.class forKey:@"reason"];
465 _remoteReason = [coder decodeObjectOfClass:NSString.class forKey:@"remoteReason"];
466 _socksUsername = [coder decodeObjectOfClass:NSString.class forKey:@"socksUsername"];
467 _socksPassword = [coder decodeObjectOfClass:NSString.class forKey:@"socksPassword"];
468 }
469
470 return self;
471}
472
473- (void)encodeWithCoder:(NSCoder *)coder
474{
475 [coder encodeObject:self.raw forKey:@"raw"];
476 [coder encodeObject:self.circuitId forKey:@"circuitId"];
477 [coder encodeObject:self.status forKey:@"status"];
478 [coder encodeObject:self.nodes forKey:@"nodes"];
479 [coder encodeObject:self.buildFlags forKey:@"buildFlags"];
480 [coder encodeObject:self.purpose forKey:@"purpose"];
481 [coder encodeObject:self.hsState forKey:@"hsState"];
482 [coder encodeObject:self.rendQuery forKey:@"rendQuery"];
483 [coder encodeObject:self.timeCreated forKey:@"timeCreated"];
484 [coder encodeObject:self.reason forKey:@"reason"];
485 [coder encodeObject:self.remoteReason forKey:@"remoteReason"];
486 [coder encodeObject:self.socksUsername forKey:@"socksUsername"];
487 [coder encodeObject:self.socksPassword forKey:@"socksPassword"];
488}
489
490
491// MARK: NSObject
492
493- (NSString *)description
494{
495 return [NSString stringWithFormat:@"<%@: %p> circuitId=%@, status=%@, nodes=%@, buildFlags=%@, purpose=%@, hsState=%@, rendQuery=%@, timeCreated=%@, reason=%@, remoteReason=%@, socksUsername=%@, socksPassword=%@, raw=%@]",
496 self.class, self, self.circuitId, self.status, self.nodes, self.buildFlags,
497 self.purpose, self.hsState, self.rendQuery, self.timeCreated,
498 self.reason, self.remoteReason, self.socksUsername, self.socksPassword, self.raw];
499}
500
501
502@end