Signed-off-by: Yuichi Ito <[email protected]>
---
 doc/source/library_packet_ref.rst  |    6 +
 ryu/lib/packet/igmp.py             |  359 ++++++++++++++-
 ryu/tests/unit/packet/test_igmp.py |  840 +++++++++++++++++++++++++++++++++++-
 3 files changed, 1196 insertions(+), 9 deletions(-)

diff --git a/doc/source/library_packet_ref.rst 
b/doc/source/library_packet_ref.rst
index 4426cfd..a39aa56 100644
--- a/doc/source/library_packet_ref.rst
+++ b/doc/source/library_packet_ref.rst
@@ -95,6 +95,12 @@ Protocol Header classes

 .. autoclass:: ryu.lib.packet.igmp.igmp
    :members:
+.. autoclass:: ryu.lib.packet.igmp.igmpv3_query
+   :members:
+.. autoclass:: ryu.lib.packet.igmp.igmpv3_report
+   :members:
+.. autoclass:: ryu.lib.packet.igmp.igmpv3_report_group
+   :members:

 .. autoclass:: ryu.lib.packet.bgp.BGPMessage
    :members:
diff --git a/ryu/lib/packet/igmp.py b/ryu/lib/packet/igmp.py
index 88eab85..b434ca2 100644
--- a/ryu/lib/packet/igmp.py
+++ b/ryu/lib/packet/igmp.py
@@ -37,11 +37,90 @@ IGMP v2 format
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |                         Group Address                         |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+
+RFC 3376
+IGMP v3 Membership Query format
+
+    0                   1                   2                   3
+    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+   |  Type = 0x11  | Max Resp Code |           Checksum            |
+   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+   |                         Group Address                         |
+   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+   | Resv  |S| QRV |     QQIC      |     Number of Sources (N)     |
+   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+   |                       Source Address [1]                      |
+   +-                                                             -+
+   |                       Source Address [2]                      |
+   +-                              .                              -+
+   .                               .                               .
+   .                               .                               .
+   +-                                                             -+
+   |                       Source Address [N]                      |
+   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+
+IGMP v3 Membership Report format
+
+    0                   1                   2                   3
+    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+   |  Type = 0x22  |    Reserved   |           Checksum            |
+   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+   |           Reserved            |  Number of Group Records (M)  |
+   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+   |                                                               |
+   .                                                               .
+   .                        Group Record [1]                       .
+   .                                                               .
+   |                                                               |
+   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+   |                                                               |
+   .                                                               .
+   .                        Group Record [2]                       .
+   .                                                               .
+   |                                                               |
+   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+   |                               .                               |
+   .                               .                               .
+   |                               .                               |
+   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+   |                                                               |
+   .                                                               .
+   .                        Group Record [M]                       .
+   .                                                               .
+   |                                                               |
+   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+
+where each Group Record has the following internal format:
+
+   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+   |  Record Type  |  Aux Data Len |     Number of Sources (N)     |
+   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+   |                       Multicast Address                       |
+   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+   |                       Source Address [1]                      |
+   +-                                                             -+
+   |                       Source Address [2]                      |
+   +-                                                             -+
+   .                               .                               .
+   .                               .                               .
+   .                               .                               .
+   +-                                                             -+
+   |                       Source Address [N]                      |
+   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+   |                                                               |
+   .                                                               .
+   .                         Auxiliary Data                        .
+   .                                                               .
+   |                                                               |
+   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 """

 import struct

 from ryu.lib import addrconv
+from ryu.lib import stringify
 from ryu.lib.packet import packet_base
 from ryu.lib.packet import packet_utils

@@ -58,6 +137,14 @@ LAST_MEMBER_QUERY_INTERVAL = 1.0
 MULTICAST_IP_ALL_HOST = '224.0.0.1'
 MULTICAST_MAC_ALL_HOST = '01:00:5e:00:00:01'

+# for types of IGMPv3 Report Group Records
+MODE_IS_INCLUDE = 1
+MODE_IS_EXCLUDE = 2
+CHANGE_TO_INCLUDE_MODE = 3
+CHANGE_TO_EXCLUDE_MODE = 4
+ALLOW_NEW_SOURCES = 5
+BLOCK_OLD_SOURCES = 6
+

 class igmp(packet_base.PacketBase):
     """
@@ -84,8 +171,6 @@ class igmp(packet_base.PacketBase):
                     when encoding.
     address         a group address value.
     =============== ====================================================
-
-    * NOTE: IGMP v3(RFC 3376) is not supported yet.
     """
     _PACK_STR = '!BBH4s'
     _MIN_LEN = struct.calcsize(_PACK_STR)
@@ -101,12 +186,20 @@ class igmp(packet_base.PacketBase):
     @classmethod
     def parser(cls, buf):
         assert cls._MIN_LEN <= len(buf)
-        (msgtype, maxresp, csum, address
-         ) = struct.unpack_from(cls._PACK_STR, buf)
-        return (cls(msgtype, maxresp, csum,
-                    addrconv.ipv4.bin_to_text(address)),
-                None,
-                buf[cls._MIN_LEN:])
+        (msgtype, ) = struct.unpack_from('!B', buf)
+        if (IGMP_TYPE_QUERY == msgtype and
+                igmpv3_query.MIN_LEN <= len(buf)):
+            (instance, subclass, rest,) = igmpv3_query.parser(buf)
+        elif IGMP_TYPE_REPORT_V3 == msgtype:
+            (instance, subclass, rest,) = igmpv3_report.parser(buf)
+        else:
+            (msgtype, maxresp, csum, address
+             ) = struct.unpack_from(cls._PACK_STR, buf)
+            instance = cls(msgtype, maxresp, csum,
+                           addrconv.ipv4.bin_to_text(address))
+            subclass = None
+            rest = buf[cls._MIN_LEN:]
+        return instance, subclass, rest

     def serialize(self, payload, prev):
         hdr = bytearray(struct.pack(self._PACK_STR, self.msgtype,
@@ -118,3 +211,253 @@ class igmp(packet_base.PacketBase):
             struct.pack_into('!H', hdr, 2, self.csum)

         return hdr
+
+
+class igmpv3_query(igmp):
+    """
+    Internet Group Management Protocol(IGMP, RFC 3376)
+    Membership Query message encoder/decoder class.
+
+    http://www.ietf.org/rfc/rfc3376.txt
+
+    An instance has the following attributes at least.
+    Most of them are same to the on-wire counterparts but in host byte
+    order.
+    __init__ takes the corresponding args in this order.
+
+    .. tabularcolumns:: |l|L|
+
+    =============== ====================================================
+    Attribute       Description
+    =============== ====================================================
+    msgtype         a message type for v3.
+    maxresp         max response time in unit of 1/10 second.
+    csum            a check sum value. 0 means automatically-calculate
+                    when encoding.
+    address         a group address value.
+    s_flg           when set to 1, routers suppress the timer process.
+    qrv             robustness variable for a querier.
+    qqic            an interval time for a querier in unit of seconds.
+    num             a number of the multicast servers.
+    srcs            a list of IPv4 addresses of the multicast servers.
+    =============== ====================================================
+    """
+
+    _PACK_STR = '!BBH4sBBH'
+    _MIN_LEN = struct.calcsize(_PACK_STR)
+    MIN_LEN = _MIN_LEN
+
+    def __init__(self, msgtype=IGMP_TYPE_QUERY, maxresp=100, csum=0,
+                 address='0.0.0.0', s_flg=0, qrv=2, qqic=0, num=0,
+                 srcs=None):
+        super(igmpv3_query, self).__init__(
+            msgtype, maxresp, csum, address)
+        self.s_flg = s_flg
+        self.qrv = qrv
+        self.qqic = qqic
+        self.num = num
+        srcs = srcs or []
+        assert isinstance(srcs, list)
+        for src in srcs:
+            assert isinstance(src, str)
+        self.srcs = srcs
+
+    @classmethod
+    def parser(cls, buf):
+        (msgtype, maxresp, csum, address, s_qrv, qqic, num
+         ) = struct.unpack_from(cls._PACK_STR, buf)
+        s_flg = (s_qrv >> 3) & 0b1
+        qrv = s_qrv & 0b111
+        offset = cls._MIN_LEN
+        srcs = []
+        while 0 < len(buf[offset:]) and num > len(srcs):
+            assert 4 <= len(buf[offset:])
+            (src, ) = struct.unpack_from('4s', buf, offset)
+            srcs.append(addrconv.ipv4.bin_to_text(src))
+            offset += 4
+        assert num == len(srcs)
+        return (cls(msgtype, maxresp, csum,
+                    addrconv.ipv4.bin_to_text(address), s_flg, qrv,
+                    qqic, num, srcs),
+                None,
+                buf[offset:])
+
+    def serialize(self, payload, prev):
+        s_qrv = self.s_flg << 3 | self.qrv
+        buf = bytearray(struct.pack(self._PACK_STR, self.msgtype,
+                        self.maxresp, self.csum,
+                        addrconv.ipv4.text_to_bin(self.address),
+                        s_qrv, self.qqic, self.num))
+        for src in self.srcs:
+            buf.extend(struct.pack('4s', addrconv.ipv4.text_to_bin(src)))
+        if 0 == self.num:
+            self.num = len(self.srcs)
+            struct.pack_into('!H', buf, 10, self.num)
+        if 0 == self.csum:
+            self.csum = packet_utils.checksum(buf)
+            struct.pack_into('!H', buf, 2, self.csum)
+        return str(buf)
+
+    def __len__(self):
+        return self._MIN_LEN + len(self.srcs) * 4
+
+
+class igmpv3_report(igmp):
+    """
+    Internet Group Management Protocol(IGMP, RFC 3376)
+    Membership Report message encoder/decoder class.
+
+    http://www.ietf.org/rfc/rfc3376.txt
+
+    An instance has the following attributes at least.
+    Most of them are same to the on-wire counterparts but in host byte
+    order.
+    __init__ takes the corresponding args in this order.
+
+    .. tabularcolumns:: |l|L|
+
+    =============== ====================================================
+    Attribute       Description
+    =============== ====================================================
+    msgtype         a message type for v3.
+    csum            a check sum value. 0 means automatically-calculate
+                    when encoding.
+    record_num      a number of the group records.
+    records         a list of ryu.lib.packet.igmp.igmpv3_report_group.
+                    None if no records.
+    =============== ====================================================
+    """
+
+    _PACK_STR = '!BxH2xH'
+    _MIN_LEN = struct.calcsize(_PACK_STR)
+    _class_prefixes = ['igmpv3_report_group']
+
+    def __init__(self, msgtype=IGMP_TYPE_REPORT_V3, csum=0, record_num=0,
+                 records=None):
+        self.msgtype = msgtype
+        self.csum = csum
+        self.record_num = record_num
+        records = records or []
+        assert isinstance(records, list)
+        for record in records:
+            assert isinstance(record, igmpv3_report_group)
+        self.records = records
+
+    @classmethod
+    def parser(cls, buf):
+        (msgtype, csum, record_num
+         ) = struct.unpack_from(cls._PACK_STR, buf)
+        offset = cls._MIN_LEN
+        records = []
+        while 0 < len(buf[offset:]) and record_num > len(records):
+            record = igmpv3_report_group.parser(buf[offset:])
+            records.append(record)
+            offset += len(record)
+        assert record_num == len(records)
+        return (cls(msgtype, csum, record_num, records),
+                None,
+                buf[offset:])
+
+    def serialize(self, payload, prev):
+        buf = bytearray(struct.pack(self._PACK_STR, self.msgtype,
+                        self.csum, self.record_num))
+        for record in self.records:
+            buf.extend(record.serialize())
+        if 0 == self.record_num:
+            self.record_num = len(self.records)
+            struct.pack_into('!H', buf, 6, self.record_num)
+        if 0 == self.csum:
+            self.csum = packet_utils.checksum(buf)
+            struct.pack_into('!H', buf, 2, self.csum)
+        return str(buf)
+
+    def __len__(self):
+        records_len = 0
+        for record in self.records:
+            records_len += len(record)
+        return self._MIN_LEN + records_len
+
+
+class igmpv3_report_group(stringify.StringifyMixin):
+    """
+    Internet Group Management Protocol(IGMP, RFC 3376)
+    Membership Report Group Record message encoder/decoder class.
+
+    http://www.ietf.org/rfc/rfc3376.txt
+
+    This is used with ryu.lib.packet.igmp.igmpv3_report.
+
+    An instance has the following attributes at least.
+    Most of them are same to the on-wire counterparts but in host byte
+    order.
+    __init__ takes the corresponding args in this order.
+
+    .. tabularcolumns:: |l|L|
+
+    =============== ====================================================
+    Attribute       Description
+    =============== ====================================================
+    type\_          a group record type for v3.
+    aux_len         the length of the auxiliary data.
+    num             a number of the multicast servers.
+    address         a group address value.
+    srcs            a list of IPv4 addresses of the multicast servers.
+    aux             the auxiliary data.
+    =============== ====================================================
+    """
+    _PACK_STR = '!BBH4s'
+    _MIN_LEN = struct.calcsize(_PACK_STR)
+
+    def __init__(self, type_=0, aux_len=0, num=0, address='0.0.0.0',
+                 srcs=None, aux=None):
+        self.type_ = type_
+        self.aux_len = aux_len
+        self.num = num
+        self.address = address
+        srcs = srcs or []
+        assert isinstance(srcs, list)
+        for src in srcs:
+            assert isinstance(src, str)
+        self.srcs = srcs
+        self.aux = aux
+
+    @classmethod
+    def parser(cls, buf):
+        (type_, aux_len, num, address
+         ) = struct.unpack_from(cls._PACK_STR, buf)
+        offset = cls._MIN_LEN
+        srcs = []
+        while 0 < len(buf[offset:]) and num > len(srcs):
+            assert 4 <= len(buf[offset:])
+            (src, ) = struct.unpack_from('4s', buf, offset)
+            srcs.append(addrconv.ipv4.bin_to_text(src))
+            offset += 4
+        assert num == len(srcs)
+        aux = None
+        if aux_len:
+            (aux, ) = struct.unpack_from('%ds' % (aux_len * 4), buf, offset)
+        return cls(type_, aux_len, num,
+                   addrconv.ipv4.bin_to_text(address), srcs, aux)
+
+    def serialize(self):
+        buf = bytearray(struct.pack(self._PACK_STR, self.type_,
+                        self.aux_len, self.num,
+                        addrconv.ipv4.text_to_bin(self.address)))
+        for src in self.srcs:
+            buf.extend(struct.pack('4s', addrconv.ipv4.text_to_bin(src)))
+        if 0 == self.num:
+            self.num = len(self.srcs)
+            struct.pack_into('!H', buf, 2, self.num)
+        if self.aux is not None:
+            mod = len(self.aux) % 4
+            if mod:
+                self.aux += bytearray(4 - mod)
+                self.aux = str(self.aux)
+            buf.extend(self.aux)
+            if 0 == self.aux_len:
+                self.aux_len = len(self.aux) / 4
+                struct.pack_into('!B', buf, 1, self.aux_len)
+        return str(buf)
+
+    def __len__(self):
+        return self._MIN_LEN + len(self.srcs) * 4 + self.aux_len * 4
diff --git a/ryu/tests/unit/packet/test_igmp.py 
b/ryu/tests/unit/packet/test_igmp.py
index 04f5bb9..d99253d 100644
--- a/ryu/tests/unit/packet/test_igmp.py
+++ b/ryu/tests/unit/packet/test_igmp.py
@@ -19,7 +19,7 @@ import unittest
 import inspect
 import logging

-from struct import pack, unpack_from
+from struct import pack, unpack_from, pack_into
 from nose.tools import ok_, eq_, raises
 from ryu.ofproto import ether
 from ryu.ofproto import inet
@@ -29,7 +29,12 @@ from ryu.lib.packet.packet import Packet
 from ryu.lib.packet.packet_utils import checksum
 from ryu.lib import addrconv
 from ryu.lib.packet.igmp import igmp
+from ryu.lib.packet.igmp import igmpv3_query
+from ryu.lib.packet.igmp import igmpv3_report
+from ryu.lib.packet.igmp import igmpv3_report_group
 from ryu.lib.packet.igmp import IGMP_TYPE_QUERY
+from ryu.lib.packet.igmp import IGMP_TYPE_REPORT_V3
+from ryu.lib.packet.igmp import MODE_IS_INCLUDE

 LOG = logging.getLogger(__name__)

@@ -159,3 +164,836 @@ class Test_igmp(unittest.TestCase):
         jsondict = self.g.to_jsondict()
         g = igmp.from_jsondict(jsondict['igmp'])
         eq_(str(self.g), str(g))
+
+
+class Test_igmpv3_query(unittest.TestCase):
+    """ Test case for Internet Group Management Protocol v3
+    Membership Query Message"""
+    def setUp(self):
+        self.msgtype = IGMP_TYPE_QUERY
+        self.maxresp = 100
+        self.csum = 0
+        self.address = '225.0.0.1'
+        self.s_flg = 0
+        self.qrv = 2
+        self.qqic = 10
+        self.num = 0
+        self.srcs = []
+
+        self.s_qrv = self.s_flg << 3 | self.qrv
+
+        self.buf = pack(igmpv3_query._PACK_STR, self.msgtype,
+                        self.maxresp, self.csum,
+                        addrconv.ipv4.text_to_bin(self.address),
+                        self.s_qrv, self.qqic, self.num)
+
+        self.g = igmpv3_query(
+            self.msgtype, self.maxresp, self.csum, self.address,
+            self.s_flg, self.qrv, self.qqic, self.num, self.srcs)
+
+    def setUp_with_srcs(self):
+        self.srcs = ['192.168.1.1', '192.168.1.2', '192.168.1.3']
+        self.num = len(self.srcs)
+        self.buf = pack(igmpv3_query._PACK_STR, self.msgtype,
+                        self.maxresp, self.csum,
+                        addrconv.ipv4.text_to_bin(self.address),
+                        self.s_qrv, self.qqic, self.num)
+        for src in self.srcs:
+            self.buf += pack('4s', addrconv.ipv4.text_to_bin(src))
+        self.g = igmpv3_query(
+            self.msgtype, self.maxresp, self.csum, self.address,
+            self.s_flg, self.qrv, self.qqic, self.num, self.srcs)
+
+    def tearDown(self):
+        pass
+
+    def find_protocol(self, pkt, name):
+        for p in pkt.protocols:
+            if p.protocol_name == name:
+                return p
+
+    def test_init(self):
+        eq_(self.msgtype, self.g.msgtype)
+        eq_(self.maxresp, self.g.maxresp)
+        eq_(self.csum, self.g.csum)
+        eq_(self.address, self.g.address)
+        eq_(self.s_flg, self.g.s_flg)
+        eq_(self.qrv, self.g.qrv)
+        eq_(self.qqic, self.g.qqic)
+        eq_(self.num, self.g.num)
+        eq_(self.srcs, self.g.srcs)
+
+    def test_init_with_srcs(self):
+        self.setUp_with_srcs()
+        self.test_init()
+
+    def test_parser(self):
+        _res = self.g.parser(self.buf)
+        if type(_res) is tuple:
+            res = _res[0]
+        else:
+            res = _res
+
+        eq_(res.msgtype, self.msgtype)
+        eq_(res.maxresp, self.maxresp)
+        eq_(res.csum, self.csum)
+        eq_(res.address, self.address)
+        eq_(res.s_flg, self.s_flg)
+        eq_(res.qrv, self.qrv)
+        eq_(res.qqic, self.qqic)
+        eq_(res.num, self.num)
+        eq_(res.srcs, self.srcs)
+
+    def test_parser_with_srcs(self):
+        self.setUp_with_srcs()
+        self.test_parser()
+
+    def test_serialize(self):
+        data = bytearray()
+        prev = None
+        buf = self.g.serialize(data, prev)
+
+        res = unpack_from(igmpv3_query._PACK_STR, buffer(buf))
+
+        eq_(res[0], self.msgtype)
+        eq_(res[1], self.maxresp)
+        eq_(res[2], checksum(self.buf))
+        eq_(res[3], addrconv.ipv4.text_to_bin(self.address))
+        eq_(res[4], self.s_qrv)
+        eq_(res[5], self.qqic)
+        eq_(res[6], self.num)
+
+    def test_serialize_with_srcs(self):
+        self.setUp_with_srcs()
+        data = bytearray()
+        prev = None
+        buf = self.g.serialize(data, prev)
+
+        res = unpack_from(igmpv3_query._PACK_STR, buffer(buf))
+        (src1, src2, src3) = unpack_from('4s4s4s', buffer(buf),
+                                         igmpv3_query._MIN_LEN)
+
+        eq_(res[0], self.msgtype)
+        eq_(res[1], self.maxresp)
+        eq_(res[2], checksum(self.buf))
+        eq_(res[3], addrconv.ipv4.text_to_bin(self.address))
+        eq_(res[4], self.s_qrv)
+        eq_(res[5], self.qqic)
+        eq_(res[6], self.num)
+        eq_(src1, addrconv.ipv4.text_to_bin(self.srcs[0]))
+        eq_(src2, addrconv.ipv4.text_to_bin(self.srcs[1]))
+        eq_(src3, addrconv.ipv4.text_to_bin(self.srcs[2]))
+
+    def _build_igmp(self):
+        dl_dst = '11:22:33:44:55:66'
+        dl_src = 'aa:bb:cc:dd:ee:ff'
+        dl_type = ether.ETH_TYPE_IP
+        e = ethernet(dl_dst, dl_src, dl_type)
+
+        total_length = len(ipv4()) + len(self.g)
+        nw_proto = inet.IPPROTO_IGMP
+        nw_dst = '11.22.33.44'
+        nw_src = '55.66.77.88'
+        i = ipv4(total_length=total_length, src=nw_src, dst=nw_dst,
+                 proto=nw_proto, ttl=1)
+
+        p = Packet()
+
+        p.add_protocol(e)
+        p.add_protocol(i)
+        p.add_protocol(self.g)
+        p.serialize()
+        return p
+
+    def test_build_igmp(self):
+        p = self._build_igmp()
+
+        e = self.find_protocol(p, "ethernet")
+        ok_(e)
+        eq_(e.ethertype, ether.ETH_TYPE_IP)
+
+        i = self.find_protocol(p, "ipv4")
+        ok_(i)
+        eq_(i.proto, inet.IPPROTO_IGMP)
+
+        g = self.find_protocol(p, "igmpv3_query")
+        ok_(g)
+
+        eq_(g.msgtype, self.msgtype)
+        eq_(g.maxresp, self.maxresp)
+        eq_(g.csum, checksum(self.buf))
+        eq_(g.address, self.address)
+        eq_(g.s_flg, self.s_flg)
+        eq_(g.qrv, self.qrv)
+        eq_(g.qqic, self.qqic)
+        eq_(g.num, self.num)
+        eq_(g.srcs, self.srcs)
+
+    def test_build_igmp_with_srcs(self):
+        self.setUp_with_srcs()
+        self.test_build_igmp()
+
+    def test_to_string(self):
+        igmp_values = {'msgtype': repr(self.msgtype),
+                       'maxresp': repr(self.maxresp),
+                       'csum': repr(self.csum),
+                       'address': repr(self.address),
+                       's_flg': repr(self.s_flg),
+                       'qrv': repr(self.qrv),
+                       'qqic': repr(self.qqic),
+                       'num': repr(self.num),
+                       'srcs': repr(self.srcs)}
+        _g_str = ','.join(['%s=%s' % (k, igmp_values[k])
+                           for k, v in inspect.getmembers(self.g)
+                           if k in igmp_values])
+        g_str = '%s(%s)' % (igmpv3_query.__name__, _g_str)
+
+        eq_(str(self.g), g_str)
+        eq_(repr(self.g), g_str)
+
+    def test_to_string_with_srcs(self):
+        self.setUp_with_srcs()
+        self.test_to_string()
+
+    @raises(Exception)
+    def test_num_larger_than_srcs(self):
+        self.srcs = ['192.168.1.1', '192.168.1.2', '192.168.1.3']
+        self.num = len(self.srcs) + 1
+        self.buf = pack(igmpv3_query._PACK_STR, self.msgtype,
+                        self.maxresp, self.csum,
+                        addrconv.ipv4.text_to_bin(self.address),
+                        self.s_qrv, self.qqic, self.num)
+        for src in self.srcs:
+            self.buf += pack('4s', addrconv.ipv4.text_to_bin(src))
+        self.g = igmpv3_query(
+            self.msgtype, self.maxresp, self.csum, self.address,
+            self.s_flg, self.qrv, self.qqic, self.num, self.srcs)
+        self.test_parser()
+
+    @raises(Exception)
+    def test_num_smaller_than_srcs(self):
+        self.srcs = ['192.168.1.1', '192.168.1.2', '192.168.1.3']
+        self.num = len(self.srcs) - 1
+        self.buf = pack(igmpv3_query._PACK_STR, self.msgtype,
+                        self.maxresp, self.csum,
+                        addrconv.ipv4.text_to_bin(self.address),
+                        self.s_qrv, self.qqic, self.num)
+        for src in self.srcs:
+            self.buf += pack('4s', addrconv.ipv4.text_to_bin(src))
+        self.g = igmpv3_query(
+            self.msgtype, self.maxresp, self.csum, self.address,
+            self.s_flg, self.qrv, self.qqic, self.num, self.srcs)
+        self.test_parser()
+
+    def test_default_args(self):
+        prev = ipv4(proto=inet.IPPROTO_IGMP)
+        g = igmpv3_query()
+        prev.serialize(g, None)
+        buf = g.serialize(bytearray(), prev)
+        res = unpack_from(igmpv3_query._PACK_STR, str(buf))
+        buf = bytearray(buf)
+        pack_into('!H', buf, 2, 0)
+        buf = str(buf)
+
+        eq_(res[0], IGMP_TYPE_QUERY)
+        eq_(res[1], 100)
+        eq_(res[2], checksum(buf))
+        eq_(res[3], addrconv.ipv4.text_to_bin('0.0.0.0'))
+        eq_(res[4], 2)
+        eq_(res[5], 0)
+        eq_(res[6], 0)
+
+        # srcs without num
+        prev = ipv4(proto=inet.IPPROTO_IGMP)
+        srcs = ['192.168.1.1', '192.168.1.2', '192.168.1.3']
+        g = igmpv3_query(srcs=srcs)
+        prev.serialize(g, None)
+        buf = g.serialize(bytearray(), prev)
+        res = unpack_from(igmpv3_query._PACK_STR, str(buf))
+        buf = bytearray(buf)
+        pack_into('!H', buf, 2, 0)
+        buf = str(buf)
+
+        eq_(res[0], IGMP_TYPE_QUERY)
+        eq_(res[1], 100)
+        eq_(res[2], checksum(buf))
+        eq_(res[3], addrconv.ipv4.text_to_bin('0.0.0.0'))
+        eq_(res[4], 2)
+        eq_(res[5], 0)
+        eq_(res[6], len(srcs))
+
+        res = unpack_from('4s4s4s', str(buf), igmpv3_query._MIN_LEN)
+
+        eq_(res[0], addrconv.ipv4.text_to_bin(srcs[0]))
+        eq_(res[1], addrconv.ipv4.text_to_bin(srcs[1]))
+        eq_(res[2], addrconv.ipv4.text_to_bin(srcs[2]))
+
+    def test_json(self):
+        jsondict = self.g.to_jsondict()
+        g = igmpv3_query.from_jsondict(jsondict['igmpv3_query'])
+        eq_(str(self.g), str(g))
+
+    def test_json_with_srcs(self):
+        self.setUp_with_srcs()
+        self.test_json()
+
+
+class Test_igmpv3_report(unittest.TestCase):
+    """ Test case for Internet Group Management Protocol v3
+    Membership Report Message"""
+    def setUp(self):
+        self.msgtype = IGMP_TYPE_REPORT_V3
+        self.csum = 0
+        self.record_num = 0
+        self.records = []
+
+        self.buf = pack(igmpv3_report._PACK_STR, self.msgtype,
+                        self.csum, self.record_num)
+
+        self.g = igmpv3_report(
+            self.msgtype, self.csum, self.record_num, self.records)
+
+    def setUp_with_records(self):
+        self.record1 = igmpv3_report_group(
+            MODE_IS_INCLUDE, 0, 0, '225.0.0.1')
+        self.record2 = igmpv3_report_group(
+            MODE_IS_INCLUDE, 0, 2, '225.0.0.2',
+            ['172.16.10.10', '172.16.10.27'])
+        self.record3 = igmpv3_report_group(
+            MODE_IS_INCLUDE, 1, 0, '225.0.0.3', [], 'abc\x00')
+        self.record4 = igmpv3_report_group(
+            MODE_IS_INCLUDE, 2, 2, '225.0.0.4',
+            ['172.16.10.10', '172.16.10.27'], 'abcde\x00\x00\x00')
+        self.records = [self.record1, self.record2, self.record3,
+                        self.record4]
+        self.record_num = len(self.records)
+        self.buf = pack(igmpv3_report._PACK_STR, self.msgtype,
+                        self.csum, self.record_num)
+        self.buf += self.record1.serialize()
+        self.buf += self.record2.serialize()
+        self.buf += self.record3.serialize()
+        self.buf += self.record4.serialize()
+        self.g = igmpv3_report(
+            self.msgtype, self.csum, self.record_num, self.records)
+
+    def tearDown(self):
+        pass
+
+    def find_protocol(self, pkt, name):
+        for p in pkt.protocols:
+            if p.protocol_name == name:
+                return p
+
+    def test_init(self):
+        eq_(self.msgtype, self.g.msgtype)
+        eq_(self.csum, self.g.csum)
+        eq_(self.record_num, self.g.record_num)
+        eq_(self.records, self.g.records)
+
+    def test_init_with_records(self):
+        self.setUp_with_records()
+        self.test_init()
+
+    def test_parser(self):
+        _res = self.g.parser(str(self.buf))
+        if type(_res) is tuple:
+            res = _res[0]
+        else:
+            res = _res
+
+        eq_(res.msgtype, self.msgtype)
+        eq_(res.csum, self.csum)
+        eq_(res.record_num, self.record_num)
+        eq_(repr(res.records), repr(self.records))
+
+    def test_parser_with_records(self):
+        self.setUp_with_records()
+        self.test_parser()
+
+    def test_serialize(self):
+        data = bytearray()
+        prev = None
+        buf = self.g.serialize(data, prev)
+
+        res = unpack_from(igmpv3_report._PACK_STR, buffer(buf))
+
+        eq_(res[0], self.msgtype)
+        eq_(res[1], checksum(self.buf))
+        eq_(res[2], self.record_num)
+
+    def test_serialize_with_records(self):
+        self.setUp_with_records()
+        data = bytearray()
+        prev = None
+        buf = self.g.serialize(data, prev)
+
+        res = unpack_from(igmpv3_report._PACK_STR, buffer(buf))
+        offset = igmpv3_report._MIN_LEN
+        rec1 = igmpv3_report_group.parser(buffer(buf[offset:]))
+        offset += len(rec1)
+        rec2 = igmpv3_report_group.parser(buffer(buf[offset:]))
+        offset += len(rec2)
+        rec3 = igmpv3_report_group.parser(buffer(buf[offset:]))
+        offset += len(rec3)
+        rec4 = igmpv3_report_group.parser(buffer(buf[offset:]))
+
+        eq_(res[0], self.msgtype)
+        eq_(res[1], checksum(self.buf))
+        eq_(res[2], self.record_num)
+        eq_(repr(rec1), repr(self.record1))
+        eq_(repr(rec2), repr(self.record2))
+        eq_(repr(rec3), repr(self.record3))
+        eq_(repr(rec4), repr(self.record4))
+
+    def _build_igmp(self):
+        dl_dst = '11:22:33:44:55:66'
+        dl_src = 'aa:bb:cc:dd:ee:ff'
+        dl_type = ether.ETH_TYPE_IP
+        e = ethernet(dl_dst, dl_src, dl_type)
+
+        total_length = len(ipv4()) + len(self.g)
+        nw_proto = inet.IPPROTO_IGMP
+        nw_dst = '11.22.33.44'
+        nw_src = '55.66.77.88'
+        i = ipv4(total_length=total_length, src=nw_src, dst=nw_dst,
+                 proto=nw_proto, ttl=1)
+
+        p = Packet()
+
+        p.add_protocol(e)
+        p.add_protocol(i)
+        p.add_protocol(self.g)
+        p.serialize()
+        return p
+
+    def test_build_igmp(self):
+        p = self._build_igmp()
+
+        e = self.find_protocol(p, "ethernet")
+        ok_(e)
+        eq_(e.ethertype, ether.ETH_TYPE_IP)
+
+        i = self.find_protocol(p, "ipv4")
+        ok_(i)
+        eq_(i.proto, inet.IPPROTO_IGMP)
+
+        g = self.find_protocol(p, "igmpv3_report")
+        ok_(g)
+
+        eq_(g.msgtype, self.msgtype)
+        eq_(g.csum, checksum(self.buf))
+        eq_(g.record_num, self.record_num)
+        eq_(g.records, self.records)
+
+    def test_build_igmp_with_records(self):
+        self.setUp_with_records()
+        self.test_build_igmp()
+
+    def test_to_string(self):
+        igmp_values = {'msgtype': repr(self.msgtype),
+                       'csum': repr(self.csum),
+                       'record_num': repr(self.record_num),
+                       'records': repr(self.records)}
+        _g_str = ','.join(['%s=%s' % (k, igmp_values[k])
+                           for k, v in inspect.getmembers(self.g)
+                           if k in igmp_values])
+        g_str = '%s(%s)' % (igmpv3_report.__name__, _g_str)
+
+        eq_(str(self.g), g_str)
+        eq_(repr(self.g), g_str)
+
+    def test_to_string_with_records(self):
+        self.setUp_with_records()
+        self.test_to_string()
+
+    @raises(Exception)
+    def test_record_num_larger_than_records(self):
+        self.record1 = igmpv3_report_group(
+            MODE_IS_INCLUDE, 0, 0, '225.0.0.1')
+        self.record2 = igmpv3_report_group(
+            MODE_IS_INCLUDE, 0, 2, '225.0.0.2',
+            ['172.16.10.10', '172.16.10.27'])
+        self.record3 = igmpv3_report_group(
+            MODE_IS_INCLUDE, 1, 0, '225.0.0.3', [], 'abc\x00')
+        self.record4 = igmpv3_report_group(
+            MODE_IS_INCLUDE, 1, 2, '225.0.0.4',
+            ['172.16.10.10', '172.16.10.27'], 'abc\x00')
+        self.records = [self.record1, self.record2, self.record3,
+                        self.record4]
+        self.record_num = len(self.records) + 1
+        self.buf = pack(igmpv3_report._PACK_STR, self.msgtype,
+                        self.csum, self.record_num)
+        self.buf += self.record1.serialize()
+        self.buf += self.record2.serialize()
+        self.buf += self.record3.serialize()
+        self.buf += self.record4.serialize()
+        self.g = igmpv3_report(
+            self.msgtype, self.csum, self.record_num, self.records)
+        self.test_parser()
+
+    @raises(Exception)
+    def test_record_num_smaller_than_records(self):
+        self.record1 = igmpv3_report_group(
+            MODE_IS_INCLUDE, 0, 0, '225.0.0.1')
+        self.record2 = igmpv3_report_group(
+            MODE_IS_INCLUDE, 0, 2, '225.0.0.2',
+            ['172.16.10.10', '172.16.10.27'])
+        self.record3 = igmpv3_report_group(
+            MODE_IS_INCLUDE, 1, 0, '225.0.0.3', [], 'abc\x00')
+        self.record4 = igmpv3_report_group(
+            MODE_IS_INCLUDE, 1, 2, '225.0.0.4',
+            ['172.16.10.10', '172.16.10.27'], 'abc\x00')
+        self.records = [self.record1, self.record2, self.record3,
+                        self.record4]
+        self.record_num = len(self.records) - 1
+        self.buf = pack(igmpv3_report._PACK_STR, self.msgtype,
+                        self.csum, self.record_num)
+        self.buf += self.record1.serialize()
+        self.buf += self.record2.serialize()
+        self.buf += self.record3.serialize()
+        self.buf += self.record4.serialize()
+        self.g = igmpv3_report(
+            self.msgtype, self.csum, self.record_num, self.records)
+        self.test_parser()
+
+    def test_default_args(self):
+        prev = ipv4(proto=inet.IPPROTO_IGMP)
+        g = igmpv3_report()
+        prev.serialize(g, None)
+        buf = g.serialize(bytearray(), prev)
+        res = unpack_from(igmpv3_report._PACK_STR, str(buf))
+        buf = bytearray(buf)
+        pack_into('!H', buf, 2, 0)
+        buf = str(buf)
+
+        eq_(res[0], IGMP_TYPE_REPORT_V3)
+        eq_(res[1], checksum(buf))
+        eq_(res[2], 0)
+
+        # records without record_num
+        prev = ipv4(proto=inet.IPPROTO_IGMP)
+        record1 = igmpv3_report_group(
+            MODE_IS_INCLUDE, 0, 0, '225.0.0.1')
+        record2 = igmpv3_report_group(
+            MODE_IS_INCLUDE, 0, 2, '225.0.0.2',
+            ['172.16.10.10', '172.16.10.27'])
+        record3 = igmpv3_report_group(
+            MODE_IS_INCLUDE, 1, 0, '225.0.0.3', [], 'abc\x00')
+        record4 = igmpv3_report_group(
+            MODE_IS_INCLUDE, 1, 2, '225.0.0.4',
+            ['172.16.10.10', '172.16.10.27'], 'abc\x00')
+        records = [record1, record2, record3, record4]
+        g = igmpv3_report(records=records)
+        prev.serialize(g, None)
+        buf = g.serialize(bytearray(), prev)
+        res = unpack_from(igmpv3_report._PACK_STR, str(buf))
+        buf = bytearray(buf)
+        pack_into('!H', buf, 2, 0)
+        buf = str(buf)
+
+        eq_(res[0], IGMP_TYPE_REPORT_V3)
+        eq_(res[1], checksum(buf))
+        eq_(res[2], len(records))
+
+    def test_json(self):
+        jsondict = self.g.to_jsondict()
+        g = igmpv3_report.from_jsondict(jsondict['igmpv3_report'])
+        eq_(str(self.g), str(g))
+
+    def test_json_with_records(self):
+        self.setUp_with_records()
+        self.test_json()
+
+
+class Test_igmpv3_report_group(unittest.TestCase):
+    """Test case for Group Records of
+    Internet Group Management Protocol v3 Membership Report Message"""
+    def setUp(self):
+        self.type_ = MODE_IS_INCLUDE
+        self.aux_len = 0
+        self.num = 0
+        self.address = '225.0.0.1'
+        self.srcs = []
+        self.aux = None
+
+        self.buf = pack(igmpv3_report_group._PACK_STR, self.type_,
+                        self.aux_len, self.num,
+                        addrconv.ipv4.text_to_bin(self.address))
+
+        self.g = igmpv3_report_group(
+            self.type_, self.aux_len, self.num, self.address,
+            self.srcs, self.aux)
+
+    def setUp_with_srcs(self):
+        self.srcs = ['192.168.1.1', '192.168.1.2', '192.168.1.3']
+        self.num = len(self.srcs)
+        self.buf = pack(igmpv3_report_group._PACK_STR, self.type_,
+                        self.aux_len, self.num,
+                        addrconv.ipv4.text_to_bin(self.address))
+        for src in self.srcs:
+            self.buf += pack('4s', addrconv.ipv4.text_to_bin(src))
+        self.g = igmpv3_report_group(
+            self.type_, self.aux_len, self.num, self.address,
+            self.srcs, self.aux)
+
+    def setUp_with_aux(self):
+        self.aux = '\x01\x02\x03\x04\x05\x00\x00\x00'
+        self.aux_len = len(self.aux) / 4
+        self.buf = pack(igmpv3_report_group._PACK_STR, self.type_,
+                        self.aux_len, self.num,
+                        addrconv.ipv4.text_to_bin(self.address))
+        self.buf += self.aux
+        self.g = igmpv3_report_group(
+            self.type_, self.aux_len, self.num, self.address,
+            self.srcs, self.aux)
+
+    def setUp_with_srcs_and_aux(self):
+        self.srcs = ['192.168.1.1', '192.168.1.2', '192.168.1.3']
+        self.num = len(self.srcs)
+        self.aux = '\x01\x02\x03\x04\x05\x00\x00\x00'
+        self.aux_len = len(self.aux) / 4
+        self.buf = pack(igmpv3_report_group._PACK_STR, self.type_,
+                        self.aux_len, self.num,
+                        addrconv.ipv4.text_to_bin(self.address))
+        for src in self.srcs:
+            self.buf += pack('4s', addrconv.ipv4.text_to_bin(src))
+        self.buf += self.aux
+        self.g = igmpv3_report_group(
+            self.type_, self.aux_len, self.num, self.address,
+            self.srcs, self.aux)
+
+    def tearDown(self):
+        pass
+
+    def test_init(self):
+        eq_(self.type_, self.g.type_)
+        eq_(self.aux_len, self.g.aux_len)
+        eq_(self.num, self.g.num)
+        eq_(self.address, self.g.address)
+        eq_(self.srcs, self.g.srcs)
+        eq_(self.aux, self.g.aux)
+
+    def test_init_with_srcs(self):
+        self.setUp_with_srcs()
+        self.test_init()
+
+    def test_init_with_aux(self):
+        self.setUp_with_aux()
+        self.test_init()
+
+    def test_init_with_srcs_and_aux(self):
+        self.setUp_with_srcs_and_aux()
+        self.test_init()
+
+    def test_parser(self):
+        _res = self.g.parser(self.buf)
+        if type(_res) is tuple:
+            res = _res[0]
+        else:
+            res = _res
+
+        eq_(res.type_, self.type_)
+        eq_(res.aux_len, self.aux_len)
+        eq_(res.num, self.num)
+        eq_(res.address, self.address)
+        eq_(res.srcs, self.srcs)
+        eq_(res.aux, self.aux)
+
+    def test_parser_with_srcs(self):
+        self.setUp_with_srcs()
+        self.test_parser()
+
+    def test_parser_with_aux(self):
+        self.setUp_with_aux()
+        self.test_parser()
+
+    def test_parser_with_srcs_and_aux(self):
+        self.setUp_with_srcs_and_aux()
+        self.test_parser()
+
+    def test_serialize(self):
+        buf = self.g.serialize()
+        res = unpack_from(igmpv3_report_group._PACK_STR, buffer(buf))
+
+        eq_(res[0], self.type_)
+        eq_(res[1], self.aux_len)
+        eq_(res[2], self.num)
+        eq_(res[3], addrconv.ipv4.text_to_bin(self.address))
+
+    def test_serialize_with_srcs(self):
+        self.setUp_with_srcs()
+        buf = self.g.serialize()
+        res = unpack_from(igmpv3_report_group._PACK_STR, buffer(buf))
+        (src1, src2, src3) = unpack_from('4s4s4s', buffer(buf),
+                                         igmpv3_report_group._MIN_LEN)
+        eq_(res[0], self.type_)
+        eq_(res[1], self.aux_len)
+        eq_(res[2], self.num)
+        eq_(res[3], addrconv.ipv4.text_to_bin(self.address))
+        eq_(src1, addrconv.ipv4.text_to_bin(self.srcs[0]))
+        eq_(src2, addrconv.ipv4.text_to_bin(self.srcs[1]))
+        eq_(src3, addrconv.ipv4.text_to_bin(self.srcs[2]))
+
+    def test_serialize_with_aux(self):
+        self.setUp_with_aux()
+        buf = self.g.serialize()
+        res = unpack_from(igmpv3_report_group._PACK_STR, buffer(buf))
+        (aux, ) = unpack_from('%ds' % (self.aux_len * 4), buffer(buf),
+                              igmpv3_report_group._MIN_LEN)
+        eq_(res[0], self.type_)
+        eq_(res[1], self.aux_len)
+        eq_(res[2], self.num)
+        eq_(res[3], addrconv.ipv4.text_to_bin(self.address))
+        eq_(aux, self.aux)
+
+    def test_serialize_with_srcs_and_aux(self):
+        self.setUp_with_srcs_and_aux()
+        buf = self.g.serialize()
+        res = unpack_from(igmpv3_report_group._PACK_STR, buffer(buf))
+        (src1, src2, src3) = unpack_from('4s4s4s', buffer(buf),
+                                         igmpv3_report_group._MIN_LEN)
+        (aux, ) = unpack_from('%ds' % (self.aux_len * 4), buffer(buf),
+                              igmpv3_report_group._MIN_LEN + 12)
+        eq_(res[0], self.type_)
+        eq_(res[1], self.aux_len)
+        eq_(res[2], self.num)
+        eq_(res[3], addrconv.ipv4.text_to_bin(self.address))
+        eq_(src1, addrconv.ipv4.text_to_bin(self.srcs[0]))
+        eq_(src2, addrconv.ipv4.text_to_bin(self.srcs[1]))
+        eq_(src3, addrconv.ipv4.text_to_bin(self.srcs[2]))
+        eq_(aux, self.aux)
+
+    def test_to_string(self):
+        igmp_values = {'type_': repr(self.type_),
+                       'aux_len': repr(self.aux_len),
+                       'num': repr(self.num),
+                       'address': repr(self.address),
+                       'srcs': repr(self.srcs),
+                       'aux': repr(self.aux)}
+        _g_str = ','.join(['%s=%s' % (k, igmp_values[k])
+                           for k, v in inspect.getmembers(self.g)
+                           if k in igmp_values])
+        g_str = '%s(%s)' % (igmpv3_report_group.__name__, _g_str)
+
+        eq_(str(self.g), g_str)
+        eq_(repr(self.g), g_str)
+
+    def test_to_string_with_srcs(self):
+        self.setUp_with_srcs()
+        self.test_to_string()
+
+    def test_to_string_with_aux(self):
+        self.setUp_with_aux()
+        self.test_to_string()
+
+    def test_to_string_with_srcs_and_aux(self):
+        self.setUp_with_srcs_and_aux()
+        self.test_to_string()
+
+    def test_len(self):
+        eq_(len(self.g), 8)
+
+    def test_len_with_srcs(self):
+        self.setUp_with_srcs()
+        eq_(len(self.g), 20)
+
+    def test_len_with_aux(self):
+        self.setUp_with_aux()
+        eq_(len(self.g), 16)
+
+    def test_len_with_srcs_and_aux(self):
+        self.setUp_with_srcs_and_aux()
+        eq_(len(self.g), 28)
+
+    @raises
+    def test_num_larger_than_srcs(self):
+        self.srcs = ['192.168.1.1', '192.168.1.2', '192.168.1.3']
+        self.num = len(self.srcs) + 1
+        self.buf = pack(igmpv3_report_group._PACK_STR, self.type_,
+                        self.aux_len, self.num,
+                        addrconv.ipv4.text_to_bin(self.address))
+        for src in self.srcs:
+            self.buf += pack('4s', addrconv.ipv4.text_to_bin(src))
+        self.g = igmpv3_report_group(
+            self.type_, self.aux_len, self.num, self.address,
+            self.srcs, self.aux)
+        self.test_parser()
+
+    @raises
+    def test_num_smaller_than_srcs(self):
+        self.srcs = ['192.168.1.1', '192.168.1.2', '192.168.1.3']
+        self.num = len(self.srcs) - 1
+        self.buf = pack(igmpv3_report_group._PACK_STR, self.type_,
+                        self.aux_len, self.num,
+                        addrconv.ipv4.text_to_bin(self.address))
+        for src in self.srcs:
+            self.buf += pack('4s', addrconv.ipv4.text_to_bin(src))
+        self.g = igmpv3_report_group(
+            self.type_, self.aux_len, self.num, self.address,
+            self.srcs, self.aux)
+        self.test_parser()
+
+    @raises
+    def test_aux_len_larger_than_aux(self):
+        self.aux = '\x01\x02\x03\x04\x05\x00\x00\x00'
+        self.aux_len = len(self.aux) / 4 + 1
+        self.buf = pack(igmpv3_report_group._PACK_STR, self.type_,
+                        self.aux_len, self.num,
+                        addrconv.ipv4.text_to_bin(self.address))
+        self.buf += self.aux
+        self.g = igmpv3_report_group(
+            self.type_, self.aux_len, self.num, self.address,
+            self.srcs, self.aux)
+        self.test_parser()
+
+    @raises
+    def test_aux_len_smaller_than_aux(self):
+        self.aux = '\x01\x02\x03\x04\x05\x00\x00\x00'
+        self.aux_len = len(self.aux) / 4 - 1
+        self.buf = pack(igmpv3_report_group._PACK_STR, self.type_,
+                        self.aux_len, self.num,
+                        addrconv.ipv4.text_to_bin(self.address))
+        self.buf += self.aux
+        self.g = igmpv3_report_group(
+            self.type_, self.aux_len, self.num, self.address,
+            self.srcs, self.aux)
+        self.test_parser()
+
+    def test_default_args(self):
+        rep = igmpv3_report_group()
+        buf = rep.serialize()
+        res = unpack_from(igmpv3_report_group._PACK_STR, str(buf))
+
+        eq_(res[0], 0)
+        eq_(res[1], 0)
+        eq_(res[2], 0)
+        eq_(res[3], addrconv.ipv4.text_to_bin('0.0.0.0'))
+
+        # srcs without num
+        srcs = ['192.168.1.1', '192.168.1.2', '192.168.1.3']
+        rep = igmpv3_report_group(srcs=srcs)
+        buf = rep.serialize()
+        res = unpack_from(igmpv3_report_group._PACK_STR, str(buf))
+
+        eq_(res[0], 0)
+        eq_(res[1], 0)
+        eq_(res[2], len(srcs))
+        eq_(res[3], addrconv.ipv4.text_to_bin('0.0.0.0'))
+
+        res = unpack_from('4s4s4s', str(buf), igmpv3_report_group._MIN_LEN)
+
+        eq_(res[0], addrconv.ipv4.text_to_bin(srcs[0]))
+        eq_(res[1], addrconv.ipv4.text_to_bin(srcs[1]))
+        eq_(res[2], addrconv.ipv4.text_to_bin(srcs[2]))
+
+        # aux without aux_len
+        aux = 'abcde'
+        rep = igmpv3_report_group(aux=aux)
+        buf = rep.serialize()
+        res = unpack_from(igmpv3_report_group._PACK_STR, str(buf))
+
+        eq_(res[0], 0)
+        eq_(res[1], 2)
+        eq_(res[2], 0)
+        eq_(res[3], addrconv.ipv4.text_to_bin('0.0.0.0'))
+        eq_(buf[igmpv3_report_group._MIN_LEN:], 'abcde\x00\x00\x00')
-- 
1.7.10.4


------------------------------------------------------------------------------
Rapidly troubleshoot problems before they affect your business. Most IT 
organizations don't have a clear picture of how application performance 
affects their revenue. With AppDynamics, you get 100% visibility into your 
Java,.NET, & PHP application. Start your 15-day FREE TRIAL of AppDynamics Pro!
http://pubads.g.doubleclick.net/gampad/clk?id=84349831&iu=/4140/ostg.clktrk
_______________________________________________
Ryu-devel mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/ryu-devel

Reply via email to