ReadFrame reads a framed message, returning the decoded tag and data. If the message size exceeds MaxMessageSizeV1, ErrMessageTooLarge is returned. The provided buf is used if it has sufficient capacity; otherwise a new buffer is allocated. To reuse the buffer across calls, pass in the returned data
(r io.Reader, buf []byte)
| 94 | // _, buf, _ = ReadFrame(r, buf) |
| 95 | // } |
| 96 | func ReadFrame(r io.Reader, buf []byte) (Tag, []byte, error) { |
| 97 | var header uint32 |
| 98 | if err := binary.Read(r, binary.BigEndian, &header); err != nil { |
| 99 | return 0, nil, xerrors.Errorf("read header error: %w", err) |
| 100 | } |
| 101 | |
| 102 | const lengthMask = (1 << DataLength) - 1 |
| 103 | length := header & lengthMask |
| 104 | const tagMask = (1 << tagLength) - 1 // 0xFF |
| 105 | shifted := (header >> DataLength) & tagMask |
| 106 | if shifted > tagMask { |
| 107 | // This is really only here to satisfy the gosec linter. We know from above that |
| 108 | // shifted <= tagMask. |
| 109 | return 0, nil, xerrors.Errorf("invalid tag: %d", shifted) |
| 110 | } |
| 111 | tag := Tag(shifted) |
| 112 | |
| 113 | maxSize, err := maxSizeForTag(tag) |
| 114 | if err != nil { |
| 115 | return 0, nil, err |
| 116 | } |
| 117 | |
| 118 | if length > maxSize { |
| 119 | return 0, nil, ErrMessageTooLarge |
| 120 | } |
| 121 | |
| 122 | if cap(buf) < int(length) { |
| 123 | buf = make([]byte, length) |
| 124 | } else { |
| 125 | buf = buf[:length:cap(buf)] |
| 126 | } |
| 127 | |
| 128 | if _, err := io.ReadFull(r, buf[:length]); err != nil { |
| 129 | return 0, nil, xerrors.Errorf("read full error: %w", err) |
| 130 | } |
| 131 | |
| 132 | return tag, buf[:length], nil |
| 133 | } |
| 134 | |
| 135 | // maxSizeForTag returns the maximum payload size for the given tag. |
| 136 | func maxSizeForTag(tag Tag) (uint32, error) { |