/* * Copyright (c) Contributors to the Open 3D Engine Project. * For complete copyright and license terms please see the LICENSE at the root of this distribution. * * SPDX-License-Identifier: Apache-2.0 OR MIT * */ #include #include #include #include #include #include #include #include #include #include #include ////////////////////////////////////////////////////////////////////////// // Implementations namespace GridMate { class LANMember; /** * Member ID is unique identifier for a member in the session. * \note we can typedef MemberID to a string, this will be as generic as it gets! */ struct LANMemberID : public MemberID { friend class LANMemberIDMarshaler; static LANMemberID Create(MemberIDCompact id, const string& address) { LANMemberID mid; mid.m_id = id; mid.m_address = address; return mid; } void SetAddress(const string& address) { m_address = address; } MemberIDCompact GetID() const { return m_id; } virtual string ToString() const { return string::format("%x", m_id); } virtual string ToAddress() const { return m_address; } virtual MemberIDCompact Compact() const { return m_id; } virtual bool IsValid() const { return m_id != 0; } private: MemberIDCompact m_id; string m_address; }; class LANMemberIDMarshaler { public: void Marshal(WriteBuffer& wb, const LANMemberID& id) const { wb.Write(id.m_id); } void Unmarshal(LANMemberID& id, ReadBuffer& rb) const { rb.Read(id.m_id); } }; class LANSessionReplica; struct LANSessionMsg { LANSessionMsg(const VersionType& version) : m_version(version) , m_message(0) {} bool ValidateVersion(const VersionType& version) { return m_version == version; } enum MessageType { MT_SEARCH = 0, ///< Called from a LAN client searches for LAN sessions. MT_SEARCH_RESULT, // Other messages are communicated trough replicas. }; VersionType m_version; ///< Session crc AZ::u8 m_message; ///< messsage \ref MessageType }; /** * */ class LANSession : public GridSession { friend class LANSessionService; friend class LANSessionReplica; friend class LANMember; public: GM_CLASS_ALLOCATOR(LANSession); void Update() override; // TEMP so the factory can have access to the function. bool AddMember(GridMember* member) override { return GridSession::AddMember(member); } /// Creates the local player. GridMember* CreateLocalMember(bool isHost, bool isInvited, RemotePeerMode peerMode); /// Creates remote player, when he wants to join. GridMember* CreateRemoteMember(const string& address, ReadBuffer& data, RemotePeerMode peerMode, ConnectionID connId = InvalidConnectionID) override; /// Called when we receive the session replica. We create one and return the pointer. LANSessionReplica* OnSessionReplicaArrived(); /// Called when session parameters have changed. void OnSessionParamChanged(const GridSessionParam& param) override { (void)param; } void OnSessionParamRemoved(const string& paramId) override { (void)paramId; } private: explicit LANSession(LANSessionService* service); ~LANSession() override; /// Hosting a session. bool Initialize(const LANSessionParams& params, const CarrierDesc& carrierDesc); /// Joining a session. bool Initialize(const LANSearchInfo& info, const JoinParams& params, const CarrierDesc& carrierDesc); bool MatchMake(const LANSearchParams& sp); string MakeSessionId(); Driver* m_driver; ///< Driver for network searches ////////////////////////////////////////////////////////////////////////// // State machine // just override a few state handlers, the right way would be to sub state the // current ones. But this is less work. bool OnStateCreate(AZ::HSM& sm, const AZ::HSM::Event& e) override; bool OnStateDelete(AZ::HSM& sm, const AZ::HSM::Event& e) override; bool OnStateHostMigrateSession(AZ::HSM& sm, const AZ::HSM::Event& e) override; ////////////////////////////////////////////////////////////////////////// }; /** * \note many features will be common for most platforms, make sure they are moved to * a base class, where users can reuse and enhance them. */ class LANSessionReplica : public Internal::GridSessionReplica { public: class LANSessionReplicaDesc : public ReplicaChunkDescriptor { public: LANSessionReplicaDesc() : ReplicaChunkDescriptor(LANSessionReplica::GetChunkName(), sizeof(LANSessionReplica)) { } ReplicaChunkBase* CreateFromStream(UnmarshalContext& mc) override { LANSession* session = static_cast(static_cast(mc.m_rm->GetUserContext(AZ_CRC("GridSession", 0x099df4e6)))); AZ_Assert(session, "We need to have a valid session!"); return session->OnSessionReplicaArrived(); } void DiscardCtorStream(UnmarshalContext&) override {} void DeleteReplicaChunk(ReplicaChunkBase* chunkInstance) override { delete chunkInstance; } void MarshalCtorData(ReplicaChunkBase*, WriteBuffer&) override {} }; GM_CLASS_ALLOCATOR(LANSessionReplica); static const char* GetChunkName() { return "GridMateLANSessionReplica"; } LANSessionReplica(LANSession* session) : GridSessionReplica(session) , m_hostPort("HostPort") {} DataSet m_hostPort; ///< Port on which the host should provide matchmaking services. }; struct SessionMemberInfoCtorContext : public CtorContextBase { CtorDataSet m_memberId; CtorDataSet m_memberAddress; ///< As the server/host sees it! CtorDataSet m_peerMode; CtorDataSet m_isHost; }; /** * */ class LANMember : public GridMember { friend class LANSession; friend class LANMemberState; public: class LANMemberDesc : public ReplicaChunkDescriptor { public: LANMemberDesc() : ReplicaChunkDescriptor(LANMember::GetChunkName(), sizeof(LANMember)) { } ReplicaChunkBase* CreateFromStream(UnmarshalContext& mc) override { SessionMemberInfoCtorContext ctorContext; ctorContext.Unmarshal(*mc.m_iBuf); LANSession* session = static_cast(static_cast(mc.m_rm->GetUserContext(AZ_CRC("GridSession", 0x099df4e6)))); AZ_Assert(session, "We need to have a valid session!"); LANMember* member; MemberIDCompact memberId = ctorContext.m_memberId.Get(); string memberAddress = ctorContext.m_memberAddress.Get(); RemotePeerMode remotePeerMode = ctorContext.m_peerMode.Get(); bool isMemberHost = ctorContext.m_isHost.Get(); if (memberId != static_cast(session->GetMyMember()->GetId()).GetID()) { ReadBuffer rb(EndianType::IgnoreEndian, reinterpret_cast(&memberId), sizeof(memberId)); member = static_cast(session->CreateRemoteMember(memberAddress, rb, remotePeerMode, isMemberHost ? mc.m_peer->GetConnectionId() : InvalidConnectionID)); } else { // just bind our local member member = static_cast(session->GetMyMember()); } bool isAdded = session->AddMember(member); AZ_Assert(isAdded, "Failed to add a member, there is something wrong with the member replicas!"); if (!isAdded) { member = nullptr; } return member; } void DiscardCtorStream(UnmarshalContext& mc) override { SessionMemberInfoCtorContext ctorContext; ctorContext.Unmarshal(*mc.m_iBuf); } void DeleteReplicaChunk(ReplicaChunkBase* chunkInstance) override { if (!static_cast(chunkInstance)->IsLocal()) { delete chunkInstance; } } void MarshalCtorData(ReplicaChunkBase* chunkInstance, WriteBuffer& wb) override { LANMember* member = static_cast(chunkInstance); SessionMemberInfoCtorContext ctorContext; ctorContext.m_memberId.Set(member->m_memberId.GetID()); ctorContext.m_memberAddress.Set(member->m_memberId.ToAddress()); ctorContext.m_peerMode.Set(member->m_peerMode.Get()); ctorContext.m_isHost.Set(member->IsHost()); ctorContext.Marshal(wb); } }; GM_CLASS_ALLOCATOR(LANMember); static const char* GetChunkName() { return "GridMateLANMember"; } /// return an abstracted member id. (member ID is world unique but unrelated to player ID it's related to the session). virtual const MemberID& GetId() const { return m_memberId; } /// returns a base player id, it's implementation is platform dependent. (NOT supported) virtual const PlayerId* GetPlayerId() const { return nullptr; } /// Remote member ctor. LANMember(ConnectionID connId, const LANMemberID& id, LANSession* session); /// Local member ctor. LANMember(const LANMemberID& id, LANSession* session); LANMemberID m_memberId; }; /** * */ class LANMemberState : public Internal::GridMemberStateReplica { public: GM_CLASS_ALLOCATOR(LANMemberState); static const char* GetChunkName() { return "GridMateLANMemberState"; } LANMemberState(GridMember* member = nullptr) : GridMemberStateReplica(member) {} }; /** * */ class LANSearch : public GridSearch { friend class LANSessionService; public: GM_CLASS_ALLOCATOR(LANSearch); virtual ~LANSearch(); /// Return true if the search has finished, otherwise false. virtual unsigned int GetNumResults() const { return static_cast(m_results.size()); } virtual const SearchInfo* GetResult(unsigned int index) const { return &m_results[index]; } virtual void AbortSearch(); private: LANSearch(const LANSearchParams& searchParams, SessionService* service); virtual void Update(); void SearchDone(); Driver* m_driver; TimeStamp m_timeStart; TimeStamp m_lastSearchSend; ///< Time when last search was broadcasted. unsigned int m_broadcastInterval; ///< How ofter to send broadcast to find sessions in milliseconds. unsigned int m_timeOutMS; ///< Timeout in milliseconds. vector m_results; ///< Search results LANSearchParams m_searchParams; IGridMate* m_gridMate; }; } ////////////////////////////////////////////////////////////////////////// namespace GridMate { namespace Platform { void AssignExtendedName(GridMate::string& extendedName); } } using namespace GridMate; //========================================================================= // LANMember // Local member ctor. // [2/1/2011] //========================================================================= LANMember::LANMember(const LANMemberID& id, LANSession* session) : GridMember(id.Compact()) , m_memberId(id) { string extendedName; Platform::AssignExtendedName(extendedName); m_session = session; m_clientState = CreateReplicaChunk(this); m_clientState->m_name.Set(extendedName); NatType natType = NAT_OPEN; // for LAN this is the case m_clientState->m_natType.Set(natType); m_clientStateReplica = Replica::CreateReplica(extendedName.c_str()); m_clientStateReplica->AttachReplicaChunk(m_clientState); } //========================================================================= // LANMember // Remote member ctor. // [12/13/2010] //========================================================================= LANMember::LANMember(ConnectionID connId, const LANMemberID& id, LANSession* session) : GridMember(id.Compact()) , m_memberId(id) { m_session = session; m_connectionId = connId; } //========================================================================= // LANSession // [12/13/2010] //========================================================================= LANSession::LANSession(LANSessionService* service) : GridSession(service) , m_driver(nullptr) { } bool LANSession::Initialize(const LANSessionParams& params, const CarrierDesc& carrierDesc) { if (!GridSession::Initialize(carrierDesc)) { Shutdown(); return false; } // socket driver to handle session search and matchmake LANSessionReplica::ParamContainer sessionParams; Driver::ResultCode initResult = Driver::EC_OK; if (params.m_port != 0) { m_driver = aznew SocketDriver(false, true); initResult = m_driver->Initialize(m_carrierDesc.m_familyType, params.m_address.c_str(), params.m_port, true, 2048, 2048); if (initResult != Driver::EC_OK) { Shutdown(); return false; } for (AZStd::size_t i = 0; i < params.m_numParams; ++i) { sessionParams.push_back(params.m_params[i]); } } ////////////////////////////////////////////////////////////////////////// // start up the session state we will bind it later AZ_Assert(params.m_numPublicSlots < 0xff && params.m_numPrivateSlots < 0xff, "Can't have more than 255 slots!"); AZ_Assert(params.m_numPublicSlots > 0 || params.m_numPrivateSlots > 0, "You don't have any slots open!"); LANSessionReplica* state = CreateReplicaChunk(this); state->m_numFreePrivateSlots.Set(static_cast(params.m_numPrivateSlots)); state->m_numFreePublicSlots.Set(static_cast(params.m_numPublicSlots)); state->m_peerToPeerTimeout.Set(params.m_peerToPeerTimeout); state->m_hostMigrationTimeout.Set(params.m_hostMigrationTimeout); state->m_hostMigrationVotingTime.Set(AZStd::GetMin(params.m_hostMigrationVotingTime, params.m_hostMigrationTimeout / 2)); state->m_flags.Set(params.m_flags); state->m_topology.Set(params.m_topology); state->m_params.Set(sessionParams); state->m_hostPort.Set(params.m_port); m_state = state; ////////////////////////////////////////////////////////////////////////// m_myMember = CreateLocalMember(true, true, Mode_Peer); SetUpStateMachine(); RequestEventData(SE_HOST, params); return true; } bool LANSession::Initialize(const LANSearchInfo& info, const JoinParams& params, const CarrierDesc& carrierDesc) { if (!GridSession::Initialize(carrierDesc)) { Shutdown(); return false; } m_hostAddress = SocketDriverCommon::IPPortToAddressString(info.m_serverIP.c_str(), info.m_serverPort); m_sessionId = info.m_sessionId; // start up the session state we will bind it later m_state = CreateReplicaChunk(this); m_myMember = CreateLocalMember(false, false, params.m_desiredPeerMode); SetUpStateMachine(); RequestEventData(SE_JOIN, info); // trigger the join event return true; } //========================================================================= // ~LANSession // [12/13/2010] //========================================================================= LANSession::~LANSession() { delete m_driver; m_driver = nullptr; } //========================================================================= // Update // [12/13/2010] //========================================================================= void LANSession::Update() { ////////////////////////////////////////////////////////////////////////// // Hosting session if (m_driver) { // receive connection requests static const int maxDataSize = 2048; char data[maxDataSize]; while (true) { AZStd::intrusive_ptr from; Driver::ResultCode result; unsigned int recvd = m_driver->Receive(data, maxDataSize, from, &result); if (result != Driver::EC_OK) { AZ_TracePrintf("GridMate", "LANSession::Receive - recvfrom failed with code 0x%08x\n", result); break; } if (recvd == 0) { break; } ReadBuffer rb(kSessionEndian, data, recvd); LANSessionMsg msg(m_carrierDesc.m_version); rb.Read(msg.m_version); if (!msg.ValidateVersion(m_carrierDesc.m_version)) { continue; // wrong message, ignore } rb.Read(msg.m_message); if (msg.m_message == LANSessionMsg::MT_SEARCH) { ////////////////////////////////////////////////////////////////////////// // Matchmake // read the user attributes LANSearchParams ssp; rb.Read(ssp.m_numParams); for (unsigned int i = 0; i < ssp.m_numParams; ++i) { rb.Read(ssp.m_params[i].m_op); rb.Read(ssp.m_params[i].m_id); rb.Read(ssp.m_params[i].m_value); rb.Read(ssp.m_params[i].m_type); } // compare params and filter results. if (!MatchMake(ssp)) { continue; } ////////////////////////////////////////////////////////////////////////// // send back the session info structure LANSearchInfo si; si.m_serverPort = static_cast(m_carrier ? m_carrier->GetPort() : 0);// send the port for the session si.m_numFreePrivateSlots = m_state->m_numFreePrivateSlots.Get(); si.m_numFreePublicSlots = m_state->m_numFreePublicSlots.Get(); si.m_numUsedPrivateSlots = m_state->m_numUsedPrivateSlots.Get(); si.m_numUsedPublicSlots = m_state->m_numUsedPublicSlots.Get(); const LANSessionReplica::ParamContainer& params = AZStd::static_pointer_cast(m_state)->m_params.Get(); si.m_numParams = static_cast(params.size()); for (AZ::u32 i = 0; i < si.m_numParams; ++i) { si.m_params[i].m_id = params[i].m_id; si.m_params[i].m_value = params[i].m_value; si.m_params[i].m_type = params[i].m_type; } msg.m_message = LANSessionMsg::MT_SEARCH_RESULT; // write and send session information WriteBufferStatic<> wb(kSessionEndian); wb.Write(msg.m_version); wb.Write(msg.m_message); wb.Write(si.m_serverPort); wb.Write(m_sessionId); wb.Write(si.m_numFreePrivateSlots); wb.Write(si.m_numFreePublicSlots); wb.Write(si.m_numUsedPrivateSlots); wb.Write(si.m_numUsedPublicSlots); wb.Write(si.m_numParams); for (unsigned int i = 0; i < si.m_numParams; ++i) { wb.Write(si.m_params[i].m_id); wb.Write(si.m_params[i].m_value); wb.Write(si.m_params[i].m_type); } // send back the information. result = m_driver->Send(from, wb.Get(), static_cast(wb.Size())); if (result != Driver::EC_OK) { AZ_TracePrintf("GridMate", "LANSession::Send - sendto failed with code 0x%08x at %s", result, from->ToString().c_str()); } } else { AZ_Assert(false, "We don't handle %d message!", msg.m_message); } } } ////////////////////////////////////////////////////////////////////////// // THIS should be last in the update, it might delete the session internally (this pointer will become invalid! GridSession::Update(); } //========================================================================= // CompareParams // [7/15/2013] //========================================================================= template inline bool CompareParams(const Param& left, const Param& right, GridSessionSearchOperators op) { switch (op) { case GridSessionSearchOperators::SSO_OPERATOR_EQUAL: if (left != right) { return false; } break; case GridSessionSearchOperators::SSO_OPERATOR_NOT_EQUAL: if (left == right) { return false; } break; case GridSessionSearchOperators::SSO_OPERATOR_GREATER_EQUAL_THAN: if (left < right) { return false; } break; case GridSessionSearchOperators::SSO_OPERATOR_GREATER_THAN: if (left <= right) { return false; } break; case GridSessionSearchOperators::SSO_OPERATOR_LESS_EQUAL_THAN: if (left > right) { return false; } break; case GridSessionSearchOperators::SSO_OPERATOR_LESS_THAN: if (left >= right) { return false; } break; default: AZ_Warning("GridMate", false, "Invalid operator type %d", op); return false; } return true; } //========================================================================= // MatchMake // [12/13/2010] //========================================================================= bool LANSession::MatchMake(const LANSearchParams& sp) { bool isGoodMatch = true; for (unsigned int i = 0; i < sp.m_numParams; ++i) { // find parameter const LANSessionReplica::ParamContainer& params = AZStd::static_pointer_cast(m_state)->m_params.Get(); AZStd::size_t j = 0; for (; j < params.size(); ++j) { if (sp.m_params[i].m_id == params[j].m_id && sp.m_params[i].m_type == params[j].m_type) { break; } } if (j == params.size()) { isGoodMatch = false; // parameter was NOT found break; } switch (sp.m_params[i].m_type) { case GridSessionParam::VT_INT32: { AZ::s32 left = AZStd::stoi(params[j].m_value); AZ::s32 right = AZStd::stoi(sp.m_params[i].m_value); isGoodMatch = CompareParams(left, right, sp.m_params[i].m_op); break; }; case GridSessionParam::VT_INT64: { AZ::s64 left = AZStd::stoll(params[j].m_value); AZ::s64 right = AZStd::stoll(sp.m_params[i].m_value); isGoodMatch = CompareParams(left, right, sp.m_params[i].m_op); break; }; case GridSessionParam::VT_FLOAT: { float left = AZStd::stof(params[j].m_value); float right = AZStd::stof(sp.m_params[i].m_value); isGoodMatch = CompareParams(left, right, sp.m_params[i].m_op); break; }; case GridSessionParam::VT_DOUBLE: { double left = AZStd::stof(params[j].m_value); double right = AZStd::stof(sp.m_params[i].m_value); isGoodMatch = CompareParams(left, right, sp.m_params[i].m_op); break; }; case GridSessionParam::VT_STRING: { isGoodMatch = CompareParams(params[j].m_value, sp.m_params[i].m_value, sp.m_params[i].m_op); break; }; default: AZ_Assert(false, "Unsupported parameter type %d", params[j].m_type); isGoodMatch = false; } if (!isGoodMatch) { break; } } return isGoodMatch; } //========================================================================= // CreateLocalMember // [2/1/2011] //========================================================================= GridMember* LANSession::CreateLocalMember(bool isHost, bool isInvited, RemotePeerMode peerMode) { AZ_Assert(m_myMember == nullptr, "We already have added a local member!"); string ip = Utils::GetMachineAddress(m_carrierDesc.m_familyType); string address = SocketDriverCommon::IPPortToAddressString(ip.c_str(), m_carrierDesc.m_port); ///////////////////////////////////////////////////////////////////////////// // TODO: Use the UUID as an ID, we will need to add marshalers and so on AZ::Uuid uuid = AZ::Uuid::CreateRandom(); MemberIDCompact id = AZ::Crc32(&uuid.data, sizeof(uuid.data)); ///////////////////////////////////////////////////////////////////////////// LANMemberID myId = LANMemberID::Create(id, address); LANMember* member = CreateReplicaChunk(myId, this); member->SetHost(isHost); member->SetInvited(isInvited); member->m_peerMode.Set(peerMode); return member; } //========================================================================= // CreateRemoteMember // [2/2/2011] //========================================================================== GridMember* LANSession::CreateRemoteMember(const string& address, ReadBuffer& data, RemotePeerMode peerMode, ConnectionID connId) { MemberIDCompact id; data.Read(id); LANMemberID memberId = LANMemberID::Create(id, address); AZ_Warning("GridMate", GetTopology() != ST_INVALID, "Invalid sesson topology! Did session replica arrive yet?"); if (GetTopology() == ST_PEER_TO_PEER && peerMode == Mode_Peer && static_cast(m_myMember)->m_peerMode.Get() == Mode_Peer && connId == InvalidConnectionID) { connId = m_carrier->Connect(memberId.ToAddress()); } return CreateReplicaChunk(connId, memberId, this); } //========================================================================= // OnSessionReplicaArrived // [2/1/2011] //========================================================================= LANSessionReplica* LANSession::OnSessionReplicaArrived() { AZ_TracePrintf("GridMate", "(%s - %s) has joined session: %s\n", m_myMember->GetId().ToString().c_str(), m_myMember->GetId().ToAddress().c_str(), m_sessionId.c_str()); // join is complete RequestEvent(LANSession::SE_JOINED); return static_cast(m_state.get()); } ////////////////////////////////////////////////////////////////////////// // Session state machine overrides bool LANSession::OnStateCreate(AZ::HSM& sm, const AZ::HSM::Event& e) { bool isProcessed = GridSession::OnStateCreate(sm, e); switch (e.id) { case AZ::HSM::EnterEventId: { // patch the ID if we use implicit port AZ_Assert(m_carrier, "Carrier must be created!"); string ip; unsigned int port; SocketDriverCommon::AddressStringToIPPort(m_myMember->GetId().ToAddress(), ip, port); AZ_Assert(port == 0 || port == m_carrier->GetPort(), "Carrier port missmatch! It should either be 0 (and patched here) in the implicit bind or the port number for explicit bind!"); if (port == 0) { static_cast(m_myMember)->m_memberId.SetAddress(SocketDriverCommon::IPPortToAddressString(ip.c_str(), m_carrier->GetPort())); } // Set my ID as userdata for the handshake (so we can proparage to the host when we connect) MemberIDCompact id = static_cast(m_myMember->GetId()).GetID(); WriteBufferStatic<> wb(kSessionEndian); wb.Write(id); SetHandshakeUserData(wb.Get(), wb.Size()); if (IsHost()) { m_sessionId = MakeSessionId(); } RequestEvent(SE_CREATED); // just send create even } return true; } ; return isProcessed; } bool LANSession::OnStateDelete(AZ::HSM& sm, const AZ::HSM::Event& e) { bool isProcessed = GridSession::OnStateDelete(sm, e); switch (e.id) { case AZ::HSM::EnterEventId: { RequestEvent(SE_DELETED); } return true; } return isProcessed; } bool LANSession::OnStateHostMigrateSession(AZ::HSM& sm, const AZ::HSM::Event& e) { bool isProcessed = GridSession::OnStateHostMigrateSession(sm, e); switch (e.id) { case AZ::HSM::EnterEventId: { if (m_sessionId.empty()) { // we are the new host create a new session and let the rest of the clients know. m_sessionId = MakeSessionId(); // possibly reset the room slots and reassign them after host migration is complete. RequestEvent(SE_HM_SESSION_MIGRATED); } else { // we are a client and we need to migrate the session to the new session ID RequestEvent(SE_HM_CLIENT_SESSION_MIGRATED); } } return true; case AZ::HSM::ExitEventId: { if (IsHost()) { AZ_Assert(m_driver == nullptr, "If we just became the host we should not have a matchmaking services enabled!"); unsigned int hostPort = AZStd::static_pointer_cast(m_state)->m_hostPort.Get(); // socket driver to handle session search and matchmake... if (hostPort != 0) { m_driver = aznew SocketDriver(false, true); // TODO: To support Ipv6 properly we need to store the family and bind address in the LANSessionReplica if (m_driver->Initialize(Driver::BSD_AF_INET, nullptr, hostPort, true) != Driver::EC_OK) { // check the output for more info string errorMsg = string::format("Failed to initialize socket at port %d!", hostPort); EBUS_DBG_EVENT(Debug::SessionDrillerBus, OnSessionError, this, errorMsg); EBUS_EVENT_ID(m_gridMate, SessionEventBus, OnSessionError, this, errorMsg); // We can't be a real host if we failed to provide matching services. Leave(false); } } } } return true; } return isProcessed; } //========================================================================= // MakeSessionId // [3/7/2013] //========================================================================= string LANSession::MakeSessionId() { char temp[64]; AZ::Uuid::CreateRandom().ToString(temp, AZ_ARRAY_SIZE(temp), false, false); return temp; } ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// // LANSearch ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// //========================================================================= // LANSearch // [12/13/2010] //========================================================================= LANSearch::LANSearch(const LANSearchParams& searchParams, SessionService* service) : GridSearch(service) , m_timeOutMS(searchParams.m_timeOutMs) , m_searchParams(searchParams) { m_gridMate = service->GetGridMate(); AZ_Assert(m_gridMate, "SessionService is not bound to GridMate instance"); m_results.reserve(m_searchParams.m_maxSessions); m_driver = aznew SocketDriver(false, true); if (m_driver->Initialize(searchParams.m_familyType, searchParams.m_listenAddress.c_str(), searchParams.m_listenPort, true) != Driver::EC_OK) { SearchDone(); } m_timeStart = AZStd::chrono::system_clock::now(); m_broadcastInterval = searchParams.m_broadcastFrequencyMs; Update(); // Call an instant update to send a broadcast } //========================================================================= // ~LANSearch // [12/13/2010] //========================================================================= LANSearch::~LANSearch() { if (!IsDone()) { AbortSearch(); } } //========================================================================= // Update // [12/13/2010] //========================================================================= void LANSearch::Update() { if (m_isDone) { return; } TimeStamp now = AZStd::chrono::system_clock::now(); // broadcast the LAN search. AZStd::chrono::milliseconds timeElapsed = now - m_lastSearchSend; if (timeElapsed.count() >= m_broadcastInterval) { m_lastSearchSend = now; // write and send session information WriteBufferStatic<> wb(kSessionEndian); LANSessionMsg msg(m_searchParams.m_version); msg.m_message = LANSessionMsg::MT_SEARCH; wb.Write(msg.m_version); wb.Write(msg.m_message); // Matchmake // don't send the broadcast and listen ports wb.Write(m_searchParams.m_numParams); for (unsigned int i = 0; i < m_searchParams.m_numParams; ++i) { wb.Write(m_searchParams.m_params[i].m_op); wb.Write(m_searchParams.m_params[i].m_id); wb.Write(m_searchParams.m_params[i].m_value); wb.Write(m_searchParams.m_params[i].m_type); } string serverAddress = m_searchParams.m_serverAddress; if (serverAddress.empty()) { serverAddress = Utils::GetBroadcastAddress(m_searchParams.m_familyType); } Driver::ResultCode result = m_driver->Send(m_driver->CreateDriverAddress(SocketDriverCommon::IPPortToAddressString(serverAddress.c_str(), m_searchParams.m_serverPort)), wb.Get(), static_cast(wb.Size())); if (result != Driver::EC_OK) { AZ_TracePrintf("GridMate", "LANSearch::Send - to %s port %d failed with code 0x%08x\n", serverAddress.c_str(), m_searchParams.m_serverPort, result); } } // Receive all the static const int k_maxDataSize = 2048; char data[k_maxDataSize]; bool haveMaxResults = false; while (true) { AZStd::intrusive_ptr from; Driver::ResultCode result; unsigned int recvd = m_driver->Receive(data, k_maxDataSize, from, &result); if (result != Driver::EC_OK) { AZ_TracePrintf("GridMate", "LANSearch::Receive - recvfrom failed with code 0x%08x\n", result); break; } if (recvd == 0) { break; } ReadBuffer rb(kSessionEndian, data, recvd); LANSessionMsg msg(m_searchParams.m_version); rb.Read(msg.m_version); if (!msg.ValidateVersion(m_searchParams.m_version)) { continue; // wrong message, ignore } rb.Read(msg.m_message); if (msg.m_message != LANSessionMsg::MT_SEARCH_RESULT) { continue; } LANSearchInfo si; unsigned int port; // we don't use that port (as this is the search port) SocketDriverCommon::AddressStringToIPPort(from->ToAddress(), si.m_serverIP, port); rb.Read(si.m_serverPort); rb.Read(si.m_sessionId); rb.Read(si.m_numFreePrivateSlots); rb.Read(si.m_numFreePublicSlots); rb.Read(si.m_numUsedPrivateSlots); rb.Read(si.m_numUsedPublicSlots); rb.Read(si.m_numParams); for (unsigned int i = 0; i < si.m_numParams; ++i) { rb.Read(si.m_params[i].m_id); rb.Read(si.m_params[i].m_value); rb.Read(si.m_params[i].m_type); } bool isInResults = false; for (AZStd::size_t i = 0; i < m_results.size(); ++i) { if (m_results[i].m_sessionId == si.m_sessionId) { isInResults = true; break; } } if (!isInResults) { m_results.push_back(si); } if (m_results.size() == m_searchParams.m_maxSessions) { haveMaxResults = true; break; } } // check the timeout timeElapsed = now - m_timeStart; if (haveMaxResults || timeElapsed.count() > m_timeOutMS) { // we are done SearchDone(); return; } } //========================================================================= // AbortSearch // [12/13/2010] //========================================================================= void LANSearch::AbortSearch() { SearchDone(); } //========================================================================= // SearchDone // [12/13/2010] //========================================================================= void LANSearch::SearchDone() { m_isDone = true; delete m_driver; m_driver = nullptr; } ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// // LANSessionService ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// //========================================================================= // LANSessionService // [12/13/2010] //========================================================================= LANSessionService::LANSessionService(const SessionServiceDesc& desc) : SessionService(desc) { #if AZ_TRAIT_OS_USE_WINDOWS_SOCKETS WSAData wsaData; int err = WSAStartup(MAKEWORD(2, 2), &wsaData); if (err != 0) { AZ_TracePrintf("GridMate", "Failed on WSAStartup with code %d\n", err); } #endif } LANSessionService::~LANSessionService() { #if AZ_TRAIT_OS_USE_WINDOWS_SOCKETS WSACleanup(); #endif } void LANSessionService::OnServiceRegistered(IGridMate* gridMate) { SessionService::OnServiceRegistered(gridMate); ReplicaChunkDescriptorTable::Get().RegisterChunkType(); ReplicaChunkDescriptorTable::Get().RegisterChunkType(); ReplicaChunkDescriptorTable::Get().RegisterChunkType(); LANSessionServiceBus::Handler::BusConnect(gridMate); EBUS_DBG_EVENT(Debug::SessionDrillerBus, OnSessionServiceReady); EBUS_EVENT_ID(m_gridMate, SessionEventBus, OnSessionServiceReady); } void LANSessionService::OnServiceUnregistered(IGridMate* gridMate) { SessionService::OnServiceUnregistered(gridMate); LANSessionServiceBus::Handler::BusDisconnect(gridMate); } GridSession* LANSessionService::HostSession(const LANSessionParams& params, const CarrierDesc& carrierDesc) { LANSession* session = aznew LANSession(this); if (!session->Initialize(params, carrierDesc)) { delete session; return nullptr; } return session; } GridSession* LANSessionService::JoinSessionBySearchInfo(const LANSearchInfo& searchInfo, const JoinParams& params, const CarrierDesc& carrierDesc) { LANSession* session = aznew LANSession(this); if (!session->Initialize(searchInfo, params, carrierDesc)) { delete session; return nullptr; } return session; } GridSession* LANSessionService::JoinSessionBySessionIdInfo(const SessionIdInfo* info, const JoinParams& params, const CarrierDesc& carrierDesc) { LANSearchInfo si; si.m_sessionId = info->m_sessionId; LANSession* session = aznew LANSession(this); if (!session->Initialize(si, params, carrierDesc)) { delete session; return nullptr; } return session; } GridSearch* LANSessionService::StartGridSearch(const LANSearchParams& params) { return aznew LANSearch(params, this); }