package plan9.lib;
import java.lang.Integer;
import java.util.Map;
import java.util.HashMap;
import java.util.StringTokenizer;
import java.nio.channels.SocketChannel;
import java.net.*;
// Plan 9/Inferno Dial interface to make network connections
// from styx-n-9p.googlecode.com (MIT licence)
/**
* Dial makes network connections, using a network-independent textual form of addressing services on hosts.
* It is based on Plan 9's dial(2). The current implementation supports only TCP/IP,
* but the same interface can support non-Internet protocols without change.
* It encapsulates the translation of textual forms of host and service names into IP addresses and port numbers,
* allowing network connections to be made with a single call. Unlike new Socket(hostname, port),
* which is also a single operation, Dial guarantees that it will try all addresses for a host, has an interface not limited to IP networking, and will translate
* Plan 9 service names to port numbers.
*
* It knows the correct port numbers for various Plan 9 services, such as 9fs, exportfs and ticket. *
* Instead of raising exceptions, Dial returns an error value and makes the diagnostic available in a per-thread error string. *
* There is no constructor: Dial contains only static values and methods. *
* * For example, combining dial and netmkaddr to connect to a 9fs service: *
* ByteChannel fd = Dial.dial(Dial.netmkaddr(args[0], "net", "9fs"), null);
* if(fd == null)
* error(String.format("can't dial %s: %s", args[0], Dial.errstr());
*
*/
public class Dial {
private Dial(){}
private static ThreadLocal* Dial tries each possible network address for a host (a host name might correspond to several network addresses), * until one succeeds, or all have failed. On success, dial returns a SocketChannel value giving a full-duplex * connection to the remote host. On an error, it returns null, setting the error string to the diagnostic. * * @param dest network address in the form net!host!service. * @param local leave null or "" to use any local port (other values reserved for future use) * @return a SocketChannel value representing a full-duplex connection to the remote host, or null on any error */ static public final SocketChannel dial(String dest, String local){ StringTokenizer flds = new StringTokenizer(dest, "!"); int n = flds.countTokens(); if(n < 3){ werrstr("invalid network address for dial: "+dest); return null; } String net = flds.nextToken(); String host = flds.nextToken(); String svc = flds.nextToken(); if(net.equals("net")) net = "tcp"; else if(!net.equals("tcp")){ // TO DO: "udp" werrstr("no network to host: "+dest); return null; } InetAddress[] addrs; try{ addrs = InetAddress.getAllByName(host); }catch(UnknownHostException e){ werrstr("unknown host: "+host); return null; } int port = portno(svc); if(port < 0){ Integer p = portmap.get(net+"!"+svc); if(p == null){ werrstr("can't translate service: "+svc); return null; } port = p.intValue(); } for(InetAddress ip : addrs){ try{ SocketChannel sc = SocketChannel.open(new InetSocketAddress(ip, port)); if(sc != null){ sc.socket().setTcpNoDelay(true); return sc; } }catch(java.io.IOException e){ werrstr(e.getMessage()); } } return null; } /** * Add default network and service components as required to an address in {@link Dial#dial}'s form. * The address has {@link Dial#dial}'s net!host!service form, * but if net and svc are missing (ie, the address is just a host name), netmkaddr * will add them, using the values of its {@code net} and {@code svc} parameters. If only the service * is missing, netmkaddr will add it, using {@code svc}. * @param addr an address in one of the forms net!host!svc, net!host, or simply host * @param net default network name to use if addr hasn't got one * @param svc default service name to use if addr hasn't got one * @return a complete dial string */ static public final String netmkaddr(String addr, String net, String svc){ StringTokenizer flds = new StringTokenizer(addr, "!"); int n = flds.countTokens(); if(n <= 1){ if(net == null) net = "net"; if(svc == null) return net+"!"+addr; return net+"!"+addr+"!"+svc; } if(svc != null && n < 2) return addr+"!"+svc; return addr; } static private int portno(String s){ int n = 0; for(int i = 0; i < s.length(); i++){ char c = s.charAt(i); if(!(c >= '0' && c <= '9')) return -1; n = n*10 + ((int)c-'0'); } return n; } }