204 lines
4.1 KiB
Go
204 lines
4.1 KiB
Go
|
/*
|
||
|
Copyright 2014 SAP SE
|
||
|
|
||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||
|
you may not use this file except in compliance with the License.
|
||
|
You may obtain a copy of the License at
|
||
|
|
||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||
|
|
||
|
Unless required by applicable law or agreed to in writing, software
|
||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
|
See the License for the specific language governing permissions and
|
||
|
limitations under the License.
|
||
|
*/
|
||
|
|
||
|
package protocol
|
||
|
|
||
|
import (
|
||
|
"log"
|
||
|
"net"
|
||
|
|
||
|
"github.com/SAP/go-hdb/internal/bufio"
|
||
|
)
|
||
|
|
||
|
type dir bool
|
||
|
|
||
|
const (
|
||
|
maxBinarySize = 128
|
||
|
)
|
||
|
|
||
|
type fragment interface {
|
||
|
read(rd *bufio.Reader) error
|
||
|
write(wr *bufio.Writer) error
|
||
|
}
|
||
|
|
||
|
func (d dir) String() string {
|
||
|
if d {
|
||
|
return "->"
|
||
|
}
|
||
|
return "<-"
|
||
|
}
|
||
|
|
||
|
// A Sniffer is a simple proxy for logging hdb protocol requests and responses.
|
||
|
type Sniffer struct {
|
||
|
conn net.Conn
|
||
|
dbAddr string
|
||
|
dbConn net.Conn
|
||
|
|
||
|
//client
|
||
|
clRd *bufio.Reader
|
||
|
clWr *bufio.Writer
|
||
|
//database
|
||
|
dbRd *bufio.Reader
|
||
|
dbWr *bufio.Writer
|
||
|
|
||
|
mh *messageHeader
|
||
|
sh *segmentHeader
|
||
|
ph *partHeader
|
||
|
|
||
|
buf []byte
|
||
|
}
|
||
|
|
||
|
// NewSniffer creates a new sniffer instance. The conn parameter is the net.Conn connection, where the Sniffer
|
||
|
// is listening for hdb protocol calls. The dbAddr is the hdb host port address in "host:port" format.
|
||
|
func NewSniffer(conn net.Conn, dbAddr string) (*Sniffer, error) {
|
||
|
s := &Sniffer{
|
||
|
conn: conn,
|
||
|
dbAddr: dbAddr,
|
||
|
clRd: bufio.NewReader(conn),
|
||
|
clWr: bufio.NewWriter(conn),
|
||
|
mh: &messageHeader{},
|
||
|
sh: &segmentHeader{},
|
||
|
ph: &partHeader{},
|
||
|
buf: make([]byte, 0),
|
||
|
}
|
||
|
|
||
|
dbConn, err := net.Dial("tcp", s.dbAddr)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
s.dbRd = bufio.NewReader(dbConn)
|
||
|
s.dbWr = bufio.NewWriter(dbConn)
|
||
|
s.dbConn = dbConn
|
||
|
return s, nil
|
||
|
}
|
||
|
|
||
|
func (s *Sniffer) getBuffer(size int) []byte {
|
||
|
if cap(s.buf) < size {
|
||
|
s.buf = make([]byte, size)
|
||
|
}
|
||
|
return s.buf[:size]
|
||
|
}
|
||
|
|
||
|
// Go starts the protocol request and response logging.
|
||
|
func (s *Sniffer) Go() {
|
||
|
defer s.dbConn.Close()
|
||
|
defer s.conn.Close()
|
||
|
|
||
|
req := newInitRequest()
|
||
|
if err := s.streamFragment(dir(true), s.clRd, s.dbWr, req); err != nil {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
rep := newInitReply()
|
||
|
if err := s.streamFragment(dir(false), s.dbRd, s.clWr, rep); err != nil {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
for {
|
||
|
//up stream
|
||
|
if err := s.stream(dir(true), s.clRd, s.dbWr); err != nil {
|
||
|
return
|
||
|
}
|
||
|
//down stream
|
||
|
if err := s.stream(dir(false), s.dbRd, s.clWr); err != nil {
|
||
|
return
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (s *Sniffer) stream(d dir, from *bufio.Reader, to *bufio.Writer) error {
|
||
|
|
||
|
if err := s.streamFragment(d, from, to, s.mh); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
size := int(s.mh.varPartLength)
|
||
|
|
||
|
for i := 0; i < int(s.mh.noOfSegm); i++ {
|
||
|
|
||
|
if err := s.streamFragment(d, from, to, s.sh); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
size -= int(s.sh.segmentLength)
|
||
|
|
||
|
for j := 0; j < int(s.sh.noOfParts); j++ {
|
||
|
|
||
|
if err := s.streamFragment(d, from, to, s.ph); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
// protocol error workaraound
|
||
|
padding := (size == 0) || (j != (int(s.sh.noOfParts) - 1))
|
||
|
|
||
|
if err := s.streamPart(d, from, to, s.ph, padding); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return to.Flush()
|
||
|
}
|
||
|
|
||
|
func (s *Sniffer) streamPart(d dir, from *bufio.Reader, to *bufio.Writer, ph *partHeader, padding bool) error {
|
||
|
|
||
|
switch ph.partKind {
|
||
|
|
||
|
default:
|
||
|
return s.streamBinary(d, from, to, int(ph.bufferLength), padding)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (s *Sniffer) streamBinary(d dir, from *bufio.Reader, to *bufio.Writer, size int, padding bool) error {
|
||
|
var b []byte
|
||
|
|
||
|
//protocol error workaraound
|
||
|
if padding {
|
||
|
pad := padBytes(size)
|
||
|
b = s.getBuffer(size + pad)
|
||
|
} else {
|
||
|
b = s.getBuffer(size)
|
||
|
}
|
||
|
|
||
|
from.ReadFull(b)
|
||
|
err := from.GetError()
|
||
|
if err != nil {
|
||
|
log.Print(err)
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
if size > maxBinarySize {
|
||
|
log.Printf("%s %v", d, b[:maxBinarySize])
|
||
|
} else {
|
||
|
log.Printf("%s %v", d, b[:size])
|
||
|
}
|
||
|
to.Write(b)
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (s *Sniffer) streamFragment(d dir, from *bufio.Reader, to *bufio.Writer, f fragment) error {
|
||
|
if err := f.read(from); err != nil {
|
||
|
log.Print(err)
|
||
|
return err
|
||
|
}
|
||
|
log.Printf("%s %s", d, f)
|
||
|
if err := f.write(to); err != nil {
|
||
|
log.Print(err)
|
||
|
return err
|
||
|
}
|
||
|
return nil
|
||
|
}
|