1 /* -*- Mode: Java; tab-width: 4 -*- 2 * 3 * Copyright (c) 2004 Apple Computer, Inc. All rights reserved. 4 * 5 * Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple Computer, Inc. 6 * ("Apple") in consideration of your agreement to the following terms, and your 7 * use, installation, modification or redistribution of this Apple software 8 * constitutes acceptance of these terms. If you do not agree with these terms, 9 * please do not use, install, modify or redistribute this Apple software. 10 * 11 * In consideration of your agreement to abide by the following terms, and subject 12 * to these terms, Apple grants you a personal, non-exclusive license, under Apple's 13 * copyrights in this original Apple software (the "Apple Software"), to use, 14 * reproduce, modify and redistribute the Apple Software, with or without 15 * modifications, in source and/or binary forms; provided that if you redistribute 16 * the Apple Software in its entirety and without modifications, you must retain 17 * this notice and the following text and disclaimers in all such redistributions of 18 * the Apple Software. Neither the name, trademarks, service marks or logos of 19 * Apple Computer, Inc. may be used to endorse or promote products derived from the 20 * Apple Software without specific prior written permission from Apple. Except as 21 * expressly stated in this notice, no other rights or licenses, express or implied, 22 * are granted by Apple herein, including but not limited to any patent rights that 23 * may be infringed by your derivative works or by other works in which the Apple 24 * Software may be incorporated. 25 * 26 * The Apple Software is provided by Apple on an "AS IS" basis. APPLE MAKES NO 27 * WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED 28 * WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR 29 * PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN 30 * COMBINATION WITH YOUR PRODUCTS. 31 * 32 * IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR 33 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE 34 * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 35 * ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION 36 * OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY OF CONTRACT, TORT 37 * (INCLUDING NEGLIGENCE), STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN 38 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 39 40 BrowserApp demonstrates how to use DNS-SD to browse for and resolve services. 41 42 To do: 43 - display resolved TXTRecord 44 */ 45 46 47 import java.awt.*; 48 import java.awt.event.*; 49 import java.util.*; 50 import java.text.*; 51 import javax.swing.*; 52 import javax.swing.event.*; 53 54 import com.apple.dnssd.*; 55 56 57 class BrowserApp implements ListSelectionListener, ResolveListener, Runnable 58 { 59 static BrowserApp app; 60 JFrame frame; 61 DomainListModel domainList; 62 BrowserListModel servicesList, serviceList; 63 JList domainPane, servicesPane, servicePane; 64 DNSSDService servicesBrowser, serviceBrowser, domainBrowser; 65 JLabel hostLabel, portLabel; 66 String hostNameForUpdate; 67 int portForUpdate; 68 BrowserApp()69 public BrowserApp() 70 { 71 frame = new JFrame("DNS-SD Service Browser"); 72 frame.addWindowListener(new WindowAdapter() { 73 public void windowClosing(WindowEvent e) {System.exit(0);} 74 }); 75 76 domainList = new DomainListModel(); 77 servicesList = new ServicesBrowserListModel(); 78 serviceList = new BrowserListModel(); 79 80 try { 81 domainBrowser = DNSSD.enumerateDomains( DNSSD.BROWSE_DOMAINS, 0, domainList); 82 83 servicesBrowser = DNSSD.browse( 0, 0, "_services._dns-sd._udp.", "", servicesList); 84 serviceBrowser = null; 85 } 86 catch ( Exception ex) { terminateWithException( ex); } 87 88 this.setupSubPanes( frame.getContentPane()); 89 frame.pack(); 90 frame.setVisible(true); 91 } 92 setupSubPanes( Container parent)93 protected void setupSubPanes( Container parent) 94 { 95 parent.setLayout( new BoxLayout( parent, BoxLayout.Y_AXIS)); 96 97 JPanel browserRow = new JPanel(); 98 browserRow.setLayout( new BoxLayout( browserRow, BoxLayout.X_AXIS)); 99 domainPane = new JList( domainList); 100 domainPane.addListSelectionListener( this); 101 JScrollPane domainScroller = new JScrollPane( domainPane, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS); 102 browserRow.add( domainScroller); 103 servicesPane = new JList( servicesList); 104 servicesPane.addListSelectionListener( this); 105 JScrollPane servicesScroller = new JScrollPane( servicesPane, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS); 106 browserRow.add( servicesScroller); 107 servicePane = new JList( serviceList); 108 servicePane.addListSelectionListener( this); 109 JScrollPane serviceScroller = new JScrollPane( servicePane, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS); 110 browserRow.add( serviceScroller); 111 112 /* 113 JPanel buttonRow = new JPanel(); 114 buttonRow.setLayout( new BoxLayout( buttonRow, BoxLayout.X_AXIS)); 115 buttonRow.add( Box.createHorizontalGlue()); 116 JButton connectButton = new JButton( "Don't Connect"); 117 buttonRow.add( connectButton); 118 buttonRow.add( Box.createRigidArea( new Dimension( 16, 0))); 119 */ 120 121 JPanel labelRow = new JPanel(); 122 labelRow.setLayout( new BoxLayout( labelRow, BoxLayout.X_AXIS)); 123 labelRow.add( new JLabel( " Host: ")); 124 hostLabel = new JLabel(); 125 labelRow.add( hostLabel); 126 labelRow.add( Box.createRigidArea( new Dimension( 32, 0))); 127 labelRow.add( new JLabel( "Port: ")); 128 portLabel = new JLabel(); 129 labelRow.add( portLabel); 130 labelRow.add( Box.createHorizontalGlue()); 131 132 parent.add( browserRow); 133 parent.add( Box.createRigidArea( new Dimension( 0, 8))); 134 parent.add( labelRow); 135 // parent.add( buttonRow); 136 parent.add( Box.createRigidArea( new Dimension( 0, 16))); 137 } 138 valueChanged( ListSelectionEvent e)139 public void valueChanged( ListSelectionEvent e) 140 { 141 try { 142 if ( e.getSource() == domainPane && !e.getValueIsAdjusting()) 143 { 144 int newSel = domainPane.getSelectedIndex(); 145 if ( -1 != newSel) 146 { 147 if ( serviceBrowser != null) 148 serviceBrowser.stop(); 149 serviceList.removeAllElements(); 150 servicesBrowser = DNSSD.browse( 0, 0, "_services._dns-sd._udp.", "", servicesList); 151 } 152 } 153 else if ( e.getSource() == servicesPane && !e.getValueIsAdjusting()) 154 { 155 int newSel = servicesPane.getSelectedIndex(); 156 if ( serviceBrowser != null) 157 serviceBrowser.stop(); 158 serviceList.removeAllElements(); 159 if ( -1 != newSel) 160 serviceBrowser = DNSSD.browse( 0, 0, servicesList.getNthRegType( newSel), "", serviceList); 161 } 162 else if ( e.getSource() == servicePane && !e.getValueIsAdjusting()) 163 { 164 int newSel = servicePane.getSelectedIndex(); 165 166 hostLabel.setText( ""); 167 portLabel.setText( ""); 168 169 if ( -1 != newSel) 170 { 171 DNSSD.resolve( 0, serviceList.getNthInterface( newSel), 172 serviceList.getNthServiceName( newSel), 173 serviceList.getNthRegType( newSel), 174 serviceList.getNthDomain( newSel), 175 this); 176 } 177 } 178 } 179 catch ( Exception ex) { terminateWithException( ex); } 180 } 181 run()182 public void run() 183 { 184 hostLabel.setText( hostNameForUpdate); 185 portLabel.setText( String.valueOf( portForUpdate)); 186 } 187 serviceResolved( DNSSDService resolver, int flags, int ifIndex, String fullName, String hostName, int port, TXTRecord txtRecord)188 public void serviceResolved( DNSSDService resolver, int flags, int ifIndex, String fullName, 189 String hostName, int port, TXTRecord txtRecord) 190 { 191 // We want to update GUI on the AWT event dispatching thread, but we can't stop 192 // the resolve from that thread, since stop() is synchronized with this callback. 193 // So, we stop the resolve on this thread, then invokeAndWait on the AWT event thread. 194 195 resolver.stop(); 196 197 hostNameForUpdate = hostName; 198 portForUpdate = port; 199 200 try { 201 SwingUtilities.invokeAndWait(this); 202 } 203 catch ( Exception e) 204 { 205 e.printStackTrace(); 206 } 207 } 208 operationFailed( DNSSDService service, int errorCode)209 public void operationFailed( DNSSDService service, int errorCode) 210 { 211 service.stop(); 212 // handle failure here 213 } 214 terminateWithException( Exception e)215 protected static void terminateWithException( Exception e) 216 { 217 e.printStackTrace(); 218 System.exit( -1); 219 } 220 main(String s[])221 public static void main(String s[]) 222 { 223 app = new BrowserApp(); 224 } 225 } 226 227 228 class BrowserListModel extends DefaultListModel implements BrowseListener, Runnable 229 { BrowserListModel()230 public BrowserListModel() 231 { 232 addCache = new Vector(); 233 removeCache = new Vector(); 234 } 235 236 /* The Browser invokes this callback when a service is discovered. */ serviceFound( DNSSDService browser, int flags, int ifIndex, String serviceName, String regType, String domain)237 public void serviceFound( DNSSDService browser, int flags, int ifIndex, 238 String serviceName, String regType, String domain) 239 { 240 addCache.add( new BrowserListElem( serviceName, domain, regType, ifIndex)); 241 if ( ( flags & DNSSD.MORE_COMING) == 0) 242 this.scheduleOnEventThread(); 243 } 244 serviceLost( DNSSDService browser, int flags, int ifIndex, String serviceName, String regType, String domain)245 public void serviceLost( DNSSDService browser, int flags, int ifIndex, 246 String serviceName, String regType, String domain) 247 { 248 removeCache.add( serviceName); 249 if ( ( flags & DNSSD.MORE_COMING) == 0) 250 this.scheduleOnEventThread(); 251 } 252 run()253 public void run() 254 { 255 while ( removeCache.size() > 0) 256 { 257 String serviceName = (String) removeCache.remove( removeCache.size() - 1); 258 int matchInd = this.findMatching( serviceName); // probably doesn't handle near-duplicates well. 259 if ( matchInd != -1) 260 this.removeElementAt( matchInd); 261 } 262 while ( addCache.size() > 0) 263 { 264 BrowserListElem elem = (BrowserListElem) addCache.remove( addCache.size() - 1); 265 if ( -1 == this.findMatching( elem.fServiceName)) // probably doesn't handle near-duplicates well. 266 this.addInSortOrder( elem); 267 } 268 } 269 operationFailed( DNSSDService service, int errorCode)270 public void operationFailed( DNSSDService service, int errorCode) 271 { 272 // handle failure here 273 } 274 275 /* The list contains BrowserListElem's */ 276 class BrowserListElem 277 { BrowserListElem( String serviceName, String domain, String type, int ifIndex)278 public BrowserListElem( String serviceName, String domain, String type, int ifIndex) 279 { fServiceName = serviceName; fDomain = domain; fType = type; fInt = ifIndex; } 280 toString()281 public String toString() { return fServiceName; } 282 283 public String fServiceName, fDomain, fType; 284 public int fInt; 285 } 286 getNthServiceName( int n)287 public String getNthServiceName( int n) 288 { 289 BrowserListElem sel = (BrowserListElem) this.get( n); 290 return sel.fServiceName; 291 } 292 getNthRegType( int n)293 public String getNthRegType( int n) 294 { 295 BrowserListElem sel = (BrowserListElem) this.get( n); 296 return sel.fType; 297 } 298 getNthDomain( int n)299 public String getNthDomain( int n) 300 { 301 BrowserListElem sel = (BrowserListElem) this.get( n); 302 return sel.fDomain; 303 } 304 getNthInterface( int n)305 public int getNthInterface( int n) 306 { 307 BrowserListElem sel = (BrowserListElem) this.get( n); 308 return sel.fInt; 309 } 310 addInSortOrder( Object obj)311 protected void addInSortOrder( Object obj) 312 { 313 int i; 314 for ( i = 0; i < this.size(); i++) 315 if ( sCollator.compare( obj.toString(), this.getElementAt( i).toString()) < 0) 316 break; 317 this.add( i, obj); 318 } 319 findMatching( String match)320 protected int findMatching( String match) 321 { 322 for ( int i = 0; i < this.size(); i++) 323 if ( match.equals( this.getElementAt( i).toString())) 324 return i; 325 return -1; 326 } 327 scheduleOnEventThread()328 protected void scheduleOnEventThread() 329 { 330 try { 331 SwingUtilities.invokeAndWait( this); 332 } 333 catch ( Exception e) 334 { 335 e.printStackTrace(); 336 } 337 } 338 339 protected Vector removeCache; // list of serviceNames to remove 340 protected Vector addCache; // list of BrowserListElem's to add 341 342 protected static Collator sCollator; 343 344 static // Initialize our static variables 345 { 346 sCollator = Collator.getInstance(); 347 sCollator.setStrength( Collator.PRIMARY); 348 } 349 } 350 351 352 class ServicesBrowserListModel extends BrowserListModel 353 { 354 /* The Browser invokes this callback when a service is discovered. */ serviceFound( DNSSDService browser, int flags, int ifIndex, String serviceName, String regType, String domain)355 public void serviceFound( DNSSDService browser, int flags, int ifIndex, 356 String serviceName, String regType, String domain) 357 // Overridden to stuff serviceName into regType and make serviceName human-readable. 358 { 359 regType = serviceName + ( regType.startsWith( "_udp.") ? "._udp." : "._tcp."); 360 super.serviceFound( browser, flags, ifIndex, this.mapTypeToName( serviceName), regType, domain); 361 } 362 serviceLost( DNSSDService browser, int flags, int ifIndex, String serviceName, String regType, String domain)363 public void serviceLost( DNSSDService browser, int flags, int ifIndex, 364 String serviceName, String regType, String domain) 365 // Overridden to make serviceName human-readable. 366 { 367 super.serviceLost( browser, flags, ifIndex, this.mapTypeToName( serviceName), regType, domain); 368 } 369 mapTypeToName( String type)370 protected String mapTypeToName( String type) 371 // Convert a registration type into a human-readable string. Returns original string on no-match. 372 { 373 final String[] namedServices = { 374 "_afpovertcp", "Apple File Sharing", 375 "_http", "World Wide Web servers", 376 "_daap", "Digital Audio Access", 377 "_apple-sasl", "Apple Password Servers", 378 "_distcc", "Distributed Compiler nodes", 379 "_finger", "Finger servers", 380 "_ichat", "iChat clients", 381 "_presence", "iChat AV clients", 382 "_ssh", "SSH servers", 383 "_telnet", "Telnet servers", 384 "_workstation", "Macintosh Manager clients", 385 "_bootps", "BootP servers", 386 "_xserveraid", "XServe RAID devices", 387 "_eppc", "Remote AppleEvents", 388 "_ftp", "FTP services", 389 "_tftp", "TFTP services" 390 }; 391 392 for ( int i = 0; i < namedServices.length; i+=2) 393 if ( namedServices[i].equals( type)) 394 return namedServices[i + 1]; 395 return type; 396 } 397 } 398 399 400 class DomainListModel extends DefaultListModel implements DomainListener 401 { 402 /* Called when a domain is discovered. */ domainFound( DNSSDService domainEnum, int flags, int ifIndex, String domain)403 public void domainFound( DNSSDService domainEnum, int flags, int ifIndex, String domain) 404 { 405 if ( !this.contains( domain)) 406 this.addElement( domain); 407 } 408 domainLost( DNSSDService domainEnum, int flags, int ifIndex, String domain)409 public void domainLost( DNSSDService domainEnum, int flags, int ifIndex, String domain) 410 { 411 if ( this.contains( domain)) 412 this.removeElement( domain); 413 } 414 operationFailed( DNSSDService service, int errorCode)415 public void operationFailed( DNSSDService service, int errorCode) 416 { 417 // handle failure here 418 } 419 } 420 421