Fix: segment TCP packet when the payload is larger than MTU size

Change-Id: I241fceac51e6511b394e16ab2aefdf69fa97eb8c
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackMetadataProxyHandler.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackMetadataProxyHandler.java
index dbbc26b..7567364 100644
--- a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackMetadataProxyHandler.java
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackMetadataProxyHandler.java
@@ -17,6 +17,7 @@
 package org.onosproject.openstacknetworking.impl;
 
 import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Lists;
 import org.apache.commons.lang.StringUtils;
 import org.apache.http.Header;
 import org.apache.http.HttpEntityEnclosingRequest;
@@ -64,6 +65,7 @@
 import org.onosproject.openstacknode.api.OpenstackNodeEvent;
 import org.onosproject.openstacknode.api.OpenstackNodeListener;
 import org.onosproject.openstacknode.api.OpenstackNodeService;
+import org.openstack4j.model.network.Network;
 import org.openstack4j.model.network.Port;
 import org.osgi.service.component.annotations.Activate;
 import org.osgi.service.component.annotations.Component;
@@ -75,6 +77,7 @@
 import java.io.IOException;
 import java.nio.ByteBuffer;
 import java.util.Arrays;
+import java.util.List;
 import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.ExecutorService;
@@ -109,12 +112,16 @@
     private static final short ACK_FLAG = (short) 0x10;
     private static final short SYN_ACK_FLAG = (short) 0x12;
     private static final short FIN_ACK_FLAG = (short) 0x11;
+    private static final short FIN_ACK_PUSH_FLAG = (short) 0x19;
     private static final byte DATA_OFFSET = (byte) 0x5;
     private static final short URGENT_POINTER = (short) 0x1;
     private static final byte PACKET_TTL = (byte) 127;
     private static final String HTTP_PREFIX = "http://";
     private static final String COLON = ":";
 
+    private static final int IP_HEADER_SIZE = 20;
+    private static final int TCP_HEADER_SIZE = 20;
+
     private static final String INSTANCE_ID_HEADER = "X-Instance-ID";
     private static final String INSTANCE_ID_SIGNATURE_HEADER = "X-Instance-ID-Signature";
     private static final String TENANT_ID_HEADER = "X-Tenant-ID";
@@ -267,13 +274,13 @@
             response.setEntity(proxyResponse.getEntity());
             response.setHeaders(proxyResponse.getAllHeaders());
 
-            Http httpResponse = new Http();
-            httpResponse.setType(RESPONSE);
-            httpResponse.setMessage(response);
+            Network osNetwork = osNetworkService.network(instPort.networkId());
+            int tcpPayloadSize = osNetwork.getMTU() - IP_HEADER_SIZE - TCP_HEADER_SIZE;
 
-            TCP tcpReply = buildTcpDataPacket(tcpPacket, byteData.length, response);
-            Ethernet ethReply = buildEthFrame(ethPacket, ipv4Packet, tcpReply);
-            sendReply(context, ethReply);
+            List<TCP> tcpReplies = buildTcpDataPackets(tcpPacket,
+                    byteData.length, response, tcpPayloadSize);
+            List<Ethernet> ethReplies = buildEthFrames(ethPacket, ipv4Packet, tcpReplies);
+            ethReplies.forEach(e -> sendReply(context, e));
 
             try {
                 proxyResponse.close();
@@ -363,25 +370,70 @@
          * @param response      HTTP response
          * @return a TCP data packet
          */
-        private TCP buildTcpDataPacket(TCP tcpRequest, int requestLength,
-                                       HttpResponse response) {
-            TCP tcpReply = new TCP();
-            tcpReply.setSourcePort(tcpRequest.getDestinationPort());
-            tcpReply.setDestinationPort(tcpRequest.getSourcePort());
-            tcpReply.setSequence(tcpRequest.getAcknowledge());
-            tcpReply.setAcknowledge(tcpRequest.getSequence() + requestLength);
-            tcpReply.setDataOffset(DATA_OFFSET);        // no options
-            tcpReply.setFlags(ACK_FLAG);
-            tcpReply.setWindowSize(WINDOW_SIZE);
-            tcpReply.setUrgentPointer(URGENT_POINTER);
+        private List<TCP> buildTcpDataPackets(TCP tcpRequest, int requestLength,
+                                              HttpResponse response, int payloadSize) {
+            List<TCP> tcpReplies = Lists.newArrayList();
 
             Http httpResponse = new Http();
             httpResponse.setType(RESPONSE);
             httpResponse.setMessage(response);
 
-            tcpReply.setPayload(httpResponse);
+            byte[] httpBytes = httpResponse.serialize();
 
-            return tcpReply;
+            int numOfSegments = (int) Math.ceil((double) httpBytes.length / payloadSize);
+
+            if (numOfSegments == 1) {
+                TCP tcpReply = new TCP();
+                tcpReply.setSourcePort(tcpRequest.getDestinationPort());
+                tcpReply.setDestinationPort(tcpRequest.getSourcePort());
+                tcpReply.setSequence(tcpRequest.getAcknowledge());
+                tcpReply.setAcknowledge(tcpRequest.getSequence() + requestLength);
+                tcpReply.setDataOffset(DATA_OFFSET);        // no options, 20 bytes
+                tcpReply.setFlags(ACK_FLAG);
+                tcpReply.setWindowSize(WINDOW_SIZE);
+                tcpReply.setUrgentPointer(URGENT_POINTER);
+
+                Data data = new Data(httpBytes);
+                tcpReply.setPayload(data);
+
+                tcpReplies.add(tcpReply);
+            }
+
+            if (numOfSegments > 1) {
+
+                for (int i = 0; i < numOfSegments; i++) {
+
+                    int byteStartIndex = i * payloadSize;
+                    int byteEndIndex;
+
+                    TCP tcpReply = new TCP();
+                    tcpReply.setSourcePort(tcpRequest.getDestinationPort());
+                    tcpReply.setDestinationPort(tcpRequest.getSourcePort());
+                    tcpReply.setSequence(tcpRequest.getAcknowledge() + byteStartIndex);
+                    tcpReply.setAcknowledge(tcpRequest.getSequence() + requestLength);
+                    tcpReply.setDataOffset(DATA_OFFSET);        // no options, 20 bytes
+                    tcpReply.setWindowSize(WINDOW_SIZE);
+                    tcpReply.setUrgentPointer(URGENT_POINTER);
+
+                    if (i == numOfSegments - 1) {
+                        tcpReply.setFlags(FIN_ACK_PUSH_FLAG);
+                        byteEndIndex = httpBytes.length;
+                    } else {
+                        tcpReply.setFlags(ACK_FLAG);
+                        byteEndIndex = (i + 1) * payloadSize;
+                    }
+
+                    byte[] httpSegmentBytes = Arrays.copyOfRange(httpBytes,
+                            byteStartIndex, byteEndIndex);
+
+                    Data data = new Data(httpSegmentBytes);
+                    tcpReply.setPayload(data);
+
+                    tcpReplies.add(tcpReply);
+                }
+            }
+
+            return tcpReplies;
         }
 
         /**
@@ -411,6 +463,43 @@
         }
 
         /**
+         * Builds a set of ethernet frames with the given IPv4 and TCP payload.
+         *
+         * @param ethRequest    ethernet request frame
+         * @param ipv4Request   IPv4 request
+         * @param tcpReplies      TCP replies
+         * @return a set of ethernet frames
+         */
+        private List<Ethernet> buildEthFrames(Ethernet ethRequest,
+                                              IPv4 ipv4Request,
+                                              List<TCP> tcpReplies) {
+
+            List<Ethernet> ethReplies = Lists.newArrayList();
+
+            for (TCP tcpReply : tcpReplies) {
+
+                Ethernet ethReply = new Ethernet();
+                ethReply.setSourceMACAddress(ethRequest.getDestinationMAC());
+                ethReply.setDestinationMACAddress(ethRequest.getSourceMAC());
+                ethReply.setEtherType(ethRequest.getEtherType());
+
+                IPv4 ipv4Reply = new IPv4();
+                ipv4Reply.setSourceAddress(ipv4Request.getDestinationAddress());
+                ipv4Reply.setDestinationAddress(ipv4Request.getSourceAddress());
+                ipv4Reply.setTtl(PACKET_TTL);
+
+                ipv4Reply.setProtocol(IPv4.PROTOCOL_TCP);
+                ipv4Reply.setPayload(tcpReply);
+
+                ethReply.setPayload(ipv4Reply);
+
+                ethReplies.add(ethReply);
+            }
+
+            return ethReplies;
+        }
+
+        /**
          * Obtains the metadata path.
          *
          * @param uri metadata request URI