github.com/nats-io/nats-server/v2@v2.11.0-preview.2/server/util.go (about)

     1  // Copyright 2012-2019 The NATS Authors
     2  // Licensed under the Apache License, Version 2.0 (the "License");
     3  // you may not use this file except in compliance with the License.
     4  // You may obtain a copy of the License at
     5  //
     6  // http://www.apache.org/licenses/LICENSE-2.0
     7  //
     8  // Unless required by applicable law or agreed to in writing, software
     9  // distributed under the License is distributed on an "AS IS" BASIS,
    10  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    11  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  
    14  package server
    15  
    16  import (
    17  	"bytes"
    18  	"context"
    19  	"encoding/json"
    20  	"errors"
    21  	"fmt"
    22  	"math"
    23  	"net"
    24  	"net/url"
    25  	"reflect"
    26  	"strconv"
    27  	"strings"
    28  	"time"
    29  )
    30  
    31  // This map is used to store URLs string as the key with a reference count as
    32  // the value. This is used to handle gossiped URLs such as connect_urls, etc..
    33  type refCountedUrlSet map[string]int
    34  
    35  // Ascii numbers 0-9
    36  const (
    37  	asciiZero = 48
    38  	asciiNine = 57
    39  )
    40  
    41  func versionComponents(version string) (major, minor, patch int, err error) {
    42  	m := semVerRe.FindStringSubmatch(version)
    43  	if len(m) == 0 {
    44  		return 0, 0, 0, errors.New("invalid semver")
    45  	}
    46  	major, err = strconv.Atoi(m[1])
    47  	if err != nil {
    48  		return -1, -1, -1, err
    49  	}
    50  	minor, err = strconv.Atoi(m[2])
    51  	if err != nil {
    52  		return -1, -1, -1, err
    53  	}
    54  	patch, err = strconv.Atoi(m[3])
    55  	if err != nil {
    56  		return -1, -1, -1, err
    57  	}
    58  	return major, minor, patch, err
    59  }
    60  
    61  func versionAtLeastCheckError(version string, emajor, eminor, epatch int) (bool, error) {
    62  	major, minor, patch, err := versionComponents(version)
    63  	if err != nil {
    64  		return false, err
    65  	}
    66  	if major > emajor ||
    67  		(major == emajor && minor > eminor) ||
    68  		(major == emajor && minor == eminor && patch >= epatch) {
    69  		return true, nil
    70  	}
    71  	return false, err
    72  }
    73  
    74  func versionAtLeast(version string, emajor, eminor, epatch int) bool {
    75  	res, _ := versionAtLeastCheckError(version, emajor, eminor, epatch)
    76  	return res
    77  }
    78  
    79  // parseSize expects decimal positive numbers. We
    80  // return -1 to signal error.
    81  func parseSize(d []byte) (n int) {
    82  	const maxParseSizeLen = 9 //999M
    83  
    84  	l := len(d)
    85  	if l == 0 || l > maxParseSizeLen {
    86  		return -1
    87  	}
    88  	var (
    89  		i   int
    90  		dec byte
    91  	)
    92  
    93  	// Note: Use `goto` here to avoid for loop in order
    94  	// to have the function be inlined.
    95  	// See: https://github.com/golang/go/issues/14768
    96  loop:
    97  	dec = d[i]
    98  	if dec < asciiZero || dec > asciiNine {
    99  		return -1
   100  	}
   101  	n = n*10 + (int(dec) - asciiZero)
   102  
   103  	i++
   104  	if i < l {
   105  		goto loop
   106  	}
   107  	return n
   108  }
   109  
   110  // parseInt64 expects decimal positive numbers. We
   111  // return -1 to signal error
   112  func parseInt64(d []byte) (n int64) {
   113  	if len(d) == 0 {
   114  		return -1
   115  	}
   116  	for _, dec := range d {
   117  		if dec < asciiZero || dec > asciiNine {
   118  			return -1
   119  		}
   120  		n = n*10 + (int64(dec) - asciiZero)
   121  	}
   122  	return n
   123  }
   124  
   125  // Helper to move from float seconds to time.Duration
   126  func secondsToDuration(seconds float64) time.Duration {
   127  	ttl := seconds * float64(time.Second)
   128  	return time.Duration(ttl)
   129  }
   130  
   131  // Parse a host/port string with a default port to use
   132  // if none (or 0 or -1) is specified in `hostPort` string.
   133  func parseHostPort(hostPort string, defaultPort int) (host string, port int, err error) {
   134  	if hostPort != "" {
   135  		host, sPort, err := net.SplitHostPort(hostPort)
   136  		if ae, ok := err.(*net.AddrError); ok && strings.Contains(ae.Err, "missing port") {
   137  			// try appending the current port
   138  			host, sPort, err = net.SplitHostPort(fmt.Sprintf("%s:%d", hostPort, defaultPort))
   139  		}
   140  		if err != nil {
   141  			return "", -1, err
   142  		}
   143  		port, err = strconv.Atoi(strings.TrimSpace(sPort))
   144  		if err != nil {
   145  			return "", -1, err
   146  		}
   147  		if port == 0 || port == -1 {
   148  			port = defaultPort
   149  		}
   150  		return strings.TrimSpace(host), port, nil
   151  	}
   152  	return "", -1, errors.New("no hostport specified")
   153  }
   154  
   155  // Returns true if URL u1 represents the same URL than u2,
   156  // false otherwise.
   157  func urlsAreEqual(u1, u2 *url.URL) bool {
   158  	return reflect.DeepEqual(u1, u2)
   159  }
   160  
   161  // comma produces a string form of the given number in base 10 with
   162  // commas after every three orders of magnitude.
   163  //
   164  // e.g. comma(834142) -> 834,142
   165  //
   166  // This function was copied from the github.com/dustin/go-humanize
   167  // package and is Copyright Dustin Sallings <dustin@spy.net>
   168  func comma(v int64) string {
   169  	sign := ""
   170  
   171  	// Min int64 can't be negated to a usable value, so it has to be special cased.
   172  	if v == math.MinInt64 {
   173  		return "-9,223,372,036,854,775,808"
   174  	}
   175  
   176  	if v < 0 {
   177  		sign = "-"
   178  		v = 0 - v
   179  	}
   180  
   181  	parts := []string{"", "", "", "", "", "", ""}
   182  	j := len(parts) - 1
   183  
   184  	for v > 999 {
   185  		parts[j] = strconv.FormatInt(v%1000, 10)
   186  		switch len(parts[j]) {
   187  		case 2:
   188  			parts[j] = "0" + parts[j]
   189  		case 1:
   190  			parts[j] = "00" + parts[j]
   191  		}
   192  		v = v / 1000
   193  		j--
   194  	}
   195  	parts[j] = strconv.Itoa(int(v))
   196  	return sign + strings.Join(parts[j:], ",")
   197  }
   198  
   199  // Adds urlStr to the given map. If the string was already present, simply
   200  // bumps the reference count.
   201  // Returns true only if it was added for the first time.
   202  func (m refCountedUrlSet) addUrl(urlStr string) bool {
   203  	m[urlStr]++
   204  	return m[urlStr] == 1
   205  }
   206  
   207  // Removes urlStr from the given map. If the string is not present, nothing
   208  // is done and false is returned.
   209  // If the string was present, its reference count is decreased. Returns true
   210  // if this was the last reference, false otherwise.
   211  func (m refCountedUrlSet) removeUrl(urlStr string) bool {
   212  	removed := false
   213  	if ref, ok := m[urlStr]; ok {
   214  		if ref == 1 {
   215  			removed = true
   216  			delete(m, urlStr)
   217  		} else {
   218  			m[urlStr]--
   219  		}
   220  	}
   221  	return removed
   222  }
   223  
   224  // Returns the unique URLs in this map as a slice
   225  func (m refCountedUrlSet) getAsStringSlice() []string {
   226  	a := make([]string, 0, len(m))
   227  	for u := range m {
   228  		a = append(a, u)
   229  	}
   230  	return a
   231  }
   232  
   233  // natsListenConfig provides a common configuration to match the one used by
   234  // net.Listen() but with our own defaults.
   235  // Go 1.13 introduced default-on TCP keepalives with aggressive timings and
   236  // there's no sane portable way in Go with stdlib to split the initial timer
   237  // from the retry timer.  Linux/BSD defaults are 2hrs/75s and Go sets both
   238  // to 15s; the issue re making them indepedently tunable has been open since
   239  // 2014 and this code here is being written in 2020.
   240  // The NATS protocol has its own L7 PING/PONG keepalive system and the Go
   241  // defaults are inappropriate for IoT deployment scenarios.
   242  // Replace any NATS-protocol calls to net.Listen(...) with
   243  // natsListenConfig.Listen(ctx,...) or use natsListen(); leave calls for HTTP
   244  // monitoring, etc, on the default.
   245  var natsListenConfig = &net.ListenConfig{
   246  	KeepAlive: -1,
   247  }
   248  
   249  // natsListen() is the same as net.Listen() except that TCP keepalives are
   250  // disabled (to match Go's behavior before Go 1.13).
   251  func natsListen(network, address string) (net.Listener, error) {
   252  	return natsListenConfig.Listen(context.Background(), network, address)
   253  }
   254  
   255  // natsDialTimeout is the same as net.DialTimeout() except the TCP keepalives
   256  // are disabled (to match Go's behavior before Go 1.13).
   257  func natsDialTimeout(network, address string, timeout time.Duration) (net.Conn, error) {
   258  	d := net.Dialer{
   259  		Timeout:   timeout,
   260  		KeepAlive: -1,
   261  	}
   262  	return d.Dial(network, address)
   263  }
   264  
   265  // redactURLList() returns a copy of a list of URL pointers where each item
   266  // in the list will either be the same pointer if the URL does not contain a
   267  // password, or to a new object if there is a password.
   268  // The intended use-case is for logging lists of URLs safely.
   269  func redactURLList(unredacted []*url.URL) []*url.URL {
   270  	r := make([]*url.URL, len(unredacted))
   271  	// In the common case of no passwords, if we don't let the new object leave
   272  	// this function then GC should be easier.
   273  	needCopy := false
   274  	for i := range unredacted {
   275  		if unredacted[i] == nil {
   276  			r[i] = nil
   277  			continue
   278  		}
   279  		if _, has := unredacted[i].User.Password(); !has {
   280  			r[i] = unredacted[i]
   281  			continue
   282  		}
   283  		needCopy = true
   284  		ru := *unredacted[i]
   285  		ru.User = url.UserPassword(ru.User.Username(), "xxxxx")
   286  		r[i] = &ru
   287  	}
   288  	if needCopy {
   289  		return r
   290  	}
   291  	return unredacted
   292  }
   293  
   294  // redactURLString() attempts to redact a URL string.
   295  func redactURLString(raw string) string {
   296  	if !strings.ContainsRune(raw, '@') {
   297  		return raw
   298  	}
   299  	u, err := url.Parse(raw)
   300  	if err != nil {
   301  		return raw
   302  	}
   303  	return u.Redacted()
   304  }
   305  
   306  // getURLsAsString returns a slice of u.Host from the given slice of url.URL's
   307  func getURLsAsString(urls []*url.URL) []string {
   308  	a := make([]string, 0, len(urls))
   309  	for _, u := range urls {
   310  		a = append(a, u.Host)
   311  	}
   312  	return a
   313  }
   314  
   315  // copyBytes make a new slice of the same size than `src` and copy its content.
   316  // If `src` is nil or its length is 0, then this returns `nil`
   317  func copyBytes(src []byte) []byte {
   318  	if len(src) == 0 {
   319  		return nil
   320  	}
   321  	dst := make([]byte, len(src))
   322  	copy(dst, src)
   323  	return dst
   324  }
   325  
   326  // copyStrings make a new slice of the same size than `src` and copy its content.
   327  // If `src` is nil, then this returns `nil`
   328  func copyStrings(src []string) []string {
   329  	if src == nil {
   330  		return nil
   331  	}
   332  	dst := make([]string, len(src))
   333  	copy(dst, src)
   334  	return dst
   335  }
   336  
   337  // Returns a byte slice for the INFO protocol.
   338  func generateInfoJSON(info *Info) []byte {
   339  	b, _ := json.Marshal(info)
   340  	pcs := [][]byte{[]byte("INFO"), b, []byte(CR_LF)}
   341  	return bytes.Join(pcs, []byte(" "))
   342  }