IT戦記

プログラミング、起業などについて書いているプログラマーのブログです😚

libxml2 内のメモリの解放

ちょっとメモ

今日も valgrind 使ってます

libxml2 を使って以下のような XML のパース処理を書く

#include <iostream>
#include <sstream>
#include <boost/shared_ptr.hpp>
#include <libxml/xmlreader.h>

static int read(void* in, char* buf, int len)
{
    return static_cast<std::istream*>(in)->readsome(buf, len);
}

int main()
{
    std::istringstream in("<a><b/>hoge<c/></a>");
    boost::shared_ptr<xmlTextReader> reader(xmlReaderForIO(read, NULL, &in, NULL, NULL, 0), xmlFreeTextReader);

    while (xmlTextReaderRead(reader.get()) == 1)
    {   
        std::cout << "node depth: " << xmlTextReaderDepth(reader.get()) << std::endl;
        std::cout << "node type : " << xmlTextReaderNodeType(reader.get()) << std::endl;

        const char* name = reinterpret_cast<const char*>(xmlTextReaderConstName(reader.get()));
        std::cout << "node name : " << (name ? name : "") << std::endl;

        const char* value = reinterpret_cast<const char*>(xmlTextReaderConstValue(reader.get()));
        std::cout << "node value: " << (value ? value : "") << std::endl;
        std::cout << std::endl;
    }   
}

で、コンパイルして実行

一見ウマくいっているように見える。というかウマくいっている。

$ g++ -g -lxml2 main.cpp && ./a.out
node depth: 0
node type : 1
node name : a
node value: 

node depth: 1
node type : 1
node name : b
node value: 

node depth: 1
node type : 3
node name : #text
node value: hoge

node depth: 1
node type : 1
node name : c
node value: 

node depth: 0
node type : 15
node name : a
node value: 

でも、 valgrind してみると

$ valgrind ./a.out 
==3940== Memcheck, a memory error detector.
==3940== Copyright (C) 2002-2008, and GNU GPL'd, by Julian Seward et al.
==3940== Using LibVEX rev 1884, a library for dynamic binary translation.
==3940== Copyright (C) 2004-2008, and GNU GPL'd, by OpenWorks LLP.
==3940== Using valgrind-3.4.1-Debian, a dynamic binary instrumentation framework.
==3940== Copyright (C) 2000-2008, and GNU GPL'd, by Julian Seward et al.
==3940== For more details, rerun with: -v
==3940== 
node depth: 0
node type : 1
node name : a
node value: 

node depth: 1
node type : 1
node name : b
node value: 

node depth: 1
node type : 3
node name : #text
node value: hoge

node depth: 1
node type : 1
node name : c
node value: 

node depth: 0
node type : 15
node name : a
node value: 

==3940== 
==3940== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 23 from 1)
==3940== malloc/free: in use at exit: 506 bytes in 18 blocks.
==3940== malloc/free: 52 allocs, 34 frees, 22,072 bytes allocated.
==3940== For counts of detected errors, rerun with: -v
==3940== searching for pointers to 18 not-freed blocks.
==3940== checked 132,412 bytes.
==3940== 
==3940== LEAK SUMMARY:
==3940==    definitely lost: 0 bytes in 0 blocks.
==3940==      possibly lost: 0 bytes in 0 blocks.
==3940==    still reachable: 506 bytes in 18 blocks.
==3940==         suppressed: 0 bytes in 0 blocks.
==3940== Rerun with --leak-check=full to see details of leaked memory.

解放されなかったメモリがあることが分かる

ここで、メモリが解放されないパターンを2つ考えてみる

  • ライブラリが内部でメモリプールとして使っているメモリ
    • 別に解放しなくてもいい
  • 解放し忘れ
    • 解放したほうがいい

というわけで、以下のように同じコードを繰り返し走らせてメモリが増加しないかを調べる

#include <iostream>
#include <sstream>
#include <boost/shared_ptr.hpp>
#include <libxml/xmlreader.h>

static int read(void* in, char* buf, int len)
{
    return static_cast<std::istream*>(in)->readsome(buf, len);
}

void sub()
{
    std::istringstream in("<a><b/>hoge<c/></a>");
    boost::shared_ptr<xmlTextReader> reader(xmlReaderForIO(read, NULL, &in, NULL, NULL, 0), xmlFreeTextReader);

    while (xmlTextReaderRead(reader.get()) == 1)
    {   
        std::cout << "node depth: " << xmlTextReaderDepth(reader.get()) << std::endl;
        std::cout << "node type : " << xmlTextReaderNodeType(reader.get()) << std::endl;

        const char* name = reinterpret_cast<const char*>(xmlTextReaderConstName(reader.get()));
        std::cout << "node name : " << (name ? name : "") << std::endl;

        const char* value = reinterpret_cast<const char*>(xmlTextReaderConstValue(reader.get()));
        std::cout << "node value: " << (value ? value : "") << std::endl;
        std::cout << std::endl;
    }   
}

int main() { sub(); sub(); sub; } // こんな感じ

これでも、同じメモリしか残っていないので多分ライブラリ内のメモリプールだろうと分かる。

それでも解放できたら解放したい

他のリークに気が付かないかもしれないから。

valgrind の --leak-check=full オプションでメモリが確保された場所を調べる

メモリの確保された箇所が分かる

$ valgrind --leak-check=full --show-reachable=yes ./a.out 

  (snip)

==3979== 
==3979== 62 bytes in 8 blocks are still reachable in loss record 1 of 4
==3979==    at 0x4026FDE: malloc (vg_replace_malloc.c:207)
==3979==    by 0x40E0C93: xmlStrndup (xmlstring.c:45)
==3979==    by 0x40E0D27: xmlStrdup (xmlstring.c:71)
==3979==    by 0x404A815: xmlNewCharEncodingHandler (encoding.c:1286)
==3979==    by 0x404A984: xmlInitCharEncodingHandlers (encoding.c:1351)
==3979==    by 0x404AC35: xmlGetCharEncodingHandler (encoding.c:1444)
==3979==    by 0x408346A: xmlAllocParserInputBuffer (xmlIO.c:2239)
==3979==    by 0x408415E: xmlParserInputBufferCreateIO (xmlIO.c:2836)
==3979==    by 0x4133230: xmlReaderForIO (xmlreader.c:5267)
==3979==    by 0x8048FC7: main (main.cpp:14)
==3979== 
==3979== 
==3979== 84 bytes in 1 blocks are still reachable in loss record 2 of 4
==3979==    at 0x4026FDE: malloc (vg_replace_malloc.c:207)
==3979==    by 0x40DDAA2: xmlNewRMutex (threads.c:291)
==3979==    by 0x4147587: xmlInitializeDict (dict.c:95)
==3979==    by 0x4147C42: xmlDictCreate (dict.c:327)
==3979==    by 0x4050597: xmlInitParserCtxt (parserInternals.c:1517)
==3979==    by 0x405101F: xmlNewParserCtxt (parserInternals.c:1768)
==3979==    by 0x406BA9F: xmlCreatePushParserCtxt (parser.c:10683)
==3979==    by 0x412E5FF: xmlNewTextReader (xmlreader.c:2075)
==3979==    by 0x4133252: xmlReaderForIO (xmlreader.c:5271)
==3979==    by 0x8048FC7: main (main.cpp:14)
==3979== 
==3979== 
==3979== 160 bytes in 8 blocks are still reachable in loss record 3 of 4
==3979==    at 0x4026FDE: malloc (vg_replace_malloc.c:207)
==3979==    by 0x404A844: xmlNewCharEncodingHandler (encoding.c:1295)
==3979==    by 0x404A984: xmlInitCharEncodingHandlers (encoding.c:1351)
==3979==    by 0x404AC35: xmlGetCharEncodingHandler (encoding.c:1444)
==3979==    by 0x408346A: xmlAllocParserInputBuffer (xmlIO.c:2239)
==3979==    by 0x408415E: xmlParserInputBufferCreateIO (xmlIO.c:2836)
==3979==    by 0x4133230: xmlReaderForIO (xmlreader.c:5267)
==3979==    by 0x8048FC7: main (main.cpp:14)
==3979== 
==3979== 
==3979== 200 bytes in 1 blocks are still reachable in loss record 4 of 4
==3979==    at 0x4026FDE: malloc (vg_replace_malloc.c:207)
==3979==    by 0x404A8F5: xmlInitCharEncodingHandlers (encoding.c:1337)
==3979==    by 0x404AC35: xmlGetCharEncodingHandler (encoding.c:1444)
==3979==    by 0x408346A: xmlAllocParserInputBuffer (xmlIO.c:2239)
==3979==    by 0x408415E: xmlParserInputBufferCreateIO (xmlIO.c:2836)
==3979==    by 0x4133230: xmlReaderForIO (xmlreader.c:5267)
==3979==    by 0x8048FC7: main (main.cpp:14)
==3979== 
==3979== LEAK SUMMARY:
==3979==    definitely lost: 0 bytes in 0 blocks.
==3979==      possibly lost: 0 bytes in 0 blocks.
==3979==    still reachable: 506 bytes in 18 blocks.
==3979==         suppressed: 0 bytes in 0 blocks.

で、該当の箇所をみてみると

  • xmlCleanupCharEncodingHandlers()
  • xmlDictCleanup()

で、メモリプールを解放出来ることが分かる

コードを修正

#include <iostream>
#include <sstream>
#include <boost/shared_ptr.hpp>
#include <libxml/xmlreader.h>

static int read(void* in, char* buf, int len)
{
    return static_cast<std::istream*>(in)->readsome(buf, len);
}

int main()
{
    // 以下を追加
    struct Finalizer
    {   
        ~Finalizer()
        {   
            xmlCleanupCharEncodingHandlers();
            xmlDictCleanup();
        }   
    } finalizer;

    std::istringstream in("<a><b/>hoge<c/></a>");
    boost::shared_ptr<xmlTextReader> reader(xmlReaderForIO(read, NULL, &in, NULL, NULL, 0), xmlFreeTextReader);

    while (xmlTextReaderRead(reader.get()) == 1)
    {   
        std::cout << "node depth: " << xmlTextReaderDepth(reader.get()) << std::endl;
        std::cout << "node type : " << xmlTextReaderNodeType(reader.get()) << std::endl;

        const char* name = reinterpret_cast<const char*>(xmlTextReaderConstName(reader.get()));
        std::cout << "node name : " << (name ? name : "") << std::endl;

        const char* value = reinterpret_cast<const char*>(xmlTextReaderConstValue(reader.get()));
        std::cout << "node value: " << (value ? value : "") << std::endl;
        std::cout << std::endl;
    }   
}

で、再度コンパイルして valgrind する

これで、 still reachable なメモリがなくなりました。

$ g++ -g -lxml2 main.cpp && valgrind ./a.out

  (snip)

==4182== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 23 from 1)
==4182== malloc/free: in use at exit: 0 bytes in 0 blocks.
==4182== malloc/free: 52 allocs, 52 frees, 22,072 bytes allocated.
==4182== For counts of detected errors, rerun with: -v
==4182== All heap blocks were freed -- no leaks are possible.

このように

libxml2 では操作に対して適切に cleanup 系のメソッドを読んでやれば、奇麗にメモリを解放することができるんですね。

まとめ

valgrind で多い日も安心!

ICU の解放順序でハマった

ちょっとメモ

以下のようなコードを書いて失敗した。

#include <boost/shared_ptr.hpp>
#include <unicode/uclean.h>
#include <unicode/ucnv.h>

int main()
{
    UErrorCode status = U_ZERO_ERROR;
    u_init(&status);

    // (snip)

    boost::shared_ptr<UConverter> ucnv(ucnv_open("Shift_JIS", &status), ucnv_close);

    // (snip)

    u_cleanup();
}

u_cleanup より後に ucnv_close が呼ばれてしまうのが問題。

valgrind で検出

このコード問題があるのに、普通にエラーも何もでないので気が付かない。 valgrind を使ったら、 still reachable なメモリがあると教えてくれた。

$ valgrind ./a.out
==17685== Memcheck, a memory error detector.
==17685== Copyright (C) 2002-2006, and GNU GPL'd, by Julian Seward et al.
==17685== Using LibVEX rev 1658, a library for dynamic binary translation.
==17685== Copyright (C) 2004-2006, and GNU GPL'd, by OpenWorks LLP.
==17685== Using valgrind-3.2.1, a dynamic binary instrumentation framework.
==17685== Copyright (C) 2000-2006, and GNU GPL'd, by Julian Seward et al.
==17685== For more details, rerun with: -v
==17685== 
==17685== 
==17685== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 7 from 1)
==17685== malloc/free: in use at exit: 24,944 bytes in 4 blocks.
==17685== malloc/free: 8 allocs, 4 frees, 25,376 bytes allocated.
==17685== For counts of detected errors, rerun with: -v
==17685== searching for pointers to 4 not-freed blocks.
==17685== checked 285,112 bytes.
==17685== 
==17685== LEAK SUMMARY:
==17685==    definitely lost: 0 bytes in 0 blocks.
==17685==      possibly lost: 0 bytes in 0 blocks.
==17685==    still reachable: 24,944 bytes in 4 blocks. // ←ここ!
==17685==         suppressed: 0 bytes in 0 blocks.
==17685== Reachable blocks (those to which a pointer was found) are not shown.
==17685== To see them, rerun with: --show-reachable=yes

修正

で、さっきのコードを以下のようにすればいい。

#include <boost/shared_ptr.hpp>
#include <unicode/uclean.h>
#include <unicode/ucnv.h>

struct ICUInitializer
{
    ICUInitializer() : status_(U_ZERO_ERROR) { u_init(&status_); }
    ~ICUInitializer() { u_cleanup(); }
    UErrorCode status_;
};

int main()
{
    ICUInitializer init;

    // (snip)

    UErrorCode status = U_ZERO_ERROR;
    boost::shared_ptr<UConverter> ucnv(ucnv_open("Shift_JIS", &status), ucnv_close);

    // (snip)
}

valgrind で確認

$ valgrind ./a.out
==17834== Memcheck, a memory error detector.
==17834== Copyright (C) 2002-2006, and GNU GPL'd, by Julian Seward et al.
==17834== Using LibVEX rev 1658, a library for dynamic binary translation.
==17834== Copyright (C) 2004-2006, and GNU GPL'd, by OpenWorks LLP.
==17834== Using valgrind-3.2.1, a dynamic binary instrumentation framework.
==17834== Copyright (C) 2000-2006, and GNU GPL'd, by Julian Seward et al.
==17834== For more details, rerun with: -v
==17834== 
==17834== 
==17834== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 7 from 1)
==17834== malloc/free: in use at exit: 0 bytes in 0 blocks.
==17834== malloc/free: 8 allocs, 8 frees, 25,376 bytes allocated.
==17834== For counts of detected errors, rerun with: -v
==17834== All heap blocks were freed -- no leaks are possible.

まとめ

valgrind で多い日も安心!

XMPP ライブラリ Gloox のトランスポート層を触る(2)

SASL → Resource Bind → Create Session まで

#include <cassert>
#include <iostream>
#include <boost/make_shared.hpp>
#include <gloox/iq.h>
#include <gloox/parser.h>
#include <gloox/base64.h>
#include <gloox/connectiontcpclient.h>
#include <gloox/connectiontls.h>

class Bot : public gloox::ConnectionDataHandler, gloox::TagHandler, gloox::TLSHandler
{
public:
    Bot() :
        client_(new gloox::ConnectionTCPClient(this, log_, "talk.google.com", 5222)),
        raw_client_(client_),
        parser_(this)
    {
        gloox::ConnectionError ret = client_->connect();

        assert(ret == gloox::ConnNoError);

        client_->receive();
    }

    virtual void handleReceivedData(const gloox::ConnectionBase* con, const std::string& data)
    {
        std::cerr << "[recv] " << data << std::endl;
        std::string copied = data;
        int pos = parser_.feed(copied);

        assert(pos < 0);
    }

    virtual void handleConnect(const gloox::ConnectionBase* con)
    {
        send(
            "<?xml version='1.0' ?>"
            "<stream:stream "
                "xmlns:stream=\"http://etherx.jabber.org/streams\" "
                "version=\"1.0\" xmlns=\"jabber:client\" "
                "to=\"gmail.com\" "
                "xml:lang=\"en\" "
                "xmlns:xml=\"http://www.w3.org/XML/1998/namespace\" >");
    }

    virtual void handleTag(gloox::Tag* tag)
    {
        if (tag->name() == "stream" && tag->xmlns() == "http://etherx.jabber.org/streams") {
            sid_ = tag->findAttribute( "id" );
        }
        else {
            if(tag->name() == "features" && tag->xmlns() == "http://etherx.jabber.org/streams")
            {
                if(tag->hasChild("starttls", "xmlns", "urn:ietf:params:xml:ns:xmpp-tls") )
                {
                    send(gloox::Tag("starttls", "xmlns", "urn:ietf:params:xml:ns:xmpp-tls").xml());
                }
                else if(tag->hasChild("mechanisms", "xmlns", "urn:ietf:params:xml:ns:xmpp-sasl") &&
                        tag->findChild("mechanisms")->hasChildWithCData("mechanism", "PLAIN")) // only PLAIN :(
                {
                    gloox::Tag a("auth", "xmlns", "urn:ietf:params:xml:ns:xmpp-sasl");
                    a.addAttribute("mechanism", "PLAIN");

                    std::string tmp;
                    tmp += '\0';
                    tmp += "seijro";
                    tmp += '\0';
                    tmp += "PASSWORD";

                    a.setCData(gloox::Base64::encode64(tmp));
                    send(a.xml());
                }
                else if(tag->hasChild("bind", "xmlns", "urn:ietf:params:xml:ns:xmpp-bind"))
                {
                    gloox::Tag iq("iq", "xmlns", "jabber:client");
                    iq.addAttribute("type", "set");
                    iq.addAttribute("id", "binding");
                    gloox::Tag *bind = new gloox::Tag("bind", "xmlns", "urn:ietf:params:xml:ns:xmpp-bind");
                    gloox::Tag *resource = new gloox::Tag("resource");
                    resource->setCData("myresource");
                    bind->addChild(resource);
                    iq.addChild(bind);

                    send(iq.xml());
                }

            }

            else if(tag->name() == "proceed" && tag->xmlns() == "urn:ietf:params:xml:ns:xmpp-tls")
            {
                gloox::ConnectionTLS* encryption_client = new gloox::ConnectionTLS(this, client_.get(), log_);
                client_.reset(encryption_client);
                encryption_client->registerTLSHandler(this);
                gloox::ConnectionError ret = client_->connect();
                assert(ret == gloox::ConnNoError);
            }

            else if(tag->name() == "success" && tag->xmlns() == "urn:ietf:params:xml:ns:xmpp-sasl")
            {
                send(
                    "<?xml version='1.0' ?>"
                    "<stream:stream "
                        "xmlns:stream=\"http://etherx.jabber.org/streams\" "
                        "version=\"1.0\" xmlns=\"jabber:client\" "
                        "to=\"gmail.com\" "
                        "xml:lang=\"en\" "
                        "xmlns:xml=\"http://www.w3.org/XML/1998/namespace\" >");
            }

            else if (tag->name() == "iq")
            {
                if (tag->hasChild("bind", "xmlns", "urn:ietf:params:xml:ns:xmpp-bind"))
                {
                    resource_ = tag->findChild("bind")->findChild("jid")->cdata();

                    gloox::Tag iq("iq", "xmlns", "jabber:client");
                    iq.addAttribute("type", "set");
                    iq.addAttribute("id", "create_session");
                    iq.addChild(new gloox::Tag("session", "xmlns", "urn:ietf:params:xml:ns:xmpp-session"));

                    send(iq.xml());
                }
            }
        }
    }

    virtual void handleEncryptedData(const gloox::TLSBase* tls, const std::string& data)
    {
        std::cout << "handleEncryptedData" << std::endl;
    }

    virtual void handleDecryptedData(const gloox::TLSBase* tls, const std::string& data)
    {
        std::cout << "handleDecryptedData" << std::endl;
    }

    virtual void handleHandshakeResult(const gloox::TLSBase* tls, bool, gloox::CertInfo& cert)
    {
        std::cout << "handleHandshakeResult" << std::endl;
    }

    virtual void handleDisconnect(const gloox::ConnectionBase* con, gloox::ConnectionError)
    {
        std::cout << "handleDisonnect" << std::endl;
    }

private:

    void send(const std::string &data)
    {
        std::cerr << "[send] " << data << std::endl;
        client_->send(data);
    }

    gloox::LogSink log_;

    boost::shared_ptr<gloox::ConnectionBase> client_;

    boost::shared_ptr<gloox::ConnectionBase> raw_client_;

    gloox::Parser parser_;

    std::string sid_;

    std::string resource_;
};

int main()
{
    Bot bot;
}
[send] <?xml version='1.0' ?><stream:stream xmlns:stream="http://etherx.jabber.org/streams" version="1.0" xmlns="jabber:client" to="gmail.com" xml:lang="en" xmlns:xml="http://www.w3.org/XML/1998/namespace" >
[recv] <stream:stream from="gmail.com" id="7D3EB506BD78CED4" version="1.0" xmlns:stream="http://etherx.jabber.org/streams" xmlns="jabber:client"><stream:features><starttls xmlns="urn:ietf:params:xml:ns:xmpp-tls"><required/></starttls><mechanisms xmlns="urn:ietf:params:xml:ns:xmpp-sasl"><mechanism>X-GOOGLE-TOKEN</mechanism></mechanisms></stream:features>
[send] <starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>
[recv] <proceed xmlns="urn:ietf:params:xml:ns:xmpp-tls"/>
handleHandshakeResult
[send] <?xml version='1.0' ?><stream:stream xmlns:stream="http://etherx.jabber.org/streams" version="1.0" xmlns="jabber:client" to="gmail.com" xml:lang="en" xmlns:xml="http://www.w3.org/XML/1998/namespace" >
[recv] <stream:stream from="gmail.com" id="B2A71EB8DE19C974" version="1.0" xmlns:stream="http://etherx.jabber.org/streams" xmlns="jabber:client">
[recv] <stream:features><mechanisms xmlns="urn:ietf:params:xml:ns:xmpp-sasl"><mechanism>PLAIN</mechanism><mechanism>X-GOOGLE-TOKEN</mechanism></mechanisms></stream:features>
[send] <auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='PLAIN'>XXXXXXXXXXXXXXXXXXXXXXX</auth>
[recv] <success xmlns="urn:ietf:params:xml:ns:xmpp-sasl"/>
[send] <?xml version='1.0' ?><stream:stream xmlns:stream="http://etherx.jabber.org/streams" version="1.0" xmlns="jabber:client" to="gmail.com" xml:lang="en" xmlns:xml="http://www.w3.org/XML/1998/namespace" >
[recv] <stream:stream from="gmail.com" id="0522E247621D8813" version="1.0" xmlns:stream="http://etherx.jabber.org/streams" xmlns="jabber:client">
[recv] <stream:features><bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"/><session xmlns="urn:ietf:params:xml:ns:xmpp-session"/></stream:features>
[send] <iq xmlns='jabber:client' type='set' id='binding'><bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'><resource>myresource</resource></bind></iq>
[recv] <iq id="binding" type="result"><bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"><jid>seijro@gmail.com/myresource65631060</jid></bind></iq>
[send] <iq xmlns='jabber:client' type='set' id='create_session'><session xmlns='urn:ietf:params:xml:ns:xmpp-session'/></iq>
[recv]  
[recv] <iq type="result" id="create_session"/>
[recv]  
[recv] <message type="chat" to="seijro@gmail.com/myresource65631060" id="test_002" from="seijro@gmail.com/amachang4B803057"><body>Hello, World!!</body></message>

XMPP ライブラリ Gloox のトランスポート層を触る(1)

一日一回 XMPP 勉強中

とりあえず、 TLS のコネクション貼るところまでできた

#include <cassert>
#include <iostream>
#include <boost/make_shared.hpp>
#include <gloox/parser.h>
#include <gloox/connectiontcpclient.h>
#include <gloox/connectiontls.h>

class Bot : public gloox::ConnectionDataHandler, gloox::TagHandler, gloox::TLSHandler
{
public:
    Bot() :
        client_(new gloox::ConnectionTCPClient(this, log_, "talk.google.com", 5222)),
        raw_client_(client_),
        parser_(this)
    {
        gloox::ConnectionError ret = client_->connect();

        assert(ret == gloox::ConnNoError);

        client_->receive();
    }

    virtual void handleReceivedData(const gloox::ConnectionBase* con, const std::string& data)
    {
        std::cerr << "[recv] " << data << std::endl;
        std::string copied = data;
        int pos = parser_.feed(copied);

        assert(pos < 0);
    }

    virtual void handleConnect(const gloox::ConnectionBase* con)
    {
        client_->send(
            "<?xml version='1.0' ?>"
            "<stream:stream "
                "xmlns:stream=\"http://etherx.jabber.org/streams\" "
                "version=\"1.0\" xmlns=\"jabber:client\" "
                "to=\"gmail.com\" "
                "xml:lang=\"en\" "
                "xmlns:xml=\"http://www.w3.org/XML/1998/namespace\" >");
    }

    virtual void handleTag(gloox::Tag* tag)
    {
        if (tag->name() == "stream" && tag->xmlns() == "http://etherx.jabber.org/streams") {
            sid_ = tag->findAttribute( "id" );
        }
        else {
            if(tag->name() == "features" && tag->xmlns() == "http://etherx.jabber.org/streams")
            {
                if(tag->hasChild("starttls", "xmlns", "urn:ietf:params:xml:ns:xmpp-tls") )
                {
                    client_->send(gloox::Tag("starttls", "xmlns", "urn:ietf:params:xml:ns:xmpp-tls").xml());
                }
            }

            if(tag->name() == "proceed" && tag->xmlns() == "urn:ietf:params:xml:ns:xmpp-tls")
            {
                gloox::ConnectionTLS* encryption_client = new gloox::ConnectionTLS(this, client_.get(), log_);
                client_.reset(encryption_client);
                encryption_client->registerTLSHandler(this);
                gloox::ConnectionError ret = client_->connect();
                assert(ret == gloox::ConnNoError);
            }
        }
    }

    virtual void handleEncryptedData(const gloox::TLSBase* tls, const std::string& data)
    {
        std::cout << "handleEncryptedData" << std::endl;
    }

    virtual void handleDecryptedData(const gloox::TLSBase* tls, const std::string& data)
    {
        std::cout << "handleDecryptedData" << std::endl;
    }

    virtual void handleHandshakeResult(const gloox::TLSBase* tls, bool, gloox::CertInfo& cert)
    {
        std::cout << "handleHandshakeResult" << std::endl;
    }

    virtual void handleDisconnect(const gloox::ConnectionBase* con, gloox::ConnectionError)
    {
        std::cout << "handleDisonnect" << std::endl;
    }

private:

    gloox::LogSink log_;

    boost::shared_ptr<gloox::ConnectionBase> client_;

    boost::shared_ptr<gloox::ConnectionBase> raw_client_;

    gloox::Parser parser_;

    std::string sid_;
};

int main()
{
    Bot bot;
}

XMPP クライアント Psi で XML のトレース

一日、数十分ずつ XMPP を勉強

Psi というクライアントを使うと XML の生のメッセージが確認できる

やり方は簡単 Tools メニューの XML Console を選択するだけ

以下は実際にトレースしたところ

これを見て

Client: XMPP 始めますよ (・∀・)
Server: TSL と SASL でお願いします
Client: TSL 始めますよ(・∀・)
Server: それでは、のちほど
・・・レイヤー追加中(・∀・;)(;・∀・)
Client: TSL で XMPP 始めましたよ(・∀・)
Server: SASL でお願いします。 PLAIN と X-GOOGLE-TOKEN とどちらの方式でやりますか
Client: PLAIN で( ;ω;)つ XXXBASE64XXX
Server: 認可
Client: TSL-SASL で XMPP 始めましたよ(・∀・)
って流れまで分かった

Gloox で XMPP を書いてみた

camaya.net | Home of gloox – The Portable XMPP Engine
ちなみに 1.0 beta を使った

Gtalk に Hello!

#include <iostream>
#include <gloox/client.h>
#include <gloox/connectionlistener.h>
#include <gloox/messagesession.h>

class Bot : public gloox::ConnectionListener
{
public:
    Bot() : client_(gloox::JID("USER@gmail.com"), "PASS") {
        client_.registerConnectionListener(this);
        client_.setServer("talk.google.com");
        session_ = new gloox::MessageSession(&client_, gloox::JID("USER@gmail.com"));
        client_.connect();
    }   

    virtual void onConnect() {
        session_->send("Hello, Hello!", "Hellooooooooo!");
        client_.disconnect();
    }   

    virtual void onDisconnect(gloox::ConnectionError reason) { } 
    virtual bool onTLSConnect(const gloox::CertInfo &info) { return true; }

private:
    gloox::Client client_;
    gloox::MessageSession* session_;

};

int main() {
    Bot bot;
}

けっこうシンプルに書ける

Message のソース(生 XML)を見る

#include <iostream>
#include <gloox/client.h>
#include <gloox/connectionlistener.h>
#include <gloox/messagesession.h>
#include <gloox/messagehandler.h>
#include <gloox/message.h>

class Bot : public gloox::ConnectionListener, gloox::MessageHandler
{
public:
    Bot() : client_(gloox::JID("USER@gmail.com"), "PASS") {
        client_.registerConnectionListener(this);
        client_.setServer("talk.google.com");
        session_ = new gloox::MessageSession(&client_, gloox::JID("USER@gmail.com"));
        session_->registerMessageHandler(this);
        client_.connect();
    }   

    virtual void onConnect() {
        session_->send("Hello, Hello!", "Hellooooooooo!");
    }   

    virtual void handleMessage(const gloox::Message &msg, gloox::MessageSession *session)
    {   
        std::cout << msg.tag()->xml() << std::endl; // ここ
    }   

    virtual void onDisconnect(gloox::ConnectionError reason) { } 
    virtual bool onTLSConnect(const gloox::CertInfo &info) { return true; }

private:
    gloox::Client client_;
    gloox::MessageSession* session_;

};

int main() {
    Bot bot;
}

割とすんなり試せた。 XMPP おもしろい

double が IEEE 754 かどうか

double をシリアライズするときとかは、 std::numeric_limit< double >::is_iec559 を assert しておけばいいのかなー。

#include <cassert>
#include <limits>
#include <boost/cstdint.hpp>

template<class Itr>
void serialize(Itr it, const double d)
{
    // ↓ こんな感じ
    assert(sizeof(double) == sizeof(boost::uint64_t)); // 64 bit
    assert(std::numeric_limits< double >::is_iec559);  //    IEEE 754

    union { double d; boost::uint64_t u; } du; 
    du.d = d;
    boost::uint64_t u = du.u;

    *it++ = static_cast<unsigned char>(u & 0xff);
    *it++ = static_cast<unsigned char>((u>>>8) & 0xff);
    *it++ = static_cast<unsigned char>((u>>>16) & 0xff);
    *it++ = static_cast<unsigned char>((u>>>24) & 0xff);
    *it++ = static_cast<unsigned char>((u>>>32) & 0xff);
    *it++ = static_cast<unsigned char>((u>>>40) & 0xff);
    *it++ = static_cast<unsigned char>((u>>>48) & 0xff);
    *it++ = static_cast<unsigned char>((u>>>56) & 0xff);
}