1 """
2 The server classes.
3 """
4
5 import sys
6
7 from twisted.internet import reactor
8 from twisted.internet import protocol
9 from twisted.internet.task import LoopingCall
10
11 from lacewing.protocol import BaseProtocol
12 from lacewing.idpool import IDPool
13 from lacewing.multidict import MultikeyDict
14 from lacewing.packet import ServerPacket, ClientPacket
15 from lacewing.packetloaders import client, server
16 from lacewing.packetloaders.common import (detectType, CONNECT, SET_NAME,
17 JOIN_CHANNEL, LEAVE_CHANNEL, CHANNEL_LIST)
18 import lacewing
19
21 """
22 Represents a channel.
23
24 @ivar id: ID of the channel.
25 @ivar name: The name of the channel.
26 @ivar connections: List of the clients in the channel.
27 @ivar autoClose: True if this channel will close after the master leaves
28 @ivar master: The master connection of this channel
29 @type master: L{ServerProtocol} object
30 """
31
32 id = None
33 name = None
34 hidden = False
35 autoClose = False
36 master = None
37
38 - def __init__(self, name, id, hidden, autoClose, master):
45
60
87
88 - def sendMessage(self, fromConnection, message, subchannel = 0,
89 asObject = False, typeName = None, **settings):
90 """
91 Send a channel message from the given protocol instance.
92
93 @param fromConnection: The client that the message is sent
94 from.
95 @param message: The message to send.
96 @type message: str/number/ByteReader
97 @param subchannel: The subchannel to send the message on.
98 @type subchannel: number
99 @param typeName: If not specified, the type will be
100 automatically detected (see L{lacewing.packetloaders.common.DATA_TYPES}
101 for possible values)
102 """
103 if typeName is None:
104 typeName = detectType(message)
105 if asObject:
106 newMessage = server.ObjectChannelMessage()
107 else:
108 newMessage = server.BinaryChannelMessage()
109 newMessage.channel = self.id
110 newMessage.value = message
111 newMessage.subchannel = subchannel
112 newMessage.peer = fromConnection.id
113 newMessage.setDataType(typeName)
114 self.sendLoader(newMessage, [fromConnection], **settings)
115
116 - def sendPrivateMessage(self, message, subchannel, sender, recipient,
117 asObject = False, typeName = None, **settings):
118 """
119 Send a message from this client to a recipient
120
121 @type message: str/number/ByteReader
122 @param subchannel: The subchannel of the message in the range 0-256
123 @param sender: Sending connection
124 @type sender: L{ServerProtocol} object
125 @param recipient: Connection to send message to
126 @type recipient: L{ServerProtocol} object
127 @param typeName: If not specified, the type will be
128 automatically detected (see L{lacewing.packetloaders.common.DATA_TYPES}
129 for possible values)
130 """
131 if typeName is None:
132 typeName = detectType(message)
133 if asObject:
134 newMessage = server.ObjectPeerMessage()
135 else:
136 newMessage = server.BinaryPeerMessage()
137 newMessage.channel = self.id
138 newMessage.value = message
139 newMessage.subchannel = subchannel
140 newMessage.peer = sender.id
141 newMessage.setDataType(typeName)
142 recipient.sendLoader(newMessage, **settings)
143
144 - def sendLoader(self, type, notClients = [], **settings):
155
157 """
158 The server protocol.
159 @ivar latency: The latency between the server and client.
160 @ivar datagramPort: The UDP port that the connection
161 sends/receives data on.
162 """
163 _timeoutCall = None
164
165 _currentPing = None
166 _pingTime = None
167 latency = None
168
169 datagramPort = None
170 _receivePacket = ClientPacket
171
183
184 - def ping(self, timeOut = None):
199
219
221 packetId = loader.id
222 if packetId == client.Request.id:
223 if loader.request == CONNECT:
224 if self.isAccepted:
225 self.disconnect(CONNECT, 'Already accepted')
226 return
227
228 if self._timeoutCall is not None:
229 self._timeoutCall.cancel()
230 self._timeoutCall = None
231
232 factory = self.factory
233
234 if loader.version != self.revision:
235 self.disconnect(CONNECT, 'Invalid revision')
236 return
237
238
239
240 acceptedConnections = [connection for connection in
241 factory.connections.values() if connection.isAccepted]
242
243 if len(acceptedConnections)+1 > self.factory.maxUsers:
244 self.disconnect(CONNECT, 'Server full')
245 return
246
247
248 if self.acceptConnection(loader) == False:
249 return
250 self.id = factory.userPool.pop()
251
252 self.factory.connections[(self.id,)] = self
253
254 newWelcome = server.Response()
255 newWelcome.response = CONNECT
256 newWelcome.playerId = self.id
257 newWelcome.success = True
258 newWelcome.welcome = self.factory.getWelcomeMessage(self)
259 self.sendLoader(newWelcome)
260 self.isAccepted = True
261 self.connectionAccepted(loader)
262
263 elif loader.request == SET_NAME:
264 name = loader.name
265
266 if self.acceptLogin(name) == False:
267 return
268
269 if not self.validName(name):
270 self.warn(SET_NAME, 'Invalid name', name = name)
271 return
272
273 for channel in self.channels.values():
274 if name in channel.connections:
275 self.warn(SET_NAME, 'Name already taken', name = name)
276 return
277
278 if not self.loggedIn:
279 self.setName(name)
280 self.loginAccepted(name)
281 else:
282 self.setName(name)
283 self.nameChanged(name)
284
285 elif loader.request == JOIN_CHANNEL:
286 channelName = loader.name
287 if not self.loggedIn:
288 self.disconnect(JOIN_CHANNEL, 'Name not set',
289 name = channelName)
290 return
291 if channelName in self.channels:
292 self.warn(JOIN_CHANNEL, 'Already part of channel',
293 name = channelName)
294 return
295 if self.acceptChannelJoin(channelName) == False:
296 return
297 if not self.validName(channelName):
298 self.warn(JOIN_CHANNEL, 'Invalid channel name',
299 name = channelName)
300 return
301 try:
302 channel, = self.factory.channels[channelName]
303 if self.name in channel.connections:
304 self.warn(JOIN_CHANNEL, 'Name already taken',
305 name = channelName)
306 return
307 except KeyError:
308 pass
309 flags = loader.flags
310 channel = self.joinChannel(channelName, flags['HideChannel'],
311 flags['AutoClose'])
312 self.channelJoined(channel)
313
314 elif loader.request == LEAVE_CHANNEL:
315 channelId = loader.channel
316 if not channelId in self.channels:
317 self.warn(LEAVE_CHANNEL, 'No such channel',
318 channel = channelId)
319 return
320 channel, = self.channels[channelId]
321 if self.acceptChannelLeave(channel) == False:
322 return
323 self.leaveChannel(channel)
324 self.channelLeft(channel)
325
326 elif loader.request == CHANNEL_LIST:
327 if not self.factory.channelListing:
328 self.disconnect(CHANNEL_LIST, 'Channel listing not enabled')
329 return
330 if self.acceptChannelListRequest() == False:
331 return
332 channels = []
333 for channel in self.factory.channels.values():
334 if not channel.hidden:
335 channels.append(
336 (channel.name, len(channel.connections)))
337 self.sendChannelList(channels)
338 self.channelListSent()
339
340 elif packetId in (client.BinaryServerMessage.id,
341 client.ObjectServerMessage.id):
342 self.messageReceived(loader)
343
344 elif packetId in (client.BinaryChannelMessage.id,
345 client.ObjectChannelMessage.id):
346 channelId = loader.channel
347
348 if not channelId in self.channels:
349 return
350
351 channel, = self.channels[channelId]
352
353 if self.acceptChannelMessage(channel, loader) == False:
354 return
355 channel.sendMessage(self, loader.value, loader.subchannel,
356 typeName = loader.getDataType(), asObject = loader.isObject,
357 asDatagram = loader.settings.get('datagram', False))
358 self.channelMessageReceived(channel, loader)
359
360 elif packetId in (client.BinaryPeerMessage.id,
361 client.ObjectPeerMessage.id):
362 channelId = loader.channel
363
364 if channelId not in self.channels:
365 return
366
367 channel, = self.channels[channelId]
368 try:
369 player, = channel.connections[loader.peer]
370 except KeyError:
371 return
372 if self.acceptPrivateMessage(channel, player, loader) == False:
373 return
374 channel.sendPrivateMessage(loader.value, loader.subchannel,
375 self, player, typeName = loader.getDataType(),
376 asObject = loader.isObject,
377 asDatagram = loader.settings.get('datagram', False))
378 self.privateMessageReceived(channel, player, loader)
379
380 elif packetId == client.ChannelMaster.id:
381 if not self.factory.masterRights:
382 return
383 channelId = loader.channel
384 if channelId not in self.channels:
385 return
386 channel, = self.channels[channelId]
387 if channel.master is not self:
388 return
389 try:
390 peer, = channel.connections[loader.peer]
391 except KeyError:
392 return
393 action = loader.action
394 if self.acceptMasterAction(channel, peer, action) == False:
395 return
396 if action == client.MASTER_KICK:
397 peer.leaveChannel(channel)
398 self.masterActionExecuted(channel, peer, action)
399
400 elif packetId == client.UDPHello.id and isDatagram:
401 if self.udpEnabled:
402 return
403 self.udpEnabled = True
404 self.sendLoader(server.UDPWelcome())
405
406 elif packetId == client.Pong.id:
407 if self._pingTime is None:
408 self.disconnect(CONNECT, 'No pong requested')
409 return
410 if self._currentPing:
411 self._currentPing.cancel()
412 self._currentPing = None
413 self.latency = reactor.seconds() - self._pingTime
414 self._pingTime = None
415 self.pongReceived(self.latency)
416 else:
417 print '(unexpected packet: %r)' % loader
418 self.disconnect()
419
420 - def sendLoader(self, loader, asDatagram = False):
421 """
422 Sends a packetloader to the client
423 @param loader: The packetloader to send.
424 @param asDatagram: True if the packet should be sent as a datagram
425 packet
426 """
427
428
429 if asDatagram:
430 if (self.factory.datagram is not None
431 and self.datagramPort is not None):
432 self.factory.datagram.sendLoader(self, loader)
433 else:
434 newPacket = ServerPacket()
435 newPacket.loader = loader
436 data = newPacket.generate()
437 self.transport.write(data)
438
439 - def disconnect(self, response = None, *arg, **kw):
440 """
441 Disconnect the the user for a specific reason.
442 """
443 if response is not None:
444 self.warn(response, **kw)
445 self.transport.loseConnection()
446
447 - def warn(self, response, warning = '', name = None, channel = None):
458
459 - def sendMessage(self, message, subchannel, typeName = None,
460 asObject = False, asDatagram = False):
461 """
462 Send a direct message to the client.
463
464 @type message: str/number/ByteReader
465 @param subchannel: The subchannel of the message in the range 0-256
466 @param typeName: If not specified, the type will be
467 automatically detected (see L{lacewing.packetloaders.common.DATA_TYPES}
468 for possible values)
469 """
470 if typeName is None:
471 typeName = detectType(message)
472 if asObject:
473 newMessage = server.ObjectServerMessage()
474 else:
475 newMessage = server.BinaryServerMessage()
476 newMessage.value = message
477 newMessage.subchannel = subchannel
478 newMessage.setDataType(typeName)
479 self.sendLoader(newMessage, asDatagram)
480
501
502 - def joinChannel(self, channelName, hidden = False, autoClose = False):
527
555
557 """
558 Sends a list of channels and their peer count to the other end of
559 the connection.
560
561 @param channels: List of 2-item tuples (channelName, peerCount)
562 """
563 channelList = server.Response()
564 channelList.response = CHANNEL_LIST
565 channelList.success = True
566 channelList.channels = channels
567 self.sendLoader(channelList)
568
570 """
571 Called when the server has accepted the requested connection.
572 @type welcome: L{client.Request} object
573 """
574
576 """
577 Called when the server has accepted the requested name.
578 @arg name: The name of the client.
579 @type name: str object
580 """
581
583 """
584 Called after a requested channel list is sent.
585 """
586
588 """
589 Called when the client changed name (after logging in).
590 @type name: str
591 @arg name: The name the client has changed to.
592 """
593
595 """
596 Called when a client message arrives.
597 @type message: L{client.BinaryServerMessage} or
598 L{client.ObjectServerMessage}
599 """
600
602 """
603 Called when the client sends a channel message.
604 @arg channel: the channel the user is sending to.
605 @type message: L{client.BinaryChannelMessage} or
606 L{client.ObjectChannelMessage}
607 """
608
610 """
611 Called when the client has sent a private message
612 @arg channel: the channel the user is sending from.
613 @type message: L{client.BinaryPeerMessage} or
614 L{client.ObjectPeerMessage}
615 @type recipient: L{ServerProtocol} object
616 """
617
619 """
620 Called when the client has joined a new channel.
621 @arg channel: The channel the client has joined.
622 """
623
625 """
626 Called when the client has left a channel.
627 @arg channel: The channel the client has left.
628 """
629
631 """
632 Called when a ping response has been received.
633 @arg latency: Round-trip time in seconds between the client and
634 the server.
635 """
636
638 """
639 Called when after a master action has been executed.
640 @arg channel: The channel the action took place in
641 @arg peer: The peer the action was executed on
642 @arg action: The action the master requested
643 @type action: Currently, only {pylacewing.constants.MASTER_KICK}
644 """
645
646
647
649 """
650 Cancels the client login if False is returned.
651 Useful if the server specifies client names.
652 @arg name: The requested client name.
653 @return: Return False to cancel client login.
654 @rtype: bool
655 """
656
658 """
659 Cancels the client name change if False is returned.
660 Useful if the server specifies client names.
661 @arg name: The requested client name.
662 @return: Return False to cancel name change.
663 @rtype: bool
664 """
665
667 """
668 Cancels the connection accept response if False is returned.
669 Useful for banning.
670 @type welcome: L{client.Request} object
671 @return: Return False to cancel connection acceptance.
672 @rtype: bool
673 """
674
676 """
677 Cancels the channel message if False is returned.
678 Useful for blocking invalid messages.
679 @type message: L{client.BinaryChannelMessage} or
680 L{client.ObjectChannelMessage}
681 @return: Return False to stop the channel message
682 before it is sent.
683 @rtype: bool
684 """
685
687 """
688 Cancels the private message if False is returned.
689 Useful for blocking invalid messages.
690 @arg channel: the channel the user sending from.
691 @type message: L{client.BinaryPeerMessage} or
692 L{client.ObjectPeerMessage}
693 @type recipient: L{ServerProtocol} object
694 @return: Return False to stop the channel message
695 before it is sent.
696 @rtype: bool
697 """
698
700 """
701 Cancels the sending of the channel list
702 if False is returned.
703 Useful for servers with a strict policy.
704 @return: Return False to send no channel list.
705 @rtype: bool
706 """
707
709 """
710 Cancels the channel join accept response if False is returned.
711 Useful for channel-less servers.
712 @arg channelName: The name of the channel.
713 @return: Return False to cancel channel join acceptance.
714 @rtype: bool
715 """
716
718 """
719 Cancels the channel leave accept response if False is returned.
720 Useful if the client may not leave the current channel.
721 @arg channel: The channel the client is leaving.
722 @return: Return False to cancel channel join acceptance.
723 @rtype: bool
724 """
725
727 """
728 Cancels a master action if False is returned.
729 Useful for blocking some specific master actions.
730 @arg channel: The channel the action is taking place in.
731 @arg peer: The peer this action is executed on.
732 @arg action: The action the master has requested.
733 @type action: Currently, only {pylacewing.constants.MASTER_KICK}
734 @rtype: bool
735 """
736
763
765 """
766 The server factory.
767
768 @ivar channelClass: This is the channel class that will be used when
769 creating a new channel. Subclass L{ServerChannel} and replace this
770 attribute if you want to change the behaviour of channels.
771 @ivar maxPing: This is the time the client has to respond
772 to pings (in seconds). Can be a number or None for no max ping (default)
773 @ivar pingTime: The interval between pings in seconds
774 @ivar maxUsers: The max number of users allowed on the server.
775 @ivar timeOut: The number of seconds the client has to send a Hello packet
776 before being disconnected. Can be a number or None for no timeout
777 @ivar welcomeMessage: The message sent to accepted clients.
778 @ivar ping: If True, pinging will be enabled on the server
779 @ivar channelListing: If True, channelListing is enabled on the server
780 @ivar masterRights: If True, this enables the autoclose feature for
781 clients when creating channels
782 """
783 channelClass = ServerChannel
784 timeOut = 8
785 pingTime = 8
786 maxPing = None
787 maxUsers = 1000
788 welcomeMessage = 'Welcome! Server is running pylacewing %s (%s)' % (
789 lacewing.__version__, sys.platform)
790
791 datagram = None
792 ping = True
793 channelListing = True
794 masterRights = False
795
796 _pinger = None
797
806
813
815 """
816 This method is called when a connection has been accepted, and
817 a welcome message has to be sent. The default implementation just
818 returns L{welcomeMessage}, but override this method to change that
819 behaviour.
820 @param connection: Connection that has been accepted
821 @type connection: L{ServerProtocol} object
822 @rtype: str
823 """
824 return self.welcomeMessage
825
837
841
843 """
844 Called when a channel has no users in it, and
845 is therefore removed.
846 @arg channel: The channel that is being removed.
847 """
848
850 """
851 Called when a new channel is created.
852 @arg channel: The channel that has been created.
853 """
854