1package sockaddr
2
3import (
4	"encoding/json"
5	"fmt"
6	"strings"
7)
8
9type SockAddrType int
10type AttrName string
11
12const (
13	TypeUnknown SockAddrType = 0x0
14	TypeUnix                 = 0x1
15	TypeIPv4                 = 0x2
16	TypeIPv6                 = 0x4
17
18	// TypeIP is the union of TypeIPv4 and TypeIPv6
19	TypeIP = 0x6
20)
21
22type SockAddr interface {
23	// CmpRFC returns 0 if SockAddr exactly matches one of the matched RFC
24	// networks, -1 if the receiver is contained within the RFC network, or
25	// 1 if the address is not contained within the RFC.
26	CmpRFC(rfcNum uint, sa SockAddr) int
27
28	// Contains returns true if the SockAddr arg is contained within the
29	// receiver
30	Contains(SockAddr) bool
31
32	// Equal allows for the comparison of two SockAddrs
33	Equal(SockAddr) bool
34
35	DialPacketArgs() (string, string)
36	DialStreamArgs() (string, string)
37	ListenPacketArgs() (string, string)
38	ListenStreamArgs() (string, string)
39
40	// String returns the string representation of SockAddr
41	String() string
42
43	// Type returns the SockAddrType
44	Type() SockAddrType
45}
46
47// sockAddrAttrMap is a map of the SockAddr type-specific attributes.
48var sockAddrAttrMap map[AttrName]func(SockAddr) string
49var sockAddrAttrs []AttrName
50
51func init() {
52	sockAddrInit()
53}
54
55// New creates a new SockAddr from the string.  The order in which New()
56// attempts to construct a SockAddr is: IPv4Addr, IPv6Addr, SockAddrUnix.
57//
58// NOTE: New() relies on the heuristic wherein if the path begins with either a
59// '.'  or '/' character before creating a new UnixSock.  For UNIX sockets that
60// are absolute paths or are nested within a sub-directory, this works as
61// expected, however if the UNIX socket is contained in the current working
62// directory, this will fail unless the path begins with "./"
63// (e.g. "./my-local-socket").  Calls directly to NewUnixSock() do not suffer
64// this limitation.  Invalid IP addresses such as "256.0.0.0/-1" will run afoul
65// of this heuristic and be assumed to be a valid UNIX socket path (which they
66// are, but it is probably not what you want and you won't realize it until you
67// stat(2) the file system to discover it doesn't exist).
68func NewSockAddr(s string) (SockAddr, error) {
69	ipv4Addr, err := NewIPv4Addr(s)
70	if err == nil {
71		return ipv4Addr, nil
72	}
73
74	ipv6Addr, err := NewIPv6Addr(s)
75	if err == nil {
76		return ipv6Addr, nil
77	}
78
79	// Check to make sure the string begins with either a '.' or '/', or
80	// contains a '/'.
81	if len(s) > 1 && (strings.IndexAny(s[0:1], "./") != -1 || strings.IndexByte(s, '/') != -1) {
82		unixSock, err := NewUnixSock(s)
83		if err == nil {
84			return unixSock, nil
85		}
86	}
87
88	return nil, fmt.Errorf("Unable to convert %q to an IPv4 or IPv6 address, or a UNIX Socket", s)
89}
90
91// ToIPAddr returns an IPAddr type or nil if the type conversion fails.
92func ToIPAddr(sa SockAddr) *IPAddr {
93	ipa, ok := sa.(IPAddr)
94	if !ok {
95		return nil
96	}
97	return &ipa
98}
99
100// ToIPv4Addr returns an IPv4Addr type or nil if the type conversion fails.
101func ToIPv4Addr(sa SockAddr) *IPv4Addr {
102	switch v := sa.(type) {
103	case IPv4Addr:
104		return &v
105	default:
106		return nil
107	}
108}
109
110// ToIPv6Addr returns an IPv6Addr type or nil if the type conversion fails.
111func ToIPv6Addr(sa SockAddr) *IPv6Addr {
112	switch v := sa.(type) {
113	case IPv6Addr:
114		return &v
115	default:
116		return nil
117	}
118}
119
120// ToUnixSock returns a UnixSock type or nil if the type conversion fails.
121func ToUnixSock(sa SockAddr) *UnixSock {
122	switch v := sa.(type) {
123	case UnixSock:
124		return &v
125	default:
126		return nil
127	}
128}
129
130// SockAddrAttr returns a string representation of an attribute for the given
131// SockAddr.
132func SockAddrAttr(sa SockAddr, selector AttrName) string {
133	fn, found := sockAddrAttrMap[selector]
134	if !found {
135		return ""
136	}
137
138	return fn(sa)
139}
140
141// String() for SockAddrType returns a string representation of the
142// SockAddrType (e.g. "IPv4", "IPv6", "UNIX", "IP", or "unknown").
143func (sat SockAddrType) String() string {
144	switch sat {
145	case TypeIPv4:
146		return "IPv4"
147	case TypeIPv6:
148		return "IPv6"
149	// There is no concrete "IP" type.  Leaving here as a reminder.
150	// case TypeIP:
151	// 	return "IP"
152	case TypeUnix:
153		return "UNIX"
154	default:
155		panic("unsupported type")
156	}
157}
158
159// sockAddrInit is called once at init()
160func sockAddrInit() {
161	sockAddrAttrs = []AttrName{
162		"type", // type should be first
163		"string",
164	}
165
166	sockAddrAttrMap = map[AttrName]func(sa SockAddr) string{
167		"string": func(sa SockAddr) string {
168			return sa.String()
169		},
170		"type": func(sa SockAddr) string {
171			return sa.Type().String()
172		},
173	}
174}
175
176// UnixSockAttrs returns a list of attributes supported by the UnixSock type
177func SockAddrAttrs() []AttrName {
178	return sockAddrAttrs
179}
180
181// Although this is pretty trivial to do in a program, having the logic here is
182// useful all around. Note that this marshals into a *string* -- the underlying
183// string representation of the sockaddr. If you then unmarshal into this type
184// in Go, all will work as expected, but externally you can take what comes out
185// and use the string value directly.
186type SockAddrMarshaler struct {
187	SockAddr
188}
189
190func (s *SockAddrMarshaler) MarshalJSON() ([]byte, error) {
191	return json.Marshal(s.SockAddr.String())
192}
193
194func (s *SockAddrMarshaler) UnmarshalJSON(in []byte) error {
195	var str string
196	err := json.Unmarshal(in, &str)
197	if err != nil {
198		return err
199	}
200	sa, err := NewSockAddr(str)
201	if err != nil {
202		return err
203	}
204	s.SockAddr = sa
205	return nil
206}
207