parse.go 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104
  1. // Copyright 2020-2022 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. package parser
  14. import (
  15. "errors"
  16. "fmt"
  17. )
  18. const (
  19. AckDomainTokenPos = iota + 2
  20. AckAccHashTokenPos
  21. AckStreamTokenPos
  22. AckConsumerTokenPos
  23. AckNumDeliveredTokenPos
  24. AckStreamSeqTokenPos
  25. AckConsumerSeqTokenPos
  26. AckTimestampSeqTokenPos
  27. AckNumPendingTokenPos
  28. )
  29. var ErrInvalidSubjectFormat = errors.New("invalid format of ACK subject")
  30. // Quick parser for positive numbers in ack reply encoding.
  31. // NOTE: This parser does not detect uint64 overflow
  32. func ParseNum(d string) (n uint64) {
  33. if len(d) == 0 {
  34. return 0
  35. }
  36. // ASCII numbers 0-9
  37. const (
  38. asciiZero = 48
  39. asciiNine = 57
  40. )
  41. for _, dec := range d {
  42. if dec < asciiZero || dec > asciiNine {
  43. return 0
  44. }
  45. n = n*10 + uint64(dec) - asciiZero
  46. }
  47. return
  48. }
  49. func GetMetadataFields(subject string) ([]string, error) {
  50. v1TokenCounts, v2TokenCounts := 9, 12
  51. var start int
  52. tokens := make([]string, 0, v2TokenCounts)
  53. for i := 0; i < len(subject); i++ {
  54. if subject[i] == '.' {
  55. tokens = append(tokens, subject[start:i])
  56. start = i + 1
  57. }
  58. }
  59. tokens = append(tokens, subject[start:])
  60. //
  61. // Newer server will include the domain name and account hash in the subject,
  62. // and a token at the end.
  63. //
  64. // Old subject was:
  65. // $JS.ACK.<stream>.<consumer>.<delivered>.<sseq>.<cseq>.<tm>.<pending>
  66. //
  67. // New subject would be:
  68. // $JS.ACK.<domain>.<account hash>.<stream>.<consumer>.<delivered>.<sseq>.<cseq>.<tm>.<pending>.<a token with a random value>
  69. //
  70. // v1 has 9 tokens, v2 has 12, but we must not be strict on the 12th since
  71. // it may be removed in the future. Also, the library has no use for it.
  72. // The point is that a v2 ACK subject is valid if it has at least 11 tokens.
  73. //
  74. tokensLen := len(tokens)
  75. // If lower than 9 or more than 9 but less than 11, report an error
  76. if tokensLen < v1TokenCounts || (tokensLen > v1TokenCounts && tokensLen < v2TokenCounts-1) {
  77. return nil, ErrInvalidSubjectFormat
  78. }
  79. if tokens[0] != "$JS" || tokens[1] != "ACK" {
  80. return nil, fmt.Errorf("%w: subject should start with $JS.ACK", ErrInvalidSubjectFormat)
  81. }
  82. // For v1 style, we insert 2 empty tokens (domain and hash) so that the
  83. // rest of the library references known fields at a constant location.
  84. if tokensLen == v1TokenCounts {
  85. // Extend the array (we know the backend is big enough)
  86. tokens = append(tokens[:AckDomainTokenPos+2], tokens[AckDomainTokenPos:]...)
  87. // Clear the domain and hash tokens
  88. tokens[AckDomainTokenPos], tokens[AckAccHashTokenPos] = "", ""
  89. } else if tokens[AckDomainTokenPos] == "_" {
  90. // If domain is "_", replace with empty value.
  91. tokens[AckDomainTokenPos] = ""
  92. }
  93. return tokens, nil
  94. }