import dgram from 'dgram' ; import net from 'net' ; import uuid from 'uuid/v4' ; import { ExtendedSocket } from 'extendedsocket' ; import { PacketId } from 'packets/definitions' ; import { InPacketBase } from 'packets/in/packet' ; import { OutVersionPacket } from 'packets/out/version' ; import { InHolepunchPacketUdp } from 'packets/holepunch/inholepunch' ; import { OutHolepunchPacketUdp } from 'packets/holepunch/outholepunch' ; import { ChannelManager } from 'channel/channelmanager' ; import { Room } from 'room/room' ; import { UserManager } from 'user/usermanager' ; import { UserSession } from 'user/usersession' ; import { UserService } from 'services/userservice' ; import { userSvcAuthority } from 'authorities' ; import { ActiveConnections } from 'storage/activeconnections' ; import { PacketLogger } from 'packetlogger' ; import { AchievementHandler } from 'handlers/achievementhandler' ; import { ChatHandler } from 'handlers/chathandler' ; import { HostHandler } from 'handlers/hosthandler' ; /**
 * The welcome message sent to the client
 */ const clientWelcomeMessage = '~SERVERCONNECTED\n\0' ; export interface IServerOptions { hostname : string ; portMaster : number ; portHolepunch : number ; shouldLogPackets ? : boolean ; } class export class ServerInstance { private static onVersionPacket ( versionData , sourceConn ) { sourceConn . send ( new OutVersionPacket ( false , '6246015df9a7d1f7311f888e7e861f18' ) ) ; return true ; } private server : net . Server ; private holepunchServer : dgram . Socket ; private masterPort : number ; private holepunchPort : number ; private hostname : string ; private achievementHandler : AchievementHandler ; private chatHandler : ChatHandler ; private hostHandler : HostHandler ; private packetLogging : PacketLogger ; param options constructor ( options ) { this . masterPort = options . portMaster ; this . holepunchPort = options . portHolepunch ; this . hostname = options . hostname ; this . server = net . createServer ( ) ; this . holepunchServer = dgram . createSocket ( 'udp4' ) ; this . achievementHandler = new AchievementHandler ( ) ; this . chatHandler = new ChatHandler ( ) ; this . hostHandler = new HostHandler ( ) ; if ( options . shouldLogPackets ) { this . packetLogging = new PacketLogger ( ) ; } this . server . on ( 'connection' , ( socket ) => { this . onServerConnection ( socket ) ; } ) . on ( 'close' , ( ) => { this . onServerClose ( ) ; } ) . on ( 'error' , ( err ) => { this . onServerError ( err ) ; } ) . on ( 'listening' , ( ) => { this . onServerListening ( ) ; } ) ; this . holepunchServer . on ( 'error' , ( err ) => { this . onServerError ( err ) ; } ) . on ( 'message' , ( msg , rinfo ) => { this . onHolepunchMessage ( msg , rinfo ) ; } ) . on ( 'listening' , ( ) => { this . onHolepunchListening ( ) ; } ) ; UserService . Init ( userSvcAuthority ( ) ) ; } public listen ( ) { this . server . listen ( this . masterPort , this . hostname ) ; this . holepunchServer . bind ( this . holepunchPort , this . hostname ) ; } public stop ( ) { this . server . close ( ) ; process . exit ( 0 ) ; } param socket private onServerConnection ( socket ) { const newConn = this . addSocket ( socket ) ; newConn . write ( clientWelcomeMessage ) ; console . debug ( 'new connection created, uuid ' + newConn . uuid ) ; newConn . on ( 'data' , ( data ) => { void this . onSocketData ( newConn , data ) ; } ) ; newConn . on ( 'error' , ( err ) => { this . onSocketError ( newConn , err ) ; } ) ; newConn . on ( 'close' , ( hadError ) => { void this . onSocketClose ( newConn , hadError ) ; } ) ; newConn . on ( 'end' , ( ) => { this . onSocketEnd ( newConn ) ; } ) ; newConn . on ( 'timeout' , ( ) => { this . onSocketTimeout ( newConn ) ; } ) ; } /**
     * Called when the server stops
     */ private onServerClose ( ) { console . log ( 'server closed' ) ; this . holepunchServer . close ( ) ; } param err private onServerError ( err ) { console . log ( 'server error: ' + err . message ) ; throw err ; } /**
     * Called when the server starts listening
     */ private onServerListening ( ) { const address = this . server . address ( ) as net . AddressInfo ; console . log ( `server is now started listening at ${ address . address }:${ address . port }` ) ; } private onHolepunchListening ( ) { const address = this . holepunchServer . address ( ) as net . AddressInfo ; console . log ( `holepunch listening at ${ address . address }:${ address . port }` ) ; } param msg param rinfo private onHolepunchMessage ( msg , rinfo ) { const packet = new InHolepunchPacketUdp ( msg ) ; if ( packet . isParsed ( ) === false ) { console . warn ( '%s sent a bad holepunch packet' , rinfo . address ) ; return ; } if ( packet . isHeartbeat ( ) ) { return ; } const conn = ActiveConnections . Singleton ( ) . FindByOwnerId ( packet . userId ) ; if ( conn == null ) { return ; } const session = conn . session ; if ( session == null ) { console . warn ( `Could not get user ID ${ packet . userId }'s session when holepunching` ) ; return ; } if ( session . shouldUpdatePorts ( packet . portId , packet . port , rinfo . port ) === false ) { return ; } session . externalNet . ipAddress = rinfo . address ; session . internalNet . ipAddress = packet . ipAddress ; const portIndex = session . setHolepunch ( packet . portId , packet . port , rinfo . port ) ; if ( portIndex === - 1 ) { console . warn ( 'Unknown hole punch port' ) ; return ; } const reply = new OutHolepunchPacketUdp ( portIndex ) . build ( ) ; this . holepunchServer . send ( reply , packet . port , packet . ipAddress ) ; } param conn param data private async onSocketData ( conn , data ) { const newData = Buffer . from ( data , 'hex' ) ; await this . processPackets ( newData , conn ) ; } private GetPacketLength ( data ) { try { const pkt = new InPacketBase ( data ) ; return pkt . length + InPacketBase . headerLength ; } catch ( error ) { return 0 ; } } private async processPackets ( packetData , conn ) { const fullDataLen = packetData . length ; let nextPktLen = 0 ; for ( let curOffset = 0 ; curOffset + InPacketBase . headerLength <= fullDataLen ; curOffset += nextPktLen ) { nextPktLen = this . GetPacketLength ( packetData . slice ( curOffset ) ) ; if ( nextPktLen === 0 ) { console . error ( 'processPackets: got a null length packet' ) ; return ; } const curPacket = new InPacketBase ( packetData . slice ( curOffset , curOffset + nextPktLen ) ) ; await this . onIncomingPacket ( curPacket , conn ) ; } } param packet param connection returns private async onIncomingPacket ( packet , connection ) { if ( packet . isValid ( ) === false ) { console . warn ( 'bad packet from ' + connection . uuid ) ; return false ; } const data = packet . getData ( ) ; if ( this . packetLogging ) { this . packetLogging . dumpIn ( connection . uuid , connection . getRealInSeq ( ) , packet . id , data ) ; } switch ( packet . id ) { case PacketId . Host : return this . hostHandler . onPacket ( data , connection ) ; case PacketId . Room : return ChannelManager . onRoomRequest ( data , connection ) ; case PacketId . Chat : return this . chatHandler . OnPacket ( data , connection ) ; case PacketId . Achievement : return this . achievementHandler . OnPacket ( data , connection ) ; case PacketId . RequestChannels : return ChannelManager . onChannelListPacket ( connection ) ; case PacketId . RequestRoomList : return ChannelManager . onRoomListPacket ( data , connection ) ; case PacketId . AboutMe : return await UserManager . onAboutmePacket ( data , connection ) ; case PacketId . Option : return await UserManager . onOptionPacket ( data , connection ) ; case PacketId . Favorite : return await UserManager . onFavoritePacket ( data , connection ) ; case PacketId . Login : return await UserManager . onLoginPacket ( data , connection , this . holepunchPort ) ; case PacketId . Version : return ServerInstance . onVersionPacket ( data , connection ) ; } console . warn ( `unknown packet id ${ packet . id } from ${ connection . uuid }` ) ; return false ; } param conn param hadError private async onSocketClose ( conn , hadError ) { console . log ( `socket ${ conn . uuid } closed hadError: ${ hadError ? 'true' : 'false' }` ) ; Room . cleanUpUser ( conn ) ; await UserManager . OnSocketClosed ( conn ) ; ActiveConnections . Singleton ( ) . Remove ( conn ) ; } param conn param err private onSocketError ( conn , err ) { console . warn ( `socket ${ conn . uuid } had an error: ${ err . name }` ) ; } param conn private onSocketEnd ( conn ) { console . log ( `socket ${ conn . uuid } ended` ) ; } param conn private onSocketTimeout ( conn ) { console . warn ( 'socket ' + conn . uuid + ' timed out' ) ; conn . destroy ( new Error ( 'Connection timed out' ) ) ; } param conn returns private addSocket ( conn ) { const newConn = ExtendedSocket . from ( conn , this . packetLogging ) ; newConn . setEncoding ( 'hex' ) ; newConn . uuid = uuid ( ) ; newConn . resetReq ( ) ; return newConn ; } }