#include "stdafx.h" #include "Emu/Cell/PPUModule.h" #include "signaling_handler.h" #include "Emu/IdManager.h" #include "Emu/System.h" #include "Emu/Cell/Modules/cellSysutil.h" #include "np_handler.h" #include #ifdef _WIN32 #include #else #include #include #endif LOG_CHANNEL(sign_log, "Signaling"); std::vector, std::vector>> get_sign_msgs(); s32 send_packet_from_p2p_port(const std::vector& data, const sockaddr_in& addr); template <> void fmt_class_string::format(std::string& out, u64 arg) { format_enum(out, arg, [](auto value) { switch (value) { case signal_ping: return "PING"; case signal_pong: return "PONG"; case signal_connect: return "CONNECT"; case signal_connect_ack: return "CONNECT_ACK"; case signal_confirm: return "CONFIRM"; case signal_finished: return "FINISHED"; case signal_finished_ack: return "FINISHED_ACK"; } return unknown; }); } ///////////////////////////// //// SIGNALING CALLBACKS //// ///////////////////////////// void signaling_handler::set_sig_cb(u32 sig_cb_ctx, vm::ptr sig_cb, vm::ptr sig_cb_arg) { std::lock_guard lock(data_mutex); this->sig_cb_ctx = sig_cb_ctx; this->sig_cb = sig_cb; this->sig_cb_arg = sig_cb_arg; } void signaling_handler::set_ext_sig_cb(u32 sig_ext_cb_ctx, vm::ptr sig_ext_cb, vm::ptr sig_ext_cb_arg) { std::lock_guard lock(data_mutex); this->sig_ext_cb_ctx = sig_ext_cb_ctx; this->sig_ext_cb = sig_ext_cb; this->sig_ext_cb_arg = sig_ext_cb_arg; } void signaling_handler::set_sig2_cb(u16 sig2_cb_ctx, vm::ptr sig2_cb, vm::ptr sig2_cb_arg) { std::lock_guard lock(data_mutex); this->sig2_cb_ctx = sig2_cb_ctx; this->sig2_cb = sig2_cb; this->sig2_cb_arg = sig2_cb_arg; } void signaling_handler::signal_sig_callback(u32 conn_id, int event) { if (sig_cb) { sysutil_register_cb([sig_cb = this->sig_cb, sig_cb_ctx = this->sig_cb_ctx, conn_id, event, sig_cb_arg = this->sig_cb_arg](ppu_thread& cb_ppu) -> s32 { sig_cb(cb_ppu, sig_cb_ctx, conn_id, event, 0, sig_cb_arg); return 0; }); sign_log.notice("Called sig CB: 0x%x (conn_id: %d)", event, conn_id); } // extended callback also receives normal events signal_ext_sig_callback(conn_id, event); } void signaling_handler::signal_ext_sig_callback(u32 conn_id, int event) { if (sig_ext_cb) { sysutil_register_cb([sig_ext_cb = this->sig_ext_cb, sig_ext_cb_ctx = this->sig_ext_cb_ctx, conn_id, event, sig_ext_cb_arg = this->sig_ext_cb_arg](ppu_thread& cb_ppu) -> s32 { sig_ext_cb(cb_ppu, sig_ext_cb_ctx, conn_id, event, 0, sig_ext_cb_arg); return 0; }); sign_log.notice("Called EXT sig CB: 0x%x (conn_id: %d, member_id: %d)", event, conn_id); } } void signaling_handler::signal_sig2_callback(u64 room_id, u16 member_id, SceNpMatching2Event event) { // Signal the callback if (sig2_cb) { sysutil_register_cb([sig2_cb = this->sig2_cb, sig2_cb_ctx = this->sig2_cb_ctx, room_id, member_id, event, sig2_cb_arg = this->sig2_cb_arg](ppu_thread& cb_ppu) -> s32 { sig2_cb(cb_ppu, sig2_cb_ctx, room_id, member_id, event, 0, sig2_cb_arg); return 0; }); } sign_log.notice("Called sig2 CB: 0x%x (room_id: %d, member_id: %d)", event, room_id, member_id); } /////////////////////////////////// //// SIGNALING MSGS PROCESSING //// /////////////////////////////////// void signaling_handler::reschedule_packet(std::shared_ptr& si, SignalingCommand cmd, steady_clock::time_point new_timepoint) { for (auto it = qpackets.begin(); it != qpackets.end(); it++) { if (it->second.packet.command == cmd && it->second.sig_info == si) { auto new_queue = qpackets.extract(it); new_queue.key() = new_timepoint; qpackets.insert(std::move(new_queue)); return; } } } void signaling_handler::retire_packet(std::shared_ptr& si, SignalingCommand cmd) { for (auto it = qpackets.begin(); it != qpackets.end(); it++) { if (it->second.packet.command == cmd && it->second.sig_info == si) { qpackets.erase(it); return; } } } void signaling_handler::retire_all_packets(std::shared_ptr& si) { for (auto it = qpackets.begin(); it != qpackets.end();) { if (it->second.sig_info == si) it = qpackets.erase(it); else it++; } } bool signaling_handler::validate_signaling_packet(const signaling_packet* sp) { if (sp->signature != SIGNALING_SIGNATURE) { sign_log.error("Received a signaling packet with an invalid signature"); return false; } if (sp->version != 1u && sp->version != 2u) { sign_log.error("Invalid version in signaling packet"); return false; } return true; } void signaling_handler::process_incoming_messages() { auto msgs = get_sign_msgs(); for (const auto& msg : msgs) { if (msg.second.size() != sizeof(signaling_packet)) { sign_log.error("Received an invalid signaling packet"); continue; } auto op_addr = msg.first.first; auto op_port = msg.first.second; auto* sp = reinterpret_cast(msg.second.data()); if (!validate_signaling_packet(sp)) continue; if (sign_log.enabled == logs::level::trace) { in_addr addr; addr.s_addr = op_addr; if (sp->version == 1u) { char npid_buf[17]{}; memcpy(npid_buf, sp->V1.npid.handle.data, 16); std::string npid(npid_buf); sign_log.trace("sig1 %s from %s:%d(%s)", sp->command, inet_ntoa(addr), op_port, npid); } else { sign_log.trace("sig2 %s from %s:%d(%d:%d)", sp->command, inet_ntoa(addr), op_port, sp->V2.room_id, sp->V2.member_id); } } bool reply = false, schedule_repeat = false; auto& sent_packet = sp->version == 1u ? sig1_packet : sig2_packet; // Get signaling info for user to know if we should even bother looking further auto si = get_signaling_ptr(sp); if (!si && sp->version == 1u && sp->command == signal_connect) { // Connection can be remotely established and not mutual const u32 conn_id = create_sig_infos(&sp->V1.npid); si = sig1_peers.at(conn_id); // Activate the connection without triggering the main CB si->connStatus = SCE_NP_SIGNALING_CONN_STATUS_ACTIVE; si->addr = op_addr; si->port = op_port; si->ext_status = ext_sign_peer; // Notify extended callback that peer activated signal_ext_sig_callback(conn_id, SCE_NP_SIGNALING_EVENT_EXT_PEER_ACTIVATED); } if (!si || (si->connStatus == SCE_NP_SIGNALING_CONN_STATUS_INACTIVE && sp->command != signal_finished)) { // User is unknown to us or the connection is inactive // Ignore packet unless it's a finished packet in case the finished_ack wasn't received by opponent continue; } const auto now = steady_clock::now(); if (si) si->time_last_msg_recvd = now; const auto setup_ping = [&]() { for (auto it = qpackets.begin(); it != qpackets.end(); it++) { if (it->second.packet.command == signal_ping && it->second.sig_info == si) { return; } } sent_packet.command = signal_ping; queue_signaling_packet(sent_packet, si, now + REPEAT_PING_DELAY); }; switch (sp->command) { case signal_ping: reply = true; schedule_repeat = false; sent_packet.command = signal_pong; break; case signal_pong: reply = false; schedule_repeat = false; reschedule_packet(si, signal_ping, now + 15s); break; case signal_connect: reply = true; schedule_repeat = true; sent_packet.command = signal_connect_ack; // connection is established // TODO: notify extended callback! break; case signal_connect_ack: reply = true; schedule_repeat = false; setup_ping(); sent_packet.command = signal_confirm; retire_packet(si, signal_connect); // connection is active update_si_addr(si, op_addr, op_port); update_si_status(si, SCE_NP_SIGNALING_CONN_STATUS_ACTIVE); break; case signal_confirm: reply = false; schedule_repeat = false; setup_ping(); retire_packet(si, signal_connect_ack); // connection is active update_si_addr(si, op_addr, op_port); update_si_status(si, SCE_NP_SIGNALING_CONN_STATUS_ACTIVE, true); break; case signal_finished: reply = true; schedule_repeat = false; sent_packet.command = signal_finished_ack; // terminate connection update_si_status(si, SCE_NP_SIGNALING_CONN_STATUS_INACTIVE); break; case signal_finished_ack: reply = false; schedule_repeat = false; retire_packet(si, signal_finished); break; default: sign_log.error("Invalid signaling command received"); continue; } if (reply) { send_signaling_packet(sent_packet, op_addr, op_port); if (schedule_repeat) queue_signaling_packet(sent_packet, si, now + REPEAT_CONNECT_DELAY); } } } void signaling_handler::operator()() { while (thread_ctrl::state() != thread_state::aborting) { std::unique_lock lock(data_mutex); if (qpackets.size()) wakey.wait_until(lock, qpackets.begin()->first); else wakey.wait(lock); if (thread_ctrl::state() == thread_state::aborting) return; process_incoming_messages(); const auto now = steady_clock::now(); for (auto it = qpackets.begin(); it != qpackets.end();) { if (it->first > now) break; if (it->second.sig_info->time_last_msg_recvd < now - 60s) { // We had no connection to opponent for 60 seconds, consider the connection dead sign_log.trace("Timeout disconnection"); update_si_status(it->second.sig_info, SCE_NP_SIGNALING_CONN_STATUS_INACTIVE); break; // qpackets will be emptied of all packets from this user so we're requeuing } // Resend the packet send_signaling_packet(it->second.packet, it->second.sig_info->addr, it->second.sig_info->port); // Reschedule another packet SignalingCommand cmd = it->second.packet.command; auto& si = it->second.sig_info; std::chrono::milliseconds delay(500); switch (cmd) { case signal_ping: case signal_pong: delay = REPEAT_PING_DELAY; break; case signal_connect: case signal_connect_ack: case signal_confirm: delay = REPEAT_CONNECT_DELAY; break; case signal_finished: case signal_finished_ack: delay = REPEAT_FINISHED_DELAY; break; } it++; reschedule_packet(si, cmd, now + delay); } } } void signaling_handler::wake_up() { wakey.notify_one(); } void signaling_handler::update_si_addr(std::shared_ptr& si, u32 new_addr, u16 new_port) { ensure(si); if (si->addr != new_addr || si->port != new_port) { in_addr addr_old, addr_new; addr_old.s_addr = si->addr; addr_new.s_addr = new_addr; sign_log.trace("Updated Address from %s:%d to %s:%d", inet_ntoa(addr_old), si->port, inet_ntoa(addr_new), new_port); si->addr = new_addr; si->port = new_port; } } void signaling_handler::update_si_status(std::shared_ptr& si, s32 new_status, bool confirm_packet) { if (!si) return; if (si->connStatus == SCE_NP_SIGNALING_CONN_STATUS_PENDING && new_status == SCE_NP_SIGNALING_CONN_STATUS_ACTIVE) { si->connStatus = SCE_NP_SIGNALING_CONN_STATUS_ACTIVE; ensure(si->version == 1u || si->version == 2u); if (si->version == 1u) signal_sig_callback(si->conn_id, SCE_NP_SIGNALING_EVENT_ESTABLISHED); else signal_sig2_callback(si->room_id, si->member_id, SCE_NP_MATCHING2_SIGNALING_EVENT_Established); return; } if ((si->connStatus == SCE_NP_SIGNALING_CONN_STATUS_PENDING || si->connStatus == SCE_NP_SIGNALING_CONN_STATUS_ACTIVE) && new_status == SCE_NP_SIGNALING_CONN_STATUS_INACTIVE) { si->connStatus = SCE_NP_SIGNALING_CONN_STATUS_INACTIVE; ensure(si->version == 1u || si->version == 2u); if (si->version == 1u) signal_sig_callback(si->conn_id, SCE_NP_SIGNALING_EVENT_DEAD); else signal_sig2_callback(si->room_id, si->member_id, SCE_NP_MATCHING2_SIGNALING_EVENT_Dead); retire_all_packets(si); return; } if (confirm_packet && si->version == 1u && si->ext_status == ext_sign_none) { si->ext_status = ext_sign_mutual; signal_ext_sig_callback(si->conn_id, SCE_NP_SIGNALING_EVENT_EXT_MUTUAL_ACTIVATED); } } void signaling_handler::set_self_sig_info(SceNpId& npid) { std::lock_guard lock(data_mutex); sig1_packet.V1.npid = npid; } void signaling_handler::set_self_sig2_info(u64 room_id, u16 member_id) { std::lock_guard lock(data_mutex); sig2_packet.V2.room_id = room_id; sig2_packet.V2.member_id = member_id; } void signaling_handler::send_signaling_packet(signaling_packet& sp, u32 addr, u16 port) { std::vector packet(sizeof(signaling_packet) + sizeof(u16)); reinterpret_cast&>(packet.data()[0]) = 65535; memcpy(packet.data() + sizeof(u16), &sp, sizeof(signaling_packet)); sockaddr_in dest; memset(&dest, 0, sizeof(sockaddr_in)); dest.sin_family = AF_INET; dest.sin_addr.s_addr = addr; dest.sin_port = std::bit_cast>(port); sign_log.trace("Sending %s packet to %s:%d", sp.command, inet_ntoa(dest.sin_addr), port); if (send_packet_from_p2p_port(packet, dest) == -1) { sign_log.error("Failed to send signaling packet to %s:%d", inet_ntoa(dest.sin_addr), port); } } void signaling_handler::queue_signaling_packet(signaling_packet& sp, std::shared_ptr si, steady_clock::time_point wakeup_time) { queued_packet qp; qp.sig_info = si; qp.packet = sp; qpackets.emplace(wakeup_time, std::move(qp)); } std::shared_ptr signaling_handler::get_signaling_ptr(const signaling_packet* sp) { // V1 if (sp->version == 1u) { char npid_buf[17]{}; memcpy(npid_buf, sp->V1.npid.handle.data, 16); std::string npid(npid_buf); if (!npid_to_conn_id.count(npid)) return nullptr; auto conn_id = npid_to_conn_id.at(npid); if (!sig1_peers.count(conn_id)) { sign_log.error("Discrepancy in signaling 1 data"); return nullptr; } return sig1_peers.at(conn_id); } // V2 auto room_id = sp->V2.room_id; auto member_id = sp->V2.member_id; if (!sig2_peers.count(room_id) || !sig2_peers.at(room_id).count(member_id)) return nullptr; return sig2_peers.at(room_id).at(member_id); } void signaling_handler::start_sig(u32 conn_id, u32 addr, u16 port) { std::lock_guard lock(data_mutex); start_sig_nl(conn_id, addr, port); } void signaling_handler::start_sig_nl(u32 conn_id, u32 addr, u16 port) { auto& sent_packet = sig1_packet; sent_packet.command = signal_connect; auto si = sig1_peers.at(conn_id); si->addr = addr; si->port = port; send_signaling_packet(sent_packet, si->addr, si->port); queue_signaling_packet(sent_packet, si, steady_clock::now() + REPEAT_CONNECT_DELAY); } void signaling_handler::start_sig2(u64 room_id, u16 member_id) { std::lock_guard lock(data_mutex); auto& sent_packet = sig2_packet; sent_packet.command = signal_connect; auto si = sig2_peers.at(room_id).at(member_id); send_signaling_packet(sent_packet, si->addr, si->port); queue_signaling_packet(sent_packet, si, steady_clock::now() + REPEAT_CONNECT_DELAY); } void signaling_handler::disconnect_sig2_users(u64 room_id) { std::lock_guard lock(data_mutex); if (!sig2_peers.count(room_id)) return; auto& sent_packet = sig2_packet; sent_packet.command = signal_finished; for (const auto& member : sig2_peers.at(room_id)) { auto& si = member.second; if (si->connStatus != SCE_NP_SIGNALING_CONN_STATUS_INACTIVE && !si->self) { send_signaling_packet(sent_packet, si->addr, si->port); queue_signaling_packet(sent_packet, si, steady_clock::now() + REPEAT_FINISHED_DELAY); } } } u32 signaling_handler::create_sig_infos(const SceNpId* npid) { ensure(npid->handle.data[16] == 0); std::string npid_str(reinterpret_cast(npid->handle.data)); if (npid_to_conn_id.count(npid_str)) { return npid_to_conn_id.at(npid_str); } const u32 conn_id = cur_conn_id++; npid_to_conn_id.emplace(npid_str, conn_id); sig1_peers.emplace(conn_id, std::make_shared()); sig1_peers.at(conn_id)->version = 1; sig1_peers.at(conn_id)->conn_id = conn_id; return conn_id; } u32 signaling_handler::init_sig_infos(const SceNpId* npid) { std::lock_guard lock(data_mutex); const u32 conn_id = create_sig_infos(npid); if (sig1_peers[conn_id]->connStatus == SCE_NP_SIGNALING_CONN_STATUS_INACTIVE) { sign_log.trace("Creating new sig1 connection and requesting infos from RPCN"); sig1_peers[conn_id]->connStatus = SCE_NP_SIGNALING_CONN_STATUS_PENDING; // Request peer infos from RPCN std::string npid_str(reinterpret_cast(npid->handle.data)); auto& nph = g_fxo->get>(); nph.req_sign_infos(npid_str, conn_id); } else { // Connection already exists(from passive connection) if (sig1_peers[conn_id]->connStatus == SCE_NP_SIGNALING_CONN_STATUS_ACTIVE && sig1_peers[conn_id]->ext_status == ext_sign_peer) { sign_log.trace("Activating already peer activated connection"); sig1_peers[conn_id]->ext_status = ext_sign_mutual; start_sig_nl(conn_id, sig1_peers[conn_id]->addr, sig1_peers[conn_id]->port); signal_sig_callback(conn_id, SCE_NP_SIGNALING_EVENT_ESTABLISHED); signal_ext_sig_callback(conn_id, SCE_NP_SIGNALING_EVENT_EXT_MUTUAL_ACTIVATED); } } return conn_id; } signaling_info signaling_handler::get_sig_infos(u32 conn_id) { return *sig1_peers[conn_id]; } void signaling_handler::set_sig2_infos(u64 room_id, u16 member_id, s32 status, u32 addr, u16 port, bool self) { std::lock_guard lock(data_mutex); if (!sig2_peers[room_id][member_id]) sig2_peers[room_id][member_id] = std::make_shared(); auto& peer = sig2_peers[room_id][member_id]; peer->connStatus = status; peer->addr = addr; peer->port = port; peer->self = self; peer->version = 2; peer->room_id = room_id; peer->member_id = member_id; } signaling_info signaling_handler::get_sig2_infos(u64 room_id, u16 member_id) { std::lock_guard lock(data_mutex); if (!sig2_peers[room_id][member_id]) { sig2_peers[room_id][member_id] = std::make_shared(); auto& peer = sig2_peers[room_id][member_id]; peer->room_id = room_id; peer->member_id = member_id; peer->version = 2; } return *sig2_peers[room_id][member_id]; }