mysql-haskell-1.4.0: test/HandshakeGolden.hs
module HandshakeGolden (tests) where
import qualified Data.Binary as Binary
import Data.Bits ((.|.))
import qualified Data.ByteString as B
import qualified Data.ByteString.Base16 as Hex
import qualified Data.ByteString.Char8 as BC
import qualified Data.ByteString.Lazy as L
import Database.MySQL.Connection (handshakeResponsePacket, sslRequestPacket)
import Database.MySQL.Protocol.Auth
import Database.MySQL.Protocol.Packet (Packet, getFromPacket, putToPacket)
import Data.Binary.Put (putByteString)
import Test.Tasty
import Test.Tasty.HUnit
-- Greetings captured from the servers nix/ci.nix tests against, and the
-- packets mysql-haskell 1.3.3 (92a9401) answered them with for user and
-- schema testMySQLHaskell, password "secret" and charset 33.
mariadbGreeting, mysqlGreeting :: B.ByteString
mariadbGreeting = hex "0a31312e342e392d4d6172696144422d6c6f67000a00000068513b714856583500feffe00200ff81150000000000001d0000003e78646c727473657e6b7a47006d7973716c5f6e61746976655f70617373776f726400"
mysqlGreeting = hex "0a382e302e3435001200000073454f634d16264e00ffffff0200ffdf15000000000000000000005d2658501266396b0d7546110063616368696e675f736861325f70617373776f726400"
masterAuthMariaDB, masterTLSAuthMariaDB, masterAuthMySQL, masterTLSAuthMySQL, masterSSLRequest :: B.ByteString
masterAuthMariaDB = hex "6d0000010da30b00ffffff00210000000000000000000000000000000000000000000000746573744d7953514c4861736b656c6c00148993d478ffb390507c14ce8d6da10baff99ac06c746573744d7953514c4861736b656c6c006d7973716c5f6e61746976655f70617373776f726400"
masterTLSAuthMariaDB = hex "6d0000020da30b00ffffff00210000000000000000000000000000000000000000000000746573744d7953514c4861736b656c6c00148993d478ffb390507c14ce8d6da10baff99ac06c746573744d7953514c4861736b656c6c006d7973716c5f6e61746976655f70617373776f726400"
masterAuthMySQL = hex "790000010da30b00ffffff00210000000000000000000000000000000000000000000000746573744d7953514c4861736b656c6c00208d6350e1245ca38229fd5cc86234ea67d2d19056795007230dbec8bd483e4b89746573744d7953514c4861736b656c6c0063616368696e675f736861325f70617373776f726400"
masterTLSAuthMySQL = hex "790000020da30b00ffffff00210000000000000000000000000000000000000000000000746573744d7953514c4861736b656c6c00208d6350e1245ca38229fd5cc86234ea67d2d19056795007230dbec8bd483e4b89746573744d7953514c4861736b656c6c0063616368696e675f736861325f70617373776f726400"
masterSSLRequest = hex "200000010dab0b00ffffff00210000000000000000000000000000000000000000000000"
hex :: String -> B.ByteString
hex = either error id . Hex.decode . BC.pack
parseGreeting :: B.ByteString -> IO (Greeting, ExtendedCapabilities)
parseGreeting = getFromPacket getGreetingExtended . putToPacket 0 . putByteString
wire :: Packet -> B.ByteString
wire = L.toStrict . Binary.encode
-- | Every byte one handshake sends: SSL request, response after it, and the
-- response without TLS.
handshakeBytes :: ExtendedCapabilities -> (Greeting, ExtendedCapabilities) -> (B.ByteString, B.ByteString, B.ByteString)
handshakeBytes requested (greeting, offered) =
let negotiated = negotiateExtended requested greeting offered
response seqN = wire (handshakeResponsePacket seqN negotiated "testMySQLHaskell" "testMySQLHaskell" "secret" 33 greeting)
in (wire (sslRequestPacket negotiated 33), response 2, response 1)
extendedMetadata, everyCapability :: ExtendedCapabilities
extendedMetadata = ExtendedCapabilities 0x08
everyCapability = ExtendedCapabilities 0xffffffff
tests :: TestTree
tests = testGroup "handshake"
[ testCase "greetings: MariaDB offers extended capabilities, MySQL none" $ do
(_, mariadbOffer) <- parseGreeting mariadbGreeting
(_, mysqlOffer) <- parseGreeting mysqlGreeting
mariadbOffer @?= ExtendedCapabilities 0x1d
mysqlOffer @?= noExtendedCapabilities
, testCase "MariaDB is told apart by its greeting's capabilities, not its version string" $ do
(mariadb, _) <- parseGreeting mariadbGreeting
(mysql, _) <- parseGreeting mysqlGreeting
offersExtendedCapabilities mariadb @?= True
offersExtendedCapabilities mysql @?= False
-- mariadbd --version=8.0.36 sends exactly this greeting.
offersExtendedCapabilities mariadb {greetingVersion = "8.0.36"} @?= True
, testCase "MySQL: 1.3.3's bytes whatever is requested" $ do
greeting <- parseGreeting mysqlGreeting
let master = (masterSSLRequest, masterTLSAuthMySQL, masterAuthMySQL)
handshakeBytes noExtendedCapabilities greeting @?= master
handshakeBytes extendedMetadata greeting @?= master
handshakeBytes everyCapability greeting @?= master
, testCase "MariaDB, nothing requested: 1.3.3's bytes" $ do
greeting <- parseGreeting mariadbGreeting
handshakeBytes noExtendedCapabilities greeting
@?= (masterSSLRequest, masterTLSAuthMariaDB, masterAuthMariaDB)
, testCase "MariaDB, a capability it does not offer: 1.3.3's bytes" $ do
greeting <- parseGreeting mariadbGreeting
handshakeBytes (ExtendedCapabilities 0x02) greeting
@?= (masterSSLRequest, masterTLSAuthMariaDB, masterAuthMariaDB)
, testCase "a server setting CLIENT_MYSQL gets 1.3.3's bytes, whatever it offers" $ do
(greeting, offered) <- parseGreeting mariadbGreeting
let mysqlFlagged = greeting { greetingCaps = greetingCaps greeting .|. 1 }
handshakeBytes extendedMetadata (mysqlFlagged, offered)
@?= (masterSSLRequest, masterTLSAuthMariaDB, masterAuthMariaDB)
, testCase "MariaDB, extended metadata: CLIENT_MYSQL cleared, bit 35 sent" $ do
greeting <- parseGreeting mariadbGreeting
let (ssl, tlsAuth, auth) = handshakeBytes extendedMetadata greeting
-- 4 header bytes, then capabilities; the extended capabilities
-- are the last four of the 23 reserved bytes.
expect master = B.concat
[ B.take 4 master, hex "0ca30b00", B.take 24 (B.drop 8 master)
, hex "08000000", B.drop 36 master ]
auth @?= expect masterAuthMariaDB
tlsAuth @?= expect masterTLSAuthMariaDB
ssl @?= B.concat [B.take 4 masterSSLRequest, hex "0cab0b00", B.take 24 (B.drop 8 masterSSLRequest), hex "08000000"]
]