igmp proxy app

Change-Id: If053dc39b611a6176b1d59004a480d65bfac9091
diff --git a/src/main/java/org/opencord/igmpproxy/SingleStateMachine.java b/src/main/java/org/opencord/igmpproxy/SingleStateMachine.java
new file mode 100644
index 0000000..ceaac52
--- /dev/null
+++ b/src/main/java/org/opencord/igmpproxy/SingleStateMachine.java
@@ -0,0 +1,159 @@
+package org.opencord.igmpproxy;
+
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.Ip4Address;
+import org.onosproject.net.DeviceId;
+
+import java.util.Random;
+
+/**
+ * State machine for single IGMP group member. The state machine is implemented on 
+ * RFC 2236 "6. Host State Diagram".
+ */
+public class SingleStateMachine {
+    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 int count = 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) {
+        this.devId = devId;
+        this.groupIp = groupIp;
+        this.srcIp = src;
+    }
+
+
+    public DeviceId getDeviceId() {
+        return devId;
+    }
+    public boolean increaseCounter() {
+        count++;
+        return true;
+    }
+
+    public boolean decreaseCounter() {
+        if (count > 0) {
+            count--;
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    public int getCounter() {
+        return count;
+    }
+    public int currentState() {
+        return currentState;
+    }
+
+    private void next(int msg) {
+        currentState = transition[currentState][msg];
+    }
+
+    public void join() {
+        states[currentState].join();
+        next(TRANSITION_JOIN);
+    }
+
+    public void leave() {
+        states[currentState].leave();
+        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() {
+        }
+
+        public void leave() {
+            Ethernet eth = IgmpSender.getInstance().buildIgmpV3Leave(groupIp, srcIp);
+            IgmpSender.getInstance().sendIgmpPacketUplink(eth, devId);
+        }
+
+        public void query(int maxResp) {
+        }
+
+        public void timeOut() {
+        }
+
+    }
+
+    class NonMember extends State {
+        public void join() {
+            Ethernet eth = IgmpSender.getInstance().buildIgmpV3Join(groupIp, srcIp);
+            IgmpSender.getInstance().sendIgmpPacketUplink(eth, devId);
+            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() {
+            Ethernet eth = IgmpSender.getInstance().buildIgmpV3ResponseQuery(groupIp, srcIp);
+            IgmpSender.getInstance().sendIgmpPacketUplink(eth, devId);
+            timeOut = DEFAULT_MAX_RESP;
+        }
+
+    }
+
+    class IdleMember extends State {
+        public void query(int maxResp) {
+            timeOut = getTimeOut(maxResp);
+            timerId = IgmpTimer.start(SingleStateMachine.this, timeOut);
+        }
+    }
+}