| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| #include <config.h> |
| |
| #include <common/ring.h> |
| |
| #include "red-common.h" |
| #include "reds.h" |
| #include "red-channel-client.h" |
| #include "red-client.h" |
| #include "main-channel.h" |
| #include "main-channel-client.h" |
| |
| struct MainChannel |
| { |
| RedChannel parent; |
| |
| |
| RedsMigSpice mig_target; |
| int num_clients_mig_wait; |
| }; |
| |
| struct MainChannelClass |
| { |
| RedChannelClass parent_class; |
| }; |
| |
| G_DEFINE_TYPE(MainChannel, main_channel, RED_TYPE_CHANNEL) |
| |
| int main_channel_is_connected(MainChannel *main_chan) |
| { |
| return red_channel_is_connected(RED_CHANNEL(main_chan)); |
| } |
| |
| RedClient *main_channel_get_client_by_link_id(MainChannel *main_chan, uint32_t connection_id) |
| { |
| RedChannelClient *rcc; |
| |
| FOREACH_CLIENT(main_chan, rcc) { |
| MainChannelClient *mcc = MAIN_CHANNEL_CLIENT(rcc); |
| if (main_channel_client_get_connection_id(mcc) == connection_id) { |
| return red_channel_client_get_client(rcc); |
| } |
| } |
| return NULL; |
| } |
| |
| static void main_channel_push_channels(MainChannelClient *mcc) |
| { |
| RedChannelClient *rcc = RED_CHANNEL_CLIENT(mcc); |
| if (red_client_during_migrate_at_target(red_channel_client_get_client(rcc))) { |
| red_channel_warning(red_channel_client_get_channel(rcc), |
| "warning: ignoring unexpected SPICE_MSGC_MAIN_ATTACH_CHANNELS" |
| "during migration"); |
| return; |
| } |
| red_channel_client_pipe_add_type(rcc, RED_PIPE_ITEM_TYPE_MAIN_CHANNELS_LIST); |
| } |
| |
| void main_channel_push_mouse_mode(MainChannel *main_chan, SpiceMouseMode current_mode, |
| int is_client_mouse_allowed) |
| { |
| red_channel_pipes_add(RED_CHANNEL(main_chan), |
| main_mouse_mode_item_new(current_mode, is_client_mouse_allowed)); |
| } |
| |
| void main_channel_push_agent_connected(MainChannel *main_chan) |
| { |
| RedChannelClient *rcc; |
| FOREACH_CLIENT(RED_CHANNEL(main_chan), rcc) { |
| if (red_channel_client_test_remote_cap(rcc, |
| SPICE_MAIN_CAP_AGENT_CONNECTED_TOKENS)) { |
| red_channel_client_pipe_add_type(rcc, |
| RED_PIPE_ITEM_TYPE_MAIN_AGENT_CONNECTED_TOKENS); |
| } else { |
| red_channel_client_pipe_add_empty_msg(rcc, SPICE_MSG_MAIN_AGENT_CONNECTED); |
| } |
| } |
| } |
| |
| void main_channel_push_agent_disconnected(MainChannel *main_chan) |
| { |
| red_channel_pipes_add_type(RED_CHANNEL(main_chan), RED_PIPE_ITEM_TYPE_MAIN_AGENT_DISCONNECTED); |
| } |
| |
| static void main_channel_push_migrate_data_item(MainChannel *main_chan) |
| { |
| red_channel_pipes_add_type(RED_CHANNEL(main_chan), RED_PIPE_ITEM_TYPE_MAIN_MIGRATE_DATA); |
| } |
| |
| static bool main_channel_handle_migrate_data(RedChannelClient *rcc, |
| uint32_t size, void *message) |
| { |
| RedChannel *channel = red_channel_client_get_channel(rcc); |
| MainChannelClient *mcc = MAIN_CHANNEL_CLIENT(rcc); |
| SpiceMigrateDataHeader *header = (SpiceMigrateDataHeader *)message; |
| |
| |
| spice_assert(red_channel_get_n_clients(channel) == 1); |
| |
| if (size < sizeof(SpiceMigrateDataHeader) + sizeof(SpiceMigrateDataMain)) { |
| red_channel_warning(red_channel_client_get_channel(rcc), |
| "bad message size %u", size); |
| return FALSE; |
| } |
| if (!migration_protocol_validate_header(header, |
| SPICE_MIGRATE_DATA_MAIN_MAGIC, |
| SPICE_MIGRATE_DATA_MAIN_VERSION)) { |
| spice_error("bad header"); |
| return FALSE; |
| } |
| return reds_handle_migrate_data(red_channel_get_server(channel), mcc, |
| (SpiceMigrateDataMain *)(header + 1), |
| size); |
| } |
| |
| void main_channel_push_multi_media_time(MainChannel *main_chan, uint32_t time) |
| { |
| red_channel_pipes_add(RED_CHANNEL(main_chan), main_multi_media_time_item_new(time)); |
| } |
| |
| static void main_channel_fill_mig_target(MainChannel *main_channel, RedsMigSpice *mig_target) |
| { |
| spice_assert(mig_target); |
| g_free(main_channel->mig_target.host); |
| main_channel->mig_target.host = g_strdup(mig_target->host); |
| g_free(main_channel->mig_target.cert_subject); |
| if (mig_target->cert_subject) { |
| main_channel->mig_target.cert_subject = g_strdup(mig_target->cert_subject); |
| } else { |
| main_channel->mig_target.cert_subject = NULL; |
| } |
| main_channel->mig_target.port = mig_target->port; |
| main_channel->mig_target.sport = mig_target->sport; |
| } |
| |
| void |
| main_channel_registered_new_channel(MainChannel *main_chan, RedChannel *channel) |
| { |
| red_channel_pipes_add(RED_CHANNEL(main_chan), registered_channel_item_new(channel)); |
| } |
| |
| void main_channel_migrate_switch(MainChannel *main_chan, RedsMigSpice *mig_target) |
| { |
| main_channel_fill_mig_target(main_chan, mig_target); |
| red_channel_pipes_add_type(RED_CHANNEL(main_chan), RED_PIPE_ITEM_TYPE_MAIN_MIGRATE_SWITCH_HOST); |
| } |
| |
| static bool main_channel_handle_message(RedChannelClient *rcc, uint16_t type, |
| uint32_t size, void *message) |
| { |
| RedChannel *channel = red_channel_client_get_channel(rcc); |
| MainChannelClient *mcc = MAIN_CHANNEL_CLIENT(rcc); |
| RedsState *reds = red_channel_get_server(channel); |
| |
| switch (type) { |
| case SPICE_MSGC_MAIN_AGENT_START: { |
| SpiceMsgcMainAgentStart *tokens; |
| |
| tokens = (SpiceMsgcMainAgentStart *)message; |
| reds_on_main_agent_start(reds, mcc, tokens->num_tokens); |
| break; |
| } |
| case SPICE_MSGC_MAIN_AGENT_DATA: |
| reds_on_main_agent_data(reds, mcc, message, size); |
| break; |
| case SPICE_MSGC_MAIN_AGENT_TOKEN: { |
| SpiceMsgcMainAgentTokens *tokens; |
| |
| tokens = (SpiceMsgcMainAgentTokens *)message; |
| reds_on_main_agent_tokens(reds, mcc, tokens->num_tokens); |
| break; |
| } |
| case SPICE_MSGC_MAIN_ATTACH_CHANNELS: |
| main_channel_push_channels(mcc); |
| break; |
| case SPICE_MSGC_MAIN_MIGRATE_CONNECTED: |
| main_channel_client_handle_migrate_connected(mcc, |
| TRUE , |
| FALSE ); |
| break; |
| case SPICE_MSGC_MAIN_MIGRATE_CONNECTED_SEAMLESS: |
| main_channel_client_handle_migrate_connected(mcc, |
| TRUE , |
| TRUE ); |
| break; |
| case SPICE_MSGC_MAIN_MIGRATE_CONNECT_ERROR: |
| main_channel_client_handle_migrate_connected(mcc, FALSE, FALSE); |
| break; |
| case SPICE_MSGC_MAIN_MIGRATE_DST_DO_SEAMLESS: |
| main_channel_client_handle_migrate_dst_do_seamless(mcc, |
| ((SpiceMsgcMainMigrateDstDoSeamless *)message)->src_version); |
| break; |
| case SPICE_MSGC_MAIN_MIGRATE_END: |
| main_channel_client_handle_migrate_end(mcc); |
| break; |
| case SPICE_MSGC_MAIN_MOUSE_MODE_REQUEST: |
| reds_on_main_mouse_mode_request(reds, message, size); |
| break; |
| case SPICE_MSGC_PONG: |
| main_channel_client_handle_pong(mcc, (SpiceMsgPing *)message, size); |
| break; |
| default: |
| return red_channel_client_handle_message(rcc, type, size, message); |
| } |
| return TRUE; |
| } |
| |
| static bool main_channel_handle_migrate_flush_mark(RedChannelClient *rcc) |
| { |
| RedChannel *channel = red_channel_client_get_channel(rcc); |
| spice_debug("trace"); |
| main_channel_push_migrate_data_item(MAIN_CHANNEL(channel)); |
| return TRUE; |
| } |
| |
| MainChannelClient *main_channel_link(MainChannel *channel, RedClient *client, |
| RedStream *stream, uint32_t connection_id, int migration, |
| RedChannelCapabilities *caps) |
| { |
| MainChannelClient *mcc; |
| |
| spice_assert(channel); |
| |
| |
| |
| |
| mcc = main_channel_client_create(channel, client, stream, connection_id, caps); |
| return mcc; |
| } |
| |
| MainChannel* main_channel_new(RedsState *reds) |
| { |
| |
| return g_object_new(TYPE_MAIN_CHANNEL, |
| "spice-server", reds, |
| "core-interface", reds_get_core_interface(reds), |
| "channel-type", (gint)SPICE_CHANNEL_MAIN, |
| "id", 0, |
| "handle-acks", FALSE, |
| "migration-flags", |
| (SPICE_MIGRATE_NEED_FLUSH | SPICE_MIGRATE_NEED_DATA_TRANSFER), |
| NULL); |
| } |
| |
| static void |
| main_channel_constructed(GObject *object) |
| { |
| MainChannel *self = MAIN_CHANNEL(object); |
| |
| G_OBJECT_CLASS(main_channel_parent_class)->constructed(object); |
| |
| red_channel_set_cap(RED_CHANNEL(self), SPICE_MAIN_CAP_SEMI_SEAMLESS_MIGRATE); |
| red_channel_set_cap(RED_CHANNEL(self), SPICE_MAIN_CAP_SEAMLESS_MIGRATE); |
| } |
| |
| static void |
| main_channel_init(MainChannel *self) |
| { |
| } |
| |
| static void |
| main_channel_class_init(MainChannelClass *klass) |
| { |
| GObjectClass *object_class = G_OBJECT_CLASS(klass); |
| RedChannelClass *channel_class = RED_CHANNEL_CLASS(klass); |
| |
| object_class->constructed = main_channel_constructed; |
| |
| channel_class->parser = spice_get_client_channel_parser(SPICE_CHANNEL_MAIN, NULL); |
| channel_class->handle_message = main_channel_handle_message; |
| |
| |
| channel_class->send_item = main_channel_client_send_item; |
| channel_class->handle_migrate_flush_mark = main_channel_handle_migrate_flush_mark; |
| channel_class->handle_migrate_data = main_channel_handle_migrate_data; |
| |
| |
| channel_class->migrate = main_channel_client_migrate; |
| } |
| |
| static int main_channel_connect_semi_seamless(MainChannel *main_channel) |
| { |
| RedChannelClient *rcc; |
| |
| FOREACH_CLIENT(main_channel, rcc) { |
| MainChannelClient *mcc = MAIN_CHANNEL_CLIENT(rcc); |
| if (main_channel_client_connect_semi_seamless(mcc)) |
| main_channel->num_clients_mig_wait++; |
| } |
| return main_channel->num_clients_mig_wait; |
| } |
| |
| static int main_channel_connect_seamless(MainChannel *main_channel) |
| { |
| RedChannelClient *rcc; |
| |
| spice_assert(red_channel_get_n_clients(RED_CHANNEL(main_channel)) == 1); |
| |
| FOREACH_CLIENT(main_channel, rcc) { |
| MainChannelClient *mcc = MAIN_CHANNEL_CLIENT(rcc); |
| main_channel_client_connect_seamless(mcc); |
| main_channel->num_clients_mig_wait++; |
| } |
| return main_channel->num_clients_mig_wait; |
| } |
| |
| int main_channel_migrate_connect(MainChannel *main_channel, RedsMigSpice *mig_target, |
| int try_seamless) |
| { |
| main_channel_fill_mig_target(main_channel, mig_target); |
| main_channel->num_clients_mig_wait = 0; |
| |
| if (!main_channel_is_connected(main_channel)) { |
| return 0; |
| } |
| |
| if (!try_seamless) { |
| return main_channel_connect_semi_seamless(main_channel); |
| } else { |
| RedChannelClient *rcc; |
| GList *clients = red_channel_get_clients(RED_CHANNEL(main_channel)); |
| |
| |
| rcc = g_list_nth_data(clients, 0); |
| |
| if (!red_channel_client_test_remote_cap(rcc, |
| SPICE_MAIN_CAP_SEAMLESS_MIGRATE)) { |
| return main_channel_connect_semi_seamless(main_channel); |
| } else { |
| return main_channel_connect_seamless(main_channel); |
| } |
| } |
| |
| } |
| |
| void main_channel_migrate_cancel_wait(MainChannel *main_chan) |
| { |
| RedChannelClient *rcc; |
| |
| FOREACH_CLIENT(main_chan, rcc) { |
| MainChannelClient *mcc = MAIN_CHANNEL_CLIENT(rcc); |
| main_channel_client_migrate_cancel_wait(mcc); |
| } |
| main_chan->num_clients_mig_wait = 0; |
| } |
| |
| int main_channel_migrate_src_complete(MainChannel *main_chan, int success) |
| { |
| int semi_seamless_count = 0; |
| RedChannelClient *rcc; |
| |
| if (!red_channel_get_clients(RED_CHANNEL(main_chan))) { |
| red_channel_warning(RED_CHANNEL(main_chan), "no peer connected"); |
| return 0; |
| } |
| |
| FOREACH_CLIENT(main_chan, rcc) { |
| MainChannelClient *mcc = MAIN_CHANNEL_CLIENT(rcc); |
| if (main_channel_client_migrate_src_complete(mcc, success)) |
| semi_seamless_count++; |
| } |
| return semi_seamless_count; |
| } |
| |
| void main_channel_on_migrate_connected(MainChannel *main_channel, |
| gboolean success, gboolean seamless) |
| { |
| spice_assert(main_channel->num_clients_mig_wait); |
| spice_assert(!seamless || main_channel->num_clients_mig_wait == 1); |
| if (!--main_channel->num_clients_mig_wait) { |
| reds_on_main_migrate_connected(red_channel_get_server(RED_CHANNEL(main_channel)), |
| seamless && success); |
| } |
| } |
| |
| const RedsMigSpice* main_channel_get_migration_target(MainChannel *main_chan) |
| { |
| return &main_chan->mig_target; |
| } |