Commits

Ivan Vučica committed 9b93021

Basic adversiting.

  • Participants

Comments (0)

Files changed (3)

+CFLAGS=-g3
+LDFLAGS=-framework Foundation -framework AppKit -g3
+all: advertiseairprint
+clean:
+	-rm advertiseairprint advertiseairprint.o
+A small test for creating a non-existing AirPrint printer on the network
+
+Find the service with:
+	dns-sd -Z _ipp._tcp
+
+TODO: implement minimal http/ipp server.

File advertiseairprint.m

+#include <Foundation/Foundation.h>
+#if USE_APPKIT
+#include <AppKit/AppKit.h>
+#endif
+// in theory, this could work with cups.
+// add:
+//    image/urf urf string(0,UNIRAST)
+// to
+//    /usr/share/cups/mime/apple.types
+// we still need a PDF-capable printer specified.
+
+// for more details, see:
+// see http://www.jrwz.net/technical/mDNS-AirPrint/s06.html
+
+int port = 0;
+
+// advertiseService() could probably be implemented with "dns-sd" in command-line
+void advertiseService()
+{
+	NSNetService * service = nil;
+
+	// set subtype: _universal._sub._ipp._tcp
+	service = [[NSNetService alloc] initWithDomain:@"" type:@"_ipp._tcp,_universal" name:@"" port:port];
+
+	// fill out txt dict
+	id one = @"1"; // [NSNumber numberWithInt:1]
+	id zero = @"0"; // [NSNumber numberWithInt:0]
+	id three = @"3"; // [NSNumber numberWithInt:3]
+	id hexoneohonesix = @"0x1016"; // [NSNumber numberWithInt:0x1016]
+	NSDictionary * txtDict = [NSMutableDictionary dictionary];
+	[txtDict setValue:one forKey:@"txtver"];
+	[txtDict setValue:one forKey:@"qtotal"];
+	[txtDict setValue:@"/printers/DemoPrinter" forKey:@"rp"];
+	[txtDict setValue:@"Demo Printer" forKey:@"ty"];
+	[txtDict setValue:@"http://example.com/" forKey:@"adminurl"];
+	[txtDict setValue:@"A demo AirPrint printer" forKey:@"note"];
+	[txtDict setValue:zero forKey:@"priority"];
+	[txtDict setValue:@"virtual Printer" forKey:@"product"];
+	[txtDict setValue:three forKey:@"printer-state"];
+	[txtDict setValue:hexoneohonesix forKey:@"printer-type"];
+	[txtDict setValue:@"T" forKey:@"Transparent"];
+	[txtDict setValue:@"T" forKey:@"Binary"];
+	[txtDict setValue:@"F" forKey:@"Fax"];
+	[txtDict setValue:@"T" forKey:@"Color"];
+	[txtDict setValue:@"T" forKey:@"Duplex"];
+	[txtDict setValue:@"T" forKey:@"Staple"];
+	[txtDict setValue:@"T" forKey:@"Copies"];
+	[txtDict setValue:@"F" forKey:@"Collate"];
+	[txtDict setValue:@"F" forKey:@"Punch"];
+	[txtDict setValue:@"F" forKey:@"Bind"];
+	[txtDict setValue:@"F" forKey:@"Sort"];
+	[txtDict setValue:@"F" forKey:@"Scan"];
+	[txtDict setValue:@"application/octet-stream,application/pdf,application/postscript,image/jpeg,image/png,image/urf" forKey:@"pdl"];
+	[txtDict setValue:@"W8,SRGB24,CP1,RS600" forKey:@"URF"];
+
+	// apply txt dict
+	[service publish];
+	[service setTXTRecordData:[NSNetService dataFromTXTRecordDictionary:txtDict]];
+}
+
+#import <sys/socket.h>
+#import <netinet/in.h>
+NSInputStream * inputStream;
+NSOutputStream * outputStream;
+CFSocketRef listeningSocket;
+id streamDelegate;
+@interface StreamDelegate : NSObject
+@end
+@implementation StreamDelegate
+- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode
+{
+	assert(aStream == inputStream || outputStream);
+	
+	switch (eventCode) {
+		case NSStreamEventOpenCompleted:
+		{
+			NSLog(@"Opened connection");
+			break;
+		}
+		case NSStreamEventHasBytesAvailable:
+		{
+			NSInteger	   bytesRead;
+			uint8_t		 buffer[32768];
+			
+			// Pull some data off the network.
+		 	NSLog(@"Receiving"); 
+			bytesRead = [inputStream read:buffer maxLength:sizeof(buffer)];
+			if (bytesRead == -1)
+			{
+				//[self _stopReceiveWithStatus:@"Network read error"];
+			}
+			else if (bytesRead == 0)
+			{
+				//[self _stopReceiveWithStatus:nil];
+			}
+			else
+			{
+				NSData	*data = [NSData dataWithBytes:buffer length:bytesRead];
+				
+#pragma unused(data)
+				NSLog(@"Data: %@", data);				
+				//[self _didReceiveData:data];
+			}
+			break;
+		}
+		case NSStreamEventHasSpaceAvailable:
+		{
+			// FIXME handle output stream's event
+			break;
+		}
+		case NSStreamEventErrorOccurred:
+		{
+			//[self _stopReceiveWithStatus:@"Stream open error"];
+			break;
+		}
+		case NSStreamEventEndEncountered:
+		{
+			// ignore
+			NSLog(@"Connection closed");
+			
+			[inputStream release];
+			[outputStream release];
+			inputStream = nil;
+			outputStream = nil;
+			break;
+		}
+		default:
+		{
+			assert(NO);
+			break;
+		}
+	}	
+}
+
+@end
+void acceptConnection(int fd)
+{
+	if(inputStream == NULL)
+	{
+		// attach stream
+		CFReadStreamRef readStream;
+		CFWriteStreamRef writeStream;
+				
+		CFStreamCreatePairWithSocket(NULL, fd, &readStream, &writeStream);
+		
+		inputStream = (NSInputStream *) readStream;
+		outputStream = (NSOutputStream *) writeStream;
+		//CFRelease(readStream);
+		//CFRelease(writeStream);
+
+		if(![inputStream isKindOfClass:[NSInputStream class]])
+		{
+			NSLog(@"Wrong inputstream class: %@", inputStream);
+			exit(-1);;
+		}	
+		if(![outputStream isKindOfClass:[NSOutputStream class]])
+		{
+			NSLog(@"Wrong outputstream class: %@", outputStream);
+			exit(-1);;
+		}
+		
+		[inputStream setProperty:(id)kCFBooleanTrue forKey:(NSString *)kCFStreamPropertyShouldCloseNativeSocket];
+		[outputStream setProperty:(id)kCFBooleanTrue forKey:(NSString *)kCFStreamPropertyShouldCloseNativeSocket];
+		
+		inputStream.delegate = streamDelegate;
+		outputStream.delegate = streamDelegate;
+		NSLog(@"Input %@ output %@", inputStream, outputStream);
+		[inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
+		[outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
+		
+		[inputStream open];
+		[outputStream open];
+		
+	}
+}
+
+static void serverAcceptCallback(CFSocketRef socket, CFSocketCallBackType type, CFDataRef address, const void *data, void *info) 
+{
+	//Server* s = (Server*)info;
+	//[s _acceptWithFileDescriptor:*(int*)data]; 
+
+	acceptConnection(*(int*)data);
+}
+
+void createService()
+{
+	CFSocketContext socketCtxt = {0, /*self*/nil, NULL, NULL, NULL};
+	listeningSocket = CFSocketCreate(kCFAllocatorDefault,
+					PF_INET,
+					SOCK_STREAM,
+					IPPROTO_TCP,
+					kCFSocketAcceptCallBack,
+					(CFSocketCallBack)&serverAcceptCallback,
+					&socketCtxt);
+	// for some reason, retaincount on listening socket is 2.
+	// so, release
+	CFRelease(listeningSocket);
+	int fd = CFSocketGetNative(listeningSocket);
+	int one = 1;
+	setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (void *)&one, sizeof(one));
+	struct sockaddr_in addr;
+		
+	memset(&addr, 0, sizeof(addr));
+	addr.sin_len    = sizeof(addr);
+	addr.sin_family = AF_INET;
+	addr.sin_port   = htons(port);
+	addr.sin_addr.s_addr = INADDR_ANY;
+	int err = bind(fd, (const struct sockaddr *) &addr, sizeof(addr));
+        if (err != 0)
+	{
+		NSLog(@"Binding error");
+		exit(-1);
+	}
+	err = listen(fd, 5); // 5 == backlog (how many connections can wait for accept())
+	if (err != 0)
+	{
+		NSLog(@"Listening error");
+		exit(-1);
+	}
+	socklen_t   addrLen;
+		
+        addrLen = sizeof(addr);
+        err = getsockname(fd, (struct sockaddr *) &addr, &addrLen);
+	if (err != 0) 
+	{
+		NSLog(@"Getsockname error");
+		exit(-1);
+	}
+	port = ntohs(addr.sin_port);
+	NSLog(@"Port %d", port);
+
+	CFRunLoopSourceRef  rls;
+
+	rls = CFSocketCreateRunLoopSource(NULL, listeningSocket, 0);
+	CFRunLoopAddSource(CFRunLoopGetCurrent(), rls, kCFRunLoopDefaultMode);
+	CFRelease(rls);
+}
+
+int main(int argc, char** argv)
+{
+	streamDelegate = [StreamDelegate new];
+	createService();
+	advertiseService();
+#if !USE_APPKIT
+	[[NSRunLoop currentRunLoop] run];
+#else
+	[NSApplication sharedApplication];
+	NSApplicationMain();
+#endif
+	return 0;
+}