blob: 938cd6e0b865a5758e56e22ed9868302d05ee8d8 [file] [log] [blame]
/*
* 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 java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.slf4j.Logger;
import static com.google.common.base.MoreObjects.toStringHelper;
import static com.google.common.base.Preconditions.checkNotNull;
import static org.onlab.packet.PacketUtils.checkInput;
import static org.onlab.util.SonarSuppressionConstants.SONAR_SWITCH_FALLTHROUGH;
import static org.slf4j.LoggerFactory.getLogger;
/**
* Implements IGMP control packet format.
*/
public class IGMP extends BasePacket {
private final Logger log = getLogger(getClass());
public static final byte TYPE_IGMPV3_MEMBERSHIP_QUERY = 0x11;
public static final byte TYPE_IGMPV1_MEMBERSHIP_REPORT = 0x12;
public static final byte TYPE_IGMPV2_MEMBERSHIP_REPORT = 0x16;
public static final byte TYPE_IGMPV2_LEAVE_GROUP = 0x17;
public static final byte TYPE_IGMPV3_MEMBERSHIP_REPORT = 0x22;
public static final Map<Byte, Deserializer<? extends IPacket>> PROTOCOL_DESERIALIZER_MAP = new HashMap<>();
public static final int MINIMUM_HEADER_LEN = 12;
List<IGMPGroup> groups = new ArrayList<>();
// Fields contained in the IGMP header
private byte igmpType;
private byte resField = 0;
private short checksum = 0;
private byte[] unsupportTypeData;
public IGMP() {
}
/**
* Get the IGMP message type.
*
* @return the IGMP message type
*/
public byte getIgmpType() {
return igmpType;
}
/**
* Set the IGMP message type.
*
* @param msgType IGMP message type
*/
public void setIgmpType(byte msgType) {
igmpType = msgType;
}
/**
* Get the checksum of this message.
*
* @return the checksum
*/
public short getChecksum() {
return checksum;
}
/**
* get the Max Resp Code.
*
* @return The Maximum Time allowed before before sending a responding report.
*/
public byte getMaxRespField() {
return resField;
}
/**
* Set the Max Resp Code.
*
* @param respCode the Maximum Response Code.
*/
public void setMaxRespCode(byte respCode) {
if (igmpType != IGMP.TYPE_IGMPV3_MEMBERSHIP_QUERY) {
log.debug("Requesting the max response code for an incorrect field: ");
}
this.resField = respCode;
}
/**
* Get the list of IGMPGroups. The group objects will be either IGMPQuery or IGMPMembership
* depending on the IGMP message type. For IGMP Query, the groups list should only be
* one group.
*
* @return The list of IGMP groups.
*/
public List<IGMPGroup> getGroups() {
return groups;
}
/**
* Add a multicast group to this IGMP message.
*
* @param group the IGMPGroup will be IGMPQuery or IGMPMembership depending on the message type.
* @return true if group was valid and added, false otherwise.
*/
public boolean addGroup(IGMPGroup group) {
checkNotNull(group);
switch (this.igmpType) {
case TYPE_IGMPV3_MEMBERSHIP_QUERY:
if (group instanceof IGMPMembership) {
return false;
}
if (group.sources.size() > 1) {
return false;
}
break;
case TYPE_IGMPV3_MEMBERSHIP_REPORT:
if (group instanceof IGMPMembership) {
return false;
}
break;
default:
log.debug("Warning no IGMP message type has been set");
}
this.groups.add(group);
return true;
}
/**
* Serialize this IGMP packet. This will take care
* of serializing IGMPv3 Queries and IGMPv3 Membership
* Reports.
*
* @return the serialized IGMP message
*/
@java.lang.SuppressWarnings(SONAR_SWITCH_FALLTHROUGH) // suppress switch fall through warning
@Override
public byte[] serialize() {
byte [] data = new byte[8915];
ByteBuffer bb = ByteBuffer.wrap(data);
bb.put(this.getIgmpType());
// reserved or max resp code depending on type.
bb.put(this.resField);
// Must calculate checksum
bb.putShort((short) 0);
switch (this.igmpType) {
case IGMP.TYPE_IGMPV3_MEMBERSHIP_REPORT:
// reserved
bb.putShort((short) 0);
// Number of groups
bb.putShort((short) groups.size());
// Fall through
case IGMP.TYPE_IGMPV3_MEMBERSHIP_QUERY:
for (IGMPGroup grp : groups) {
grp.serialize(bb);
}
break;
default:
bb.put(this.unsupportTypeData);
break;
}
int size = bb.position();
bb.position(0);
byte [] rdata = new byte[size];
bb.get(rdata, 0, size);
return rdata;
}
/**
* Deserialize an IGMP message.
*
* @param data bytes to deserialize
* @param offset offset to start deserializing from
* @param length length of the data to deserialize
* @return populated IGMP object
*/
@Override
public IPacket deserialize(final byte[] data, final int offset,
final int length) {
IGMP igmp = new IGMP();
try {
igmp = IGMP.deserializer().deserialize(data, offset, length);
} catch (DeserializationException e) {
log.error(e.getStackTrace().toString());
return this;
}
this.igmpType = igmp.igmpType;
this.resField = igmp.resField;
this.checksum = igmp.checksum;
this.groups = igmp.groups;
return this;
}
/**
* Deserializer function for IPv4 packets.
*
* @return deserializer function
*/
public static Deserializer<IGMP> deserializer() {
return (data, offset, length) -> {
checkInput(data, offset, length, MINIMUM_HEADER_LEN);
IGMP igmp = new IGMP();
final ByteBuffer bb = ByteBuffer.wrap(data, offset, length);
igmp.igmpType = bb.get();
igmp.resField = bb.get();
igmp.checksum = bb.getShort();
int len = MINIMUM_HEADER_LEN;
String msg;
switch (igmp.igmpType) {
case TYPE_IGMPV3_MEMBERSHIP_QUERY:
IGMPQuery qgroup = new IGMPQuery();
qgroup.deserialize(bb);
igmp.groups.add(qgroup);
break;
case TYPE_IGMPV3_MEMBERSHIP_REPORT:
bb.getShort(); // Ignore resvd
int ngrps = bb.getShort();
for (; ngrps > 0; ngrps--) {
IGMPMembership mgroup = new IGMPMembership();
mgroup.deserialize(bb);
igmp.groups.add(mgroup);
}
break;
/*
* NOTE: according to the IGMPv3 spec. These previous IGMP type fields
* must be supported. At this time we are going to <b>assume</b> we run
* in a modern network where all devices are IGMPv3 capable.
*/
case TYPE_IGMPV1_MEMBERSHIP_REPORT:
case TYPE_IGMPV2_MEMBERSHIP_REPORT:
case TYPE_IGMPV2_LEAVE_GROUP:
igmp.unsupportTypeData = bb.array(); // Is this the entire array?
msg = "IGMP message type: " + igmp.igmpType + " is not supported";
igmp.log.debug(msg);
break;
default:
msg = "IGMP message type: " + igmp.igmpType + " is not recognized";
igmp.unsupportTypeData = bb.array();
igmp.log.debug(msg);
break;
}
return igmp;
};
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(final Object obj) {
if (this == obj) {
return true;
}
if (!super.equals(obj)) {
return false;
}
if (!(obj instanceof IGMP)) {
return false;
}
final IGMP other = (IGMP) obj;
if (this.igmpType != other.igmpType) {
return false;
}
if (this.resField != other.resField) {
return false;
}
if (this.checksum != other.checksum) {
return false;
}
if (this.groups.size() != other.groups.size()) {
return false;
}
// TODO: equals should be true regardless of order.
if (!groups.equals(other.groups)) {
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.igmpType;
result = prime * result + this.groups.size();
result = prime * result + this.resField;
result = prime * result + this.checksum;
result = prime * result + this.groups.hashCode();
return result;
}
@Override
public String toString() {
return toStringHelper(getClass())
.add("igmpType", Byte.toString(igmpType))
.add("resField", Byte.toString(resField))
.add("checksum", Short.toString(checksum))
.add("unsupportTypeData", Arrays.toString(unsupportTypeData))
.toString();
// TODO: need to handle groups
}
}