1// Copyright (C) 2019 Storj Labs, Inc.
2// See LICENSE for copying information.
3
4package trust
5
6import (
7	"bufio"
8	"context"
9	"io"
10	"net"
11	"strconv"
12	"strings"
13
14	"github.com/zeebo/errs"
15
16	"storj.io/common/storj"
17)
18
19var (
20	// ErrSatelliteURL is an error class for satellite URL related errors.
21	ErrSatelliteURL = errs.Class("invalid satellite URL")
22)
23
24// SatelliteURL represents a Satellite URL.
25type SatelliteURL struct {
26	ID   storj.NodeID `json:"id"`
27	Host string       `json:"host"`
28	Port int          `json:"port"`
29}
30
31// Address returns the address (i.e. host:port) of the Satellite.
32func (u *SatelliteURL) Address() string {
33	return net.JoinHostPort(u.Host, strconv.Itoa(u.Port))
34}
35
36// NodeURL returns a full Node URL to the Satellite.
37func (u *SatelliteURL) NodeURL() storj.NodeURL {
38	return storj.NodeURL{
39		ID:      u.ID,
40		Address: u.Address(),
41	}
42}
43
44// String returns a string representation of the Satellite URL.
45func (u *SatelliteURL) String() string {
46	return u.ID.String() + "@" + u.Address()
47}
48
49// ParseSatelliteURL parses a Satellite URL. For the purposes of the trust list,
50// the Satellite URL MUST contain both an ID and port designation.
51func ParseSatelliteURL(s string) (SatelliteURL, error) {
52	url, err := storj.ParseNodeURL(s)
53	if err != nil {
54		return SatelliteURL{}, ErrSatelliteURL.Wrap(err)
55	}
56	if url.ID.IsZero() {
57		return SatelliteURL{}, ErrSatelliteURL.New("must contain an ID")
58	}
59
60	if url.Address == "" {
61		return SatelliteURL{}, ErrSatelliteURL.New("must specify the host:port")
62	}
63
64	// storj.ParseNodeURL will have already verified that the address is
65	// well-formed, so if SplitHostPort fails it should be due to the address
66	// not having a port
67	host, portStr, err := net.SplitHostPort(url.Address)
68	if err != nil {
69		return SatelliteURL{}, ErrSatelliteURL.New("must specify the port")
70	}
71
72	// Port should already be numeric so this shouldn't fail, but just in case.
73	port, err := strconv.Atoi(portStr)
74	if err != nil {
75		return SatelliteURL{}, ErrSatelliteURL.New("port is not numeric")
76	}
77
78	return SatelliteURL{
79		ID:   url.ID,
80		Host: host,
81		Port: port,
82	}, nil
83}
84
85// ParseSatelliteURLList parses a newline separated list of Satellite URLs.
86// Empty lines or lines starting with '#' (comments) are ignored.
87func ParseSatelliteURLList(ctx context.Context, r io.Reader) (urls []SatelliteURL, err error) {
88	defer mon.Task()(&ctx)(&err)
89
90	scanner := bufio.NewScanner(r)
91	for scanner.Scan() {
92		line := strings.TrimSpace(scanner.Text())
93		if len(line) == 0 {
94			continue
95		}
96		if line[0] == '#' {
97			continue
98		}
99
100		url, err := ParseSatelliteURL(line)
101		if err != nil {
102			return nil, err
103		}
104		urls = append(urls, url)
105	}
106
107	if err := scanner.Err(); err != nil {
108		return nil, Error.Wrap(err)
109	}
110
111	return urls, nil
112}
113