package mysql import ( "bytes" "encoding/binary" ) // parseHandshakeResponseHeader parses the common header of SSLRequest and HandshakeResponse41. func parseHandshakeResponseHeader(packet *handshakeResponse41, data []byte) (parsedBytes int, err error) { // Ensure there are enough data to read: // http://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::SSLRequest if len(data) < 4+4+1+23 { return 0, ErrMalformPacket } offset := 0 // capability capability := binary.LittleEndian.Uint32(data[:4]) packet.Capability = capability offset += 4 // skip max packet size offset += 4 // charset, skip, if you want to use another charset, use set names packet.Collation = data[offset] offset++ // skip reserved 23[00] offset += 23 return offset, nil } // parseHandshakeResponseBody parse the HandshakeResponse (except the common header part). func parseHandshakeResponseBody(packet *handshakeResponse41, data []byte, offset int) (err error) { defer func() { // Check malformat packet cause out of range is disgusting, but don't panic! if r := recover(); r != nil { err = ErrMalformPacket } }() // user name packet.User = string(data[offset : offset+bytes.IndexByte(data[offset:], 0)]) offset += len(packet.User) + 1 if packet.Capability&ClientPluginAuthLenencClientData > 0 { // MySQL client sets the wrong capability, it will set this bit even server doesn't // support ClientPluginAuthLenencClientData. // https://github.com/mysql/mysql-server/blob/5.7/sql-common/client.c#L3478 num, null, off := parseLengthEncodedInt(data[offset:]) offset += off if !null { packet.Auth = data[offset : offset+int(num)] offset += int(num) } } else if packet.Capability&ClientSecureConnection > 0 { // auth length and auth authLen := int(data[offset]) offset++ packet.Auth = data[offset : offset+authLen] offset += authLen } else { packet.Auth = data[offset : offset+bytes.IndexByte(data[offset:], 0)] offset += len(packet.Auth) + 1 } if packet.Capability&ClientConnectWithDB > 0 { if len(data[offset:]) > 0 { idx := bytes.IndexByte(data[offset:], 0) packet.DBName = string(data[offset : offset+idx]) offset = offset + idx + 1 } } if packet.Capability&ClientPluginAuth > 0 { // TODO: Support mysql.ClientPluginAuth, skip it now idx := bytes.IndexByte(data[offset:], 0) offset = offset + idx + 1 } if packet.Capability&ClientConnectAtts > 0 { if len(data[offset:]) == 0 { // Defend some ill-formated packet, connection attribute is not important and can be ignored. return nil } } return nil } func parseLengthEncodedInt(b []byte) (num uint64, isNull bool, n int) { switch b[0] { // 251: NULL case 0xfb: n = 1 isNull = true return // 252: value of following 2 case 0xfc: num = uint64(b[1]) | uint64(b[2])<<8 n = 3 return // 253: value of following 3 case 0xfd: num = uint64(b[1]) | uint64(b[2])<<8 | uint64(b[3])<<16 n = 4 return // 254: value of following 8 case 0xfe: num = uint64(b[1]) | uint64(b[2])<<8 | uint64(b[3])<<16 | uint64(b[4])<<24 | uint64(b[5])<<32 | uint64(b[6])<<40 | uint64(b[7])<<48 | uint64(b[8])<<56 n = 9 return } // 0-250: value of first byte num = uint64(b[0]) n = 1 return } func extractMysqlPayloadSize(payload []byte) int { header := payload[:4] return int(uint32(header[0]) | uint32(header[1])<<8 | uint32(header[2])<<16) } func bytesToInt(contents []byte) int { return int(uint32(contents[0]) | uint32(contents[1])<<8 | uint32(contents[2])<<16 | uint32(contents[3])<<24) }