pktparser.go 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144
  1. package githttp
  2. import (
  3. "encoding/hex"
  4. "errors"
  5. "fmt"
  6. )
  7. // pktLineParser is a parser for git pkt-line Format,
  8. // as documented in https://github.com/git/git/blob/master/Documentation/technical/protocol-common.txt.
  9. // A zero value of pktLineParser is valid to use as a parser in ready state.
  10. // Output should be read from Lines and Error after Step returns finished true.
  11. // pktLineParser reads until a terminating "0000" flush-pkt. It's good for a single use only.
  12. type pktLineParser struct {
  13. // Lines contains all pkt-lines.
  14. Lines []string
  15. // Error contains the first error encountered while parsing, or nil otherwise.
  16. Error error
  17. // Internal state machine.
  18. state state
  19. next int // next is the number of bytes that need to be written to buf before its contents should be processed by the state machine.
  20. buf []byte
  21. }
  22. // Feed accumulates and parses data.
  23. // It will return early if it reaches end of pkt-line data (indicated by a flush-pkt "0000"),
  24. // or if it encounters a parsing error.
  25. // It must not be called when state is done.
  26. // When done, all of pkt-lines will be available in Lines, and Error will be set if any error occurred.
  27. func (p *pktLineParser) Feed(data []byte) {
  28. for {
  29. // If not enough data to reach next state, append it to buf and return.
  30. if len(data) < p.next {
  31. p.buf = append(p.buf, data...)
  32. p.next -= len(data)
  33. return
  34. }
  35. // There's enough data to reach next state. Take from data only what's needed.
  36. b := data[:p.next]
  37. data = data[p.next:]
  38. p.buf = append(p.buf, b...)
  39. p.next = 0
  40. // Take a step to next state.
  41. err := p.step()
  42. if err != nil {
  43. p.state = done
  44. p.Error = err
  45. return
  46. }
  47. // Break out once reached done state.
  48. if p.state == done {
  49. return
  50. }
  51. }
  52. }
  53. const (
  54. // pkt-len = 4*(HEXDIG)
  55. pktLenSize = 4
  56. )
  57. type state uint8
  58. const (
  59. ready state = iota
  60. readingLen
  61. readingPayload
  62. done
  63. )
  64. // step moves the state machine to the next state.
  65. // buf must contain all the data ready for consumption for current state.
  66. // It must not be called when state is done.
  67. func (p *pktLineParser) step() error {
  68. switch p.state {
  69. case ready:
  70. p.state = readingLen
  71. p.next = pktLenSize
  72. return nil
  73. case readingLen:
  74. // len(p.buf) is 4.
  75. pktLen, err := parsePktLen(p.buf)
  76. if err != nil {
  77. return err
  78. }
  79. switch {
  80. case pktLen == 0:
  81. p.state = done
  82. p.next = 0
  83. p.buf = nil
  84. return nil
  85. default:
  86. p.state = readingPayload
  87. p.next = pktLen - pktLenSize // (pkt-len - 4)*(OCTET)
  88. p.buf = p.buf[:0]
  89. return nil
  90. }
  91. case readingPayload:
  92. p.state = readingLen
  93. p.next = pktLenSize
  94. p.Lines = append(p.Lines, string(p.buf))
  95. p.buf = p.buf[:0]
  96. return nil
  97. default:
  98. panic(fmt.Errorf("unreachable: %v", p.state))
  99. }
  100. }
  101. // parsePktLen parses a pkt-len segment.
  102. // len(b) must be 4.
  103. func parsePktLen(b []byte) (int, error) {
  104. pktLen, err := parseHex(b)
  105. switch {
  106. case err != nil:
  107. return 0, err
  108. case 1 <= pktLen && pktLen < pktLenSize:
  109. return 0, fmt.Errorf("invalid pkt-len: %v", pktLen)
  110. case pktLen > 65524:
  111. // The maximum length of a pkt-line is 65524 bytes (65520 bytes of payload + 4 bytes of length data).
  112. return 0, fmt.Errorf("invalid pkt-len: %v", pktLen)
  113. }
  114. return int(pktLen), nil
  115. }
  116. // parseHex parses a 4-byte hex number.
  117. // len(h) must be 4.
  118. func parseHex(h []byte) (uint16, error) {
  119. var b [2]uint8
  120. n, err := hex.Decode(b[:], h)
  121. switch {
  122. case err != nil:
  123. return 0, err
  124. case n != 2:
  125. return 0, errors.New("short output")
  126. }
  127. return uint16(b[0])<<8 | uint16(b[1]), nil
  128. }