τau / net /

net.tau

source
/Users/niconex/Documents/tau/stdlib/net/net.tau
1# net - TCP and UDP sockets, in the shape of Go's net package.2#3# Everything goes through syscall, there is no dependency on the C library of4# the host. Addresses are written "host:port", an empty host meaning every5# interface.67syscall = import("syscall")8strconv = import("strconv")910bufsize = 40961112# JoinHostPort builds an address out of a host and a port.13JoinHostPort = fn(host, port) { "{host}:{string(port)}" }1415# SplitHostPort returns an object with Host and Port, or an error if the16# address carries no port. The last colon wins, so IPv6 forms survive.17SplitHostPort = fn(hostport) {18	colon = -119	for i = len(hostport) - 1; i >= 0; --i {20		if hostport[i] == ":" {21			colon = i22			break23		}24	}2526	if colon == -1 {27		return error("net: missing port in address \"{hostport}\"")28	}2930	if failed(port = strconv.Atoi(slice(hostport, colon + 1, len(hostport)))) {31		return error("net: invalid port in address \"{hostport}\"")32	}3334	out = new()35	out.Host = slice(hostport, 0, colon)36	out.Port = port37	return out38}3940# sockaddr turns an address into the bytes bind and connect want.41sockaddr = fn(address) {42	if failed(hp = SplitHostPort(address)) {43		return hp44	}45	return syscall.SockaddrIn(hp.Host, hp.Port)46}4748# newConn wraps an open socket.49newConn = fn(fd, raddr) {50	conn = new()51	conn.fd = fd52	conn.RemoteAddr = raddr5354	# Read returns up to n bytes, or an empty bytes value when the other end55	# has closed the connection.56	conn.Read = fn(n) {57		if n == null {58			n = bufsize59		}6061		buf = bytes(n)62		if failed(read = syscall.Recv(conn.fd, buf, n, 0)) {63			return read64		}65		return slice(buf, 0, read)66	}6768	# Write sends a string or bytes and returns how many bytes went out.69	conn.Write = fn(data) {70		if type(data) != "bytes" {71			if failed(data = bytes(string(data))) {72				return data73			}74		}75		return syscall.Send(conn.fd, data, len(data), 0)76	}7778	conn.Close = fn() { syscall.Close(conn.fd) }7980	# SetTimeout gives up on a read or a write after ms milliseconds.81	conn.SetTimeout = fn(ms) {82		tv = syscall.Timeval(ms)83		syscall.Setsockopt(conn.fd, syscall.SOL_SOCKET, syscall.SO_RCVTIMEO, tv, len(tv))84		return syscall.Setsockopt(conn.fd, syscall.SOL_SOCKET, syscall.SO_SNDTIMEO, tv, len(tv))85	}8687	return conn88}8990# Dial connects to an address. Network is "tcp" or "udp".91Dial = fn(network, address) {92	socktype = if network == "udp" { syscall.SOCK_DGRAM } else { syscall.SOCK_STREAM }9394	if failed(sa = sockaddr(address)) {95		return sa96	}97	if failed(fd = syscall.Socket(syscall.AF_INET, socktype, 0)) {98		return fd99	}100	if failed(err = syscall.Connect(fd, sa, len(sa))) {101		syscall.Close(fd)102		return err103	}104105	return newConn(fd, address)106}107108# Listen returns a listener accepting connections on address.109Listen = fn(network, address) {110	if failed(sa = sockaddr(address)) {111		return sa112	}113	if failed(fd = syscall.Socket(syscall.AF_INET, syscall.SOCK_STREAM, 0)) {114		return fd115	}116117	# Without this a restarted server can't bind again until the kernel118	# releases the port.119	one = syscall.Int32(1)120	syscall.Setsockopt(fd, syscall.SOL_SOCKET, syscall.SO_REUSEADDR, one, len(one))121122	if failed(err = syscall.Bind(fd, sa, len(sa))) {123		syscall.Close(fd)124		return err125	}126	if failed(err = syscall.Listen(fd, syscall.SOMAXCONN)) {127		syscall.Close(fd)128		return err129	}130131	ln = new()132	ln.fd = fd133134	# The address the kernel actually bound: with a port of 0 it picks a free135	# one, and the caller learns which through ln.Addr. Falls back to what was136	# asked for if the lookup fails.137	name = bytes(syscall.SockaddrInSize())138	namelen = syscall.Int32(syscall.SockaddrInSize())139	if failed(syscall.Getsockname(fd, name, namelen)) {140		ln.Addr = address141	} else {142		ln.Addr = JoinHostPort(syscall.SockaddrIP(name), syscall.SockaddrPort(name))143	}144145	# Accept waits for the next connection.146	ln.Accept = fn() {147		peer = bytes(syscall.SockaddrInSize())148		size = syscall.Int32(syscall.SockaddrInSize())149150		if failed(cfd = syscall.Accept(ln.fd, peer, size)) {151			return cfd152		}153		return newConn(cfd, JoinHostPort(syscall.SockaddrIP(peer), syscall.SockaddrPort(peer)))154	}155156	ln.Close = fn() { syscall.Close(ln.fd) }157158	return ln159}160161# ListenPacket returns a UDP socket bound to address.162ListenPacket = fn(network, address) {163	if failed(sa = sockaddr(address)) {164		return sa165	}166	if failed(fd = syscall.Socket(syscall.AF_INET, syscall.SOCK_DGRAM, 0)) {167		return fd168	}169	if failed(err = syscall.Bind(fd, sa, len(sa))) {170		syscall.Close(fd)171		return err172	}173174	pc = new()175	pc.fd = fd176	pc.Addr = address177178	# ReadFrom returns an object with Data and Addr, the sender of the packet.179	pc.ReadFrom = fn(n) {180		if n == null {181			n = bufsize182		}183184		buf = bytes(n)185		from = bytes(syscall.SockaddrInSize())186		size = syscall.Int32(syscall.SockaddrInSize())187188		if failed(read = syscall.Recvfrom(pc.fd, buf, n, 0, from, size)) {189			return read190		}191192		out = new()193		out.Data = slice(buf, 0, read)194		out.Addr = JoinHostPort(syscall.SockaddrIP(from), syscall.SockaddrPort(from))195		return out196	}197198	# WriteTo sends a packet to address.199	pc.WriteTo = fn(data, address) {200		if type(data) != "bytes" {201			if failed(data = bytes(string(data))) {202				return data203			}204		}205		if failed(sa = sockaddr(address)) {206			return sa207		}208		return syscall.Sendto(pc.fd, data, len(data), 0, sa, len(sa))209	}210211	pc.Close = fn() { syscall.Close(pc.fd) }212213	return pc214}215216# ParseIP reports whether s is a valid dotted IPv4 address, returning it.217ParseIP = fn(s) {218	parts = 0219	start = 0220	i = 0221222	for i <= len(s) {223		if i == len(s) || s[i] == "." {224			if failed(n = strconv.Atoi(slice(s, start, i))) {225				return error("net: invalid IP address \"{s}\"")226			}227			if n < 0 || n > 255 {228				return error("net: invalid IP address \"{s}\"")229			}230			++parts231			start = i + 1232		}233		++i234	}235236	if parts != 4 {237		return error("net: invalid IP address \"{s}\"")238	}239	return s240}241242# LookupPort returns the port of a well known service, or the number itself.243LookupPort = fn(network, service) {244	ports = {245		"http": 80,246		"https": 443,247		"ftp": 21,248		"ssh": 22,249		"smtp": 25,250		"dns": 53251	}252253	if ports[service] != null {254		return ports[service]255	}256	return strconv.Atoi(service)257}