1// SPDX-License-Identifier: ISC
2// Copyright (c) 2014-2020 Bitmark Inc.
3// Use of this source code is governed by an ISC
4// license that can be found in the LICENSE file.
5
6package util
7
8import (
9	"net"
10	"strconv"
11	"strings"
12
13	"github.com/bitmark-inc/bitmarkd/fault"
14	"github.com/bitmark-inc/logger"
15)
16
17// Connection - type to hold an IP and Port
18type Connection struct {
19	ip   net.IP
20	port uint16
21}
22
23// NewConnection - create a connection from an Host:Port string
24func NewConnection(hostPort string) (*Connection, error) {
25	host, port, err := net.SplitHostPort(hostPort)
26	if nil != err {
27		return nil, err
28	}
29
30	host = strings.TrimSpace(host)
31
32	IP := net.ParseIP(host)
33	if "*" == host && nil == IP {
34		IP = net.ParseIP("::")
35	}
36	if nil == IP {
37		ips, err := net.LookupIP(host)
38		if nil != err {
39			return nil, err
40		}
41		if len(ips) < 1 {
42			return nil, fault.InvalidIpAddress
43		}
44		IP = ips[0]
45	}
46
47	numericPort, err := strconv.Atoi(strings.TrimSpace(port))
48	if nil != err {
49		return nil, err
50	}
51	if numericPort < 1 || numericPort > 65535 {
52		return nil, fault.InvalidPortNumber
53	}
54	c := &Connection{
55		ip:   IP,
56		port: uint16(numericPort),
57	}
58	return c, nil
59}
60
61// NewConnections -  convert an array of connections
62func NewConnections(hostPort []string) ([]*Connection, error) {
63	if 0 == len(hostPort) {
64		return nil, fault.InvalidLength
65	}
66	c := make([]*Connection, len(hostPort))
67	for i, hp := range hostPort {
68		var err error
69		c[i], err = NewConnection(hp)
70		if nil != err {
71			return nil, err
72		}
73	}
74	return c, nil
75}
76
77// ConnectionFromIPandPort - convert an IP and port to a connection
78func ConnectionFromIPandPort(ip net.IP, port uint16) *Connection {
79	return &Connection{
80		ip:   ip,
81		port: port,
82	}
83}
84
85// CanonicalIPandPort - make the IP:Port into canonical string
86//
87// examples:
88//   IPv4:  127.0.0.1:1234
89//   IPv6:  [::1]:1234
90//
91// prefix is optional and can be empty ("")
92// returns prefixed string and IPv6 flag
93func (conn *Connection) CanonicalIPandPort(prefix string) (string, bool) {
94
95	port := int(conn.port)
96	if nil != conn.ip.To4() {
97		return prefix + conn.ip.String() + ":" + strconv.Itoa(port), false
98	}
99	return prefix + "[" + conn.ip.String() + "]:" + strconv.Itoa(port), true
100}
101
102// basic string conversion
103func (conn Connection) String() string {
104	s, _ := conn.CanonicalIPandPort("")
105	return s
106}
107
108// MarshalText - convert to text for JSON
109func (conn Connection) MarshalText() ([]byte, error) {
110	s, _ := conn.CanonicalIPandPort("")
111	return []byte(s), nil
112}
113
114// PackedConnection - type for packed byte buffer IP and Port
115type PackedConnection []byte
116
117// Pack - pack an IP and Port into a byte buffer
118func (conn *Connection) Pack() PackedConnection {
119	b := []byte(conn.ip)
120	length := len(b)
121	if 4 != length && 16 != length {
122		logger.Panicf("connection.Pack: invalid IP length: %d", length)
123	}
124	size := length + 3 // count++port.high++port.low++ip
125	b2 := make([]byte, size)
126	b2[0] = byte(size)           // 7 or 19
127	b2[1] = byte(conn.port >> 8) // port high byte
128	b2[2] = byte(conn.port)      // port low byte
129	copy(b2[3:], b)              // 4 byte IPv4 or 16 byte IPv6
130	return b2
131}
132
133// Unpack - unpack a byte buffer into an IP and Port
134// returns nil if unpack fails
135// if successful returns connection and number of bytes used
136// so an array can be unpacked more easily
137func (packed PackedConnection) Unpack() (*Connection, int) {
138	if nil == packed {
139		return nil, 0
140	}
141	count := len(packed)
142	if count < 7 {
143		return nil, 0
144	}
145	n := packed[0]
146	if 7 != n && 19 != n { // only valid values
147		return nil, 0
148	}
149
150	ip := make([]byte, n-3) // 4 or 16 bytes
151	copy(ip, packed[3:n])
152	c := &Connection{
153		ip:   ip,
154		port: uint16(packed[1])<<8 + uint16(packed[2]),
155	}
156	return c, int(n)
157}
158
159// Unpack46 - unpack first IPv4 and first IPv6 plus Port
160func (packed PackedConnection) Unpack46() (*Connection, *Connection) {
161
162	// only expect two
163	ipv4Connection := (*Connection)(nil)
164	ipv6Connection := (*Connection)(nil)
165
166	for {
167		conn, n := packed.Unpack()
168		packed = packed[n:]
169
170		if nil == conn {
171			return ipv4Connection, ipv6Connection
172		}
173
174		if nil != conn.ip.To4() {
175			if nil == ipv4Connection {
176				ipv4Connection = conn
177			}
178		} else if nil == ipv6Connection {
179			ipv6Connection = conn
180		}
181
182		// if both kinds found
183		if nil != ipv4Connection && nil != ipv6Connection {
184			return ipv4Connection, ipv6Connection
185		}
186	}
187}
188