Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.

ircreader.go 3.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117
  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. IRCReader 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 IRCReader 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 *IRCReader with sane buffer size limits.
  30. func NewIRCReader(conn io.Reader) *IRCReader {
  31. var reader IRCReader
  32. reader.Initialize(conn, 512, 8192+1024)
  33. return &reader
  34. }
  35. // "Placement new" for an IRCReader; initializes it with custom buffer size
  36. // limits.
  37. func (cc *IRCReader) Initialize(conn io.Reader, initialSize, maxSize int) {
  38. *cc = IRCReader{}
  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 *IRCReader) 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. return line, nil
  57. }
  58. if cc.start == 0 && len(cc.buf) == cc.maxSize {
  59. return nil, ErrReadQ // out of space, can't expand or slide
  60. }
  61. if cc.eof {
  62. return nil, io.EOF
  63. }
  64. if len(cc.buf) < cc.maxSize && (len(cc.buf)-(cc.end-cc.start) < cc.initialSize/2) {
  65. // allocate a new buffer, copy any remaining data
  66. newLen := roundUpToPowerOfTwo(len(cc.buf) + 1)
  67. if newLen > cc.maxSize {
  68. newLen = cc.maxSize
  69. } else if newLen < cc.initialSize {
  70. newLen = cc.initialSize
  71. }
  72. newBuf := make([]byte, newLen)
  73. copy(newBuf, cc.buf[cc.start:cc.end])
  74. cc.buf = newBuf
  75. } else if cc.start != 0 {
  76. // slide remaining data back to the front of the buffer
  77. copy(cc.buf, cc.buf[cc.start:cc.end])
  78. }
  79. cc.end = cc.end - cc.start
  80. cc.start = 0
  81. cc.searchFrom = cc.end
  82. n, err := cc.conn.Read(cc.buf[cc.end:])
  83. cc.end += n
  84. if n != 0 && err == io.EOF {
  85. // we may have received new \n-terminated lines, try to parse them
  86. cc.eof = true
  87. } else if err != nil {
  88. return nil, err
  89. }
  90. }
  91. }
  92. // return n such that v <= n and n == 2**i for some i
  93. func roundUpToPowerOfTwo(v int) int {
  94. // http://graphics.stanford.edu/~seander/bithacks.html
  95. v -= 1
  96. v |= v >> 1
  97. v |= v >> 2
  98. v |= v >> 4
  99. v |= v >> 8
  100. v |= v >> 16
  101. return v + 1
  102. }