-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathFwtp.hs
199 lines (156 loc) · 6.66 KB
/
Fwtp.hs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
module Fwtp where
import Data.Binary (Word8)
import Data.ByteString (ByteString)
import Data.ByteString qualified as B
import Data.Text (pack, unpack)
import Data.Text.Encoding (decodeUtf8, encodeUtf8)
import Game
import Network.Socket
import Network.Socket.ByteString (recv, send)
import UserInterface (getTurn)
import Utils
defaultHostFwtp :: String
defaultHostFwtp = "127.0.0.1"
defaultPortFwtp :: PortNumber
defaultPortFwtp = 4444
fwtpVersion :: Version
fwtpVersion = 1
defaultPacketFieldDelimiter :: Delimiter
defaultPacketFieldDelimiter = '|'
defaultListDelimiter :: Delimiter
defaultListDelimiter = ','
type FwtpErrorCode = Int
type FwtpErrorMsg = String
type Version = Int
type VersionList = [Version]
data FwtpConnection = FwtpConnection Version Socket
data FwtpPacket = FwtpHandshakeInitPacket VersionList | FwtpHandshakeAckPacket Version | FwtpErrorPacket FwtpErrorCode FwtpErrorMsg | FwtpTurnPacket XIndex | FwtpInvaildPacket (Maybe String) deriving (Eq, Show)
data FtwpPacketType = FwtpHandshakeInit | FwtpHandshakeAck | FwtpError | FwtpTurn | FwtpInvaild deriving (Eq, Show)
fwtpPacketCode :: FtwpPacketType -> Int
fwtpPacketCode FwtpHandshakeInit = 0
fwtpPacketCode FwtpHandshakeAck = 1
fwtpPacketCode FwtpError = 2
fwtpPacketCode FwtpTurn = 3
fwtpPacketType :: Int -> Maybe FtwpPacketType
fwtpPacketType i
| i == fwtpPacketCode FwtpHandshakeInit = Just FwtpHandshakeInit
| i == fwtpPacketCode FwtpHandshakeAck = Just FwtpHandshakeAck
| i == fwtpPacketCode FwtpError = Just FwtpError
| i == fwtpPacketCode FwtpTurn = Just FwtpTurn
| otherwise = Nothing
fwtpPacketOfType :: FwtpPacket -> FtwpPacketType -> Bool
fwtpPacketOfType (FwtpHandshakeInitPacket _) FwtpHandshakeInit = True
fwtpPacketOfType (FwtpHandshakeAckPacket _) FwtpHandshakeAck = True
fwtpPacketOfType (FwtpErrorPacket _ _) FwtpError = True
fwtpPacketOfType (FwtpTurnPacket _) FwtpTurn = True
fwtpPacketOfType (FwtpInvaildPacket _) FwtpInvaild = True
fwtpPacketOfType _ _ = False
unpackPacket :: Version -> ByteString -> FwtpPacket
unpackPacket 1 raw
| packetType == FwtpHandshakeInit = let versionList = map read (getFieldsDelimitedBy defaultListDelimiter (getPacketField 1)) in FwtpHandshakeInitPacket versionList
| packetType == FwtpHandshakeAck = FwtpHandshakeAckPacket (read $ getPacketField 1)
| packetType == FwtpError = FwtpErrorPacket (read $ getPacketField 1) (getPacketField 2)
| packetType == FwtpTurn = FwtpTurnPacket (read $ getPacketField 1)
| otherwise = FwtpInvaildPacket (Just rawString)
where
rawString = unpack $ decodeUtf8 raw
getPacketField = getFieldDelimitedBy defaultPacketFieldDelimiter rawString
Just packetType = fwtpPacketType $ read $ getPacketField 0
receiveNext :: FwtpConnection -> IO FwtpPacket
receiveNext conn@(FwtpConnection 1 sock) =
do
raw <- recv sock 1024
let packet = unpackPacket 1 raw
return packet
receiveUntilNext :: FwtpConnection -> FtwpPacketType -> IO [FwtpPacket] -- NOTICE: This function has the problem that other packets sent before are only handled after the expected type was matched once.
receiveUntilNext conn@(FwtpConnection 1 sock) packetType = receiveUntilNext' ([] :: [FwtpPacket])
where
receiveUntilNext' :: [FwtpPacket] -> IO [FwtpPacket]
receiveUntilNext' packets =
do
packet <- receiveNext conn
if fwtpPacketOfType packet packetType
then return (packet : packets)
else receiveUntilNext' (packet : packets)
getOpponentTurn :: FwtpConnection -> IO (Int, [FwtpPacket])
getOpponentTurn conn =
do
turnPacket@(FwtpTurnPacket x) : packets <- receiveUntilNext conn FwtpTurn
return (x, packets)
sendErr :: Socket -> FwtpErrorCode -> FwtpErrorMsg -> IO ()
sendErr sock errCode errMsg =
do
_ <- send sock (encodeUtf8 (pack (show (fwtpPacketCode FwtpError) ++ defaultPacketFieldDelimiter : show errCode ++ defaultPacketFieldDelimiter : errMsg)))
return ()
sendTurn :: FwtpConnection -> Int -> IO ()
sendTurn conn@(FwtpConnection 1 sock) x =
do
_ <- send sock (encodeUtf8 (pack (show (fwtpPacketCode FwtpTurn) ++ defaultPacketFieldDelimiter : show x ++ "\n")))
return ()
incomingHandshake :: Socket -> IO (Maybe Version)
incomingHandshake sock =
do
res@(FwtpHandshakeInitPacket verList) : packets <- receiveUntilNext (FwtpConnection fwtpVersion sock) FwtpHandshakeInit -- TODO handle incoming Error instead of ack
let matchingVersion
| fwtpVersion `elem` verList = Just fwtpVersion
| otherwise = Nothing
case matchingVersion of
Just ver ->
do
_ <- send sock (encodeUtf8 (pack (show (fwtpPacketCode FwtpHandshakeAck) ++ defaultPacketFieldDelimiter : show fwtpVersion ++ "\n")))
return matchingVersion
Nothing ->
do
sendErr sock 1 "No Matching Version"
return Nothing
outgoingHandshake :: Socket -> IO (Maybe Version)
outgoingHandshake sock =
do
_ <- send sock (encodeUtf8 (pack (show (fwtpPacketCode FwtpHandshakeInit) ++ defaultPacketFieldDelimiter : show fwtpVersion ++ "\n")))
res@(FwtpHandshakeAckPacket matchingVer) : packets <- receiveUntilNext (FwtpConnection fwtpVersion sock) FwtpHandshakeAck
if matchingVer == fwtpVersion
then return (Just matchingVer)
else do
sendErr sock 1 "No Matching Version"
return Nothing
serveFwtp :: IO (Maybe FwtpConnection)
serveFwtp =
withSocketsDo $ do
let hints = defaultHints {addrFlags = [AI_NUMERICHOST], addrSocketType = Stream}
addr : _ <- getAddrInfo (Just hints) (Just "0.0.0.0") (Just (show defaultPortFwtp))
listenSock <- openSocket addr
setSocketOption listenSock ReuseAddr 1
Network.Socket.bind listenSock (addrAddress addr)
Network.Socket.listen listenSock 1
(sock, addr) <- accept listenSock
matchingVersion <- outgoingHandshake sock
print matchingVersion
case matchingVersion of
Just ver -> return $ Just $ FwtpConnection ver sock
Nothing -> return Nothing
connectFwtp :: String -> PortNumber -> IO (Maybe FwtpConnection)
connectFwtp host port =
withSocketsDo $ do
sock <- socket AF_INET Stream defaultProtocol
connect sock (SockAddrInet port (ip4StringToHostAddress host))
matchingVersion <- incomingHandshake sock
print matchingVersion
case matchingVersion of
Just ver -> return $ Just $ FwtpConnection ver sock
Nothing -> return Nothing
-- NOTICE: Just for testing
debugServe :: IO ()
debugServe =
do
Just conn <- serveFwtp
debugServe' conn
where
debugServe' :: FwtpConnection -> IO ()
debugServe' conn@(FwtpConnection 1 sock) =
do
raw <- recv sock 1024
putStr "Incoming: "
B.putStr raw
putStr "Unpacked: "
print $ unpackPacket 1 raw
debugServe' conn