I just managed to save the frames in files and wrote a small python script to display them (webcamdata.py). Please note that this is my first C++ project so it is really buggy and will likely need a complete rewrite.
The current features are: - Webcam inventations can be accepted - Webcam frames are decoded using libmimic - The decoded frames are saved in the folder /tmp/webcamdata - There is a small script to display these frames The drawbacks are: - Only receiving is implemented - I think it will only work if both clients are in the same network (There is currently no possibilty to get the external IP address) - There can be only one session - The webcam socket in msntest blocks the whole application (if linked to QtNetwork it would be possible to use a QTcpServer) Lukas Am Freitag 21 Mai 2010 21:47:38 schrieb Lukas Hetzenecker: > Hello to all libmsn and kopete developers, > > I was trying to add support for webcam conversations to libmsn and have > some questions regarding this topic. > > At first I wanted to ask if somebody knows the current state of the > telepathy plugin for kopete (as seen in the private conversation to Tiago > below). The msn connection manager telepathy-butterfly is based upon > papyon, a python library for the MSN Messenger network. Papyon would > already support webcam conversations using the "webcam protocol" and SIP > (which currently seems to be broken, maybe due to the same issues as amsn > [1]). > > Libmsn had already initial support for the handshake and I tried to > implement the other parts, but I'm currently stuck. > I got the XML description for the webcam transfer from the other client but > don't know what to respond exactly. I tried to follow the payon code as > close as possible, but couldn't manage to get it work. I also couldn't > find any useful information in the internet and sniffing data from amsn > also didn't help. Could anybody explain the handshake to me? > > I attached my current diff file, but it needs a major refactoring before > the release. The biggest changes are some debug lines that I used to > understand the protocol. I think the best way to implement the webcam P2P > protocol would be to create P2P as base class and FileTransfer and Webcam > as subclasses. Does anybody have a issue with the proposed method? > > Greetings, > Lukas > > [1] > http://kakaroto.homelinux.net/2010/03/amsn-0-98-2-to-be-released-without- > audiovideo-support/ > > Am Freitag 14 Mai 2010 20:17:19 schrieb Tiago Salem Herrmann: > > Hi, thanks for the contact. > > > > On Thu, May 13, 2010 at 5:31 PM, Lukas Hetzenecker <l...@gmx.at> wrote: > > > Hello Tiago and Will, > > > > > > This project would certainly have been interesting, it's a pity that it > > > wasn't accepted. > > > Webcam support in WLM is requested by many people [see bug 70538 for > > > example] and would be the killer feature in kopete, because many other > > > clients can't handle it. > > > > Indeed. > > > > > At the beginng I have some questions about the future developement of > > > kopete: - What is the current state of the telepathy plugin? > > > I've read that the Telepathy framework uses farsight to handle media > > > streams [1], so would it be better to use the Telepathy-Qt4 library [2] > > > directly? > > > > > > There are many different protocols for webcam / video conversations as > > > described here [3]. > > > The most important seem to be the "The Computer Call" and "The Webcam": > > > > > > The Webcam uses the ML20 codec for encoding and seems to be rather easy > > > to implement. I've used wireshark to sniff for some traffic generated > > > by amsn. If you want I can send you some tcpdump capture files. > > > libmimic could be used to decode the video stream (I don't think > > > farsight would be a good idea to use directly, because it needs Glib > > > and uses GObjects). > > > There are already implementations in amsn [4][5] and papyon [6]. > > > > > > The official Live Messenger seems to use the "Computer Call" whenever > > > it is possible. This protocol is based on SIP and RTP. It also > > > requires MSNP2PV2 and therefore MSNP18. Porting to this version is the > > > ideal long-term goal, because if also offers many other features like > > > "Multiple Points of Presence" and Group sessions [7]. Also amsn will > > > go this way soon [8]. > > > > > > I would be glad to help in the developement of libmsn and kopete. > > > > Nice, so do you know exactly what was changed from MSNp15 to MSNp18? > > It's been a while since I stopped following the msn protocol updates. > > I believe that updating libmsn to a new protocol would be a very good > > idea. > > > > > Greetings from Austria, > > > Lukas Hetzenecker > > > > > > p.s. Would you mind if i CC kopete-devel and Libmsn-discuss too? > > > > Not at all. Please do it. > > > > > [1] http://farsight.freedesktop.org/wiki/ > > > [2] http://telepathy.freedesktop.org/wiki/Components > > > [3] http://imfreedom.org/wiki/MSN:AV > > > [4] > > > http://amsn.svn.sourceforge.net/viewvc/amsn/trunk/amsn/msncam.tcl?view= > > > ma rkup [5] > > > http://amsn.svn.sourceforge.net/viewvc/amsn/trunk/amsn/msnp2p.tcl?view= > > > ma rkup [6] > > > http://git.collabora.co.uk/?p=papyon.git;a=blob;f=papyon/msnp2p/webcam. > > > p y [7] > > > http://en.wikipedia.org/wiki/Microsoft_Notification_Protocol#MSNP17 > > > [8] > > > http://kakaroto.homelinux.net/2010/03/amsn-0-98-2-to-be-released-withou > > > t - audiovideo-support/ > > > > > > Am Mittwoch 12 Mai 2010 17:32:16 schrieb Will Stephenson: > > >> Tiago: LuHe was asking in Kopete about MSN webcam so I am cc'ing him > > >> your proposal outline - perhaps you could join forces. > > >> > > >> Will > > >> > > >> > > >> "Since KDE 4.2, kopete is using WLM (based on libmsn [2]) as the main > > >> MSN plugin. WLM is working fine so far, but there is a big feature > > >> missing: Webcam support. This feature will require some work both on > > >> WLM and libmsn. Libmsn still does not recognize the webcam protocol, > > >> only the initial handshake. The webcam communication is based on a P2P > > >> protocol and unfortunately it is not well documented, not even in > > >> msnpiki [5]. Currently there is an opensource library called libmimic > > >> that helps on encoding/decoding the stream sent/received by the > > >> official client. Part of the work will be link libmimic to libmsn (or > > >> WLM, as it will be decided during the development). > > >> > > >> WLM will also need a new UI to manage the webcam calls. This UI will > > >> be created with qtdesigner and (if possible) fully integrated with > > >> the chat window. The current implementation shows the webcam calls > > >> out of the chatwindow, and this way it is not easy to track which > > >> webcam call belong to a certain chatwindow. Moving the webcam window > > >> to inside the chatwindow will improve the user experience."
Index: msn/p2p.cpp =================================================================== --- msn/p2p.cpp (Revision 120) +++ msn/p2p.cpp (Arbeitskopie) @@ -20,6 +20,8 @@ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ +#include <msn/debug.h> + #include <msn/notificationserver.h> #include <msn/errorcodes.h> #include <msn/externals.h> @@ -32,13 +34,16 @@ #include <fstream> #include <vector> -#include <string.h> +#include <cstring> +#include <mimic.h> + namespace MSN { P2P::P2P() { rand_helper = 1; + } P2P::~P2P() { @@ -51,7 +56,11 @@ p2pPacket packet; // not for me, ignore it if(headers["P2P-Dest"] != conn.myNotificationServer()->myPassport) + { + std::cout << "not my dest!" << std::endl; + std::flush(std::cout); return; + } std::istringstream header(body,std::ios::binary); @@ -73,10 +82,29 @@ if(packet.p2pHeader.flag==FLAG_ACK) { + debug_start(); + debug_line("Handle ack to P2P package " << packet.p2pHeader.ackID); + debug_end(); handle_p2pACK(conn, packet); return; } + debug_start(); + debug_line("Got P2P message!"); + debug_line("Session ID: " << packet.p2pHeader.sessionID); + debug_line("Identifier: " << packet.p2pHeader.identifier); + debug_line("Data offset: " << packet.p2pHeader.dataOffset); + debug_line("Total data size: " << packet.p2pHeader.totalDataSize); + debug_line("Message Length: " << packet.p2pHeader.messageLength); + debug_line("Flag: " << packet.p2pHeader.flag); + debug_line("Ack ID: " << packet.p2pHeader.ackID); + debug_line("Ack UID: " << packet.p2pHeader.ackUID); + debug_line("Ack Data size: " << packet.p2pHeader.ackDataSize); + debug_line("Body:"); + debug_line(packet.body); + debug_line("Footer: " << packet.p2pFooter.appID); + debug_end(); + if(packet.p2pHeader.sessionID == 0x40 && little2big_endian(packet.p2pFooter.appID)==3) // INK, oh god! { @@ -134,17 +162,24 @@ } // in these conditions, the packet is data, receive it to a file - if(packet.p2pHeader.sessionID && - (packet.p2pHeader.flag == FLAG_FILE_DATA || - packet.p2pHeader.flag == FLAG_FILE_DATA2 || - packet.p2pHeader.flag == FLAG_DATA_EMOTICONS)) + if(packet.p2pHeader.sessionID) { // we need to ensure we have a started session if(!startedSessions.count(packet.p2pHeader.sessionID)) return; + startedSessions[packet.p2pHeader.sessionID].step = STEP_RECEIVING; - startedSessions[packet.p2pHeader.sessionID].step = STEP_RECEIVING; - receiveP2PData(conn,packet); + if (packet.p2pHeader.flag == FLAG_FILE_DATA || + packet.p2pHeader.flag == FLAG_FILE_DATA2 || + packet.p2pHeader.flag == FLAG_DATA_EMOTICONS) + { + receiveP2PData(conn,packet); + } + else if (packet.p2pHeader.flag == FLAG_WEBCAM) + { + receiveP2PWebcamData(conn,packet); + } + return; } @@ -215,7 +250,8 @@ { handle_603Decline(conn,packet); } -/* std::cout << "session id: " << packet.p2pHeader.sessionID << std::endl; + /*std::cout << "------------------" << std::endl; + std::cout << "session id: " << packet.p2pHeader.sessionID << std::endl; std::cout << "identifier: " << packet.p2pHeader.identifier << std::endl; std::cout << "dataOffset: " << packet.p2pHeader.dataOffset << std::endl; std::cout << "totalDataSize: " << packet.p2pHeader.totalDataSize << std::endl; @@ -225,7 +261,9 @@ std::cout << "ackUID: " << packet.p2pHeader.ackUID << std::endl; std::cout << "ackDataSize: " << packet.p2pHeader.ackDataSize << std::endl; std::cout << "footer: " << packet.p2pFooter.appID << std::endl << std::endl; -*/ + std::cout << "------------------" << std::endl; + std::flush(std::cout);*/ + } void P2P::sendACK(MSN::SwitchboardServerConnection &conn, p2pPacket &packet, p2pSession &session) @@ -276,6 +314,26 @@ buf_ << "MSG " << conn.trID++ << " D " << full_msg.str().size() << "\r\n"; buf_ << full_msg.str(); + debug_start(); + debug_line("Send ACK to package id " << ack_pkt.p2pHeader.ackID); + debug_end(); + + /*debug_start(); + debug_line("Send ACK!"); + debug_line("Session ID: " << ack_pkt.p2pHeader.sessionID); + debug_line("Identifier: " << ack_pkt.p2pHeader.identifier); + debug_line("Data offset: " << ack_pkt.p2pHeader.dataOffset); + debug_line("Total data size: " << ack_pkt.p2pHeader.totalDataSize); + debug_line("Message Length: " << ack_pkt.p2pHeader.messageLength); + debug_line("Flag: " << ack_pkt.p2pHeader.flag); + debug_line("Ack ID: " << ack_pkt.p2pHeader.ackID); + debug_line("Ack UID: " << ack_pkt.p2pHeader.ackUID); + debug_line("Ack Data size: " << ack_pkt.p2pHeader.ackDataSize); + debug_line("Body:"); + debug_line(packet.body); + debug_line("Footer: " << ack_pkt.p2pFooter.appID); + debug_end();*/ + if (conn.write(buf_) != buf_.str().size()) return; /* std::cout << "session id: " << ack_pkt.p2pHeader.sessionID << std::endl; @@ -294,6 +352,9 @@ void P2P::receiveP2PData(MSN::SwitchboardServerConnection &conn, p2pPacket &packet) { + std::cout << "got data from session " << packet.p2pHeader.sessionID << std::endl; + std::flush(std::cout); + // check if there is no session if(!startedSessions.count(packet.p2pHeader.sessionID)) return; @@ -356,6 +417,194 @@ } } + void P2P::receiveP2PWebcamData(MSN::SwitchboardServerConnection &conn, p2pPacket &packet) + { + static std::string data = ""; + static int s = 0; + std::cout << "got webcam data from session " << packet.p2pHeader.sessionID << std::endl; + std::flush(std::cout); + + // check if there is no session + if(!startedSessions.count(packet.p2pHeader.sessionID)) + return; + + p2pSession session = startedSessions[packet.p2pHeader.sessionID]; + + // we have to wait until the message is complete... + data.insert(packet.p2pHeader.dataOffset, packet.body); + if (packet.p2pHeader.totalDataSize != data.length()) + return; + + // use _ucs2_utf8 + std::string content = ""; + for(int i = 5; i <= data.length() / 2; i++) // skip the first 10 bytes and return every second + { + if (data.at(i*2) == '\0') + break; + content.append(&data.at(i*2)); + } + + std::cout << "content is: " << content << " (" << content.length() << ")" << std::endl; + std::cout << "producer: " << content.find("<producer>") << "; npos: " << std::string::npos << std::endl; + std::flush(std::cout); + + if (content == "syn") + { + std::cout << "send ack" << std::endl; + std::flush(std::cout); + + session.currentIdentifier++; + if(session.currentIdentifier == session.baseIdentifier) // skip the original identifier + session.currentIdentifier++; + + p2pPacket pkt; + pkt.p2pHeader.sessionID = packet.p2pHeader.sessionID; + pkt.p2pHeader.flag = FLAG_NOP; + pkt.p2pHeader.identifier = session.currentIdentifier; + pkt.p2pHeader.ackID = rand()%0x8FFFFFF0 + rand_helper++; + pkt.p2pHeader.ackUID = 0; + pkt.p2pHeader.ackDataSize = 0; + // big endian + pkt.p2pFooter.appID = little2big_endian(session.appID); + + std::ostringstream content; + //content.write("a\00c\00k\00\00\00",8); + content.write("\x80\xEA\x00\x00\x08\x00\x08\x00\x00\x00\x61\x00\x63\x00\x6b\x00\x00\x00",18); + + pkt.body = content.str(); + + sendP2PPacket(conn, pkt, session); + } + else if (content.find("<producer>") != std::string::npos) + { + std::cout << "start parsing" << std::endl; + std::flush(std::cout); + + XMLNode producer = XMLNode::parseString(content.c_str(), "producer"); + //std::string version = producer.getChildNode("version").getText(); + //std::string rid = producer.getChildNode("rid").getText(); + + /* + XMLNode viewer = XMLNode::createXMLTopNode("viewer"); + viewer.addChild("version").addText("2.0"); + viewer.addChild("rid").addText(producer.getChildNode("rid").getText()); + viewer.addChild("session").addText(producer.getChildNode("session").getText()); + viewer.addChild("ctypes").addText("0"); + viewer.addChild("cpu").addText("2010"); + XMLNode tcp = viewer.addChild("tcp"); + tcp.addChild("tcpport").addText(producer.getChildNode("tcp").getChildNode("tcpport").getText()); + tcp.addChild("tcplocalport").addText(producer.getChildNode("tcp").getChildNode("tcpport").getText()); + tcp.addChild("tcpexternalport").addText("0"); + + int u = 0; + for(int i = 0; i <= producer.getChildNode("tcp").nChildNode(); i++) + { + std::string name = producer.getChildNode(i).getName(); + if (name.find("tcpipaddress") != std::string::npos) + { + tcp.addChild("tcpipaddress" + u).addText(producer.getChildNode(i).getText()); + u++; + } + } + + viewer.addChild("codec").addText(""); + viewer.addChild("channelmode").addText("2"); + */ + + /* + XMLNode viewer = XMLNode::createXMLTopNode("viewer"); + viewer.addChild("version").addText("2.0"); + viewer.addChild("rid").addText("108"); + viewer.addChild("session").addText(producer.getChildNode("session").getText()); + viewer.addChild("ctypes").addText("0"); + viewer.addChild("cpu").addText("730"); + XMLNode tcp = viewer.addChild("tcp"); + tcp.addChild("tcpport").addText("6891"); + tcp.addChild("tcplocalport").addText("6891"); + tcp.addChild("tcpexternalport").addText("6891"); + tcp.addChild("tcpipaddress1").addText(producer.getChildNode("tcp").getChildNode("tcpipaddress2").getText()); + tcp.addChild("tcpipaddress2").addText("192.168.1.101"); + + viewer.addChild("codec").addText(""); + viewer.addChild("channelmode").addText("1"); + */ + + //std::string xml = viewer.createXMLString(); + std::string xml = "<viewer>"; + xml += "<version>2.0</version>"; + xml += "<rid>143</rid>"; + xml += "<session>" + std::string(producer.getChildNode("session").getText()) + "</session>"; + xml += "<ctypes>0</ctypes>"; + xml += "<cpu>730</cpu>"; + xml += "<tcp>"; + xml += "<tcpport>6891</tcpport>"; + xml += " <tcplocalport>6891</tcplocalport>"; + xml += " <tcpexternalport>6891</tcpexternalport>"; + xml += "<tcpipaddress1>" + std::string(producer.getChildNode("tcp").getChildNode("tcpipaddress2").getText()) + "</tcpipaddress1>"; + xml += "<tcpipaddress2>" + conn.myNotificationServer()->externalCallbacks.getOurIP() + "</tcpipaddress2>"; + xml += "</tcp>"; + xml += "<codec></codec>"; + xml += "<channelmode>1</channelmode>"; + xml += "</viewer>"; + + std::cout << "send: " << xml << std::endl; + std::flush(std::cout); + + std::string temp; + for (unsigned int i = 0; i < xml.length(); i++) + { + //if (xml[i] != ' ' && xml[i] != '\n' && xml[i] != '\t') + if (xml[i] == ' ') + temp += '\t'; + if (xml[i] != '\n') + temp += xml[i]; + } + xml = temp; + + // TODO - convert filename to ucs2 + U8 *filenameutf8 = new U8[800]; + U8 *filenameutf16 = new U8[801]; + memset(filenameutf8, 0, 800); + memset(filenameutf16, 0, 801); + memcpy(filenameutf8, xml.c_str(), xml.size()); + _utf8_ucs2(filenameutf16, filenameutf8); + filenameutf16++; + + session.currentIdentifier++; + if(session.currentIdentifier == session.baseIdentifier) // skip the original identifier + session.currentIdentifier++; + + p2pPacket pkt; + pkt.p2pHeader.sessionID = packet.p2pHeader.sessionID; + pkt.p2pHeader.flag = FLAG_NOP; + pkt.p2pHeader.identifier = session.currentIdentifier; + pkt.p2pHeader.ackID = rand()%0x8FFFFFF0 + rand_helper++; + pkt.p2pHeader.ackUID = 0; + pkt.p2pHeader.ackDataSize = 0; + // big endian + pkt.p2pFooter.appID = little2big_endian(session.appID); + + std::ostringstream content; + content.write("\x80\x00\x09\x00\x08\x00\xD8\x02\x00\x00", 10); + content.write((char*)filenameutf16, xml.size()*2-1); + content.write("\x00\x0d\x00\x0a\x00\x0d\x00\x0a\x00\x00\x00", 11); + pkt.body = content.str(); + + sendP2PPacket(conn, pkt, session); + + s = conn.myNotificationServer()->externalCallbacks.listenOnPort(&conn, 6891); + } + + else if (content == "receivedViewerData") + { + conn.myNotificationServer()->externalCallbacks.waitForConnection(&conn, s); + } + + data = ""; + + } + + void P2P::sendP2PData(MSN::SwitchboardServerConnection &conn, p2pSession &session, p2pPacket &packet) { p2pPacket pkt_part = session.tempPacket; @@ -441,6 +690,23 @@ buf_ << "MSG " << conn.trID << " D " << full_msg.str().size() << "\r\n"; buf_ << full_msg.str(); + debug_start(); + debug_line("Send P2P Data!"); + debug_line("Session ID: " << pkt_part.p2pHeader.sessionID); + debug_line("Identifier: " << pkt_part.p2pHeader.identifier); + debug_line("Data offset: " << pkt_part.p2pHeader.dataOffset); + debug_line("Total data size: " << pkt_part.p2pHeader.totalDataSize); + debug_line("Message Length: " << pkt_part.p2pHeader.messageLength); + debug_line("Flag: " << pkt_part.p2pHeader.flag); + debug_line("Ack ID: " << pkt_part.p2pHeader.ackID); + debug_line("Ack UID: " << pkt_part.p2pHeader.ackUID); + debug_line("Ack Data size: " << pkt_part.p2pHeader.ackDataSize); + debug_line("Body:"); + debug_line(packet.body); + debug_line("Footer: " << pkt_part.p2pFooter.appID); + debug_end(); + + if (conn.write(buf_) != buf_.str().size()) return; @@ -506,6 +772,23 @@ buf_ << "MSG " << conn.trID++ << " D " << full_msg.str().size() << "\r\n"; buf_ << full_msg.str(); + debug_start(); + debug_line("Send P2P Packet!"); + debug_line("Session ID: " << packet.p2pHeader.sessionID); + debug_line("Identifier: " << packet.p2pHeader.identifier); + debug_line("Data offset: " << packet.p2pHeader.dataOffset); + debug_line("Total data size: " << packet.p2pHeader.totalDataSize); + debug_line("Message Length: " << packet.p2pHeader.messageLength); + debug_line("Flag: " << packet.p2pHeader.flag); + debug_line("Ack ID: " << packet.p2pHeader.ackID); + debug_line("Ack UID: " << packet.p2pHeader.ackUID); + debug_line("Ack Data size: " << packet.p2pHeader.ackDataSize); + debug_line("Body:"); + debug_line(packet.body); + debug_line("Footer: " << packet.p2pFooter.appID); + debug_end(); + + if (conn.write(buf_) != buf_.str().size()) return; } @@ -520,7 +803,7 @@ Message::Headers header_app = Message::Headers(msg[1]); switch(session.appID) { - case APP_FILE_TRANSFER: + case APP_FILE_TRANSFER or APP_WEBCAM: { session.CSeq = decimalFromString(header_slp["CSeq"]); session.Bridges = header_app["Bridges"]; @@ -592,6 +875,9 @@ void P2P::handle_INVITE(MSN::SwitchboardServerConnection &conn, p2pPacket &packet) { + std::cout << "handle invite" << std::endl; + std::flush(std::cout); + p2pSession session; std::vector<std::string> msg = splitString(packet.body, "\r\n\r\n"); msg[1]+="\r\n"; // this is really needed :( @@ -610,9 +896,15 @@ session.CallID = header_slp["Call-ID"]; session.ContentType = header_slp["Content-Type"]; + std::cout << "look for: " << session.CallID << std::endl; + std::flush(std::cout); + std::map<unsigned int, p2pSession>::iterator i = startedSessions.begin(); for(; i != startedSessions.end(); i++) { + std::cout << (*i).first << ": " << (*i).second.CallID << std::endl; + std::flush(std::cout); + if((*i).second.CallID == session.CallID) { // this isn't a new session, since I already have this callid @@ -622,6 +914,8 @@ old_session.CSeq = session.CSeq; old_session.ContentType = session.ContentType; // handle the changes received in this invitation packet + std::cout << "handle session changes!!!" << std::endl; + std::flush(std::cout); handle_session_changes(conn, packet, old_session); return; } @@ -641,19 +935,34 @@ session.baseIdentifier++; session.step = STEP_ACK_INVITATION_SENT; + std::cout << "Session appID is: " << session.appID << std::endl; + std::flush(std::cout); + switch(session.appID) { case APP_WEBCAM: { - if(header_app["EUF-GUID"] == "{4BD96FC0-AB17-4425-A14A-439185962DC8}") + std::cout << "GUID is: " << header_app["EUF-GUID"] << std::endl; + std::flush(std::cout); + if(header_app["EUF-GUID"] == "{4BD96FC0-AB17-4425-A14A-439185962DC8}") // Media session { //conn.myNotificationServer()->externalCallbacks.askWebCam(&conn, session.sessionID); + std::cout << "want media session!" << std::endl; + std::flush(std::cout); + std::string body("SessionID: "+ toStr(session.sessionID) +"\r\n"); send_200OK(conn, session, body); + //handle_session_changes(conn, packet, session); } - if(header_app["EUF-GUID"] == "{1C9AA97E-9C05-4583-A3BD-908A196F1E92}") + if(header_app["EUF-GUID"] == "{1C9AA97E-9C05-4583-A3BD-908A196F1E92}") // Media receive only { - //conn.myNotificationServer()->externalCallbacks.askWebCam(&conn, session.sessionID); + startedSessions[session.sessionID]=session; + + std::cout << "want webcam receive!\n"; + std::flush(std::cout); + + conn.myNotificationServer()->externalCallbacks.askWebCam(&conn, session.sessionID); + } break; } @@ -938,6 +1247,9 @@ void P2P::send_603Decline(MSN::SwitchboardServerConnection &conn, p2pSession &session) { + std::cout << "sending decline!" << std::endl; + std::flush(std::cout); + p2pPacket packet; std::ostringstream body2; @@ -948,6 +1260,7 @@ "From: <msnmsgr:"+session.from+">\r\n" "Via: "+session.Via+"\r\n" "CSeq: "+ toStr(++session.CSeq) + "\r\n" + //"CSeq: "+ toStr(1) + "\r\n" "Call-ID: "+session.CallID+"\r\n" "Max-Forwards: 0\r\n" "Content-Type: application/x-msnmsgr-sessionreqbody\r\n" @@ -1050,21 +1363,46 @@ this->removeCallback(packet.p2pHeader.ackUID); p2pSession session = startedSessions[sessionID]; session.step = STEP_SENDING; - std::string filepath; - filepath += b64_decode(session.Context.c_str()); // prevents empty context - if(filepath.length()) + + switch(session.appID) { - if(!conn.myNotificationServer()->msnobj.getMSNObjectRealPath(b64_decode(session.Context.c_str()), session.filename)) + case APP_FILE_TRANSFER: { - send_603Decline(conn,session); + std::string filepath; + filepath += b64_decode(session.Context.c_str()); // prevents empty context + if(filepath.length()) + { + if(!conn.myNotificationServer()->msnobj.getMSNObjectRealPath(b64_decode(session.Context.c_str()), session.filename)) + { + std::cout << "DECLINE! " << __FUNCTION__ << " " << __FILE__ << ":" << __LINE__ << std::endl; + std::flush(std::cout); + + send_603Decline(conn,session); + return; + } + } + else + { + std::cout << "DECLINE! " << __FUNCTION__ << " " << __FILE__ << ":" << __LINE__ << std::endl; + std::flush(std::cout); + send_603Decline(conn,session); + return; + } + break; + } + + /* + case APP_WEBCAM: + { + std::string body("SessionID: "+ toStr(session.sessionID) +"\r\n"); + send_200OK(conn,session,body); return; } + */ + + } - else - { - send_603Decline(conn,session); - return; - } + sendP2PData(conn, session, packet); } @@ -1091,10 +1429,35 @@ else // user rejected { // I dont want to receive your file, blergh + std::cout << "DECLINE! " << __FUNCTION__ << " " << __FILE__ << ":" << __LINE__ << std::endl; + std::flush(std::cout); send_603Decline(conn,session); } } + void P2P::handle_webcamResponse(MSN::SwitchboardServerConnection &conn, unsigned int sessionID, bool response) + { + p2pSession session = startedSessions[sessionID]; + std::cout << "Session ID:" << sessionID << std::endl; + std::cout << session.sessionID << ";" << session.to << ";" << session.from << ";" << std::endl; + std::flush(std::cout); + if(response) // user accepted + { + std::cout << "send accept" << std::endl; + std::flush(std::cout); + session.in_stream = new std::ofstream; + std::string body("SessionID: "+ toStr(session.sessionID) +"\r\n"); + send_200OK(conn, session, body); + } + else // user rejected + { + // I dont want to receive your webcam, blergh + std::cout << "DECLINE! " << __FUNCTION__ << " " << __FILE__ << ":" << __LINE__ << std::endl; + std::flush(std::cout); + send_603Decline(conn,session); + } + } + void P2P::handle_DataACK(MSN::SwitchboardServerConnection &conn, unsigned int sessionID, p2pPacket &packet) { this->removeCallback(packet.p2pHeader.ackUID); Index: msn/switchboardserver.h =================================================================== --- msn/switchboardserver.h (Revision 120) +++ msn/switchboardserver.h (Arbeitskopie) @@ -148,6 +148,7 @@ /** Response to a file transfer invitation */ void fileTransferResponse(unsigned int sessionID, std::string filename, bool response); + void webcamResponse(unsigned int sessionID, bool response); /** Cancel a file transfer in progress */ @@ -208,6 +209,8 @@ /** Request a display picture */ void requestDisplayPicture(unsigned int id, std::string filename, std::string msnobject); + + void receivedWebcamData(MSN::SwitchboardServerConnection *conn, char* data, int len); protected: virtual void handleIncomingData(); SwitchboardServerState _connectionState; Index: msn/externals.h =================================================================== --- msn/externals.h (Revision 120) +++ msn/externals.h (Arbeitskopie) @@ -308,14 +308,22 @@ */ virtual void askFileTransfer(MSN::SwitchboardServerConnection *conn, MSN::fileTransferInvite ft) = 0; - virtual int listenOnPort(int port) = 0; + virtual int listenOnPort(MSN::SwitchboardServerConnection *conn, int port) = 0; + virtual int waitForConnection(MSN::SwitchboardServerConnection *conn, int s) = 0; + + virtual void decodedWebcamFrame(MSN::SwitchboardServerConnection *conn, const char *data) = 0; + virtual std::string getOurIP() = 0; virtual std::string getSecureHTTPProxy() = 0; virtual int getSocketFileDescriptor (void *sock) = 0; + /** Notifies your application that someone is trying to make a webcam session. + */ + virtual void askWebCam(MSN::SwitchboardServerConnection * /*conn*/, unsigned int sessionID) = 0; + /** Asks your application to get @c size bytes of data available in @p sock * and store them in @p data. * It must return the real size written to @p data Index: msn/debug.h =================================================================== --- msn/debug.h (Revision 0) +++ msn/debug.h (Revision 0) @@ -0,0 +1,36 @@ +#ifndef DEBUG_H +#define DEBUG_H + +#define DEBUG + +#ifdef DEBUG +#include <ctime> +#include <iostream> + +std::string GetTime() +{ + time_t curtime = time(0); + tm now=*localtime(&curtime); + + char dest[256]={0}; + const char format[]="%X"; + strftime(dest, sizeof(dest)-1, format, &now); + return dest; +} + + +#define debug_start() std::cout << "*** " << GetTime() << " - " << __FUNCTION__ << ": " << "in " << __FILE__ << ":" << __LINE__ << std::endl; +#define debug_line(a) std::cout << "* " << a << std::endl; +#define debug_end() std::cout << "***" << std::endl << std::endl; std::flush(std::cout); + + +#else + +void debug_start() { } +void debug_line() { } +void debug_end() { } + +#endif /* DEBUG */ + + +#endif // DEBUG_H Index: msn/CMakeLists.txt =================================================================== --- msn/CMakeLists.txt (Revision 120) +++ msn/CMakeLists.txt (Arbeitskopie) @@ -61,7 +61,19 @@ libsiren/rmlt.h libsiren/siren7.h ) + +INCLUDE( ${CMAKE_ROOT}/Modules/FindPkgConfig.cmake ) +pkg_search_module(MIMIC REQUIRED libmimic) + +INCLUDE_DIRECTORIES(${MIMIC_INCLUDE_DIRS}) add_library(msn SHARED ${msn_STAT_SRCS} ${siren_STAT_SRCS}) + +TARGET_LINK_LIBRARIES(msn ${MIMIC_LIBRARIES}) +#set_target_properties(msn PROPERTIES COMPILE_FLAGS "${MIMIC_CFLAGS}" +# LINK_FLAGS ${MIMIC_LDFLAGS} +#) + + set_target_properties(msn PROPERTIES VERSION 0.3.0 SOVERSION 0.3 ) Index: msn/switchboardserver.cpp =================================================================== --- msn/switchboardserver.cpp (Revision 120) +++ msn/switchboardserver.cpp (Arbeitskopie) @@ -35,6 +35,11 @@ #include <cassert> #include <sstream> +#include <fstream> + + +#include <mimic.h> + namespace MSN { std::map<std::string, void (SwitchboardServerConnection::*)(std::vector<std::string> &)> SwitchboardServerConnection::commandHandlers; @@ -688,6 +693,13 @@ p2p.handle_fileTransferResponse(*this,sessionID, filename, response); } + void SwitchboardServerConnection::webcamResponse(unsigned int sessionID, bool response) + { + std::cout << "in SwitchboardServerConnection::webcamResponse\n"; + std::flush(std::cout); + p2p.handle_webcamResponse(*this,sessionID, response); + } + void SwitchboardServerConnection::callback_AnsweredCall(std::vector<std::string> & args, int trid, void * data) { this->assertConnectionStateIs(SB_WAITING_FOR_USERS); @@ -746,4 +758,115 @@ this->assertConnectionStateIsAtLeast(SB_CONNECTED); p2p.cancelTransfer(*this,id); } + + void SwitchboardServerConnection::receivedWebcamData(MSN::SwitchboardServerConnection *conn, char* data, int len) + { + static bool initialized = false; + static bool finished = true; + static int currentLength = 0; + static int length = 0; + static char encoded[20000]; + static MimCtx* mimic = mimic_open (); + + + unsigned char header_size; + + unsigned short int width; + unsigned short int height; + + unsigned int tag; + + if (finished) + { + header_size = data[0]; + if (header_size != 24) + { + std::cout << "invalid header size" << std::endl; + std::flush(std::cout); + return; + } + + width = (unsigned char)data[2] | ((unsigned char)data[3] << 8); + height = (unsigned char)data[4] | ((unsigned char)data[5] << 8); + + //std::cout << "width is " << width << "(" << (int)data[2] << ":" << (int)data[3] << ")" << std::endl; + //std::cout << "height is " << height << "(" << (int)data[4] << ":" << (int)data[5] << ")" << std::endl; + + if(!(width==320 && height==240) && !(width==160 && height==120)) + { + std::cout << "invalid width or height!" << std::endl; + std::flush(std::cout); + return; + } + + + length = (unsigned char)data[8] | ((unsigned char)data[9] << 8) | ((unsigned char)data[10] << 16) | ((unsigned char)data[11] << 24); + + tag = data[12] | (data[13] << 8) | (data[14] << 16) | (data[15] << 24); + + if(tag != ('M' | ('L' << 8) | ('2' << 16) | ('0' << 24))) + { + std::cout << "invalid tag!" << std::endl; + std::flush(std::cout); + return; + } + + finished = false; + + //std::cout << "header okay!" << std::endl; + //std::flush(std::cout); + + memset(encoded, 0, 20000); + } + else + { + memcpy(encoded + currentLength, data, len); + + currentLength += len; + + //std::cout << "currentLength: " << currentLength << std::endl << "total length: " << length << std::endl; + //std::flush(std::cout); + + if (currentLength >= length) + { + //std::cout << "frame is complete!" << std::endl; + //std::flush(std::cout); + + + if (!initialized) + { + if (!mimic_decoder_init(mimic, (guchar*)encoded)) + { + std::cout << "mimic could not be initialized!" << std::endl; + std::flush(std::cout); + return; + } + initialized = true; + } + + int buffer_size; + mimic_get_property(mimic, "buffer_size", &buffer_size); + guchar decoded[buffer_size]; + + if (!mimic_decode_frame(mimic, (guchar*)encoded, decoded)) + { + std::cout << "mimic could not decode frame!" << std::endl; + std::flush(std::cout); + + finished = true; + currentLength = 0; + return; + } + + finished = true; + currentLength = 0; + + //std::cout << "decoded webcam frame!" << std::endl; + //std::flush(std::cout); + + conn->myNotificationServer()->externalCallbacks.decodedWebcamFrame(conn, (const char*)decoded); + } + } + } + } Index: msn/p2p.h =================================================================== --- msn/p2p.h (Revision 120) +++ msn/p2p.h (Arbeitskopie) @@ -91,6 +91,7 @@ FLAG_ERROR = 0x8, FLAG_DATA_EMOTICONS = 0x20, FLAG_DATA_PICTURE = 0x20, + FLAG_WEBCAM = 0x1000000, FLAG_FILE_DATA = 0x01000030, FLAG_FILE_DATA2 = 0x01000020 }; @@ -213,6 +214,9 @@ void receiveP2PData(MSN::SwitchboardServerConnection &conn, p2pPacket &packet); + void receiveP2PWebcamData(MSN::SwitchboardServerConnection &conn, + p2pPacket &packet); + void handle_negotiation(MSN::SwitchboardServerConnection &conn, p2pPacket &packet); @@ -275,6 +279,10 @@ std::string filename, bool response); + void handle_webcamResponse(MSN::SwitchboardServerConnection &conn, + unsigned int sessionID, + bool response); + void handle_session_changes(MSN::SwitchboardServerConnection &conn, p2pPacket &packet, p2pSession &session); Index: msntest/msntest.cpp =================================================================== --- msntest/msntest.cpp (Revision 120) +++ msntest/msntest.cpp (Arbeitskopie) @@ -37,6 +37,7 @@ #include <openssl/ssl.h> #include <msn/msn.h> + #include <string> #include <iostream> @@ -156,8 +157,14 @@ virtual void askFileTransfer(MSN::SwitchboardServerConnection *conn, MSN::fileTransferInvite ft); - virtual int listenOnPort(int port); + virtual void askWebCam(MSN::SwitchboardServerConnection *conn, unsigned int sessionID); + + virtual int listenOnPort(MSN::SwitchboardServerConnection *conn, int port); + + virtual int waitForConnection(MSN::SwitchboardServerConnection *conn, int s); + virtual void decodedWebcamFrame(MSN::SwitchboardServerConnection *conn, const char *data); + virtual std::string getOurIP(); virtual int getSocketFileDescriptor (void *sock); @@ -477,6 +484,7 @@ clientid += MSN::InkGifSupport; clientid += MSN::SIPInvitations; clientid += MSN::SupportMultiPacketMessaging; + clientid += MSN::SupportWebcam; mainConnection.setState(MSN::buddyStatusFromString(state), clientid); } else if (!strcmp(command, "friendlyname")) { @@ -1106,7 +1114,7 @@ return (void*)s; } -int Callbacks::listenOnPort(int port) +int Callbacks::listenOnPort(MSN::SwitchboardServerConnection* conn, int port) { int s; struct sockaddr_in addr; @@ -1127,8 +1135,73 @@ } return s; + } +int Callbacks::waitForConnection(MSN::SwitchboardServerConnection* conn, int s) +{ + + struct sockaddr_in addr; + char data[15001]; + + int n; + std::string tmp; + + std::cout << "wait for accept" << std::endl; + std::flush(std::cout); + + socklen_t len = sizeof(addr); + int x = accept(s, (struct sockaddr *)(&addr), &len); + + std::cout << "accepted" << std::endl; + std::flush(std::cout); + + while(1) + { + bzero(data,15001); + n = read(x,data,15000); + + //std::cout << "read " << n << " - " << data << std::endl; + + tmp = data; + if (tmp.substr(0, 11) == "recipientid") + { + std::cout << "is recipientid!" << std::endl; + std::flush(std::cout); + write(x,"connected\x0d\x0a\x0d\x0a",13); + continue; + } + else if (tmp.substr(0, 9) == "connected") + { + std::cout << "is connected!" << std::endl; + std::flush(std::cout); + continue; + } + + conn->receivedWebcamData(conn, (char *)data, (int)n); + + } + + return n; + +} + +void Callbacks::decodedWebcamFrame(MSN::SwitchboardServerConnection *conn, const char *data) +{ + static int i = 0; + i++; + + int status; + status = mkdir("/tmp/webcamdata", S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH); + + std::stringstream filename; + filename << "/tmp/webcamdata/frame"; + filename << i; + + std::ofstream webcamFile(filename.str().c_str(), std::ios::out); + webcamFile << data; +} + std::string Callbacks::getOurIP(void) { struct hostent * hn; @@ -1180,6 +1253,12 @@ conn->fileTransferResponse(ft.sessionId, filename2, true); } +void Callbacks::askWebCam(MSN::SwitchboardServerConnection *conn, unsigned int sessionID) +{ + std::cout << "Somebody wants to make a webcam session..." << std::endl; + conn->webcamResponse(sessionID, true); +} + void Callbacks::addedContactToGroup(MSN::NotificationServerConnection * conn, bool added, std::string groupId, std::string contactId) { if(added)
from PyQt4.QtCore import * from PyQt4.QtGui import * import sys class WebcamData(QDialog): def __init__(self, parent=None): QDialog.__init__(self, parent) self.mainlayout = QVBoxLayout(self) self.diredit = QLineEdit(self) # Complete directory names in folder QLineEdit self.completer = QCompleter() self.completionmodel = QDirModel(self.completer) self.completionmodel.setFilter(QDir.AllDirs) self.completer.setModel(self.completionmodel) self.diredit.setCompleter(self.completer) self.splitter = QSplitter(Qt.Horizontal, self) self.piclabel = QLabel(self) self.piclabel.setMinimumSize(320, 240) self.piclabel.setMaximumWidth(320) self.filemodel = QFileSystemModel(self) self.filemodel.setFilter(QDir.Dirs | QDir.Files | QDir.NoDotAndDotDot) #self.filemodel.setFilter(QDir.Files) self.filemodel.setRootPath("/") self.fileview = QListView(self) self.fileview.setModel(self.filemodel) self.splitter.addWidget(self.piclabel) self.splitter.addWidget(self.fileview) self.mainlayout.addWidget(self.diredit) self.mainlayout.addWidget(self.splitter) self.connect(self.diredit, SIGNAL("returnPressed()"), self.updateFiles) self.connect(self.fileview, SIGNAL("doubleClicked ( const QModelIndex & )"), self.setPath) self.connect(self.fileview, SIGNAL("clicked ( const QModelIndex & )"), self.setImage) self.diredit.setText("/tmp/webcamdata/") self.setWindowTitle("libmsn webcam viewer - Lukas Hetzenecker") self.updateFiles() self.resize(750, 550) self.show() def updateFiles(self): dir = self.diredit.text() self.fileview.setRootIndex(self.filemodel.index(dir)) self.piclabel.setText("No file selected!") def setPath(self, index): if self.filemodel.fileInfo(index).isDir(): self.diredit.setText(self.filemodel.filePath(index)) self.updateFiles() def setImage(self, index): if self.filemodel.fileInfo(index).isFile(): cont = file(unicode(self.filemodel.filePath(index))).read() image = QImage(cont, 320, 240, QImage.Format_RGB888) pixmap = QPixmap.fromImage(image) self.piclabel.setPixmap(pixmap) app = QApplication(sys.argv) w = WebcamData() app.exec_()
_______________________________________________ kopete-devel mailing list kopete-devel@kde.org https://mail.kde.org/mailman/listinfo/kopete-devel