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 }