// handle_datagram is the handler for datagrams received from the Astron cluster void Client::handle_datagram(DatagramHandle in_dg, DatagramIterator &dgi) { lock_guard<recursive_mutex> lock(m_client_lock); if(is_terminated()) { return; } channel_t sender = dgi.read_channel(); if(sender == m_channel) { return; // ignore messages from ourselves } uint16_t msgtype = dgi.read_uint16(); switch(msgtype) { case CLIENTAGENT_EJECT: { uint16_t reason = dgi.read_uint16(); string error_string = dgi.read_string(); send_disconnect(reason, error_string); return; } break; case CLIENTAGENT_DROP: { handle_drop(); return; } break; case CLIENTAGENT_SET_STATE: { m_state = (ClientState)dgi.read_uint16(); } break; case CLIENTAGENT_ADD_INTEREST: { uint32_t context = m_next_context++; Interest i; build_interest(dgi, false, i); handle_add_interest(i, context); add_interest(i, context, sender); } break; case CLIENTAGENT_ADD_INTEREST_MULTIPLE: { uint32_t context = m_next_context++; Interest i; build_interest(dgi, true, i); handle_add_interest(i, context); add_interest(i, context, sender); } break; case CLIENTAGENT_REMOVE_INTEREST: { uint32_t context = m_next_context++; uint16_t id = dgi.read_uint16(); Interest &i = m_interests[id]; handle_remove_interest(id, context); remove_interest(i, context, sender); } break; case CLIENTAGENT_SET_CLIENT_ID: { if(m_channel != m_allocated_channel) { unsubscribe_channel(m_channel); } m_channel = dgi.read_channel(); subscribe_channel(m_channel); } break; case CLIENTAGENT_SEND_DATAGRAM: { DatagramPtr forward = Datagram::create(); forward->add_data(dgi.read_string()); forward_datagram(forward); } break; case CLIENTAGENT_OPEN_CHANNEL: { subscribe_channel(dgi.read_channel()); } break; case CLIENTAGENT_CLOSE_CHANNEL: { unsubscribe_channel(dgi.read_channel()); } break; case CLIENTAGENT_ADD_POST_REMOVE: { add_post_remove(m_allocated_channel, dgi.read_datagram()); } break; case CLIENTAGENT_CLEAR_POST_REMOVES: { clear_post_removes(m_allocated_channel); } break; case CLIENTAGENT_DECLARE_OBJECT: { doid_t do_id = dgi.read_doid(); uint16_t dc_id = dgi.read_uint16(); if(m_declared_objects.find(do_id) != m_declared_objects.end()) { m_log->warning() << "Received object declaration for previously declared object " << do_id << ".\n"; return; } DeclaredObject obj; obj.id = do_id; obj.dcc = g_dcf->get_class_by_id(dc_id); m_declared_objects[do_id] = obj; } break; case CLIENTAGENT_UNDECLARE_OBJECT: { doid_t do_id = dgi.read_doid(); if(m_declared_objects.find(do_id) == m_declared_objects.end()) { m_log->warning() << "Received undeclare object for unknown object " << do_id << ".\n"; return; } m_declared_objects.erase(do_id); } break; case CLIENTAGENT_SET_FIELDS_SENDABLE: { doid_t do_id = dgi.read_doid(); uint16_t field_count = dgi.read_uint16(); unordered_set<uint16_t> fields; for(uint16_t i{}; i < field_count; ++i) { fields.insert(dgi.read_uint16()); } m_fields_sendable[do_id] = fields; } break; case CLIENTAGENT_ADD_SESSION_OBJECT: { doid_t do_id = dgi.read_doid(); if(m_session_objects.find(do_id) != m_session_objects.end()) { m_log->warning() << "Received add session object for existing session object " << do_id << ".\n"; return; } m_log->debug() << "Added session object with id " << do_id << ".\n"; m_session_objects.insert(do_id); } break; case CLIENTAGENT_REMOVE_SESSION_OBJECT: { doid_t do_id = dgi.read_doid(); if(m_session_objects.find(do_id) == m_session_objects.end()) { m_log->warning() << "Received remove session object for non-session object " << do_id << ".\n"; return; } m_log->debug() << "Removed session object with id " << do_id << ".\n"; m_session_objects.erase(do_id); } break; case CLIENTAGENT_GET_NETWORK_ADDRESS: { DatagramPtr resp = Datagram::create(sender, m_channel, CLIENTAGENT_GET_NETWORK_ADDRESS_RESP); resp->add_uint32(dgi.read_uint32()); // Context resp->add_string(get_remote_address()); resp->add_uint16(get_remote_port()); resp->add_string(get_local_address()); resp->add_uint16(get_local_port()); route_datagram(resp); } break; case STATESERVER_OBJECT_SET_FIELD: { doid_t do_id = dgi.read_doid(); if(!lookup_object(do_id)) { if(try_queue_pending(do_id, in_dg)) { return; } m_log->warning() << "Received server-side field update for unknown object " << do_id << ".\n"; return; } if(sender != m_channel) { uint16_t field_id = dgi.read_uint16(); handle_set_field(do_id, field_id, dgi); } } break; case STATESERVER_OBJECT_SET_FIELDS: { doid_t do_id = dgi.read_doid(); if(!lookup_object(do_id)) { if(try_queue_pending(do_id, in_dg)) { return; } m_log->warning() << "Received server-side multi-field update for unknown object " << do_id << ".\n"; return; } if(sender != m_channel) { uint16_t num_fields = dgi.read_uint16(); handle_set_fields(do_id, num_fields, dgi); } } break; case STATESERVER_OBJECT_DELETE_RAM: { doid_t do_id = dgi.read_doid(); m_log->trace() << "Received DeleteRam for object with id " << do_id << "\n."; if(!lookup_object(do_id)) { if(try_queue_pending(do_id, in_dg)) { return; } m_log->warning() << "Received server-side object delete for unknown object " << do_id << ".\n"; return; } if(m_session_objects.find(do_id) != m_session_objects.end()) { // We have to erase the object from our session_objects here, because // the object has already been deleted and we don't want it to be deleted // again in the client's destructor. m_session_objects.erase(do_id); stringstream ss; ss << "The session object with id " << do_id << " has been unexpectedly deleted."; send_disconnect(CLIENT_DISCONNECT_SESSION_OBJECT_DELETED, ss.str()); return; } if(m_seen_objects.find(do_id) != m_seen_objects.end()) { handle_remove_object(do_id); m_seen_objects.erase(do_id); } if(m_owned_objects.find(do_id) != m_owned_objects.end()) { handle_remove_ownership(do_id); m_owned_objects.erase(do_id); } m_historical_objects.insert(do_id); m_visible_objects.erase(do_id); } break; case STATESERVER_OBJECT_ENTER_OWNER_WITH_REQUIRED_OTHER: case STATESERVER_OBJECT_ENTER_OWNER_WITH_REQUIRED: { doid_t do_id = dgi.read_doid(); doid_t parent = dgi.read_doid(); zone_t zone = dgi.read_zone(); uint16_t dc_id = dgi.read_uint16(); m_owned_objects.insert(do_id); if(m_visible_objects.find(do_id) == m_visible_objects.end()) { VisibleObject obj; obj.id = do_id; obj.parent = parent; obj.zone = zone; obj.dcc = g_dcf->get_class_by_id(dc_id); m_visible_objects[do_id] = obj; } bool with_other = (msgtype == STATESERVER_OBJECT_ENTER_OWNER_WITH_REQUIRED_OTHER); handle_add_ownership(do_id, parent, zone, dc_id, dgi, with_other); } break; case STATESERVER_OBJECT_ENTER_LOCATION_WITH_REQUIRED: case STATESERVER_OBJECT_ENTER_LOCATION_WITH_REQUIRED_OTHER: { doid_t do_id = dgi.read_doid(); doid_t parent = dgi.read_doid(); zone_t zone = dgi.read_zone(); for(auto& it : m_pending_interests) { InterestOperation *interest_operation = it.second; if(interest_operation->m_parent == parent && interest_operation->m_zones.find(zone) != interest_operation->m_zones.end()) { interest_operation->queue_datagram(in_dg); // Add the DoId to m_pending_objects, because while it's not an object // from opening the interest, we should begin queueing messages for it m_pending_objects.emplace(do_id, it.first); return; } } // Object entrance doesn't pertain to any pending iop, // so seek back to where we started and handle it normally dgi.seek_payload(); dgi.skip(sizeof(channel_t) + sizeof(uint16_t)); // sender + msgtype bool with_other = (msgtype == STATESERVER_OBJECT_ENTER_LOCATION_WITH_REQUIRED_OTHER); handle_object_entrance(dgi, with_other); } break; case STATESERVER_OBJECT_ENTER_INTEREST_WITH_REQUIRED: case STATESERVER_OBJECT_ENTER_INTEREST_WITH_REQUIRED_OTHER: { uint32_t request_context = dgi.read_uint32(); auto it = m_pending_interests.find(request_context); if(it == m_pending_interests.end()) { m_log->warning() << "Received object entrance into interest with unknown context " << request_context << ".\n"; return; } m_pending_objects.emplace(dgi.read_doid(), request_context); it->second->queue_expected(in_dg); if(it->second->is_ready()) { it->second->finish(); } return; } break; case STATESERVER_OBJECT_GET_ZONES_COUNT_RESP: { uint32_t context = dgi.read_uint32(); // using doid_t because <max_objects_in_zones> == <max_total_objects> doid_t count = dgi.read_doid(); auto it = m_pending_interests.find(context); if(it == m_pending_interests.end()) { m_log->error() << "Received GET_ZONES_COUNT_RESP for unknown context " << context << ".\n"; return; } it->second->set_expected(count); if(it->second->is_ready()) { it->second->finish(); } } break; case STATESERVER_OBJECT_CHANGING_LOCATION: { doid_t do_id = dgi.read_doid(); if(try_queue_pending(do_id, in_dg)) { // We received a generate for this object, and the generate is sitting in a pending iop // we'll just store this dg under the m_pending_datagrams queue on the iop return; } doid_t n_parent = dgi.read_doid(); zone_t n_zone = dgi.read_zone(); bool disable = true; for(const auto& it : m_interests) { const Interest& i = it.second; for(const auto& it2 : i.zones) { if(it2 == n_zone) { disable = false; break; } } } if(m_visible_objects.find(do_id) != m_visible_objects.end()) { m_visible_objects[do_id].parent = n_parent; m_visible_objects[do_id].zone = n_zone; } else { // We don't actually *see* this object, we're receiving this // message as a fluke. return; } if(disable && m_owned_objects.find(do_id) == m_owned_objects.end()) { if(m_session_objects.find(do_id) != m_session_objects.end()) { stringstream ss; ss << "The session object with id " << do_id << " has unexpectedly left interest."; send_disconnect(CLIENT_DISCONNECT_SESSION_OBJECT_DELETED, ss.str()); return; } handle_remove_object(do_id); m_seen_objects.erase(do_id); m_historical_objects.insert(do_id); m_visible_objects.erase(do_id); } else { handle_change_location(do_id, n_parent, n_zone); } } break; case STATESERVER_OBJECT_CHANGING_OWNER: { doid_t do_id = dgi.read_doid(); channel_t n_owner = dgi.read_channel(); dgi.skip(sizeof(channel_t)); // don't care about the old owner if(n_owner == m_channel) { // We should already own this object, nothing changes and we // might get another enter_owner message. return; } if(m_owned_objects.find(do_id) == m_owned_objects.end()) { m_log->error() << "Received ChangingOwner for unowned object with id " << do_id << ".\n"; return; } if(m_seen_objects.find(do_id) == m_seen_objects.end()) { if(m_session_objects.find(do_id) != m_session_objects.end()) { stringstream ss; ss << "The session object with id " << do_id << " has unexpectedly left ownership."; send_disconnect(CLIENT_DISCONNECT_SESSION_OBJECT_DELETED, ss.str()); return; } handle_remove_ownership(do_id); m_owned_objects.erase(do_id); m_historical_objects.insert(do_id); m_visible_objects.erase(do_id); } } break; default: m_log->error() << "Recv'd unknown server msgtype " << msgtype << "\n."; } }
// handle_datagram is the handler for datagrams received from the Astron cluster void Client::handle_datagram(DatagramHandle, DatagramIterator &dgi) { std::lock_guard<std::recursive_mutex> lock(m_client_lock); channel_t sender = dgi.read_channel(); uint16_t msgtype = dgi.read_uint16(); switch(msgtype) { case CLIENTAGENT_EJECT: { uint16_t reason = dgi.read_uint16(); std::string error_string = dgi.read_string(); send_disconnect(reason, error_string); return; } break; case CLIENTAGENT_DROP: { handle_drop(); return; } break; case CLIENTAGENT_SET_STATE: { m_state = (ClientState)dgi.read_uint16(); } break; case CLIENTAGENT_ADD_INTEREST: { uint32_t context = m_next_context++; Interest i; build_interest(dgi, false, i); handle_add_interest(i, context); add_interest(i, context, sender); } break; case CLIENTAGENT_ADD_INTEREST_MULTIPLE: { uint32_t context = m_next_context++; Interest i; build_interest(dgi, true, i); handle_add_interest(i, context); add_interest(i, context, sender); } break; case CLIENTAGENT_REMOVE_INTEREST: { uint32_t context = m_next_context++; uint16_t id = dgi.read_uint16(); Interest &i = m_interests[id]; handle_remove_interest(id, context); remove_interest(i, context, sender); } break; case CLIENTAGENT_SET_CLIENT_ID: { if(m_channel != m_allocated_channel) { unsubscribe_channel(m_channel); } m_channel = dgi.read_channel(); subscribe_channel(m_channel); } break; case CLIENTAGENT_SEND_DATAGRAM: { DatagramPtr forward = Datagram::create(); forward->add_data(dgi.read_string()); forward_datagram(forward); } break; case CLIENTAGENT_OPEN_CHANNEL: { subscribe_channel(dgi.read_channel()); } break; case CLIENTAGENT_CLOSE_CHANNEL: { unsubscribe_channel(dgi.read_channel()); } break; case CLIENTAGENT_ADD_POST_REMOVE: { add_post_remove(dgi.read_datagram()); } break; case CLIENTAGENT_CLEAR_POST_REMOVES: { clear_post_removes(); } break; case CLIENTAGENT_DECLARE_OBJECT: { doid_t do_id = dgi.read_doid(); uint16_t dc_id = dgi.read_uint16(); if(m_declared_objects.find(do_id) != m_declared_objects.end()) { m_log->warning() << "Received object declaration for previously declared object " << do_id << ".\n"; return; } DeclaredObject obj; obj.id = do_id; obj.dcc = g_dcf->get_class_by_id(dc_id); m_declared_objects[do_id] = obj; } break; case CLIENTAGENT_UNDECLARE_OBJECT: { doid_t do_id = dgi.read_doid(); if(m_declared_objects.find(do_id) == m_declared_objects.end()) { m_log->warning() << "Received undeclare object for unknown object " << do_id << ".\n"; return; } m_declared_objects.erase(do_id); } break; case CLIENTAGENT_SET_FIELDS_SENDABLE: { doid_t do_id = dgi.read_doid(); uint16_t field_count = dgi.read_uint16(); std::unordered_set<uint16_t> fields; for(unsigned int i = 0; i < field_count; ++i) { fields.insert(dgi.read_uint16()); } m_fields_sendable[do_id] = fields; } break; case CLIENTAGENT_ADD_SESSION_OBJECT: { doid_t do_id = dgi.read_doid(); if(m_session_objects.find(do_id) != m_session_objects.end()) { m_log->warning() << "Received add session object for existing session object " << do_id << ".\n"; return; } m_log->debug() << "Added session object with id " << do_id << ".\n"; m_session_objects.insert(do_id); } break; case CLIENTAGENT_REMOVE_SESSION_OBJECT: { doid_t do_id = dgi.read_doid(); if(m_session_objects.find(do_id) == m_session_objects.end()) { m_log->warning() << "Received remove session object for non-session object " << do_id << ".\n"; return; } m_log->debug() << "Removed session object with id " << do_id << ".\n"; m_session_objects.erase(do_id); } break; case STATESERVER_OBJECT_SET_FIELD: { doid_t do_id = dgi.read_doid(); if(!lookup_object(do_id)) { m_log->warning() << "Received server-side field update for unknown object " << do_id << ".\n"; return; } if(sender != m_channel) { uint16_t field_id = dgi.read_uint16(); handle_set_field(do_id, field_id, dgi); } } break; case STATESERVER_OBJECT_SET_FIELDS: { doid_t do_id = dgi.read_doid(); if(!lookup_object(do_id)) { m_log->warning() << "Received server-side multi-field update for unknown object " << do_id << ".\n"; return; } if(sender != m_channel) { uint16_t num_fields = dgi.read_uint16(); handle_set_fields(do_id, num_fields, dgi); } } break; case STATESERVER_OBJECT_DELETE_RAM: { doid_t do_id = dgi.read_doid(); m_log->trace() << "Received DeleteRam for object with id " << do_id << "\n."; if(!lookup_object(do_id)) { m_log->warning() << "Received server-side object delete for unknown object " << do_id << ".\n"; return; } if(m_session_objects.find(do_id) != m_session_objects.end()) { // We have to erase the object from our session_objects here, because // the object has already been deleted and we don't want it to be deleted // again in the client's destructor. m_session_objects.erase(do_id); std::stringstream ss; ss << "The session object with id " << do_id << " has been unexpectedly deleted."; send_disconnect(CLIENT_DISCONNECT_SESSION_OBJECT_DELETED, ss.str()); return; } if(m_seen_objects.find(do_id) != m_seen_objects.end()) { handle_remove_object(do_id); m_seen_objects.erase(do_id); } if(m_owned_objects.find(do_id) != m_owned_objects.end()) { handle_remove_ownership(do_id); m_owned_objects.erase(do_id); } m_historical_objects.insert(do_id); m_visible_objects.erase(do_id); } break; case STATESERVER_OBJECT_ENTER_OWNER_WITH_REQUIRED_OTHER: case STATESERVER_OBJECT_ENTER_OWNER_WITH_REQUIRED: { doid_t do_id = dgi.read_doid(); doid_t parent = dgi.read_doid(); zone_t zone = dgi.read_zone(); uint16_t dc_id = dgi.read_uint16(); m_owned_objects.insert(do_id); if(m_visible_objects.find(do_id) == m_visible_objects.end()) { VisibleObject obj; obj.id = do_id; obj.parent = parent; obj.zone = zone; obj.dcc = g_dcf->get_class_by_id(dc_id); m_visible_objects[do_id] = obj; } handle_add_ownership(do_id, parent, zone, dc_id, dgi, msgtype == STATESERVER_OBJECT_ENTER_OWNER_WITH_REQUIRED_OTHER); } break; case STATESERVER_OBJECT_ENTER_LOCATION_WITH_REQUIRED: case STATESERVER_OBJECT_ENTER_LOCATION_WITH_REQUIRED_OTHER: { doid_t do_id = dgi.read_doid(); doid_t parent = dgi.read_doid(); zone_t zone = dgi.read_zone(); uint16_t dc_id = dgi.read_uint16(); if(m_owned_objects.find(do_id) != m_owned_objects.end() || m_seen_objects.find(do_id) != m_seen_objects.end()) { return; } if(m_visible_objects.find(do_id) == m_visible_objects.end()) { VisibleObject obj; obj.id = do_id; obj.dcc = g_dcf->get_class_by_id(dc_id); obj.parent = parent; obj.zone = zone; m_visible_objects[do_id] = obj; } m_seen_objects.insert(do_id); handle_add_object(do_id, parent, zone, dc_id, dgi, msgtype == STATESERVER_OBJECT_ENTER_LOCATION_WITH_REQUIRED_OTHER); // TODO: This is a tad inefficient as it checks every pending interest. // In practice, there shouldn't be many add-interest operations active // at once, however. std::list<uint32_t> deferred_deletes; for(auto it = m_pending_interests.begin(); it != m_pending_interests.end(); ++it) { if(it->second.is_ready(m_visible_objects)) { notify_interest_done(&it->second); handle_interest_done(it->second.m_interest_id, it->second.m_client_context); deferred_deletes.push_back(it->first); } } for(auto it = deferred_deletes.begin(); it != deferred_deletes.end(); ++it) { m_pending_interests.erase(*it); } } break; case STATESERVER_OBJECT_GET_ZONES_COUNT_RESP: { uint32_t context = dgi.read_uint32(); // using doid_t because <max_objects_in_zones> == <max_total_objects> doid_t count = dgi.read_doid(); auto it = m_pending_interests.find(context); if(it == m_pending_interests.end()) { m_log->error() << "Received GET_ZONES_COUNT_RESP for unknown context " << context << ".\n"; return; } it->second.store_total(count); if(it->second.is_ready(m_visible_objects)) { notify_interest_done(&it->second); handle_interest_done(it->second.m_interest_id, it->second.m_client_context); m_pending_interests.erase(context); } } break; case STATESERVER_OBJECT_CHANGING_LOCATION: { doid_t do_id = dgi.read_doid(); doid_t n_parent = dgi.read_doid(); zone_t n_zone = dgi.read_zone(); dgi.skip(sizeof(doid_t) + sizeof(zone_t)); // don't care about the old location bool disable = true; for(auto it = m_interests.begin(); it != m_interests.end(); ++it) { Interest &i = it->second; for(auto it2 = i.zones.begin(); it2 != i.zones.end(); ++it2) { if(*it2 == n_zone) { disable = false; break; } } } if(m_visible_objects.find(do_id) != m_visible_objects.end()) { m_visible_objects[do_id].parent = n_parent; m_visible_objects[do_id].zone = n_zone; } if(disable && m_owned_objects.find(do_id) == m_owned_objects.end()) { if(m_session_objects.find(do_id) != m_session_objects.end()) { std::stringstream ss; ss << "The session object with id " << do_id << " has unexpectedly left interest."; send_disconnect(CLIENT_DISCONNECT_SESSION_OBJECT_DELETED, ss.str()); return; } handle_remove_object(do_id); m_seen_objects.erase(do_id); m_historical_objects.insert(do_id); m_visible_objects.erase(do_id); } else { handle_change_location(do_id, n_parent, n_zone); } } break; case STATESERVER_OBJECT_CHANGING_OWNER: { doid_t do_id = dgi.read_doid(); channel_t n_owner = dgi.read_channel(); dgi.skip(sizeof(channel_t)); // don't care about the old owner if(n_owner == m_channel) { // We should already own this object, nothing changes and we // might get another enter_owner message. return; } if(m_owned_objects.find(do_id) == m_owned_objects.end()) { m_log->error() << "Received ChangingOwner for unowned object with id " << do_id << ".\n"; return; } if(m_seen_objects.find(do_id) == m_seen_objects.end()) { if(m_session_objects.find(do_id) != m_session_objects.end()) { std::stringstream ss; ss << "The session object with id " << do_id << " has unexpectedly left ownership."; send_disconnect(CLIENT_DISCONNECT_SESSION_OBJECT_DELETED, ss.str()); return; } handle_remove_ownership(do_id); m_owned_objects.erase(do_id); m_historical_objects.insert(do_id); m_visible_objects.erase(do_id); } } break; default: m_log->error() << "Recv'd unknown server msgtype " << msgtype << "\n."; } }