blob: c2b32a46cd4b7e215402d82b77c7b097abbe1b61 [file] [log] [blame]
/*
* Copyright 2017-present Open Networking Foundation
*
* 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.opencord.igmpproxy;
import org.onlab.packet.Ethernet;
import org.onlab.packet.Ip4Address;
import org.onosproject.net.DeviceId;
import org.onosproject.net.PortNumber;
import java.util.Random;
import java.util.concurrent.atomic.AtomicInteger;
/**
* State machine for single IGMP group member. The state machine is implemented on
* RFC 2236 "6. Host State Diagram".
*/
public class SingleStateMachine {
// Only for tests purposes
static boolean sendQuery = true;
static final int STATE_NON = 0;
static final int STATE_DELAY = 1;
static final int STATE_IDLE = 2;
static final int TRANSITION_JOIN = 0;
static final int TRANSITION_LEAVE = 1;
static final int TRANSITION_QUERY = 2;
static final int TRANSITION_TIMEOUT = 3;
static final int DEFAULT_MAX_RESP = 0xfffffff;
static final int DEFAULT_COUNT = 1;
private DeviceId devId;
private Ip4Address groupIp;
private Ip4Address srcIp;
private PortNumber upLinkPort;
private AtomicInteger count = new AtomicInteger(DEFAULT_COUNT);
private int timerId = IgmpTimer.INVALID_TIMER_ID;
private int timeOut = DEFAULT_MAX_RESP;
private State[] states =
{
new NonMember(), new DelayMember(), new IdleMember()
};
private int[] nonTransition =
{STATE_DELAY, STATE_NON, STATE_NON, STATE_NON};
private int[] delayTransition =
{STATE_DELAY, STATE_NON, STATE_DELAY, STATE_IDLE};
private int[] idleTransition =
{STATE_IDLE, STATE_NON, STATE_DELAY, STATE_IDLE};
//THE TRANSITION TABLE
private int[][] transition =
{nonTransition, delayTransition, idleTransition};
private int currentState = STATE_NON;
public SingleStateMachine(DeviceId devId, Ip4Address groupIp, Ip4Address src, PortNumber upLinkPort) {
this.devId = devId;
this.groupIp = groupIp;
this.srcIp = src;
this.upLinkPort = upLinkPort;
}
public Ip4Address getGroupIp() {
return groupIp;
}
public DeviceId getDeviceId() {
return devId;
}
public boolean increaseCounter() {
count.incrementAndGet();
return true;
}
public boolean decreaseCounter() {
if (count.get() > 0) {
count.decrementAndGet();
return true;
} else {
return false;
}
}
public int getCounter() {
return count.get();
}
public int currentState() {
return currentState;
}
private void next(int msg) {
currentState = transition[currentState][msg];
}
public void join(boolean messageOutAllowed) {
states[currentState].join(messageOutAllowed);
next(TRANSITION_JOIN);
}
public void leave(boolean messageOutAllowed) {
states[currentState].leave(messageOutAllowed);
next(TRANSITION_LEAVE);
}
public void query(int maxResp) {
states[currentState].query(maxResp);
next(TRANSITION_QUERY);
}
public void timeOut() {
states[currentState].timeOut();
next(TRANSITION_TIMEOUT);
}
int getTimeOut(int maxTimeOut) {
Random random = new Random();
return Math.abs(random.nextInt()) % maxTimeOut;
}
protected void cancelTimer() {
if (IgmpTimer.INVALID_TIMER_ID != timerId) {
IgmpTimer.cancel(timerId);
}
}
class State {
public void join(boolean messageOutAllowed) {
}
public void leave(boolean messageOutAllowed) {
if (messageOutAllowed) {
Ethernet eth = IgmpSender.getInstance().buildIgmpV3Leave(groupIp, srcIp);
IgmpSender.getInstance().sendIgmpPacketUplink(eth, devId, upLinkPort);
}
}
public void query(int maxResp) {
}
public void timeOut() {
}
}
class NonMember extends State {
public void join(boolean messageOutAllowed) {
if (messageOutAllowed) {
Ethernet eth = IgmpSender.getInstance().buildIgmpV3Join(groupIp, srcIp);
IgmpSender.getInstance().sendIgmpPacketUplink(eth, devId, upLinkPort);
timeOut = getTimeOut(IgmpManager.getUnsolicitedTimeout());
timerId = IgmpTimer.start(SingleStateMachine.this, timeOut);
}
}
}
class DelayMember extends State {
public void query(int maxResp) {
if (maxResp < timeOut) {
timeOut = getTimeOut(maxResp);
timerId = IgmpTimer.reset(timerId, SingleStateMachine.this, timeOut);
}
}
public void timeOut() {
if (sendQuery) {
Ethernet eth = IgmpSender.getInstance().buildIgmpV3ResponseQuery(groupIp, srcIp);
IgmpSender.getInstance().sendIgmpPacketUplink(eth, devId, upLinkPort);
timeOut = DEFAULT_MAX_RESP;
}
}
}
class IdleMember extends State {
public void query(int maxResp) {
timeOut = getTimeOut(maxResp);
timerId = IgmpTimer.start(SingleStateMachine.this, timeOut);
}
}
}