1//
2// Copyright (c) ZeroC, Inc. All rights reserved.
3//
4
5#import "ViewController.h"
6#import <Controller.h>
7#import <TestCommon.h>
8
9#include <ifaddrs.h>
10#include <arpa/inet.h>
11#include <net/if.h>
12#include <dlfcn.h>
13
14@interface MainHelper : NSThread
15{
16    id<ViewController> _controller;
17    void* _func;
18    NSString* _exe;
19    NSArray* _args;
20    BOOL _ready;
21    BOOL _completed;
22    int _status;
23    NSMutableString* _out;
24    NSCondition* _cond;
25}
26-(void) serverReady;
27-(void) shutdown;
28-(void) print:(NSString*)str;
29-(void) completed:(int)status;
30-(void) waitReady:(int)timeout;
31-(int) waitSuccess:(int)timeout;
32-(NSString*)getOutput;
33@end
34
35@interface ProcessI : TestCommonProcess<TestCommonProcess>
36{
37    id<ViewController> _controller;
38    MainHelper* _helper;
39}
40-(void) waitReady:(int)timeout current:(ICECurrent*)current;
41-(int) waitSuccess:(int)timeout current:(ICECurrent*)current;
42-(NSString*) terminate:(ICECurrent*)current;
43@end
44
45@interface ProcessControllerI : TestCommonProcessController<TestCommonProcessController>
46{
47    id<ViewController> _controller;
48    NSString* _ipv4;
49    NSString* _ipv6;
50}
51-(id) init:(id<ViewController>) controller ipv4:(NSString*)ipv4 ipv6:(NSString*)ipv6;
52-(id<TestCommonProcessPrx>) start:(NSString*)testsuite exe:(NSString*)exe args:(NSArray*)args current:(ICECurrent*)current;
53-(NSString*) getHost:(NSString*)protocol ipv6:(BOOL)ipv6 current:(ICECurrent*)current;
54@end
55
56@implementation MainHelper
57-(id) init:(id<ViewController>)controller func:(void*)func exe:(NSString*)exe args:(NSArray*)args
58{
59    self = [super init];
60    if(self == nil)
61    {
62        return nil;
63    }
64    _controller = ICE_RETAIN(controller);
65    _func = func;
66    _exe = ICE_RETAIN(exe);
67    _args = ICE_RETAIN(args);
68    _ready = FALSE;
69    _completed = FALSE;
70    _status = 0;
71    _out = ICE_RETAIN([NSMutableString string]);
72    _cond = [NSCondition new];
73    return self;
74}
75#if defined(__clang__) && !__has_feature(objc_arc)
76-(void) dealloc
77{
78    [_controller release];
79    [_exe release];
80    [_args release];
81    [_cond release];
82    [_out release];
83    [super dealloc];
84}
85#endif
86-(void) serverReady
87{
88    [_cond lock];
89    @try
90    {
91        _ready = YES;
92        [_cond signal];
93    }
94    @finally
95    {
96        [_cond unlock];
97    }
98}
99-(void) shutdown
100{
101    [_cond lock];
102    @try
103    {
104        if(_completed)
105        {
106            return;
107        }
108        serverStop();
109    }
110    @finally
111    {
112        [_cond unlock];
113    }
114}
115-(void) print:(NSString*)msg
116{
117    [_out appendString:msg];
118}
119-(void) main
120{
121    int (*mainEntryPoint)(int, char**) = (int (*)(int, char**))_func;
122    char** argv = malloc(sizeof(char*) * (_args.count + 1));
123    int i = 0;
124    for(NSString* arg in _args)
125    {
126        argv[i++] = (char*)[arg UTF8String];
127    }
128    argv[_args.count] = 0;
129    if([_exe isEqualToString:@"client"] || [_exe isEqualToString:@"collocated"])
130    {
131        TestCommonSetOutput(self, @selector(print:));
132    }
133    else
134    {
135        TestCommonTestInit(self, @selector(serverReady), @"", NO, NO);
136    }
137    @try
138    {
139        [self completed:mainEntryPoint((int)_args.count, argv)];
140    }
141    @catch(NSException* ex)
142    {
143        [self print:[NSString stringWithFormat:@"unexpected exception while running `%s':%@\n", argv[0], ex]];
144        [self completed:EXIT_FAILURE];
145    }
146    if([_exe isEqualToString:@"client"] || [_exe isEqualToString:@"collocated"])
147    {
148        TestCommonSetOutput(nil, nil);
149    }
150    free(argv);
151}
152-(void) completed:(int)status
153{
154    [_cond lock];
155    @try
156    {
157        _completed = YES;
158        _status = status;
159        [_cond signal];
160    }
161    @finally
162    {
163        [_cond unlock];
164    }
165}
166-(void) waitReady:(int)timeout
167{
168    [_cond lock];
169    @try
170    {
171        while(!_ready && !_completed)
172        {
173            if(![_cond waitUntilDate:[NSDate dateWithTimeIntervalSinceNow:timeout]])
174            {
175               @throw [TestCommonProcessFailedException
176                    processFailedException:@"timed out waiting for the process to be ready"];
177            }
178        }
179        if(_completed && _status == EXIT_FAILURE)
180        {
181            @throw [TestCommonProcessFailedException processFailedException:_out];
182        }
183    }
184    @finally
185    {
186        [_cond unlock];
187    }
188}
189-(int) waitSuccess:(int)timeout
190{
191    [_cond lock];
192    @try
193    {
194        while(!_completed)
195        {
196            if(timeout >= 0)
197            {
198                if(![_cond waitUntilDate:[NSDate dateWithTimeIntervalSinceNow:timeout]])
199                {
200                   @throw [TestCommonProcessFailedException
201                        processFailedException:@"timed out waiting for the process to succeed"];
202                }
203            }
204            else
205            {
206                [_cond wait];
207            }
208        }
209    }
210    @finally
211    {
212        [_cond unlock];
213    }
214    return _status;
215}
216-(NSString*) getOutput
217{
218    return _out;
219}
220@end
221
222@implementation ProcessI
223-(id) init:(id<ViewController>)controller helper:(MainHelper*)helper
224{
225    self = [super init];
226    if(self == nil)
227    {
228        return nil;
229    }
230    _controller = ICE_RETAIN(controller);
231    _helper = ICE_RETAIN(helper);
232    return self;
233}
234#if defined(__clang__) && !__has_feature(objc_arc)
235-(void) dealloc
236{
237    [_controller release];
238    [_helper release];
239    [super dealloc];
240}
241#endif
242-(void) waitReady:(int)timeout current:(ICECurrent*)current
243{
244    [_helper waitReady:timeout];
245}
246-(int) waitSuccess:(int)timeout current:(ICECurrent*)current
247{
248    return [_helper waitSuccess:timeout];
249}
250-(NSString*) terminate:(ICECurrent*)current
251{
252    [_helper shutdown];
253    [current.adapter remove:current.id_];
254    [_helper waitSuccess:-1];
255    return [_helper getOutput];
256}
257@end
258
259@implementation ProcessControllerI
260-(id) init:(id<ViewController>)controller ipv4:(NSString*)ipv4 ipv6:(NSString*)ipv6
261{
262    self = [super init];
263    if(self == nil)
264    {
265        return nil;
266    }
267    _controller = ICE_RETAIN(controller);
268    _ipv4 = ICE_RETAIN(ipv4);
269    _ipv6 = ICE_RETAIN(ipv6);
270    return self;
271}
272#if defined(__clang__) && !__has_feature(objc_arc)
273-(void) dealloc
274{
275    [_controller release];
276    [_ipv4 release];
277    [_ipv6 release];
278    [super dealloc];
279}
280#endif
281-(id<TestCommonProcessPrx>) start:(NSString*)testSuite exe:(NSString*)exe args:(NSArray*)args current:(ICECurrent*)c
282{
283    [_controller println:[NSString stringWithFormat:@"starting %@ %@... ", testSuite, exe]];
284
285    NSArray<NSString*>* components = [testSuite componentsSeparatedByString:@"/"];
286    components = [components arrayByAddingObject:exe];
287    NSMutableString* func = [NSMutableString string];
288    [func appendString:[components objectAtIndex:1]];
289    for(int i = 2; i < components.count; ++i)
290    {
291        NSString* comp = [components objectAtIndex:i];
292        [func appendString:[[comp substringToIndex:1] capitalizedString]];
293        [func appendString:[comp substringFromIndex:1]];
294    }
295
296    void* sym = dlsym(RTLD_SELF, [func UTF8String]);
297    if(!sym)
298    {
299        @throw [TestCommonProcessFailedException processFailedException:
300                    [NSString stringWithFormat:@"couldn't find %@", func]];
301    }
302    args = [@[[NSString stringWithFormat:@"%@ %@", testSuite, exe]] arrayByAddingObjectsFromArray:args];
303    MainHelper* helper = ICE_AUTORELEASE([[MainHelper alloc] init:_controller func:sym exe:exe args:args]);
304
305    //
306    // Use a 768KB thread stack size for the objects test. This is necessary when running the
307    // test on arm64 devices with a debug Ice libraries which require lots of stack space.
308    //
309    [helper setStackSize:768 * 1024];
310    [helper start];
311    id<ICEObjectPrx> prx = [c.adapter addWithUUID:ICE_AUTORELEASE([[ProcessI alloc] init:_controller helper:helper])];
312    return [TestCommonProcessPrx uncheckedCast:prx];
313}
314-(NSString*) getHost:(NSString*)protocol ipv6:(BOOL)ipv6 current:(ICECurrent*)c
315{
316    return ICE_AUTORELEASE(ICE_RETAIN(ipv6 ? _ipv6 : _ipv4));
317}
318@end
319
320@implementation ViewController
321- (void) startController
322{
323    NSString* ipv4 = [interfacesIPv4 objectAtIndex:[interfaceIPv4 selectedRowInComponent:0]];
324    NSString* ipv6 = [interfacesIPv6 objectAtIndex:[interfaceIPv6 selectedRowInComponent:0]];
325
326    ICEInitializationData* initData = [ICEInitializationData initializationData];
327    initData.properties = [ICEUtil createProperties];
328    [initData.properties setProperty:@"Ice.ThreadPool.Server.SizeMax" value:@"10"];
329    [initData.properties setProperty:@"Ice.Plugin.IceDiscovery" value:@"1"];
330    [initData.properties setProperty:@"IceDiscovery.DomainId" value:@"TestController"];
331    [initData.properties setProperty:@"ControllerAdapter.Endpoints" value:@"tcp"];
332    //[initData.properties setProperty:@"Ice.Trace.Network", @"2");
333    //[initData.properties setProperty:@"Ice.Trace.Protocol", @"2");
334    [initData.properties setProperty:@"ControllerAdapter.AdapterId" value:[ICEUtil generateUUID]];
335
336    communicator = ICE_RETAIN([ICEUtil createCommunicator:initData]);
337
338    id<ICEObjectAdapter> adapter = [communicator createObjectAdapter:@"ControllerAdapter"];
339    ICEIdentity* ident = [ICEIdentity identity];
340#if TARGET_IPHONE_SIMULATOR != 0
341    ident.category = @"iPhoneSimulator";
342#else
343    ident.category = @"iPhoneOS";
344#endif
345    ident.name = [[NSBundle mainBundle] bundleIdentifier];
346    [adapter add:[[ProcessControllerI alloc] init:self ipv4:ipv4 ipv6:ipv6] identity:ident];
347    [adapter activate];
348}
349- (void) stopController
350{
351    [communicator destroy];
352    ICE_RELEASE(communicator);
353    communicator = nil;
354}
355- (void)viewDidLoad
356{
357    [super viewDidLoad];
358    ICEregisterIceDiscovery(NO);
359
360    //
361    // Search for local network interfaces
362    //
363    interfacesIPv4 = ICE_RETAIN([NSMutableArray array]);
364    [interfacesIPv4 addObject:@"127.0.0.1"];
365    interfacesIPv6 = ICE_RETAIN([NSMutableArray array]);
366    [interfacesIPv6 addObject:@"::1"];
367    struct ifaddrs* ifap;
368    if(getifaddrs(&ifap) == 0)
369    {
370        struct ifaddrs* curr = ifap;
371        while(curr != 0)
372        {
373            if(curr->ifa_addr && curr->ifa_flags & IFF_UP && !(curr->ifa_flags & IFF_LOOPBACK))
374            {
375                if(curr->ifa_addr->sa_family == AF_INET)
376                {
377                    char buf[INET_ADDRSTRLEN];
378                    const struct sockaddr_in *addr = (const struct sockaddr_in*)curr->ifa_addr;
379                    if(inet_ntop(AF_INET, &addr->sin_addr, buf, INET_ADDRSTRLEN))
380                    {
381                        [interfacesIPv4 addObject:[NSString stringWithUTF8String:buf]];
382                    }
383                }
384                else if(curr->ifa_addr->sa_family == AF_INET6)
385                {
386                    char buf[INET6_ADDRSTRLEN];
387                    const struct sockaddr_in6 *addr6 = (const struct sockaddr_in6*)curr->ifa_addr;
388                    if(inet_ntop(AF_INET6, &addr6->sin6_addr, buf, INET6_ADDRSTRLEN))
389                    {
390                        [interfacesIPv6 addObject:[NSString stringWithUTF8String:buf]];
391                    }
392                }
393            }
394            curr = curr->ifa_next;
395        }
396        freeifaddrs(ifap);
397    }
398
399    // By default, use the loopback
400    [interfaceIPv4 selectRow:0 inComponent:0 animated:NO];
401    [interfaceIPv6 selectRow:0 inComponent:0 animated:NO];
402    [self startController];
403}
404
405- (void) dealloc
406{
407    [self stopController];
408#if defined(__clang__) && !__has_feature(objc_arc)
409    [interfacesIPv4 release];
410    [interfacesIPv6 release];
411    [super dealloc];
412#endif
413}
414
415-(void) write:(NSString*)msg
416{
417    [output insertText:msg];
418    [output layoutIfNeeded];
419    [output scrollRangeToVisible:NSMakeRange([output.text length] - 1, 1)];
420}
421
422#pragma mark ViewController
423
424-(void) print:(NSString*)msg
425{
426    [self performSelectorOnMainThread:@selector(write:) withObject:msg waitUntilDone:NO];
427}
428-(void) println:(NSString*)msg
429{
430    [self print:[msg stringByAppendingString:@"\n"]];
431}
432
433#pragma mark UIPickerViewDelegate
434
435- (NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component
436{
437    if(pickerView == interfaceIPv4)
438    {
439        return [interfacesIPv4 objectAtIndex:row];
440    }
441    else
442    {
443        return [interfacesIPv6 objectAtIndex:row];
444    }
445}
446
447- (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component
448{
449    [self stopController];
450    [self startController];
451}
452
453#pragma mark UIPickerViewDataSource
454
455- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView
456{
457    return 1;
458}
459
460- (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component
461{
462    if(pickerView == interfaceIPv4)
463    {
464        return interfacesIPv4.count;
465    }
466    else
467    {
468        return interfacesIPv6.count;
469    }
470}
471
472@end
473