AirPlay 鏡像協(xié)議-上(發(fā)現(xiàn))
可以用來運行和調(diào)試的源碼
目標(biāo)
當(dāng)點擊iPhone上的屏幕鏡像設(shè)備后,屏幕鏡像設(shè)備是如何收到鏡像圖像的
在iPhone上點擊屏幕鏡像之后,iPhone發(fā)起了13次請求,每次請求都是一次rtsp協(xié)議,rtsp協(xié)議可以看做是一種特別的http協(xié)議,只不過http協(xié)議請求和返回的第一行常規(guī)的HTTP/1.1修改成了RTSP/1.0
這13次請求分別如下
1. /info
iPhone沒有發(fā)送什么信息過來,只有一個請求,這是屏幕鏡像設(shè)備需要準(zhǔn)備比較多的數(shù)據(jù),形成一個字典,字典里面可能包含信息對,也可能包含字典,還可以包含字典,并把數(shù)據(jù)保存為plist二進(jìn)制形式發(fā)送給手機,例如根字典數(shù)據(jù)如下
NSDictionary r_node = new NSDictionary();
r_node["txtAirPlay"] = new NSData(AirPlayServer_mdns.bytesProperties);
r_node["features"] =new NSNumber((UInt64)0x1E << 32 | 0x5A7FFFF7);
r_node["audioFormats"] = audio_formats_node;
r_node["pi"] = new NSString("2e388006-13ba-4041-9a67-25dd4a43d536");
r_node["vv"] = new NSNumber(2);
r_node["statusFlags"] = new NSNumber(68);
r_node["keepAliveLowPower"] = new NSNumber(1);
r_node["sourceVersion"] = new NSString("220.68");
r_node["pk"] = new NSData(HexStringToBytes("b07727d6f6cd6e08b58ede525ec3cdeaa252ad9f683feb212ef8a205246554e7"));
r_node["keepAliveSendStatsAsBody"] = new NSNumber(1);
r_node["deviceID"] = new NSString("58:55:CA:1A:E2:88");
r_node["name"] = new NSString("My AirPlay Device");
r_node["model"] = new NSString("AppleTV2,1");
r_node["macAddress"] = new NSString("58:55:CA:1A:E2:88");
NSArray audio_formats_node = new NSArray();
NSDictionary audio_format_0_node = new NSDictionary();
audio_format_0_node["type"] = new NSNumber(100);
...
audio_formats_node.Add(audio_format_0_node);
NSDictionary audio_format_1_node = new NSDictionary();
audio_format_1_node["type"] = new NSNumber(101);
...
audio_formats_node.Add(audio_format_1_node);
NSArray audio_latencies_node = new NSArray();
NSDictionary audio_latencies_0_node = new NSDictionary();
audio_latencies_0_node["outputLatencyMicros"] = new NSNumber(0);
...
audio_latencies_node.Add(audio_latencies_0_node);
NSDictionary audio_latencies_1_node = new NSDictionary();
audio_latencies_1_node["outputLatencyMicros"] = new NSNumber(0);
...
audio_latencies_node.Add(audio_latencies_1_node);
r_node["audioLatencies"] = audio_latencies_1_node;
NSArray displays_node = new NSArray();
NSDictionary displays_0_node = new NSDictionary();
displays_0_node["uuid"] = new NSString("e0ff8a27-6738-3d56-8a16-cc53aacee925");
displays_0_node["widthPhysical"] = new NSNumber(0);
displays_0_node["heightPhysical"] = new NSNumber(0);
...
displays_node.Add(displays_0_node);
r_node["displays"] = displays_node;
上述數(shù)據(jù)中,r_node["txtAirPlay"]比較特別,它是文章AirPlay 鏡像協(xié)議-上(發(fā)現(xiàn))中的字典信息,而且格式比較特別,舉例如下
假如有配對信息a->b,和cd->ef, 那么數(shù)據(jù)AirPlayServer_mdns.bytesProperties內(nèi)容為
{0x3, 'a', '=', 'b', 0x5, 'c', 'd', '=', 'e', 'f'},可以看到配對信息用等號=相連,配對信息前面有一字節(jié)的信息表明該組信息的長度,所以限制了配對信息長度不可以大于255,
在返回給iPhone的RTSP信息中,HEADER部分增加信息表明返回的是二進(jìn)制形式的plist文件
response.AddHeader("Content-Type", "application/x-apple-binary-plist");
2. /pair-setup
該請求,iPhone沒有攜帶重要的信息,鏡像設(shè)備發(fā)送一個ed25519的public key到iPhone,發(fā)送內(nèi)容作為RTSP的Body部分,該ed25519秘鑰對可以在使用的時候才生成
3&4. /pair-verify
這兩次請求是非常關(guān)鍵的,首先iPhone發(fā)送了自己的加密信息中的公鑰部分,也包含簽名需要的信息,然后鏡像設(shè)備進(jìn)行了簽名,并把簽名結(jié)果返回給iPhone,如果iPhone驗證了簽名成功,則把再次簽名的結(jié)果發(fā)送給鏡像設(shè)備來驗證,如果鏡像設(shè)備驗證成功,說明雙方都得到了對方身份已確認(rèn),稍微詳細(xì)點的信息可以看散列與加密算法的幾處實際應(yīng)用場景中的場景4:AirPlay協(xié)議
5&6. /fp-setup
兩次請求,body部分都帶有數(shù)據(jù),分別調(diào)用fairplay函數(shù)的setup和handshake,返回這兩個函數(shù)的返回值即可
7. SETUP
iPhone請求第二次,iPhone發(fā)給鏡像設(shè)備key,該key經(jīng)過步驟5和6初始化之后的fairplay解碼成一個aes加密算的aeskey,未來傳輸?shù)囊曨l編碼會用aeskey可以來加密,鏡像設(shè)備準(zhǔn)備好事件反饋端口和時間對齊端口發(fā)送給iPhone
8. GET /info RTSP/1.0
iPhone再次請求和1一樣的數(shù)據(jù),鏡像設(shè)備和1返回一樣的內(nèi)容
9. GET_PARAMETER
iPHone查詢鏡像設(shè)備的一些信息,目前在body播放只有如下內(nèi)容"volume\r\n", 鏡像設(shè)備可以返回給iPhone的body部分為"volume:0.0\r\n"
10. RECORD
iPhone發(fā)送Record命令,鏡像設(shè)備返回的RTSP Header中增加一條記錄,body為空
response.AddHeader("Audio-Latency", request.GetHeader("2205"));
11. SETUP
iPhone請求第二次setup,鏡像設(shè)備準(zhǔn)備好新的tcp服務(wù)器,準(zhǔn)備接收數(shù)據(jù),端口任意,并返回給iPhone
12. SET_PARAMETER
iPhone發(fā)送音量或者進(jìn)度條信息,可以不用處理,返回RTSP 200
13. /feedback
iPhone會不間斷發(fā)送/feedback,里面包含時間信息,可以不用處理,返回RTSP 200
這時,在步驟11中準(zhǔn)備的新tcp服務(wù)器,可以開始收到經(jīng)過步驟7中的提供的秘鑰進(jìn)行aes加密的視頻數(shù)據(jù)了。
音頻數(shù)據(jù)依然會通過roap.tcp.local來傳輸,所以現(xiàn)在收到的數(shù)據(jù)不包含音頻數(shù)據(jù)。