1/*
2 * Copyright (c) 2012 Apple Inc. All rights reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23
24#include <stdio.h>
25#include <pthread.h>
26
27#include <CoreFoundation/CoreFoundation.h>
28#include <CFNetwork/CFNetwork.h>
29#include <SystemConfiguration/SystemConfiguration.h>
30
31static SCNetworkReachabilityRef g_reachability = NULL;
32static CFURLRef g_url = NULL;
33static CFReadStreamRef g_rstream = NULL;
34
35static char *
36string2CString(CFStringRef str)
37{
38    UInt8 *buffer;
39    CFIndex clen;
40    CFRange r = CFRangeMake(0, CFStringGetLength(str));
41
42    if (CFStringGetBytes(str, r, kCFStringEncodingASCII, 0, false, NULL, 0, &clen) > 0) {
43	buffer = (UInt8 *)CFAllocatorAllocate(kCFAllocatorDefault, (clen + 1) * sizeof(UInt8), 0);
44
45	if (buffer != NULL) {
46	    if (CFStringGetBytes(str, r, kCFStringEncodingASCII, 0, false, buffer, clen, NULL)) {
47		buffer[clen] = '\0';
48		return (char *)buffer;
49	    }
50	    CFAllocatorDeallocate(kCFAllocatorDefault, buffer);
51	}
52    }
53
54    return NULL;
55}
56
57static void
58printReachabilityFlags(const char *source, SCNetworkReachabilityFlags flags)
59{
60    printf("[%s] Reachability flags (%x):\n", source, flags);
61    if (flags & kSCNetworkReachabilityFlagsTransientConnection) {
62	printf("[%s]  transient\n", source);
63    }
64    if (flags & kSCNetworkReachabilityFlagsReachable) {
65	printf("[%s]  reachable\n", source);
66    }
67    if (flags & kSCNetworkReachabilityFlagsConnectionRequired) {
68	printf("[%s]  connection required\n", source);
69    }
70    if (flags & kSCNetworkReachabilityFlagsConnectionOnTraffic) {
71	printf("[%s]  connection on traffic\n", source);
72    }
73    if (flags & kSCNetworkReachabilityFlagsInterventionRequired) {
74	printf("[%s]  intervention required\n", source);
75    }
76    if (flags & kSCNetworkReachabilityFlagsConnectionOnDemand) {
77	printf("[%s]  connection on demand\n", source);
78    }
79    if (flags & kSCNetworkReachabilityFlagsIsLocalAddress) {
80	printf("[%s]  local address\n", source);
81    }
82    if (flags & kSCNetworkReachabilityFlagsIsDirect) {
83	printf("[%s]  direct\n", source);
84    }
85#if TARGET_OS_EMBEDDED
86    if (flags & kSCNetworkReachabilityFlagsIsWWAN) {
87	printf("[%s]  wwan\n", source);
88    }
89#endif
90}
91
92static void
93handleReachabilityUpdate(
94	SCNetworkReachabilityRef target,
95	SCNetworkReachabilityFlags flags,
96	void *info)
97{
98    printReachabilityFlags("RunLoop", flags);
99}
100
101static SCNetworkReachabilityRef
102createReachabilityWithCFHost(CFHostRef theHost)
103{
104    SCNetworkReachabilityRef reachRef = NULL;
105    Boolean resolved = FALSE;
106    CFArrayRef addrs = CFHostGetAddressing(theHost, &resolved);
107
108    if (resolved && addrs != NULL && CFArrayGetCount(addrs) > 0) {
109	CFDataRef addr = (CFDataRef)CFArrayGetValueAtIndex(addrs, 0);
110
111	reachRef = SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, (struct sockaddr *)CFDataGetBytePtr(addr));
112    } else {
113	CFArrayRef names = CFHostGetNames(theHost, NULL);
114
115	if (names != NULL && CFArrayGetCount(names) > 0) {
116	    CFStringRef host = (CFStringRef)CFArrayGetValueAtIndex(names, 0);
117	    char *chost = string2CString(host);
118
119	    reachRef = SCNetworkReachabilityCreateWithName(kCFAllocatorDefault, chost);
120
121	    CFAllocatorDeallocate(kCFAllocatorDefault, chost);
122	}
123    }
124
125    if (reachRef != NULL) {
126	SCNetworkReachabilityContext reach_ctx = { 0, NULL, NULL, NULL, NULL };
127	SCNetworkReachabilitySetCallback(reachRef, handleReachabilityUpdate,
128					 &reach_ctx);
129	CFShow(reachRef);
130    } else {
131	fprintf(stderr, "Failed to create a reachability object\n");
132    }
133
134    return reachRef;
135}
136
137static void
138handleDownload(CFReadStreamRef rstream, CFStreamEventType eventType, void *info)
139{
140    Boolean done = FALSE;
141
142    if (eventType == kCFStreamEventHasBytesAvailable) {
143	UInt8 buffer[1024];
144
145	while (CFReadStreamHasBytesAvailable(rstream)) {
146	    CFIndex count = CFReadStreamRead(rstream, buffer, sizeof(buffer));
147	    if (count == 0) {
148		done = TRUE;
149	    }
150	}
151    } else if (eventType == kCFStreamEventEndEncountered) {
152	printf("Download completed\n");
153	done = TRUE;
154    } else if (eventType == kCFStreamEventErrorOccurred) {
155	printf("Download error\n");
156	done = TRUE;
157    } else {
158	printf("Got stream event: %lu\n", eventType);
159    }
160
161    if (!done) {
162	return;
163    }
164
165    CFReadStreamUnscheduleFromRunLoop(rstream, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
166    CFReadStreamClose(rstream);
167    CFRelease(rstream);
168
169    g_rstream = NULL;
170}
171
172static void
173startDownload(void)
174{
175    CFHTTPMessageRef request = CFHTTPMessageCreateRequest(kCFAllocatorDefault, CFSTR("GET"), g_url, kCFHTTPVersion1_1);
176    CFReadStreamRef rstream = CFReadStreamCreateForHTTPRequest(kCFAllocatorDefault, request);
177    CFStreamClientContext ctx = { 0, NULL, NULL, NULL, NULL };
178
179    printf("Starting download\n");
180
181    CFReadStreamSetClient(rstream,
182	    		  kCFStreamEventEndEncountered | kCFStreamEventErrorOccurred | kCFStreamEventHasBytesAvailable,
183			  handleDownload,
184			  &ctx);
185
186    CFReadStreamScheduleWithRunLoop(rstream, CFRunLoopGetMain(), kCFRunLoopDefaultMode);
187
188    CFReadStreamOpen(rstream);
189
190    g_rstream = rstream;
191
192    CFRelease(request);
193}
194
195static void
196downloadTimerFired(CFRunLoopTimerRef timer, void *info)
197{
198    if (g_rstream != NULL) {
199	handleDownload(g_rstream, kCFStreamEventErrorOccurred, NULL);
200    }
201
202
203    SCNetworkReachabilityUnscheduleFromRunLoop(g_reachability, CFRunLoopGetMain(), kCFRunLoopDefaultMode);
204
205    startDownload();
206
207    SCNetworkReachabilityScheduleWithRunLoop(g_reachability, CFRunLoopGetMain(), kCFRunLoopDefaultMode);
208}
209
210static void
211startDownloadLoop(void)
212{
213    CFRunLoopTimerRef timer;
214    CFRunLoopTimerContext ctx = { 0, NULL, NULL, NULL, NULL };
215    CFAbsoluteTime now = CFAbsoluteTimeGetCurrent();
216
217    timer = CFRunLoopTimerCreate(kCFAllocatorDefault,
218	    			 now + 0.1,
219				 7.0,
220				 0,
221				 0,
222				 downloadTimerFired,
223				 &ctx);
224
225    CFRunLoopAddTimer(CFRunLoopGetMain(), timer, kCFRunLoopCommonModes);
226}
227
228static void *
229reachabilityLoop(void *arg)
230{
231    while (1) {
232	SCNetworkReachabilityFlags flags;
233	SCNetworkReachabilityGetFlags(g_reachability, &flags);
234
235	printReachabilityFlags("thread", flags);
236
237	sleep(1);
238    }
239
240    return NULL;
241}
242
243static void
244startReachabilityThread(void)
245{
246    pthread_attr_t tattr;
247    pthread_t th;
248
249    pthread_attr_init(&tattr);
250    pthread_create(&th, &tattr, reachabilityLoop, NULL);
251    pthread_attr_destroy(&tattr);
252}
253
254static void
255addressResolutionCallback(CFHostRef theHost, CFHostInfoType typeInfo, const CFStreamError *error, void *info)
256{
257    g_reachability = createReachabilityWithCFHost(theHost);
258
259    if (g_reachability != NULL) {
260	startDownloadLoop();
261	startReachabilityThread();
262    }
263
264    CFRelease(theHost);
265}
266
267static void
268startAddressResolution(Boolean resolve)
269{
270    CFStringRef hostStr = CFURLCopyHostName(g_url);
271    CFHostRef cfhost = CFHostCreateWithName(kCFAllocatorDefault, hostStr);
272    CFHostClientContext ctx = { 0, NULL, NULL, NULL, NULL };
273    CFStreamError err;
274
275    if (resolve) {
276	CFHostSetClient(cfhost, addressResolutionCallback, &ctx);
277	CFHostScheduleWithRunLoop(cfhost, CFRunLoopGetMain(), kCFRunLoopDefaultMode);
278	CFHostStartInfoResolution(cfhost, kCFHostAddresses, &err);
279    } else {
280	addressResolutionCallback(cfhost, kCFHostNames, NULL, NULL);
281    }
282
283    CFRelease(hostStr);
284}
285
286int
287main(int argc, char *argv[])
288{
289    CFStringRef urlStr;
290    Boolean resolve = TRUE;
291
292    if (argc < 2) {
293	fprintf(stderr, "usage: %s <url> [-byname]", argv[0]);
294	return 1;
295    }
296
297    urlStr = CFStringCreateWithCString(kCFAllocatorDefault, argv[1], kCFStringEncodingASCII);
298    g_url = CFURLCreateWithString(kCFAllocatorDefault, urlStr, NULL);
299
300    CFRelease(urlStr);
301
302    if (argc > 2 && !strcmp(argv[2], "-byname")) {
303	resolve = FALSE;
304    }
305
306    startAddressResolution(resolve);
307
308    CFRunLoopRun();
309
310    CFRelease(g_url);
311
312    SCNetworkReachabilityUnscheduleFromRunLoop(g_reachability, CFRunLoopGetMain(), kCFRunLoopDefaultMode);
313    CFRelease(g_reachability);
314
315    return 0;
316}
317