/*
 * Copyright 2015 Open Networking Laboratory
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.onlab.packet;

import java.nio.ByteBuffer;
import static org.onlab.packet.PacketUtils.checkBufferLength;

public class IGMPMembership extends IGMPGroup {

    // TODO should be an enum
    public static final byte MODE_IS_INCLUDE = 0x1;
    public static final byte MODE_IS_EXCLUDE = 0x2;
    public static final byte CHANGE_TO_INCLUDE_MODE = 0x3;
    public static final byte CHANGE_TO_EXCLUDE_MODE = 0x4;
    public static final byte ALLOW_NEW_SOURCES = 0x5;
    public static final byte BLOCK_OLD_SOURCES = 0x6;

    private final int minGroupRecordLen = Ip4Address.BYTE_LENGTH + 4;

    protected byte recordType;
    protected byte auxDataLength = 0;
    protected byte[] auxData;

    /**
     * Constructor initialized with a multicast group address.
     *
     * @param gaddr A multicast group address.
     */
    public IGMPMembership(Ip4Address gaddr) {
        super(gaddr, 0);
    }

    /**
     * Default constructor.
     */
    public IGMPMembership() {
        super();
    }

    /**
     * Gets the IGMP record type.
     *
     * @return record type
     */
    public byte getRecordType() {
        return recordType;
    }

    /**
     * Serialize this Membership Report.
     *
     * @param bb the ByteBuffer to write into, positioned at the next spot to be written to.
     * @return serialized IGMP message.
     */
    @Override
    public byte[] serialize(ByteBuffer bb) {

        bb.put(recordType);
        bb.put(auxDataLength);      // reserved
        bb.putShort((short) sources.size());
        bb.put(gaddr.toOctets());
        for (IpAddress ipaddr : sources) {
            bb.put(ipaddr.toOctets());
        }

        if (auxDataLength > 0) {
            bb.put(auxData);
        }

        return bb.array();
    }

    /**
     * Deserialize the IGMP Membership report packet.
     *
     * @param bb the ByteBuffer wrapping the serialized message.  The position of the
     *           ByteBuffer should be pointing at the head of either message type.
     * @return IGMP Group
     * @throws DeserializationException if deserialization fails
     */
    public IGMPGroup deserialize(ByteBuffer bb) throws DeserializationException {

        // Make sure there is enough buffer to read the header,
        // including the number of sources
        checkBufferLength(bb.remaining(), 0, minGroupRecordLen);
        recordType = bb.get();
        auxDataLength = bb.get();
        int nsrcs = bb.getShort();

        gaddr = Ip4Address.valueOf(bb.getInt());

        // Make sure we have enough buffer to hold all of these sources
        checkBufferLength(bb.remaining(), 0, Ip4Address.BYTE_LENGTH * nsrcs);
        for (; nsrcs > 0; nsrcs--) {
            Ip4Address src = Ip4Address.valueOf(bb.getInt());
            this.sources.add(src);
        }

        if (auxDataLength > 0) {
            auxData = new byte[auxDataLength];
            bb.get(auxData, 0, auxDataLength);
        }
        return this;
    }

    /*
     * (non-Javadoc)
     *
     * @see java.lang.Object#equals()
     */
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (!(obj instanceof IGMPMembership)) {
            return false;
        }
        IGMPMembership other = (IGMPMembership) obj;

        if (!this.gaddr.equals(other.gaddr)) {
            return false;
        }
        if (this.recordType != other.recordType) {
            return false;
        }
        if (this.auxDataLength != other.auxDataLength) {
            return false;
        }
        if (this.sources.size() != other.sources.size()) {
            return false;
        }
        // TODO: make these tolerant of order
        if (!this.sources.equals(other.sources)) {
            return false;
        }

        return true;
    }

    /*
     * (non-Javadoc)
     *
     * @see java.lang.Object#hashCode()
     */
    @Override
    public int hashCode() {
        final int prime = 2521;
        int result = super.hashCode();
        result = prime * result + this.gaddr.hashCode();
        result = prime * result + this.recordType;
        result = prime * result + this.auxDataLength;
        result = prime * result + this.sources.hashCode();
        return result;
    }
}

@@ -3114,24 +3114,66 @@
 getInt());%0A%0A
+%0A        for (; nsrcs %3E 0; nsrcs--) %7B%0A    
         // M
@@ -3232,32 +3232,36 @@
 sources%0A        
+    
 checkBufferLengt
@@ -3318,45 +3318,8 @@
 s);%0A
-        for (; nsrcs %3E 0; nsrcs--) %7B%0A
     

fix igmp deserialization issue