package navicat import ( "encoding/binary" "fmt" "io" ) type reader struct { buf []byte off int } func newReader(b []byte) *reader { return &reader{buf: b} } func (r *reader) remaining() int { return len(r.buf) - r.off } func (r *reader) peek() (byte, error) { if r.off >= len(r.buf) { return 0, io.EOF } return r.buf[r.off], nil } func (r *reader) readByte() (byte, error) { if r.off >= len(r.buf) { return 0, io.ErrUnexpectedEOF } b := r.buf[r.off] r.off++ return b, nil } func (r *reader) readN(n int) ([]byte, error) { if n < 0 { return nil, fmt.Errorf("negative length") } if r.off+n > len(r.buf) { return nil, io.ErrUnexpectedEOF } s := r.buf[r.off : r.off+n] r.off += n return s, nil } func (r *reader) u16() (uint16, error) { b, err := r.readN(2) if err != nil { return 0, err } return binary.BigEndian.Uint16(b), nil } func (r *reader) u32() (uint32, error) { b, err := r.readN(4) if err != nil { return 0, err } return binary.BigEndian.Uint32(b), nil } // block reads GetBlock. nullable allows 0xFF as SQL NULL (EchoData only). func (r *reader) block(nullable bool) (val []byte, isNull bool, err error) { if r.off >= len(r.buf) { return nil, false, io.ErrUnexpectedEOF } first := r.buf[r.off] if nullable && first == markNull { r.off++ return nil, true, nil } r.off++ var n int if first < 254 { n = int(first) } else if first == markLong { ln, err := r.u32() if err != nil { return nil, false, err } if ln > 64<<20 { return nil, false, fmt.Errorf("block length %d exceeds 64MiB limit", ln) } n = int(ln) } else { return nil, false, fmt.Errorf("invalid block marker 0x%02x", first) } b, err := r.readN(n) if err != nil { return nil, false, err } return b, false, nil } func (r *reader) blockBytes() ([]byte, error) { b, isNull, err := r.block(false) if err != nil { return nil, err } if isNull { return nil, fmt.Errorf("unexpected NULL block") } return b, nil } func (r *reader) blockString() (string, error) { b, err := r.blockBytes() if err != nil { return "", err } return string(b), nil }