You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

ircreader.go 3.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125
  1. // Copyright (c) 2020-2021 Shivaram Lingamneni
  2. // released under the MIT license
  3. package ircreader
  4. import (
  5. "bytes"
  6. "errors"
  7. "io"
  8. )
  9. /*
  10. Reader is an optimized line reader for IRC lines containing tags;
  11. most IRC lines will not approach the maximum line length (8191 bytes
  12. of tag data, plus 512 bytes of message data), so we want a buffered
  13. reader that can start with a smaller buffer and expand if necessary,
  14. while also maintaining a hard upper limit on the size of the buffer.
  15. */
  16. var (
  17. ErrReadQ = errors.New("readQ exceeded (read too many bytes without terminating newline)")
  18. )
  19. type Reader struct {
  20. conn io.Reader
  21. initialSize int
  22. maxSize int
  23. buf []byte
  24. start int // start of valid (i.e., read but not yet consumed) data in the buffer
  25. end int // end of valid data in the buffer
  26. searchFrom int // start of valid data in the buffer not yet searched for \n
  27. eof bool
  28. }
  29. // Returns a new *Reader with sane buffer size limits.
  30. func NewIRCReader(conn io.Reader) *Reader {
  31. var reader Reader
  32. reader.Initialize(conn, 512, 8192+1024)
  33. return &reader
  34. }
  35. // "Placement new" for a Reader; initializes it with custom buffer size
  36. // limits.
  37. func (cc *Reader) Initialize(conn io.Reader, initialSize, maxSize int) {
  38. *cc = Reader{}
  39. cc.conn = conn
  40. cc.initialSize = initialSize
  41. cc.maxSize = maxSize
  42. }
  43. // Blocks until a full IRC line is read, then returns it. Accepts either \n
  44. // or \r\n as the line terminator (but not \r in isolation). Passes through
  45. // errors from the underlying connection. Returns ErrReadQ if the buffer limit
  46. // was exceeded without a terminating \n.
  47. func (cc *Reader) ReadLine() ([]byte, error) {
  48. for {
  49. // try to find a terminated line in the buffered data already read
  50. nlidx := bytes.IndexByte(cc.buf[cc.searchFrom:cc.end], '\n')
  51. if nlidx != -1 {
  52. // got a complete line
  53. line := cc.buf[cc.start : cc.searchFrom+nlidx]
  54. cc.start = cc.searchFrom + nlidx + 1
  55. cc.searchFrom = cc.start
  56. // treat \r\n as the line terminator if it was present
  57. if 0 < len(line) && line[len(line)-1] == '\r' {
  58. line = line[:len(line)-1]
  59. }
  60. return line, nil
  61. }
  62. // are we out of space? we can read more if any of these are true:
  63. // 1. cc.start != 0, so we can slide the existing data back
  64. // 2. cc.end < len(cc.buf), so we can read data into the end of the buffer
  65. // 3. len(cc.buf) < cc.maxSize, so we can grow the buffer
  66. if cc.start == 0 && cc.end == len(cc.buf) && len(cc.buf) == cc.maxSize {
  67. return nil, ErrReadQ
  68. }
  69. if cc.eof {
  70. return nil, io.EOF
  71. }
  72. if len(cc.buf) < cc.maxSize && (len(cc.buf)-(cc.end-cc.start) < cc.initialSize/2) {
  73. // allocate a new buffer, copy any remaining data
  74. newLen := roundUpToPowerOfTwo(len(cc.buf) + 1)
  75. if newLen > cc.maxSize {
  76. newLen = cc.maxSize
  77. } else if newLen < cc.initialSize {
  78. newLen = cc.initialSize
  79. }
  80. newBuf := make([]byte, newLen)
  81. copy(newBuf, cc.buf[cc.start:cc.end])
  82. cc.buf = newBuf
  83. } else if cc.start != 0 {
  84. // slide remaining data back to the front of the buffer
  85. copy(cc.buf, cc.buf[cc.start:cc.end])
  86. }
  87. cc.end = cc.end - cc.start
  88. cc.start = 0
  89. cc.searchFrom = cc.end
  90. n, err := cc.conn.Read(cc.buf[cc.end:])
  91. cc.end += n
  92. if n != 0 && err == io.EOF {
  93. // we may have received new \n-terminated lines, try to parse them
  94. cc.eof = true
  95. } else if err != nil {
  96. return nil, err
  97. }
  98. }
  99. }
  100. // return n such that v <= n and n == 2**i for some i
  101. func roundUpToPowerOfTwo(v int) int {
  102. // http://graphics.stanford.edu/~seander/bithacks.html
  103. v -= 1
  104. v |= v >> 1
  105. v |= v >> 2
  106. v |= v >> 4
  107. v |= v >> 8
  108. v |= v >> 16
  109. return v + 1
  110. }