Spice协议解析
----------------------ref:
在Protocol.h里定义了所有通道的公共部分
在Enums.h里定义了消息的type
Message.h里定义了消息的格式
一 简介
Spice一共有11个通道,如附录1.1所示,在所有通道中,main通道必须是第一个建立的。客户端使用不同的端口号区分不同的通道,服务端使用同一个端口号。对于所有通道来说,有一些消息是公共的。常用的通道有:a).main通道负责连接建立 b). display channel 负责图像显示 c). inputs channel for 发送鼠标键盘指令 d). cursor channel 接收鼠标指针的位置 e). Playback channel 接收音频流 and f).Record channel 发送音频。Spice协议采用网络字节序。
二 连接建立
对于所有的通道,连接建立的过程都是相似的,每个通道都要单独建立连接。每个通道建立连接时,客户端首先发送client link message,其中包括通道的类型和属性;服务端收到消息后,回复一个server link message。在server link message中有一个public_key字段,用于spice认证。
2.1 连接建立时的公告报头
client link message和server link message拥有相同的数据包头:
1) SPICE MAGIC:32位,固定值,当前版本中为REDQ;
2) 主要版本号:32位,当服务端和客户端的主要版本号一致时,忽略次要版本号;
3) 次要版本号:32位,当服务端和客户端的主要版本号不一致时,使用次要版本号;
4) 数据包长度:32位,除去spice头部外数据包的长度,单位为字节。
2.2 client link
client link message的字段如下:
1) 会话id:32位,主通道第一次建立连接时,该字段的值为0,服务端生成一个会话id,并在spice main init消息中传递给客户端。spice其他通道建立连接时,该字段值为服务端生成的会话id,通过该字段判断通道间是否属于一次会话;
2) 通道类型:8位,1为主通道,2为display通道,3为input通道,4为cursor通道,5为playback通道,6为record通道;
3) 通道id:8位,当一次会话有多个相同类型的通道时,使用通道id区分相同类型的通道;
4) 共有属性数量:32位,属性分为所有通道共有的和该通道专有属性两类;
5) 通道属性数量:32位,通道专有属性的数量;
6) 属性偏移量:32位,单位为字节,从会话id起(除去头部)到第一个属性的偏移量;
7) 共有属性:32位,每个属性占一位,set为1,not set为0,比如有4个属性,第一个和最后一个set,则值为1001(二进制),即0x09;spice的共有属性包括: AUTH_SELECTION, AUTH_SPICE, AUTH_SASL,MINI_HEADER;
8) 通道属性:32位,其他同上。主通道的专有属性包括: SEMI_SEAMLESS_MIGRATE,NAME_AND_UUID,GENT_CONNECTED_TOKENS,SEAMLESS_MIGRATE。
2.3 server link
server link message的字段如下:
1) 错误码:32位,对client link message的回复,错误码为0时,证明连接正确建立。
2) 公钥:1024位,服务端把公钥发送给客户端,客户端使用公钥对密码进行加密,并将结果在spice ticket消息中发送给服务端进行验证。
3) 属性字段的说明参考client link message。
使用ticket消息进行认证
服务端和客户端拥有相同的密码,服务端根据密码生成公钥和私钥,将公钥通过server link message发送给客户端。客户端使用公钥对密码进行加密,并将加密结果通过client ticket发送给服务端。服务端将收到的密码使用私钥进行解密,并与原来的密码进行对比,如果验证通过,将验证通过的结果通过server ticket消息(通过时该消息字段值为0)告知给客户端。
三 公共报头
除了建立连接时的四种消息外(client link message、server link message、ticket、LinkAuthMechanism)外,其他的消息使用一样公共头部,头部包括16位的消息类型和32位的数据包大小。其中,type的取值各通道间是独立的(不同通道间,即使type值相同,可能并不是同一种消息)。
四 主通道
建立连接后,主通道的第一个消息必须是Server INIT。字段里的Supported mouse mode由虚拟机的配置文件指定,服务端鼠标模式为必选模式,客户端鼠标模式为可选模式,客户端鼠标模式可能需要spice agent组件的支持。客户端模式使用鼠标的绝对位置,服务端鼠标模式使用鼠标的相对位置,只在虚拟机窗口范围内移动,主通道负责鼠标模式的控制。
4.1 server init:
1) 会话id:32位,由服务器生成并发送给客户端,客户端在建立其他通道时,使用该会话id;
2) 显示通道数量:32位,期望的显示通道的数量,不能为0;
3) 支持的鼠标模式:32位,值0x01时为只支持服务端鼠标模式,0x03时支持两种模式;
4) 当前的鼠标模式:32位,01为服务端模式,02为客户端模式;
5) 当前时间:32位,当前服务器的时间,用于视频发送时画面和声音的同步。
4.2 attach channels
服务端发送INIT消息后,客户端使用ATTACH CHANNELS消息对其进行回复,该消息类型值为104。
五 显示通道
服务端的显示通道在redworker.c里处理。qxl会将图像渲染的命令(guest系统指定的)传递给服务端,服务端通过渲染树,去除掉中重复的图像数据,并放到一个管道中(管道与客户端一一对应)。每次push pipe时,从管道尾部取出数据,在服务端进行压缩,并生成一个数据包,发送给客户端。客户端在channel_display.c里对显示通道进行处理,图像显示在canavs_base.c里。
客户端发送SPICE_MSGC_DISPLAY_INIT消息指定自己的图像缓冲区大小(单位像素)。客户端发送SPICE_MSGC_DISPLAY_PREFERRED_COMPRESSION指定压缩算法,值4是quic算法。服务端发送SPICE_MSG_DISPLAY_INVAL_PALETTE来清空客户端的缓冲区,发送SPICE_MSG_DISPLAY_SURFACE_CREATE建立一个客户端显示区域,包括区域的分辨率和格式。
5.1 client init
client init 指定客户端图像缓冲区大小:
5.2 preferred compression
PREFERRED_COMPRESSION 指定客户端使用的图像压缩算法,4为quic
5.3 surface create
surface create服务端发送,指定surface的大小分辨率格式。
5.4 draw copy
draw copy 负责将图像从guest拷贝到客户端
surface id:32位
RECT:矩阵,包含4个位置信息,左上右下,指定该draw copy消息源图像的位置。当存在rect时,该字段的4个位置信息为所有rect矩阵位置的边界。
spice clip:type为0时没有,为1时有clip,clip的若干个矩阵对整个大矩阵进行切割。
为1时,会有一个rect[]的列表,包括rect的数量和若干个rect。
地址:32位指针地址
Rect:源图像的大小,矩形矩阵,可能与上面的rect字段大小相等,不等时进行缩放。
rop_descriptor:16位 光栅操作码
scale_mode:8位,缩放模式
SpiceQMask:mask; 限制copy的区域
SpiceImage *src_bitmap; 包括图像描述符SpiceImageDescriptor {
uint64_t id;
uint8_t type; //图像类型:0是位图 1是quic
uint8_t flags;
uint32_t width;
uint32_t height;}和不同类型的图像的数据结构
type字段可能的取值有:0位图,1 quic,103 from cache 当type为from cache时,从缓存中取出特定id值的图像。
Flag的可能取值有1 为SPICE_IMAGE_FLAGS_CACHE_ME,显示该图像以id为key存储到缓存中,2为SPICE_IMAGE_FLAGS_HIGH_BITS_SET 二进制表示的最高两位忽略
Spice使用图像缓存技术,将每个图像分配一个id,以哈希表的方式存储在缓存中。在服务端对使用LRU算法图像缓存进行维护。只能由服务端控制主动清除缓存,客户端没有相应的机制。
Spice图像的消息可以分为三大部分,第一部分是目的图像的位置,由一个rect和可能存在的若干clip(rect的列表)构成;第二部分是操作符,使用指定的操作符对源图像和目的图像进行相应的操作;第三部分是源图像,可能是一个图像或者调色板,或者是从缓存中获得的数据,将源图像使用操作符操作后,转成surface的图像格式,显示到目标图像。
Spice图像的来源有三种类型,一是服务端的源图像,通过网络获取或者从缓存中获取;二是调色板,即指定的颜色或指定一幅图像的某个位置的颜色;三是客户端特定位置的图像。通常使用不同的光栅操作符将三者中的某几种进行操作,最后得到最终的图像。
4.1 保持连接
使用ping和pong来保持连接。
4.2 服务器通知(错误处理)
服务端使用SPICE_MSG_NOTIFY对客户端进行消息通告,通告有三种类型,ERROR,WARN,INFO。
4.3 其他
其他的消息包括通道迁移,通道确认,关闭连接等。
服务端发送INIT消息后,客户端使用ATTACH CHANNELS消息对其进行回复。
Server name和server uuid由虚拟机的xml指定。
客户端回复attach channels对main init消息进行回复。
收到attach channels后,服务端可以使用channels_list通告客户端可用的通道。
附录:
1通道列表
enum {
SPICE_CHANNEL_MAIN = 1,
SPICE_CHANNEL_DISPLAY,
SPICE_CHANNEL_INPUTS,
SPICE_CHANNEL_CURSOR,
SPICE_CHANNEL_PLAYBACK,
SPICE_CHANNEL_RECORD,
SPICE_CHANNEL_TUNNEL,
SPICE_CHANNEL_SMARTCARD,
SPICE_CHANNEL_USBREDIR,
SPICE_CHANNEL_PORT,
SPICE_CHANNEL_WEBDAV,
SPICE_END_CHANNEL
};
2连接建立
2.1 消息格式
reds.c 2277: reds_init_client_connection 和socket有关 新建一个RedLinkInfo对象
客户端发送RedLinkMess:
typedefstruct SPICE_ATTR_PACKED SpiceLinkHeader {
uint32_t magic;
uint32_t major_version; //2
uint32_t minor_version; //2
uint32_t size;
} SpiceLinkHeader;
typedefstruct SPICE_ATTR_PACKED SpiceLinkMess {
uint32_t connection_id;
uint8_t channel_type;
uint8_t channel_id;
uint32_t num_common_caps;
uint32_t num_channel_caps;
uint32_t caps_offset;
} SpiceLinkMess;
服务端发送RedLinkReply:
typedefstruct SPICE_ATTR_PACKED SpiceLinkHeader {
uint32_t magic;
uint32_t major_version; //1
uint32_t minor_version; //0
uint32_t size;
} SpiceLinkHeader;
typedefstruct SPICE_ATTR_PACKED SpiceLinkReply {
uint32_t error; //SPICE_LINK_ERR_OK 0成功
uint8_t pub_key[SPICE_TICKET_PUBKEY_BYTES];
uint32_t num_common_caps;
uint32_t num_channel_caps;
uint32_t caps_offset;
} SpiceLinkReply;
2.2 通用属性
enum {
SPICE_COMMON_CAP_PROTOCOL_AUTH_SELECTION,
SPICE_COMMON_CAP_AUTH_SPICE,
SPICE_COMMON_CAP_AUTH_SASL,
SPICE_COMMON_CAP_MINI_HEADER,
};
3 公共消息
3.1 实际使用的公共报头
typedefstruct SPICE_ATTR_PACKED SpiceMiniDataHeader {
uint16_t type;//类型
uint32_t size;//大小
} SpiceMiniDataHeader;
3.2 公共的消息类型Enums.h:422
// 服务器端
enum {
SPICE_MSG_MIGRATE = 1, //迁移
SPICE_MSG_MIGRATE_DATA,
SPICE_MSG_SET_ACK, //确认机制,客户端回复SPICE_MSGC_ACK_SYNC
SPICE_MSG_PING, //保持连接, SPICE_MSGC_PONG,
SPICE_MSG_WAIT_FOR_CHANNELS, //收到消息后,客户端等待
SPICE_MSG_DISCONNECTING, //关闭连接SPICE_MSGC_DISCONNECTING,
SPICE_MSG_NOTIFY, //通知,包括错误处理,警告,和信息
SPICE_MSG_LIST, //告知客户端通道的列表
SPICE_MSG_BASE_LAST = 100, //公共消息的类型取值为1-100
};
//客户端上的
enum {
SPICE_MSGC_ACK_SYNC = 1,
SPICE_MSGC_ACK,
SPICE_MSGC_PONG,
SPICE_MSGC_MIGRATE_FLUSH_MARK,
SPICE_MSGC_MIGRATE_DATA,
SPICE_MSGC_DISCONNECTING,
};
3.3 错误处理(ERROR CODE)Error_code.h
#define SPICEC_ERROR_CODE_SUCCESS 0
#define SPICEC_ERROR_CODE_ERROR 1
#define SPICEC_ERROR_CODE_GETHOSTBYNAME_FAILED 2
#define SPICEC_ERROR_CODE_CONNECT_FAILED 3
#define SPICEC_ERROR_CODE_SOCKET_FAILED 4
#define SPICEC_ERROR_CODE_SEND_FAILED 5
#define SPICEC_ERROR_CODE_RECV_FAILED 6
#define SPICEC_ERROR_CODE_SSL_ERROR 7
#define SPICEC_ERROR_CODE_NOT_ENOUGH_MEMORY 8
#define SPICEC_ERROR_CODE_AGENT_TIMEOUT 9
#define SPICEC_ERROR_CODE_AGENT_ERROR 10
#define SPICEC_ERROR_CODE_VERSION_MISMATCH 11
#define SPICEC_ERROR_CODE_PERMISSION_DENIED 12
#define SPICEC_ERROR_CODE_INVALID_ARG 13
#define SPICEC_ERROR_CODE_CMD_LINE_ERROR 14
3.4 SpiceNotify
typedef struct SpiceMsgNotify {
uint64_t time_stamp;
uint32_t severity;
uint32_t visibilty;
uint32_t what;
uint32_t message_len;
uint8_t message[0];
} SpiceMsgNotify;
pedef enum SpiceNotifySeverity {
SPICE_NOTIFY_SEVERITY_INFO,
SPICE_NOTIFY_SEVERITY_WARN,
SPICE_NOTIFY_SEVERITY_ERROR,
SPICE_NOTIFY_SEVERITY_ENUM_END
} SpiceNotifySeverity;
typedef enum SpiceNotifyVisibility {
SPICE_NOTIFY_VISIBILITY_LOW,
SPICE_NOTIFY_VISIBILITY_MEDIUM,
SPICE_NOTIFY_VISIBILITY_HIGH,
SPICE_NOTIFY_VISIBILITY_ENUM_END
} SpiceNotifyVisibility;
4.4 SPICE_MSG_LIST
4 Main Channel
4.1 主通道的专有属性
enum {
SPICE_MAIN_CAP_SEMI_SEAMLESS_MIGRATE,
SPICE_MAIN_CAP_NAME_AND_UUID,
SPICE_MAIN_CAP_AGENT_CONNECTED_TOKENS,
SPICE_MAIN_CAP_SEAMLESS_MIGRATE,
};
4.2 main通道消息类型
enum {
SPICE_MSG_MAIN_MIGRATE_BEGIN = 101,
SPICE_MSG_MAIN_MIGRATE_CANCEL,
SPICE_MSG_MAIN_INIT, //建立第一个消息
SPICE_MSG_MAIN_CHANNELS_LIST, //通道列表
SPICE_MSG_MAIN_MOUSE_MODE, //鼠标模式
SPICE_MSG_MAIN_MULTI_MEDIA_TIME, //当没有playback通道时,使用此消息进行声音和画面的同步
SPICE_MSG_MAIN_AGENT_CONNECTED,
SPICE_MSG_MAIN_AGENT_DISCONNECTED,
SPICE_MSG_MAIN_AGENT_DATA,
SPICE_MSG_MAIN_AGENT_TOKEN,
SPICE_MSG_MAIN_MIGRATE_SWITCH_HOST,
SPICE_MSG_MAIN_MIGRATE_END,
SPICE_MSG_MAIN_NAME, // 虚拟机的名字
SPICE_MSG_MAIN_UUID, //虚拟机的uuid
SPICE_MSG_MAIN_AGENT_CONNECTED_TOKENS,
SPICE_MSG_MAIN_MIGRATE_BEGIN_SEAMLESS,
SPICE_MSG_MAIN_MIGRATE_DST_SEAMLESS_ACK,
SPICE_MSG_MAIN_MIGRATE_DST_SEAMLESS_NACK,
SPICE_MSG_END_MAIN //结束主通道
};
enum {
SPICE_MSGC_MAIN_CLIENT_INFO = 101, //客户端信息
SPICE_MSGC_MAIN_MIGRATE_CONNECTED,
SPICE_MSGC_MAIN_MIGRATE_CONNECT_ERROR,
SPICE_MSGC_MAIN_ATTACH_CHANNELS, //对init信息的回复
SPICE_MSGC_MAIN_MOUSE_MODE_REQUEST, //
SPICE_MSGC_MAIN_AGENT_START,
SPICE_MSGC_MAIN_AGENT_DATA,
SPICE_MSGC_MAIN_AGENT_TOKEN,
SPICE_MSGC_MAIN_MIGRATE_END,
SPICE_MSGC_MAIN_MIGRATE_DST_DO_SEAMLESS,
SPICE_MSGC_MAIN_MIGRATE_CONNECTED_SEAMLESS,
SPICE_MSGC_END_MAIN
};
SPICE_MSG_MAIN_CHANNELS_LIST,
typedef struct SpiceMsgChannels {
uint32_t num_of_channels;
SpiceChannelId channels[0];
} SpiceMsgChannels;
SPICE_MSGC_MAIN_CLIENT_INFO
typedef struct SpiceMsgcClientInfo {
uint64_t cache_size;
} SpiceMsgcClientInfo;
Red.c :1352
服务器通过一个回调函数接收数据包,接收到不同的数据包后,调用不同的函数进行回复。
**************reds_send_link_ack 发送RedLinkReply
5 显示通道
5.1 显示通道消息类型
服务端:
enum {
SPICE_MSG_DISPLAY_MODE = 101,
SPICE_MSG_DISPLAY_MARK,
SPICE_MSG_DISPLAY_RESET,
SPICE_MSG_DISPLAY_COPY_BITS, //将指定区域复制到目标区域
SPICE_MSG_DISPLAY_INVAL_LIST,
SPICE_MSG_DISPLAY_INVAL_ALL_PIXMAPS, //清除图像缓存,spice可以缓存调色板和图像
SPICE_MSG_DISPLAY_INVAL_PALETTE, //清除调色板
SPICE_MSG_DISPLAY_INVAL_ALL_PALETTES, //清除所有调色板
SPICE_MSG_DISPLAY_STREAM_CREATE = 122, //stream开始
SPICE_MSG_DISPLAY_STREAM_DATA, //stream数据
SPICE_MSG_DISPLAY_STREAM_CLIP,
SPICE_MSG_DISPLAY_STREAM_DESTROY,
SPICE_MSG_DISPLAY_STREAM_DESTROY_ALL,
SPICE_MSG_DISPLAY_DRAW_FILL = 302, //指定区域绘制颜色
SPICE_MSG_DISPLAY_DRAW_OPAQUE, //将调色板和源图像结合
SPICE_MSG_DISPLAY_DRAW_COPY, //复制源图像到目标图像
SPICE_MSG_DISPLAY_DRAW_BLEND, //将源图像和目标图像混合
SPICE_MSG_DISPLAY_DRAW_BLACKNESS, //将目标区域变成黑色像素
SPICE_MSG_DISPLAY_DRAW_WHITENESS, //将目标区域变成白色像素
SPICE_MSG_DISPLAY_DRAW_INVERS, //翻转目标区域的像素
SPICE_MSG_DISPLAY_DRAW_ROP3, //将源图像、调色板和目标区域三者结合
SPICE_MSG_DISPLAY_DRAW_STROKE, //
SPICE_MSG_DISPLAY_DRAW_TEXT, //
SPICE_MSG_DISPLAY_DRAW_TRANSPARENT, //draw copy+指定颜色不显示
SPICE_MSG_DISPLAY_DRAW_ALPHA_BLEND, //使用alpha对源图像进行处理,复制到目标区域
SPICE_MSG_DISPLAY_SURFACE_CREATE, //生成一个surface
SPICE_MSG_DISPLAY_SURFACE_DESTROY, //销毁一个surface
SPICE_MSG_DISPLAY_STREAM_DATA_SIZED, //大量的stream数据
SPICE_MSG_DISPLAY_MONITORS_CONFIG, //显示屏的配置
SPICE_MSG_DISPLAY_DRAW_COMPOSITE,
SPICE_MSG_DISPLAY_STREAM_ACTIVATE_REPORT,
SPICE_MSG_DISPLAY_GL_SCANOUT_UNIX,
SPICE_MSG_DISPLAY_GL_DRAW,
SPICE_MSG_END_DISPLAY
};
客户端:
enum {
SPICE_MSGC_DISPLAY_INIT = 101, //指定图像缓冲区大小
SPICE_MSGC_DISPLAY_STREAM_REPORT,
SPICE_MSGC_DISPLAY_PREFERRED_COMPRESSION, //指定压缩算法
SPICE_MSGC_DISPLAY_GL_DRAW_DONE,
SPICE_MSGC_END_DISPLAY
};
5.2 DRAW_COPY
[服务端发送red_worker.c:red_marshall_image 客户端解析canvas_base.c:canvas_draw_copy]
typedef struct SpiceMsgDisplayDrawCopy {
SpiceMsgDisplayBase base;
SpiceCopy data;
} SpiceMsgDisplayDrawCopy;
typedef struct SpiceMsgDisplayBase {
uint32_t surface_id;
SpiceRect box;
SpiceClip clip;
} SpiceMsgDisplayBase;
typedef struct SpiceClip {
uint8_t type;
SpiceClipRects *rects;
} SpiceClip;
typedef struct SpiceCopy {
SpiceImage *src_bitmap;
SpiceRect src_area;
uint16_t rop_descriptor;
uint8_t scale_mode;
SpiceQMask mask;
} SpiceCopy, SpiceBlend;
copy_bits:
把源区域的图像拷贝到目标区域,两个区域可能会重叠。指定矩形区域的左上角,矩形区域和目标区域的大小一样。
Draw_fill:
将固定的颜色填充到指定区域,颜色的格式和suface的格式一样,rgba各一个字节,16进制。
Draw_opaque:
将源图和调色板结合,渲染到目标区域
SPICE_ROPD_INVERS_SRC = (1 << 0), //源图像取反
SPICE_ROPD_INVERS_BRUSH = (1 << 1), //调色刷取反
SPICE_ROPD_INVERS_DEST = (1 << 2), //目标区域渲染前取反
SPICE_ROPD_OP_PUT = (1 << 3), //复制
SPICE_ROPD_OP_OR = (1 << 4), //or
SPICE_ROPD_OP_AND = (1 << 5), //and
SPICE_ROPD_OP_XOR = (1 << 6), //xor
SPICE_ROPD_OP_BLACKNESS = (1 << 7),
SPICE_ROPD_OP_WHITENESS = (1 << 8),
SPICE_ROPD_OP_INVERS = (1 << 9), //目标像素取反
SPICE_ROPD_INVERS_RES = (1 << 10), //结果取反