diff options
author | Ashlee Young <ashlee@onosfw.com> | 2015-09-09 22:15:21 -0700 |
---|---|---|
committer | Ashlee Young <ashlee@onosfw.com> | 2015-09-09 22:15:21 -0700 |
commit | 13d05bc8458758ee39cb829098241e89616717ee (patch) | |
tree | 22a4d1ce65f15952f07a3df5af4b462b4697cb3a /framework/src/onos/utils/misc | |
parent | 6139282e1e93c2322076de4b91b1c85d0bc4a8b3 (diff) |
ONOS checkin based on commit tag e796610b1f721d02f9b0e213cf6f7790c10ecd60
Change-Id: Ife8810491034fe7becdba75dda20de4267bd15cd
Diffstat (limited to 'framework/src/onos/utils/misc')
186 files changed, 30368 insertions, 0 deletions
diff --git a/framework/src/onos/utils/misc/pom.xml b/framework/src/onos/utils/misc/pom.xml new file mode 100644 index 00000000..17951215 --- /dev/null +++ b/framework/src/onos/utils/misc/pom.xml @@ -0,0 +1,87 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright 2014 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. + --> +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>org.onosproject</groupId> + <artifactId>onlab-utils</artifactId> + <version>1.3.0-SNAPSHOT</version> + <relativePath>../pom.xml</relativePath> + </parent> + + <artifactId>onlab-misc</artifactId> + <packaging>bundle</packaging> + + <description>Miscellaneous ON.Lab utilities</description> + + <dependencies> + <dependency> + <groupId>com.google.guava</groupId> + <artifactId>guava-testlib</artifactId> + </dependency> + <dependency> + <groupId>org.onosproject</groupId> + <artifactId>onlab-junit</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.easymock</groupId> + <artifactId>easymock</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>io.netty</groupId> + <artifactId>netty</artifactId> + </dependency> + <dependency> + <groupId>commons-lang</groupId> + <artifactId>commons-lang</artifactId> + </dependency> + <dependency> + <groupId>org.apache.commons</groupId> + <artifactId>commons-lang3</artifactId> + </dependency> + + <!-- TODO: do we still need this here? --> + <dependency> + <groupId>com.eclipsesource.minimal-json</groupId> + <artifactId>minimal-json</artifactId> + </dependency> + <dependency> + <groupId>com.esotericsoftware</groupId> + <artifactId>kryo</artifactId> + </dependency> + <dependency> + <groupId>io.dropwizard.metrics</groupId> + <artifactId>metrics-core</artifactId> + <version>3.1.0</version> + </dependency> + <dependency> + <groupId>io.dropwizard.metrics</groupId> + <artifactId>metrics-json</artifactId> + <version>3.1.0</version> + </dependency> + <dependency> + <groupId>org.apache.felix</groupId> + <artifactId>org.apache.felix.scr.annotations</artifactId> + </dependency> + </dependencies> + +</project> diff --git a/framework/src/onos/utils/misc/src/main/java/org/onlab/graph/AbstractEdge.java b/framework/src/onos/utils/misc/src/main/java/org/onlab/graph/AbstractEdge.java new file mode 100644 index 00000000..3e8960e6 --- /dev/null +++ b/framework/src/onos/utils/misc/src/main/java/org/onlab/graph/AbstractEdge.java @@ -0,0 +1,76 @@ +/* + * Copyright 2014 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.graph; + +import java.util.Objects; + +import static com.google.common.base.MoreObjects.toStringHelper; +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Abstract graph edge implementation. + */ +public abstract class AbstractEdge<V extends Vertex> implements Edge<V> { + + private final V src; + private final V dst; + + /** + * Creates a new edge between the specified source and destination vertexes. + * + * @param src source vertex + * @param dst destination vertex + */ + public AbstractEdge(V src, V dst) { + this.src = checkNotNull(src, "Source vertex cannot be null"); + this.dst = checkNotNull(dst, "Destination vertex cannot be null"); + } + + @Override + public V src() { + return src; + } + + @Override + public V dst() { + return dst; + } + + @Override + public int hashCode() { + return Objects.hash(src, dst); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj instanceof AbstractEdge) { + final AbstractEdge other = (AbstractEdge) obj; + return Objects.equals(this.src, other.src) && Objects.equals(this.dst, other.dst); + } + return false; + } + + @Override + public String toString() { + return toStringHelper(this) + .add("src", src) + .add("dst", dst) + .toString(); + } +} diff --git a/framework/src/onos/utils/misc/src/main/java/org/onlab/graph/AbstractGraphPathSearch.java b/framework/src/onos/utils/misc/src/main/java/org/onlab/graph/AbstractGraphPathSearch.java new file mode 100644 index 00000000..e7e2c40d --- /dev/null +++ b/framework/src/onos/utils/misc/src/main/java/org/onlab/graph/AbstractGraphPathSearch.java @@ -0,0 +1,316 @@ +/* + * Copyright 2014-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.graph; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Basis for various graph path search algorithm implementations. + * + * @param <V> vertex type + * @param <E> edge type + */ +public abstract class AbstractGraphPathSearch<V extends Vertex, E extends Edge<V>> + implements GraphPathSearch<V, E> { + + private double samenessThreshold = Double.MIN_VALUE; + + /** + * Sets a new sameness threshold for comparing cost values; default is + * is {@link Double#MIN_VALUE}. + * + * @param threshold fractional double value + */ + public void setSamenessThreshold(double threshold) { + samenessThreshold = threshold; + } + + /** + * Returns the current sameness threshold for comparing cost values. + * + * @return current threshold + */ + public double samenessThreshold() { + return samenessThreshold; + } + + /** + * Default path search result that uses the DefaultPath to convey paths + * in a graph. + */ + protected class DefaultResult implements Result<V, E> { + + private final V src; + private final V dst; + protected final Set<Path<V, E>> paths = new HashSet<>(); + protected final Map<V, Double> costs = new HashMap<>(); + protected final Map<V, Set<E>> parents = new HashMap<>(); + protected final int maxPaths; + + /** + * Creates the result of a single-path search. + * + * @param src path source + * @param dst optional path destination + */ + public DefaultResult(V src, V dst) { + this(src, dst, 1); + } + + /** + * Creates the result of path search. + * + * @param src path source + * @param dst optional path destination + * @param maxPaths optional limit of number of paths; + * {@link GraphPathSearch#ALL_PATHS} if no limit + */ + public DefaultResult(V src, V dst, int maxPaths) { + checkNotNull(src, "Source cannot be null"); + this.src = src; + this.dst = dst; + this.maxPaths = maxPaths; + } + + @Override + public V src() { + return src; + } + + @Override + public V dst() { + return dst; + } + + @Override + public Set<Path<V, E>> paths() { + return paths; + } + + @Override + public Map<V, Double> costs() { + return costs; + } + + @Override + public Map<V, Set<E>> parents() { + return parents; + } + + /** + * Indicates whether or not the given vertex has a cost yet. + * + * @param v vertex to test + * @return true if the vertex has cost already + */ + boolean hasCost(V v) { + return costs.get(v) != null; + } + + /** + * Returns the current cost to reach the specified vertex. + * + * @param v vertex to reach + * @return cost to reach the vertex + */ + double cost(V v) { + Double c = costs.get(v); + return c == null ? Double.MAX_VALUE : c; + } + + /** + * Updates the cost of the vertex using its existing cost plus the + * cost to traverse the specified edge. If the search is in single + * path mode, only one path will be accrued. + * + * @param vertex vertex to update + * @param edge edge through which vertex is reached + * @param cost current cost to reach the vertex from the source + * @param replace true to indicate that any accrued edges are to be + * cleared; false to indicate that the edge should be + * added to the previously accrued edges as they yield + * the same cost + */ + void updateVertex(V vertex, E edge, double cost, boolean replace) { + costs.put(vertex, cost); + if (edge != null) { + Set<E> edges = parents.get(vertex); + if (edges == null) { + edges = new HashSet<>(); + parents.put(vertex, edges); + } + if (replace) { + edges.clear(); + } + if (maxPaths == ALL_PATHS || edges.size() < maxPaths) { + edges.add(edge); + } + } + } + + /** + * Removes the set of parent edges for the specified vertex. + * + * @param v vertex + */ + void removeVertex(V v) { + parents.remove(v); + } + + /** + * If possible, relax the specified edge using the supplied base cost + * and edge-weight function. + * + * @param edge edge to be relaxed + * @param cost base cost to reach the edge destination vertex + * @param ew optional edge weight function + * @param forbidNegatives if true negative values will forbid the link + * @return true if the edge was relaxed; false otherwise + */ + boolean relaxEdge(E edge, double cost, EdgeWeight<V, E> ew, + boolean... forbidNegatives) { + V v = edge.dst(); + double oldCost = cost(v); + double hopCost = ew == null ? 1.0 : ew.weight(edge); + if (hopCost < 0 && forbidNegatives.length == 1 && forbidNegatives[0]) { + return false; + } + + double newCost = cost + hopCost; + boolean relaxed = newCost < oldCost; + boolean same = Math.abs(newCost - oldCost) <= samenessThreshold; + if (same || relaxed) { + updateVertex(v, edge, newCost, !same); + } + return relaxed; + } + + /** + * Builds a set of paths for the specified src/dst vertex pair. + */ + protected void buildPaths() { + Set<V> destinations = new HashSet<>(); + if (dst == null) { + destinations.addAll(costs.keySet()); + } else { + destinations.add(dst); + } + + // Build all paths between the source and all requested destinations. + for (V v : destinations) { + // Ignore the source, if it is among the destinations. + if (!v.equals(src)) { + buildAllPaths(this, src, v, maxPaths); + } + } + } + + } + + /** + * Builds a set of all paths between the source and destination using the + * graph search result by applying breadth-first search through the parent + * edges and vertex costs. + * + * @param result graph search result + * @param src source vertex + * @param dst destination vertex + * @param maxPaths limit on the number of paths built; + * {@link GraphPathSearch#ALL_PATHS} if no limit + */ + private void buildAllPaths(DefaultResult result, V src, V dst, int maxPaths) { + DefaultMutablePath<V, E> basePath = new DefaultMutablePath<>(); + basePath.setCost(result.cost(dst)); + + Set<DefaultMutablePath<V, E>> pendingPaths = new HashSet<>(); + pendingPaths.add(basePath); + + while (!pendingPaths.isEmpty() && + (maxPaths == ALL_PATHS || result.paths.size() < maxPaths)) { + Set<DefaultMutablePath<V, E>> frontier = new HashSet<>(); + + for (DefaultMutablePath<V, E> path : pendingPaths) { + // For each pending path, locate its first vertex since we + // will be moving backwards from it. + V firstVertex = firstVertex(path, dst); + + // If the first vertex is our expected source, we have reached + // the beginning, so add the this path to the result paths. + if (firstVertex.equals(src)) { + path.setCost(result.cost(dst)); + result.paths.add(new DefaultPath<>(path.edges(), path.cost())); + + } else { + // If we have not reached the beginning, i.e. the source, + // fetch the set of edges leading to the first vertex of + // this pending path; if there are none, abandon processing + // this path for good. + Set<E> firstVertexParents = result.parents.get(firstVertex); + if (firstVertexParents == null || firstVertexParents.isEmpty()) { + break; + } + + // Now iterate over all the edges and for each of them + // cloning the current path and then insert that edge to + // the path and then add that path to the pending ones. + // When processing the last edge, modify the current + // pending path rather than cloning a new one. + Iterator<E> edges = firstVertexParents.iterator(); + while (edges.hasNext()) { + E edge = edges.next(); + boolean isLast = !edges.hasNext(); + DefaultMutablePath<V, E> pendingPath = isLast ? path : new DefaultMutablePath<>(path); + pendingPath.insertEdge(edge); + frontier.add(pendingPath); + } + } + } + + // All pending paths have been scanned so promote the next frontier + pendingPaths = frontier; + } + } + + // Returns the first vertex of the specified path. This is either the source + // of the first edge or, if there are no edges yet, the given destination. + private V firstVertex(Path<V, E> path, V dst) { + return path.edges().isEmpty() ? dst : path.edges().get(0).src(); + } + + /** + * Checks the specified path search arguments for validity. + * + * @param graph graph; must not be null + * @param src source vertex; must not be null and belong to graph + * @param dst optional target vertex; must belong to graph + */ + protected void checkArguments(Graph<V, E> graph, V src, V dst) { + checkNotNull(graph, "Graph cannot be null"); + checkNotNull(src, "Source cannot be null"); + Set<V> vertices = graph.getVertexes(); + checkArgument(vertices.contains(src), "Source not in the graph"); + checkArgument(dst == null || vertices.contains(dst), + "Destination not in graph"); + } + +} diff --git a/framework/src/onos/utils/misc/src/main/java/org/onlab/graph/AdjacencyListsGraph.java b/framework/src/onos/utils/misc/src/main/java/org/onlab/graph/AdjacencyListsGraph.java new file mode 100644 index 00000000..3890dcf8 --- /dev/null +++ b/framework/src/onos/utils/misc/src/main/java/org/onlab/graph/AdjacencyListsGraph.java @@ -0,0 +1,122 @@ +/* + * Copyright 2014 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.graph; + +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.ImmutableSetMultimap; + +import java.util.Objects; +import java.util.Set; + +import static com.google.common.base.MoreObjects.toStringHelper; +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Immutable graph implemented using adjacency lists. + * + * @param <V> vertex type + * @param <E> edge type + */ +public class AdjacencyListsGraph<V extends Vertex, E extends Edge<V>> + implements Graph<V, E> { + + private final Set<V> vertexes; + private final Set<E> edges; + + private final ImmutableSetMultimap<V, E> sources; + private final ImmutableSetMultimap<V, E> destinations; + + /** + * Creates a graph comprising of the specified vertexes and edges. + * + * @param vertexes set of graph vertexes + * @param edges set of graph edges + */ + public AdjacencyListsGraph(Set<V> vertexes, Set<E> edges) { + checkNotNull(vertexes, "Vertex set cannot be null"); + checkNotNull(edges, "Edge set cannot be null"); + + // Record ingress/egress edges for each vertex. + ImmutableSetMultimap.Builder<V, E> srcMap = ImmutableSetMultimap.builder(); + ImmutableSetMultimap.Builder<V, E> dstMap = ImmutableSetMultimap.builder(); + + // Also make sure that all edge end-points are added as vertexes + ImmutableSet.Builder<V> actualVertexes = ImmutableSet.builder(); + actualVertexes.addAll(vertexes); + + for (E edge : edges) { + srcMap.put(edge.src(), edge); + actualVertexes.add(edge.src()); + dstMap.put(edge.dst(), edge); + actualVertexes.add(edge.dst()); + } + + // Make an immutable copy of the edge and vertex sets + this.edges = ImmutableSet.copyOf(edges); + this.vertexes = actualVertexes.build(); + + // Build immutable copies of sources and destinations edge maps + sources = srcMap.build(); + destinations = dstMap.build(); + } + + @Override + public Set<V> getVertexes() { + return vertexes; + } + + @Override + public Set<E> getEdges() { + return edges; + } + + @Override + public Set<E> getEdgesFrom(V src) { + return sources.get(src); + } + + @Override + public Set<E> getEdgesTo(V dst) { + return destinations.get(dst); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj instanceof AdjacencyListsGraph) { + AdjacencyListsGraph that = (AdjacencyListsGraph) obj; + return this.getClass() == that.getClass() && + Objects.equals(this.vertexes, that.vertexes) && + Objects.equals(this.edges, that.edges); + } + return false; + } + + @Override + public int hashCode() { + return Objects.hash(vertexes, edges); + } + + @Override + public String toString() { + return toStringHelper(this) + .add("vertexes", vertexes) + .add("edges", edges) + .toString(); + } +} diff --git a/framework/src/onos/utils/misc/src/main/java/org/onlab/graph/BellmanFordGraphSearch.java b/framework/src/onos/utils/misc/src/main/java/org/onlab/graph/BellmanFordGraphSearch.java new file mode 100644 index 00000000..dc741f73 --- /dev/null +++ b/framework/src/onos/utils/misc/src/main/java/org/onlab/graph/BellmanFordGraphSearch.java @@ -0,0 +1,60 @@ +/* + * Copyright 2014-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.graph; + +/** + * Bellman-Ford graph search algorithm for locating shortest-paths in + * directed graphs that may contain negative cycles. + */ +public class BellmanFordGraphSearch<V extends Vertex, E extends Edge<V>> + extends AbstractGraphPathSearch<V, E> { + + @Override + public Result<V, E> search(Graph<V, E> graph, V src, V dst, + EdgeWeight<V, E> weight, int maxPaths) { + checkArguments(graph, src, dst); + + // Prepare the graph search result. + DefaultResult result = new DefaultResult(src, dst, maxPaths); + + // The source vertex has cost 0, of course. + result.updateVertex(src, null, 0.0, true); + + int max = graph.getVertexes().size() - 1; + for (int i = 0; i < max; i++) { + // Relax, if possible, all egress edges of the current vertex. + for (E edge : graph.getEdges()) { + if (result.hasCost(edge.src())) { + result.relaxEdge(edge, result.cost(edge.src()), weight); + } + } + } + + // Remove any vertexes reached by traversing edges with negative weights. + for (E edge : graph.getEdges()) { + if (result.hasCost(edge.src())) { + if (result.relaxEdge(edge, result.cost(edge.src()), weight)) { + result.removeVertex(edge.dst()); + } + } + } + + // Finally, but the paths on the search result and return. + result.buildPaths(); + return result; + } + +} diff --git a/framework/src/onos/utils/misc/src/main/java/org/onlab/graph/BreadthFirstSearch.java b/framework/src/onos/utils/misc/src/main/java/org/onlab/graph/BreadthFirstSearch.java new file mode 100644 index 00000000..40c8735b --- /dev/null +++ b/framework/src/onos/utils/misc/src/main/java/org/onlab/graph/BreadthFirstSearch.java @@ -0,0 +1,79 @@ +/* + * Copyright 2014-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.graph; + +import java.util.HashSet; +import java.util.Set; + +/** + * Implementation of the BFS algorithm. + */ +public class BreadthFirstSearch<V extends Vertex, E extends Edge<V>> + extends AbstractGraphPathSearch<V, E> { + + @Override + public Result<V, E> search(Graph<V, E> graph, V src, V dst, + EdgeWeight<V, E> weight, int maxPaths) { + checkArguments(graph, src, dst); + + // Prepare the graph result. + DefaultResult result = new DefaultResult(src, dst, maxPaths); + + // Setup the starting frontier with the source as the sole vertex. + Set<V> frontier = new HashSet<>(); + result.updateVertex(src, null, 0.0, true); + frontier.add(src); + + boolean reachedEnd = false; + while (!reachedEnd && !frontier.isEmpty()) { + // Prepare the next frontier. + Set<V> next = new HashSet<>(); + + // Visit all vertexes in the current frontier. + for (V vertex : frontier) { + double cost = result.cost(vertex); + + // Visit all egress edges of the current frontier vertex. + for (E edge : graph.getEdgesFrom(vertex)) { + V nextVertex = edge.dst(); + if (!result.hasCost(nextVertex)) { + // If this vertex has not been visited yet, update it. + double newCost = cost + (weight == null ? 1.0 : weight.weight(edge)); + result.updateVertex(nextVertex, edge, newCost, true); + // If we have reached our intended destination, bail. + if (nextVertex.equals(dst)) { + reachedEnd = true; + break; + } + next.add(nextVertex); + } + + if (reachedEnd) { + break; + } + } + } + + // Promote the next frontier. + frontier = next; + } + + // Finally, but the paths on the search result and return. + result.buildPaths(); + return result; + } + +} diff --git a/framework/src/onos/utils/misc/src/main/java/org/onlab/graph/DefaultMutablePath.java b/framework/src/onos/utils/misc/src/main/java/org/onlab/graph/DefaultMutablePath.java new file mode 100644 index 00000000..ad7e8402 --- /dev/null +++ b/framework/src/onos/utils/misc/src/main/java/org/onlab/graph/DefaultMutablePath.java @@ -0,0 +1,136 @@ +/* + * Copyright 2014 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.graph; + +import com.google.common.collect.ImmutableList; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +import static com.google.common.base.MoreObjects.toStringHelper; +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Simple concrete implementation of a directed graph path. + */ +public class DefaultMutablePath<V extends Vertex, E extends Edge<V>> implements MutablePath<V, E> { + + private final List<E> edges = new ArrayList<>(); + private double cost = 0.0; + + /** + * Creates a new empty path. + */ + public DefaultMutablePath() { + } + + /** + * Creates a new path as a copy of another path. + * + * @param path path to be copied + */ + public DefaultMutablePath(Path<V, E> path) { + checkNotNull(path, "Path cannot be null"); + this.cost = path.cost(); + edges.addAll(path.edges()); + } + + @Override + public V src() { + return edges.isEmpty() ? null : edges.get(0).src(); + } + + @Override + public V dst() { + return edges.isEmpty() ? null : edges.get(edges.size() - 1).dst(); + } + + @Override + public double cost() { + return cost; + } + + @Override + public List<E> edges() { + return ImmutableList.copyOf(edges); + } + + @Override + public void setCost(double cost) { + this.cost = cost; + } + + @Override + public Path<V, E> toImmutable() { + return new DefaultPath<>(edges, cost); + } + + @Override + public void insertEdge(E edge) { + checkNotNull(edge, "Edge cannot be null"); + checkArgument(edges.isEmpty() || src().equals(edge.dst()), + "Edge destination must be the same as the current path source"); + edges.add(0, edge); + } + + @Override + public void appendEdge(E edge) { + checkNotNull(edge, "Edge cannot be null"); + checkArgument(edges.isEmpty() || dst().equals(edge.src()), + "Edge source must be the same as the current path destination"); + edges.add(edge); + } + + @Override + public void removeEdge(E edge) { + checkArgument(edge.src().equals(edge.dst()) || + edges.indexOf(edge) == 0 || + edges.lastIndexOf(edge) == edges.size() - 1, + "Edge must be at start or end of path, or it must be a cyclic edge"); + edges.remove(edge); + } + + @Override + public String toString() { + return toStringHelper(this) + .add("src", src()) + .add("dst", dst()) + .add("cost", cost) + .add("edges", edges) + .toString(); + } + + @Override + public int hashCode() { + return Objects.hash(edges, cost); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj instanceof DefaultMutablePath) { + final DefaultMutablePath other = (DefaultMutablePath) obj; + return Objects.equals(this.cost, other.cost) && + Objects.equals(this.edges, other.edges); + } + return false; + } + +} diff --git a/framework/src/onos/utils/misc/src/main/java/org/onlab/graph/DefaultPath.java b/framework/src/onos/utils/misc/src/main/java/org/onlab/graph/DefaultPath.java new file mode 100644 index 00000000..816fb161 --- /dev/null +++ b/framework/src/onos/utils/misc/src/main/java/org/onlab/graph/DefaultPath.java @@ -0,0 +1,103 @@ +/* + * Copyright 2014 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.graph; + +import com.google.common.collect.ImmutableList; + +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +import static com.google.common.base.MoreObjects.toStringHelper; +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Simple concrete implementation of a directed graph path. + */ +public class DefaultPath<V extends Vertex, E extends Edge<V>> implements Path<V, E> { + + private final V src; + private final V dst; + private final List<E> edges; + private double cost = 0.0; + + /** + * Creates a new path from the specified list of edges and cost. + * + * @param edges list of path edges + * @param cost path cost as a unit-less number + */ + public DefaultPath(List<E> edges, double cost) { + checkNotNull(edges, "Edges list must not be null"); + checkArgument(!edges.isEmpty(), "There must be at least one edge"); + this.edges = ImmutableList.copyOf(edges); + this.src = edges.get(0).src(); + this.dst = edges.get(edges.size() - 1).dst(); + this.cost = cost; + } + + @Override + public V src() { + return src; + } + + @Override + public V dst() { + return dst; + } + + @Override + public double cost() { + return cost; + } + + @Override + public List<E> edges() { + return Collections.unmodifiableList(edges); + } + + @Override + public String toString() { + return toStringHelper(this) + .add("src", src) + .add("dst", dst) + .add("cost", cost) + .add("edges", edges) + .toString(); + } + + @Override + public int hashCode() { + return Objects.hash(src, dst, edges, cost); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj instanceof DefaultPath) { + final DefaultPath other = (DefaultPath) obj; + return Objects.equals(this.src, other.src) && + Objects.equals(this.dst, other.dst) && + Objects.equals(this.cost, other.cost) && + Objects.equals(this.edges, other.edges); + } + return false; + } + +} diff --git a/framework/src/onos/utils/misc/src/main/java/org/onlab/graph/DepthFirstSearch.java b/framework/src/onos/utils/misc/src/main/java/org/onlab/graph/DepthFirstSearch.java new file mode 100644 index 00000000..91b5108f --- /dev/null +++ b/framework/src/onos/utils/misc/src/main/java/org/onlab/graph/DepthFirstSearch.java @@ -0,0 +1,183 @@ +/* + * Copyright 2014-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.graph; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.Stack; + +/** + * DFS graph search algorithm implemented via iteration rather than recursion. + */ +public class DepthFirstSearch<V extends Vertex, E extends Edge<V>> + extends AbstractGraphPathSearch<V, E> { + + /** + * Graph edge types as classified by the DFS algorithm. + */ + public static enum EdgeType { + TREE_EDGE, FORWARD_EDGE, BACK_EDGE, CROSS_EDGE + } + + @Override + public SpanningTreeResult search(Graph<V, E> graph, V src, V dst, + EdgeWeight<V, E> weight, int maxPaths) { + checkArguments(graph, src, dst); + + // Prepare the search result. + SpanningTreeResult result = new SpanningTreeResult(src, dst, maxPaths); + + // The source vertex has cost 0, of course. + result.updateVertex(src, null, 0.0, true); + + // Track finished vertexes and keep a stack of vertexes that have been + // started; start this stack with the source on it. + Set<V> finished = new HashSet<>(); + Stack<V> stack = new Stack<>(); + stack.push(src); + + while (!stack.isEmpty()) { + V vertex = stack.peek(); + if (vertex.equals(dst)) { + // If we have reached our destination, bail. + break; + } + + double cost = result.cost(vertex); + boolean tangent = false; + + // Visit all egress edges of the current vertex. + for (E edge : graph.getEdgesFrom(vertex)) { + // If we have seen the edge already, skip it. + if (result.isEdgeMarked(edge)) { + continue; + } + + // Examine the destination of the current edge. + V nextVertex = edge.dst(); + if (!result.hasCost(nextVertex)) { + // If this vertex have not finished this vertex yet, + // not started it, then start it as a tree-edge. + result.markEdge(edge, EdgeType.TREE_EDGE); + double newCost = cost + (weight == null ? 1.0 : weight.weight(edge)); + result.updateVertex(nextVertex, edge, newCost, true); + stack.push(nextVertex); + tangent = true; + break; + + } else if (!finished.contains(nextVertex)) { + // We started the vertex, but did not yet finish it, so + // it must be a back-edge. + result.markEdge(edge, EdgeType.BACK_EDGE); + } else { + // The target has been finished already, so what we have + // here is either a forward-edge or a cross-edge. + result.markEdge(edge, isForwardEdge(result, edge) ? + EdgeType.FORWARD_EDGE : EdgeType.CROSS_EDGE); + } + } + + // If we have not been sent on a tangent search and reached the + // end of the current scan normally, mark the node as finished + // and pop it off the vertex stack. + if (!tangent) { + finished.add(vertex); + stack.pop(); + } + } + + // Finally, but the paths on the search result and return. + result.buildPaths(); + return result; + } + + /** + * Determines whether the specified edge is a forward edge using the + * accumulated set of parent edges for each vertex. + * + * @param result search result + * @param edge edge to be classified + * @return true if the edge is a forward edge + */ + protected boolean isForwardEdge(DefaultResult result, E edge) { + // Follow the parent edges until we hit the edge source vertex + V target = edge.src(); + V vertex = edge.dst(); + Set<E> parentEdges; + while ((parentEdges = result.parents.get(vertex)) != null) { + for (E parentEdge : parentEdges) { + vertex = parentEdge.src(); + if (vertex.equals(target)) { + return true; + } + } + } + return false; + } + + /** + * Graph search result which includes edge classification for building + * a spanning tree. + */ + public class SpanningTreeResult extends DefaultResult { + + protected final Map<E, EdgeType> edges = new HashMap<>(); + + /** + * Creates a new spanning tree result. + * + * @param src search source + * @param dst optional search destination + * @param maxPaths limit on the number of paths + */ + public SpanningTreeResult(V src, V dst, int maxPaths) { + super(src, dst, maxPaths); + } + + /** + * Returns the map of edge type. + * + * @return edge to edge type bindings + */ + public Map<E, EdgeType> edges() { + return edges; + } + + /** + * Indicates whether or not the edge has been marked with type. + * + * @param edge edge to test + * @return true if the edge has been marked already + */ + boolean isEdgeMarked(E edge) { + return edges.containsKey(edge); + } + + /** + * Marks the edge with the specified type. + * + * @param edge edge to mark + * @param type edge type + */ + void markEdge(E edge, EdgeType type) { + edges.put(edge, type); + } + + } + +} diff --git a/framework/src/onos/utils/misc/src/main/java/org/onlab/graph/DijkstraGraphSearch.java b/framework/src/onos/utils/misc/src/main/java/org/onlab/graph/DijkstraGraphSearch.java new file mode 100644 index 00000000..b1892ee1 --- /dev/null +++ b/framework/src/onos/utils/misc/src/main/java/org/onlab/graph/DijkstraGraphSearch.java @@ -0,0 +1,97 @@ +/* + * Copyright 2014-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.graph; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.Set; + +/** + * Dijkstra shortest-path graph search algorithm capable of finding not just + * one, but all shortest paths between the source and destinations. + */ +public class DijkstraGraphSearch<V extends Vertex, E extends Edge<V>> + extends AbstractGraphPathSearch<V, E> { + + @Override + public Result<V, E> search(Graph<V, E> graph, V src, V dst, + EdgeWeight<V, E> weight, int maxPaths) { + checkArguments(graph, src, dst); + + // Use the default result to remember cumulative costs and parent + // edges to each each respective vertex. + DefaultResult result = new DefaultResult(src, dst, maxPaths); + + // Cost to reach the source vertex is 0 of course. + result.updateVertex(src, null, 0.0, false); + + if (graph.getEdges().isEmpty()) { + result.buildPaths(); + return result; + } + + // Use the min priority queue to progressively find each nearest + // vertex until we reach the desired destination, if one was given, + // or until we reach all possible destinations. + Heap<V> minQueue = createMinQueue(graph.getVertexes(), + new PathCostComparator(result)); + while (!minQueue.isEmpty()) { + // Get the nearest vertex + V nearest = minQueue.extractExtreme(); + if (nearest.equals(dst)) { + break; + } + + // Find its cost and use it to determine if the vertex is reachable. + double cost = result.cost(nearest); + if (cost < Double.MAX_VALUE) { + // If the vertex is reachable, relax all its egress edges. + for (E e : graph.getEdgesFrom(nearest)) { + result.relaxEdge(e, cost, weight, true); + } + } + + // Re-prioritize the min queue. + minQueue.heapify(); + } + + // Now construct a set of paths from the results. + result.buildPaths(); + return result; + } + + // Compares path weights using their accrued costs; used for sorting the + // min priority queue. + private final class PathCostComparator implements Comparator<V> { + private final DefaultResult result; + + private PathCostComparator(DefaultResult result) { + this.result = result; + } + + @Override + public int compare(V v1, V v2) { + double delta = result.cost(v2) - result.cost(v1); + return delta < 0 ? -1 : (delta > 0 ? 1 : 0); + } + } + + // Creates a min priority queue from the specified vertexes and comparator. + private Heap<V> createMinQueue(Set<V> vertexes, Comparator<V> comparator) { + return new Heap<>(new ArrayList<>(vertexes), comparator); + } + +} diff --git a/framework/src/onos/utils/misc/src/main/java/org/onlab/graph/DisjointPathPair.java b/framework/src/onos/utils/misc/src/main/java/org/onlab/graph/DisjointPathPair.java new file mode 100644 index 00000000..b62d3b24 --- /dev/null +++ b/framework/src/onos/utils/misc/src/main/java/org/onlab/graph/DisjointPathPair.java @@ -0,0 +1,128 @@ +/* + * 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.graph; + +import java.util.List; +import java.util.Objects; +import java.util.Set; + +import static com.google.common.collect.ImmutableSet.of; +import static com.google.common.base.MoreObjects.toStringHelper; + + +public class DisjointPathPair<V extends Vertex, E extends Edge<V>> implements Path<V, E> { + public Path<V, E> path1, path2; + boolean usingPath1 = true; + + /** + * Creates a Disjoint Path Pair from two paths. + * + * @param p1 first path + * @param p2 second path + */ + public DisjointPathPair(Path<V, E> p1, Path<V, E> p2) { + path1 = p1; + path2 = p2; + } + + @Override + public V src() { + return path1.src(); + } + + @Override + public V dst() { + return path1.dst(); + } + + @Override + public double cost() { + if (!hasBackup()) { + return path1.cost(); + } + return path1.cost() + path2.cost(); + } + + @Override + public List<E> edges() { + if (usingPath1 || !hasBackup()) { + return path1.edges(); + } else { + return path2.edges(); + } + } + + /** + * Checks if this path pair contains a backup/secondary path. + * + * @return boolean representing whether it has backup + */ + public boolean hasBackup() { + return path2 != null && path2.edges() != null; + } + + @Override + public String toString() { + return toStringHelper(this) + .add("src", src()) + .add("dst", dst()) + .add("cost", cost()) + .add("edges", edges()) + .toString(); + } + + @Override + public int hashCode() { + Set<Path<V, E>> paths; + if (!hasBackup()) { + paths = of(path1); + } else { + paths = of(path1, path2); + } + return Objects.hash(paths); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj instanceof DisjointPathPair) { + final DisjointPathPair other = (DisjointPathPair) obj; + return Objects.equals(this.src(), other.src()) && + Objects.equals(this.dst(), other.dst()) && + (Objects.equals(this.path1, other.path1) && + Objects.equals(this.path2, other.path2)) || + (Objects.equals(this.path1, other.path2) && + Objects.equals(this.path2, other.path1)); + } + return false; + } + + /** + * Returns number of paths inside this path pair object. + * + * @return number of paths + */ + public int size() { + if (hasBackup()) { + return 2; + } + return 1; + } +} diff --git a/framework/src/onos/utils/misc/src/main/java/org/onlab/graph/DisjointPathPair.java.orig b/framework/src/onos/utils/misc/src/main/java/org/onlab/graph/DisjointPathPair.java.orig new file mode 100644 index 00000000..1cf22b6a --- /dev/null +++ b/framework/src/onos/utils/misc/src/main/java/org/onlab/graph/DisjointPathPair.java.orig @@ -0,0 +1,169 @@ +/* + * 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.graph; + +import java.util.List; +import java.util.Objects; +import java.util.Set; + +import static com.google.common.collect.ImmutableSet.of; +import static com.google.common.base.MoreObjects.toStringHelper; + + +public class DisjointPathPair<V extends Vertex, E extends Edge<V>> implements Path<V, E> { + public Path<V, E> path1, path2; + boolean usingPath1 = true; + +<<<<<<< HEAD + /** + * Creates a Disjoint Path Pair from two paths. + * + * @param p1 first path + * @param p2 second path + */ +======= +>>>>>>> Disjoint Path Pairs (Suurballe) utils + public DisjointPathPair(Path<V, E> p1, Path<V, E> p2) { + path1 = p1; + path2 = p2; + } +<<<<<<< HEAD + + @Override +======= +>>>>>>> Disjoint Path Pairs (Suurballe) utils + public V src() { + return path1.src(); + } + +<<<<<<< HEAD + @Override + public V dst() { + return path1.dst(); + } + + @Override +======= + public V dst() { + return path1.dst(); + } +>>>>>>> Disjoint Path Pairs (Suurballe) utils + public double cost() { + if (!hasBackup()) { + return path1.cost(); + } + return path1.cost() + path2.cost(); + } +<<<<<<< HEAD + + @Override +======= +>>>>>>> Disjoint Path Pairs (Suurballe) utils + public List<E> edges() { + if (usingPath1 || !hasBackup()) { + return path1.edges(); + } else { + return path2.edges(); + } + } +<<<<<<< HEAD + + /** + * Checks if this path pair contains a backup/secondary path. + * + * @return boolean representing whether it has backup + */ + public boolean hasBackup() { + return path2 != null && path2.edges() != null; + } + + /** + * Switches this disjoint path pair to using its backup path, instead of + * using its primary. + */ + public void useBackup() { + usingPath1 = !usingPath1; + } + + @Override +======= + public boolean hasBackup() { + return path2 != null && path2.edges() != null; + } + public void useBackup() { + usingPath1 = !usingPath1; + } +>>>>>>> Disjoint Path Pairs (Suurballe) utils + public String toString() { + return toStringHelper(this) + .add("src", src()) + .add("dst", dst()) + .add("cost", cost()) + .add("edges", edges()) + .toString(); + } +<<<<<<< HEAD + + @Override +======= +>>>>>>> Disjoint Path Pairs (Suurballe) utils + public int hashCode() { + Set<Path<V, E>> paths; + if (!hasBackup()) { + paths = of(path1); + } else { + paths = of(path1, path2); + } + return Objects.hash(paths); + } +<<<<<<< HEAD + + @Override +======= +>>>>>>> Disjoint Path Pairs (Suurballe) utils + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj instanceof DisjointPathPair) { + final DisjointPathPair other = (DisjointPathPair) obj; + return Objects.equals(this.src(), other.src()) && + Objects.equals(this.dst(), other.dst()) && + (Objects.equals(this.path1, other.path1) && + Objects.equals(this.path2, other.path2)) || + (Objects.equals(this.path1, other.path2) && + Objects.equals(this.path2, other.path1)); + } + return false; + } +<<<<<<< HEAD + + /** + * Returns number of paths inside this path pair object. + * + * @return number of paths + */ +======= +>>>>>>> Disjoint Path Pairs (Suurballe) utils + public int size() { + if (hasBackup()) { + return 2; + } + return 1; + } +} diff --git a/framework/src/onos/utils/misc/src/main/java/org/onlab/graph/Edge.java b/framework/src/onos/utils/misc/src/main/java/org/onlab/graph/Edge.java new file mode 100644 index 00000000..1bbfbf99 --- /dev/null +++ b/framework/src/onos/utils/misc/src/main/java/org/onlab/graph/Edge.java @@ -0,0 +1,39 @@ +/* + * Copyright 2014 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.graph; + +/** + * Representation of a graph edge. + * + * @param <V> vertex type + */ +public interface Edge<V extends Vertex> { + + /** + * Returns the edge source vertex. + * + * @return source vertex + */ + V src(); + + /** + * Returns the edge destination vertex. + * + * @return destination vertex + */ + V dst(); + +} diff --git a/framework/src/onos/utils/misc/src/main/java/org/onlab/graph/EdgeWeight.java b/framework/src/onos/utils/misc/src/main/java/org/onlab/graph/EdgeWeight.java new file mode 100644 index 00000000..975b59c7 --- /dev/null +++ b/framework/src/onos/utils/misc/src/main/java/org/onlab/graph/EdgeWeight.java @@ -0,0 +1,31 @@ +/* + * Copyright 2014 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.graph; + +/** + * Abstraction of a graph edge weight function. + */ +public interface EdgeWeight<V extends Vertex, E extends Edge<V>> { + + /** + * Returns the weight of the given edge as a unit-less number. + * + * @param edge edge to be weighed + * @return edge weight as a unit-less number + */ + double weight(E edge); + +} diff --git a/framework/src/onos/utils/misc/src/main/java/org/onlab/graph/GAOrganism.java b/framework/src/onos/utils/misc/src/main/java/org/onlab/graph/GAOrganism.java new file mode 100644 index 00000000..230609f8 --- /dev/null +++ b/framework/src/onos/utils/misc/src/main/java/org/onlab/graph/GAOrganism.java @@ -0,0 +1,38 @@ +package org.onlab.graph; + +/** + * Interface representing an "organism": a specific solution + * to a problem where solutions can be evaluated in terms + * of fitness. These organisms can be used to represent any + * class of problem that genetic algorithms can be run on. + */ +interface GAOrganism { + /** + * A fitness function that determines how + * optimal a given organism is. + * + * @return fitness of organism + */ + double fitness(); + + /** + * A method that slightly mutates an organism. + */ + void mutate(); + + /** + * Creates a new random organism. + * + * @return random GAOrganism + */ + GAOrganism random(); + + /** + * Returns a child organism that is the result + * of "crossing" this organism with another. + * + * @param other Other organism to cross with + * @return child organism + */ + GAOrganism crossWith(GAOrganism other); +} diff --git a/framework/src/onos/utils/misc/src/main/java/org/onlab/graph/GAPopulation.java b/framework/src/onos/utils/misc/src/main/java/org/onlab/graph/GAPopulation.java new file mode 100644 index 00000000..ae7f182e --- /dev/null +++ b/framework/src/onos/utils/misc/src/main/java/org/onlab/graph/GAPopulation.java @@ -0,0 +1,75 @@ +package org.onlab.graph; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Random; + +/** + * Represents a population of GAOrganisms. This class can be used + * to run a genetic algorithm on the population and return the fittest solutions. + */ +class GAPopulation<Organism extends GAOrganism> extends ArrayList<Organism> { + Random r = new Random(); + + /** + * Steps the population through one generation. The 75% least fit + * organisms are killed off and replaced with the children of the + * 25% (as well as some "random" newcomers). + */ + void step() { + Collections.sort(this, (org1, org2) -> { + double d = org1.fitness() - org2.fitness(); + if (d < 0) { + return -1; + } else if (d == 0) { + return 0; + } + return 1; + }); + int maxSize = size(); + for (int i = size() - 1; i > maxSize / 4; i--) { + remove(i); + } + for (Organism org: this) { + if (r.nextBoolean()) { + org.mutate(); + } + } + while (size() < maxSize * 4 / 5) { + Organism org1 = get(r.nextInt(size())); + Organism org2 = get(r.nextInt(size())); + add((Organism) org1.crossWith(org2)); + } + + while (size() < maxSize) { + Organism org1 = get(r.nextInt(size())); + add((Organism) org1.random()); + } + } + + /** + * Runs GA for the specified number of iterations, and returns + * a sample of the resulting population of solutions. + * + * @param generations Number of generations to run GA for + * @param populationSize Population size of GA + * @param sample Number of solutions to ask for + * @param template Template GAOrganism to seed the population with + * @return ArrayList containing sample number of organisms + */ + List<Organism> runGA(int generations, int populationSize, int sample, Organism template) { + for (int i = 0; i < populationSize; i++) { + add((Organism) template.random()); + } + + for (int i = 0; i < generations; i++) { + step(); + } + for (int i = size() - 1; i >= sample; i--) { + remove(i); + } + return new ArrayList<>(this); + } +} + diff --git a/framework/src/onos/utils/misc/src/main/java/org/onlab/graph/Graph.java b/framework/src/onos/utils/misc/src/main/java/org/onlab/graph/Graph.java new file mode 100644 index 00000000..bc7853ec --- /dev/null +++ b/framework/src/onos/utils/misc/src/main/java/org/onlab/graph/Graph.java @@ -0,0 +1,59 @@ +/* + * Copyright 2014 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.graph; + + +import java.util.Set; + +/** + * Abstraction of a directed graph structure. + * + * @param <V> vertex type + * @param <E> edge type + */ +public interface Graph<V extends Vertex, E extends Edge> { + + /** + * Returns the set of vertexes comprising the graph. + * + * @return set of vertexes + */ + Set<V> getVertexes(); + + /** + * Returns the set of edges comprising the graph. + * + * @return set of edges + */ + Set<E> getEdges(); + + /** + * Returns all edges leading out from the specified source vertex. + * + * @param src source vertex + * @return set of egress edges; empty if no such edges + */ + Set<E> getEdgesFrom(V src); + + /** + * Returns all edges leading towards the specified destination vertex. + * + * @param dst destination vertex + * @return set of ingress vertexes; empty if no such edges + */ + Set<E> getEdgesTo(V dst); + +} diff --git a/framework/src/onos/utils/misc/src/main/java/org/onlab/graph/GraphPathSearch.java b/framework/src/onos/utils/misc/src/main/java/org/onlab/graph/GraphPathSearch.java new file mode 100644 index 00000000..caebce47 --- /dev/null +++ b/framework/src/onos/utils/misc/src/main/java/org/onlab/graph/GraphPathSearch.java @@ -0,0 +1,87 @@ +/* + * Copyright 2014-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.graph; + +import java.util.Map; +import java.util.Set; + +/** + * Representation of a graph path search algorithm. + * + * @param <V> vertex type + * @param <E> edge type + */ +public interface GraphPathSearch<V extends Vertex, E extends Edge<V>> { + + static int ALL_PATHS = -1; + + /** + * Abstraction of a path search result. + */ + interface Result<V extends Vertex, E extends Edge<V>> { + + /** + * Returns the search source. + * + * @return search source + */ + V src(); + + /** + * Returns the search destination, if was was given. + * + * @return optional search destination + */ + V dst(); + + /** + * Returns the set of paths produced as a result of the graph search. + * + * @return set of paths + */ + Set<Path<V, E>> paths(); + + /** + * Returns bindings of each vertex to its parent edges in the path. + * + * @return map of vertex to its parent edge bindings + */ + Map<V, Set<E>> parents(); + + /** + * Return a bindings of each vertex to its cost in the path. + * + * @return map of vertex to path cost bindings + */ + Map<V, Double> costs(); + } + + /** + * Searches the specified graph for paths between vertices. + * + * @param graph graph to be searched + * @param src optional source vertex + * @param dst optional destination vertex; if null paths to all vertex + * destinations will be searched + * @param weight optional edge-weight; if null cost of each edge will be + * assumed to be 1.0 + * @param maxPaths limit on number of paths; {@link GraphPathSearch#ALL_PATHS} if no limit + * @return search results + */ + Result<V, E> search(Graph<V, E> graph, V src, V dst, + EdgeWeight<V, E> weight, int maxPaths); + +} diff --git a/framework/src/onos/utils/misc/src/main/java/org/onlab/graph/GraphSearch.java b/framework/src/onos/utils/misc/src/main/java/org/onlab/graph/GraphSearch.java new file mode 100644 index 00000000..ca369a7d --- /dev/null +++ b/framework/src/onos/utils/misc/src/main/java/org/onlab/graph/GraphSearch.java @@ -0,0 +1,43 @@ +/* + * Copyright 2014 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.graph; + +/** + * Representation of a graph search algorithm and its outcome. + * + * @param <V> vertex type + * @param <E> edge type + */ +public interface GraphSearch<V extends Vertex, E extends Edge<V>> { + + /** + * Notion of a graph search result. + */ + interface Result<V extends Vertex, E extends Edge<V>> { + } + + /** + * Searches the specified graph. + * + * @param graph graph to be searched + * @param weight optional edge-weight; if null cost of each edge will be + * assumed to be 1.0 + * + * @return search results + */ + Result search(Graph<V, E> graph, EdgeWeight<V, E> weight); + +} diff --git a/framework/src/onos/utils/misc/src/main/java/org/onlab/graph/Heap.java b/framework/src/onos/utils/misc/src/main/java/org/onlab/graph/Heap.java new file mode 100644 index 00000000..ebb1a60b --- /dev/null +++ b/framework/src/onos/utils/misc/src/main/java/org/onlab/graph/Heap.java @@ -0,0 +1,211 @@ +/* + * Copyright 2014 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.graph; + +import com.google.common.collect.ImmutableList; + +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; +import java.util.Objects; + +import static com.google.common.base.MoreObjects.toStringHelper; +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Implementation of an array-backed heap structure whose sense of order is + * imposed by the provided comparator. + * <p> + * While this provides similar functionality to {@link java.util.PriorityQueue} + * data structure, one key difference is that external entities can control + * when to restore the heap property, which is done through invocation of the + * {@link #heapify} method. + * </p> + * <p> + * This class is not thread-safe and care must be taken to prevent concurrent + * modifications. + * </p> + * + * @param <T> type of the items on the heap + */ +public class Heap<T> { + + private final List<T> data; + private final Comparator<T> comparator; + + /** + * Creates a new heap backed by the specified list. In the interest of + * efficiency, the list should be array-backed. Also, for the same reason, + * the data is not copied and therefore, the caller must assure that the + * backing data is not altered in any way. + * + * @param data backing data list + * @param comparator comparator for ordering the heap items + */ + public Heap(List<T> data, Comparator<T> comparator) { + this.data = checkNotNull(data, "Data cannot be null"); + this.comparator = checkNotNull(comparator, "Comparator cannot be null"); + heapify(); + } + + /** + * Restores the heap property by re-arranging the elements in the backing + * array as necessary following any heap modifications. + */ + public void heapify() { + for (int i = data.size() / 2; i >= 0; i--) { + heapify(i); + } + } + + /** + * Returns the current size of the heap. + * + * @return number of items in the heap + */ + public int size() { + return data.size(); + } + + /** + * Returns true if there are no items in the heap. + * + * @return true if heap is empty + */ + public boolean isEmpty() { + return data.isEmpty(); + } + + /** + * Returns the most extreme item in the heap. + * + * @return heap extreme or null if the heap is empty + */ + public T extreme() { + return data.isEmpty() ? null : data.get(0); + } + + /** + * Extracts and returns the most extreme item from the heap. + * + * @return heap extreme or null if the heap is empty + */ + public T extractExtreme() { + if (!isEmpty()) { + T extreme = extreme(); + + data.set(0, data.get(data.size() - 1)); + data.remove(data.size() - 1); + heapify(); + return extreme; + } + return null; + } + + /** + * Inserts the specified item into the heap and returns the modified heap. + * + * @param item item to be inserted + * @return the heap self + * @throws IllegalArgumentException if the heap is already full + */ + public Heap<T> insert(T item) { + data.add(item); + bubbleUp(); + return this; + } + + /** + * Returns iterator to traverse the heap level-by-level. This iterator + * does not permit removal of items. + * + * @return non-destructive heap iterator + */ + public Iterator<T> iterator() { + return ImmutableList.copyOf(data).iterator(); + } + + // Bubbles up the last item in the heap to its proper position to restore + // the heap property. + private void bubbleUp() { + int child = data.size() - 1; + while (child > 0) { + int parent = child / 2; + if (comparator.compare(data.get(child), data.get(parent)) < 0) { + break; + } + swap(child, parent); + child = parent; + } + } + + // Restores the heap property of the specified heap layer. + private void heapify(int i) { + int left = 2 * i + 1; + int right = 2 * i; + int extreme = i; + + if (left < data.size() && + comparator.compare(data.get(extreme), data.get(left)) < 0) { + extreme = left; + } + + if (right < data.size() && + comparator.compare(data.get(extreme), data.get(right)) < 0) { + extreme = right; + } + + if (extreme != i) { + swap(i, extreme); + heapify(extreme); + } + } + + // Swaps two heap items identified by their respective indexes. + private void swap(int i, int k) { + T aux = data.get(i); + data.set(i, data.get(k)); + data.set(k, aux); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj instanceof Heap) { + Heap that = (Heap) obj; + return this.getClass() == that.getClass() && + Objects.equals(this.comparator, that.comparator) && + Objects.deepEquals(this.data, that.data); + } + return false; + } + + @Override + public int hashCode() { + return Objects.hash(comparator, data); + } + + @Override + public String toString() { + return toStringHelper(this) + .add("data", data) + .add("comparator", comparator) + .toString(); + } + +} diff --git a/framework/src/onos/utils/misc/src/main/java/org/onlab/graph/KshortestPathSearch.java b/framework/src/onos/utils/misc/src/main/java/org/onlab/graph/KshortestPathSearch.java new file mode 100644 index 00000000..820e912c --- /dev/null +++ b/framework/src/onos/utils/misc/src/main/java/org/onlab/graph/KshortestPathSearch.java @@ -0,0 +1,286 @@ +/* + * Copyright 2014-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.graph; + +import java.util.ArrayList; +//import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +//import java.util.Map; +//import java.util.PriorityQueue; +import java.util.Set; + +import static org.onlab.graph.GraphPathSearch.ALL_PATHS; + +/** + * K-shortest-path graph search algorithm capable of finding not just one, + * but K shortest paths with ascending order between the source and destinations. + */ + +public class KshortestPathSearch<V extends Vertex, E extends Edge<V>> { + + // Define class variables. + private Graph<V, E> immutableGraph; + private MutableGraph<V, E> mutableGraph; + private List<List<E>> pathResults = new ArrayList<List<E>>(); + private List<List<E>> pathCandidates = new ArrayList<List<E>>(); + private V source; + private V sink; + private int numK = 0; + private EdgeWeight<V, E> weight = null; + // private PriorityQueue<List<E>> pathCandidates = new PriorityQueue<List<E>>(); + + // Initialize the graph. + public KshortestPathSearch(Graph<V, E> graph) { + immutableGraph = graph; + mutableGraph = new MutableAdjacencyListsGraph<>(graph.getVertexes(), + graph.getEdges()); + } + + public List<List<E>> search(V src, + V dst, + EdgeWeight<V, E> wei, + int k) { + + weight = wei; + source = src; + sink = dst; + numK = k; + // pathCandidates = new PriorityQueue<List<E>>(); + + pathResults.clear(); + pathCandidates.clear(); + + // Double check the parameters + checkArguments(immutableGraph, src, dst, numK); + + // DefaultResult result = new DefaultResult(src, dst); + + searchKShortestPaths(); + + return pathResults; + } + + private void checkArguments(Graph<V, E> graph, V src, V dst, int k) { + if (graph == null) { + throw new NullPointerException("graph is null"); + } + if (!graph.getVertexes().contains(src)) { + throw new NullPointerException("source node does not exist"); + } + if (!graph.getVertexes().contains(dst)) { + throw new NullPointerException("target node does not exist"); + } + if (k <= 0) { + throw new NullPointerException("K is negative or 0"); + } + if (weight == null) { + throw new NullPointerException("the cost matrix is null"); + } + } + + private void searchKShortestPaths() { + // Step 1: find the shortest path. + List<E> shortestPath = searchShortestPath(immutableGraph, source, sink); + // no path exists, exit. + if (shortestPath == null) { + return; + } + + // Step 2: update the results. + pathResults.add(shortestPath); + // pathCandidates.add(shortestPath); + + // Step 3: find the other K-1 paths. + while (/*pathCandidates.size() > 0 &&*/pathResults.size() < numK) { + // 3.1 the spur node ranges from the first node to the last node in the previous k-shortest path. + List<E> lastPath = pathResults.get(pathResults.size() - 1); + for (int i = 0; i < lastPath.size(); i++) { + // 3.1.1 convert the graph into mutable. + convertGraph(); + // 3.1.2 transform the graph. + List<E> rootPath = createSpurNode(lastPath, i); + transformGraph(rootPath); + // 3.1.3 find the deviation node. + V devNode; + devNode = getDevNode(rootPath); + List<E> spurPath; + // 3.1.4 find the shortest path in the transformed graph. + spurPath = searchShortestPath(mutableGraph, devNode, sink); + // 3.1.5 update the path candidates. + if (spurPath != null) { + // totalPath = rootPath + spurPath; + rootPath.addAll(spurPath); + pathCandidates.add(rootPath); + } + } + // 3.2 if there is no spur path, exit. + if (pathCandidates.size() == 0) { + break; + } + // 3.3 add the path into the results. + addPathResult(); + } + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + private List<E> searchShortestPath(Graph<V, E> graph, V src, V dst) { + // Determine the shortest path from the source to the destination by using the Dijkstra algorithm. + DijkstraGraphSearch dijkstraAlg = new DijkstraGraphSearch(); + Set<Path> paths = dijkstraAlg.search(graph, src, dst, weight, ALL_PATHS).paths(); + Iterator<Path> itr = paths.iterator(); + if (!itr.hasNext()) { + return null; + } + // return the first shortest path only. + return (List<E>) itr.next().edges(); + } + + private void convertGraph() { + // clear the mutableGraph first + if (mutableGraph != null) { + ((MutableAdjacencyListsGraph) mutableGraph).clear(); + } + + // create a immutableGraph + Set<E> copyEa = immutableGraph.getEdges(); + Set<V> copyVa = immutableGraph.getVertexes(); + for (V vertex : copyVa) { + mutableGraph.addVertex(vertex); + } + for (E edge : copyEa) { + mutableGraph.addEdge(edge); + } + } + + private V getDevNode(List<E> path) { + V srcA; + V dstB; + + if (path.size() == 0) { + return source; + } + + E temp1 = path.get(path.size() - 1); + srcA = temp1.src(); + dstB = temp1.dst(); + + if (path.size() == 1) { + if (srcA.equals(source)) { + return dstB; + } else { + return srcA; + } + } else { + E temp2 = path.get(path.size() - 2); + if (srcA.equals(temp2.src()) || srcA.equals(temp2.dst())) { + return dstB; + } else { + return srcA; + } + } + } + + private List<E> createSpurNode(List<E> path, int n) { + List<E> root = new ArrayList<E>(); + + for (int i = 0; i < n; i++) { + root.add(path.get(i)); + } + return root; + } + + private void transformGraph(List<E> rootPath) { + List<E> prePath; + //remove edges + for (int i = 0; i < pathResults.size(); i++) { + prePath = pathResults.get(i); + if (prePath.size() == 1) { + mutableGraph.removeEdge(prePath.get(0)); + } else if (comparePath(rootPath, prePath)) { + for (int j = 0; j <= rootPath.size(); j++) { + mutableGraph.removeEdge(prePath.get(j)); + } + } + } + for (int i = 0; i < pathCandidates.size(); i++) { + prePath = pathCandidates.get(i); + if (prePath.size() == 1) { + mutableGraph.removeEdge(prePath.get(0)); + } else if (comparePath(rootPath, prePath)) { + for (int j = 0; j <= rootPath.size(); j++) { + mutableGraph.removeEdge(prePath.get(j)); + } + } + } + + if (rootPath.size() == 0) { + return; + } + + //remove nodes + List<V> nodes = new ArrayList<V>(); + nodes.add(source); + V pre = source; + V srcA; + V dstB; + for (int i = 0; i < rootPath.size() - 1; i++) { + E temp = rootPath.get(i); + srcA = temp.src(); + dstB = temp.dst(); + + if (srcA.equals(pre)) { + nodes.add(dstB); + pre = dstB; + } else { + nodes.add(srcA); + pre = srcA; + } + } + for (int i = 0; i < nodes.size(); i++) { + mutableGraph.removeVertex(nodes.get(i)); + } + } + + private boolean comparePath(List<E> path1, List<E> path2) { + if (path1.size() > path2.size()) { + return false; + } + if (path1.size() == 0) { + return true; + } + for (int i = 0; i < path1.size(); i++) { + if (path1.get(i) != path2.get(i)) { + return false; + } + } + return true; + } + + private void addPathResult() { + List<E> sp; + sp = pathCandidates.get(0); + for (int i = 1; i < pathCandidates.size(); i++) { + if (sp.size() > pathCandidates.get(i).size()) { + sp = pathCandidates.get(i); + } + } + pathResults.add(sp); + // Log.info(sp.toString()); + pathCandidates.remove(sp); + } + +} diff --git a/framework/src/onos/utils/misc/src/main/java/org/onlab/graph/MutableAdjacencyListsGraph.java b/framework/src/onos/utils/misc/src/main/java/org/onlab/graph/MutableAdjacencyListsGraph.java new file mode 100644 index 00000000..87571c4b --- /dev/null +++ b/framework/src/onos/utils/misc/src/main/java/org/onlab/graph/MutableAdjacencyListsGraph.java @@ -0,0 +1,160 @@ +/* + * Copyright 2014 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.graph; + +import static com.google.common.base.MoreObjects.toStringHelper; + +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; + +import com.google.common.collect.HashMultimap; +import com.google.common.collect.SetMultimap; + +public class MutableAdjacencyListsGraph<V extends Vertex, E extends Edge<V>> +implements MutableGraph<V, E> { + private Set<V> vertexes = new HashSet<V>(); + private Set<E> edges = new HashSet<E>(); + + private SetMultimap<V, E> sources = HashMultimap.create(); + private SetMultimap<V, E> destinations = HashMultimap.create(); + + /** + * Creates a graph comprising of the specified vertexes and edges. + * + * @param vertex set of graph vertexes + * @param edge set of graph edges + */ + public MutableAdjacencyListsGraph(Set<V> vertex, Set<E> edge) { + vertexes.addAll(vertex); + edges.addAll(edge); + for (E e : edge) { + sources.put(e.src(), e); + vertexes.add(e.src()); + destinations.put(e.dst(), e); + vertexes.add(e.dst()); + } + } + + @Override + public Set<V> getVertexes() { + return vertexes; + } + + @Override + public Set<E> getEdges() { + return edges; + } + + @Override + public Set<E> getEdgesFrom(V src) { + return sources.get(src); + } + + @Override + public Set<E> getEdgesTo(V dst) { + return destinations.get(dst); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj instanceof MutableAdjacencyListsGraph) { + MutableAdjacencyListsGraph that = (MutableAdjacencyListsGraph) obj; + return this.getClass() == that.getClass() && + Objects.equals(this.vertexes, that.vertexes) && + Objects.equals(this.edges, that.edges); + } + return false; + } + + @Override + public int hashCode() { + return Objects.hash(vertexes, edges); + } + + @Override + public String toString() { + return toStringHelper(this) + .add("vertexes", vertexes) + .add("edges", edges) + .toString(); + } + + + @Override + public void addVertex(V vertex) { + if (vertexes != null) { + if (!vertexes.contains(vertex)) { + vertexes.add(vertex); + } + } + } + + @Override + public void removeVertex(V vertex) { + if (vertexes != null && edges != null) { + if (vertexes.contains(vertex)) { + vertexes.remove(vertex); + Set<E> srcEdgesList = sources.get(vertex); + Set<E> dstEdgesList = destinations.get(vertex); + edges.removeAll(srcEdgesList); + edges.removeAll(dstEdgesList); + sources.remove(vertex, srcEdgesList); + sources.remove(vertex, dstEdgesList); + } + } + } + + @Override + public void addEdge(E edge) { + if (edges != null) { + if (!edges.contains(edge)) { + edges.add(edge); + sources.put(edge.src(), edge); + destinations.put(edge.dst(), edge); + } + } + } + + @Override + public void removeEdge(E edge) { + if (edges != null) { + if (edges.contains(edge)) { + edges.remove(edge); + sources.remove(edge.src(), edge); + destinations.remove(edge.dst(), edge); + } + } + } + + @Override + public Graph<V, E> toImmutable() { + return null; + } + + /** + * Clear the graph. + */ + public void clear() { + edges.clear(); + vertexes.clear(); + sources.clear(); + destinations.clear(); + } +} diff --git a/framework/src/onos/utils/misc/src/main/java/org/onlab/graph/MutableGraph.java b/framework/src/onos/utils/misc/src/main/java/org/onlab/graph/MutableGraph.java new file mode 100644 index 00000000..bd2b600c --- /dev/null +++ b/framework/src/onos/utils/misc/src/main/java/org/onlab/graph/MutableGraph.java @@ -0,0 +1,59 @@ +/* + * Copyright 2014 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.graph; + +/** + * Abstraction of a mutable graph that can be constructed gradually. + */ +public interface MutableGraph<V extends Vertex, E extends Edge> extends Graph<V, E> { + + /** + * Adds the specified vertex to this graph. + * + * @param vertex new vertex + */ + void addVertex(V vertex); + + /** + * Removes the specified vertex from the graph. + * + * @param vertex vertex to be removed + */ + void removeVertex(V vertex); + + /** + * Adds the specified edge to this graph. If the edge vertexes are not + * already in the graph, they will be added as well. + * + * @param edge new edge + */ + void addEdge(E edge); + + /** + * Removes the specified edge from the graph. + * + * @param edge edge to be removed + */ + void removeEdge(E edge); + + /** + * Returns an immutable copy of this graph. + * + * @return immutable copy + */ + Graph<V, E> toImmutable(); + +} diff --git a/framework/src/onos/utils/misc/src/main/java/org/onlab/graph/MutablePath.java b/framework/src/onos/utils/misc/src/main/java/org/onlab/graph/MutablePath.java new file mode 100644 index 00000000..5cd8fd0e --- /dev/null +++ b/framework/src/onos/utils/misc/src/main/java/org/onlab/graph/MutablePath.java @@ -0,0 +1,62 @@ +/* + * Copyright 2014 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.graph; + +/** + * Abstraction of a mutable path that allows gradual construction. + */ +public interface MutablePath<V extends Vertex, E extends Edge<V>> extends Path<V, E> { + + /** + * Inserts a new edge at the beginning of this path. The edge must be + * adjacent to the prior start of the path. + * + * @param edge edge to be inserted + */ + void insertEdge(E edge); + + /** + * Appends a new edge at the end of the this path. The edge must be + * adjacent to the prior end of the path. + * + * @param edge edge to be inserted + */ + void appendEdge(E edge); + + /** + * Removes the specified edge. This edge must be either at the start or + * at the end of the path, or it must be a cyclic edge in order not to + * violate the contiguous path property. + * + * @param edge edge to be removed + */ + void removeEdge(E edge); + + /** + * Sets the total path cost as a unit-less double. + * + * @param cost new path cost + */ + void setCost(double cost); + + /** + * Returns an immutable copy of this path. + * + * @return immutable copy + */ + Path<V, E> toImmutable(); + +} diff --git a/framework/src/onos/utils/misc/src/main/java/org/onlab/graph/Path.java b/framework/src/onos/utils/misc/src/main/java/org/onlab/graph/Path.java new file mode 100644 index 00000000..ed9aa2c9 --- /dev/null +++ b/framework/src/onos/utils/misc/src/main/java/org/onlab/graph/Path.java @@ -0,0 +1,45 @@ +/* + * Copyright 2014 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.graph; + +import java.util.List; + +/** + * Representation of a path in a graph as a sequence of edges. Paths are + * assumed to be continuous, where adjacent edges must share a vertex. + * + * @param <V> vertex type + * @param <E> edge type + */ +public interface Path<V extends Vertex, E extends Edge<V>> extends Edge<V> { + + /** + * Returns the list of edges comprising the path. Adjacent edges will + * share the same vertex, meaning that a source of one edge, will be the + * same as the destination of the prior edge. + * + * @return list of path edges + */ + List<E> edges(); + + /** + * Returns the total cost of the path as a unit-less number. + * + * @return path cost as a unit-less number + */ + double cost(); + +} diff --git a/framework/src/onos/utils/misc/src/main/java/org/onlab/graph/SRLGGraphSearch.java b/framework/src/onos/utils/misc/src/main/java/org/onlab/graph/SRLGGraphSearch.java new file mode 100644 index 00000000..21f687a3 --- /dev/null +++ b/framework/src/onos/utils/misc/src/main/java/org/onlab/graph/SRLGGraphSearch.java @@ -0,0 +1,253 @@ +/* + * Copyright 2014 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.graph; + + +import java.util.Map; +import java.util.List; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Set; +import java.util.Random; + + +/** + * SRLG Graph Search finds a pair of paths with disjoint risk groups; i.e + * if one path goes through an edge in risk group 1, the other path will go + * through no edges in risk group 1. + */ +public class SRLGGraphSearch<V extends Vertex, E extends Edge<V>> + extends AbstractGraphPathSearch<V, E> { + + static final int ITERATIONS = 100; + static final int POPSIZE = 50; + + boolean useSuurballe = false; + + static final double INF = 100000000.0; + + int numGroups; + Map<E, Integer> riskGrouping; + + Graph<V, E> orig; + V src, dst; + EdgeWeight<V, E> weight; + + /** + * Creates an SRLG graph search object with the given number + * of groups and given risk mapping. + * + * @param groups the number of disjoint risk groups + * @param grouping map linking edges to integral group assignments + */ + public SRLGGraphSearch(int groups, Map<E, Integer> grouping) { + numGroups = groups; + riskGrouping = grouping; + } + + /** + * Creates an SRLG graph search object from a map, inferring + * the number of groups and creating an integral mapping. + * + * @param grouping map linking edges to object group assignments, + * with same-group status linked to equality + */ + public SRLGGraphSearch(Map<E, Object> grouping) { + if (grouping == null) { + useSuurballe = true; + return; + } + numGroups = 0; + HashMap<Object, Integer> tmpMap = new HashMap<>(); + riskGrouping = new HashMap<>(); + for (E key: grouping.keySet()) { + Object value = grouping.get(key); + if (!tmpMap.containsKey(value)) { + tmpMap.put(value, numGroups); + numGroups++; + } + riskGrouping.put(key, tmpMap.get(value)); + } + } + + @Override + public Result<V, E> search(Graph<V, E> graph, V src, V dst, + EdgeWeight<V, E> weight, int maxPaths) { + if (maxPaths == ALL_PATHS) { + maxPaths = POPSIZE; + } + if (useSuurballe) { + return new SuurballeGraphSearch<V, E>().search(graph, src, dst, weight, ALL_PATHS); + } + if (weight == null) { + weight = edge -> 1; + } + checkArguments(graph, src, dst); + orig = graph; + this.src = src; + this.dst = dst; + this.weight = weight; + List<Subset> best = new GAPopulation<Subset>() + .runGA(ITERATIONS, POPSIZE, maxPaths, new Subset(new boolean[numGroups])); + Set<DisjointPathPair> dpps = new HashSet<DisjointPathPair>(); + for (Subset s: best) { + dpps.addAll(s.buildPaths()); + } + Result<V, E> firstDijkstra = new DijkstraGraphSearch<V, E>() + .search(orig, src, dst, weight, 1); + return new Result<V, E>() { + final DefaultResult search = (DefaultResult) firstDijkstra; + + public V src() { + return src; + } + public V dst() { + return dst; + + } + public Set<Path<V, E>> paths() { + Set<Path<V, E>> pathsD = new HashSet<>(); + for (DisjointPathPair<V, E> path: dpps) { + pathsD.add(path); + } + return pathsD; + } + public Map<V, Double> costs() { + return search.costs(); + + } + public Map<V, Set<E>> parents() { + return search.parents(); + + } + }; + } + + //finds the shortest path in the graph given a subset of edge types to use + private Result<V, E> findShortestPathFromSubset(boolean[] subset) { + Graph<V, E> graph = orig; + EdgeWeight<V, E> modified = new EdgeWeight<V, E>() { + final boolean[] subsetF = subset; + + @Override + public double weight(E edge) { + if (subsetF[riskGrouping.get(edge)]) { + return weight.weight(edge); + } + return INF; + } + }; + + Result<V, E> res = new DijkstraGraphSearch<V, E>().search(graph, src, dst, modified, 1); + return res; + } + /** + * A subset is a type of GA organism that represents a subset of allowed shortest + * paths (and its complement). Its fitness is determined by the sum of the weights + * of the first two shortest paths. + */ + class Subset implements GAOrganism { + + boolean[] subset; + boolean[] not; + Random r = new Random(); + + /** + * Creates a Subset from the given subset array. + * + * @param sub subset array + */ + public Subset(boolean[] sub) { + subset = sub.clone(); + not = new boolean[subset.length]; + for (int i = 0; i < subset.length; i++) { + not[i] = !subset[i]; + } + } + + @Override + public double fitness() { + Set<Path<V, E>> paths1 = findShortestPathFromSubset(subset).paths(); + Set<Path<V, E>> paths2 = findShortestPathFromSubset(not).paths(); + if (paths1.size() == 0 || paths2.size() == 0) { + return INF; + } + return paths1.iterator().next().cost() + paths2.iterator().next().cost(); + } + + @Override + public void mutate() { + int turns = r.nextInt((int) Math.sqrt(subset.length)); + while (turns > 0) { + int choose = r.nextInt(subset.length); + subset[choose] = !subset[choose]; + not[choose] = !not[choose]; + turns--; + } + } + + @Override + public GAOrganism crossWith(GAOrganism org) { + if (!(org.getClass().equals(getClass()))) { + return this; + } + Subset other = (Subset) (org); + boolean[] sub = new boolean[subset.length]; + for (int i = 0; i < subset.length; i++) { + sub[i] = subset[i]; + if (r.nextBoolean()) { + sub[i] = other.subset[i]; + } + } + return new Subset(sub); + } + + @Override + public GAOrganism random() { + boolean[] sub = new boolean[subset.length]; + for (int i = 0; i < sub.length; i++) { + sub[i] = r.nextBoolean(); + } + return new Subset(sub); + } + + /** + * Builds the set of disjoint path pairs for a given subset + * using Dijkstra's algorithm on both the subset and complement + * and returning all pairs with one from each set. + * + * @return all shortest disjoint paths given this subset + */ + public Set<DisjointPathPair> buildPaths() { + Set<DisjointPathPair> dpps = new HashSet<>(); + for (Path<V, E> path1: findShortestPathFromSubset(subset).paths()) { + if (path1.cost() >= INF) { + continue; + } + for (Path<V, E> path2: findShortestPathFromSubset(not).paths()) { + if (path2.cost() >= INF) { + continue; + } + DisjointPathPair<V, E> dpp = new DisjointPathPair<>(path1, path2); + dpps.add(dpp); + } + } + return dpps; + } + } +} diff --git a/framework/src/onos/utils/misc/src/main/java/org/onlab/graph/SuurballeGraphSearch.java b/framework/src/onos/utils/misc/src/main/java/org/onlab/graph/SuurballeGraphSearch.java new file mode 100644 index 00000000..76591c82 --- /dev/null +++ b/framework/src/onos/utils/misc/src/main/java/org/onlab/graph/SuurballeGraphSearch.java @@ -0,0 +1,193 @@ +/* + * 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.graph; + +import java.util.ArrayList; +import java.util.Set; +import java.util.List; +import java.util.Map; +import java.util.HashMap; +import java.util.HashSet; +import java.util.stream.Collectors; + +/** + * Suurballe shortest-path graph search algorithm capable of finding both + * a shortest path, as well as a backup shortest path, between a source and a destination + * such that the sum of the path lengths is minimized. + */ +public class SuurballeGraphSearch<V extends Vertex, E extends Edge<V>> extends DijkstraGraphSearch<V, E> { + + @Override + public Result<V, E> search(Graph<V, E> graph, V src, V dst, + EdgeWeight<V, E> weight, int maxPaths) { + + if (weight == null) { + weight = edge -> 1; + } + + List<DisjointPathPair<V, E>> dpps = new ArrayList<>(); + + final EdgeWeight weightf = weight; + DefaultResult firstDijkstraS = (DefaultResult) super.search(graph, src, dst, weight, ALL_PATHS); + DefaultResult firstDijkstra = (DefaultResult) super.search(graph, src, null, weight, ALL_PATHS); + + //choose an arbitrary shortest path to run Suurballe on + Path<V, E> shortPath = null; + if (firstDijkstraS.paths().size() == 0) { + return firstDijkstraS; + } + for (Path p: firstDijkstraS.paths()) { + shortPath = p; + //transforms the graph so tree edges have 0 weight + EdgeWeight<V, Edge<V>> modified = edge -> { + if (classE().isInstance(edge)) { + return weightf.weight((E) (edge)) + firstDijkstra.cost(edge.src()) + - firstDijkstra.cost(edge.dst()); + } + return 0; + }; + EdgeWeight<V, E> modified2 = edge -> + weightf.weight(edge) + firstDijkstra.cost(edge.src()) - firstDijkstra.cost(edge.dst()); + + //create a residual graph g' by removing all src vertices and reversing 0 length path edges + MutableGraph<V, Edge<V>> gt = mutableCopy(graph); + + Map<Edge<V>, E> revToEdge = new HashMap<>(); + graph.getEdgesTo(src).forEach(gt::removeEdge); + for (E edge: shortPath.edges()) { + gt.removeEdge(edge); + Edge<V> reverse = new Edge<V>() { + final Edge<V> orig = edge; + public V src() { + return orig.dst(); + } + public V dst() { + return orig.src(); + } + public String toString() { + return "ReversedEdge " + "src=" + src() + " dst=" + dst(); + } + }; + revToEdge.put(reverse, edge); + gt.addEdge(reverse); + } + + //rerun dijkstra on the temporary graph to get a second path + Result<V, Edge<V>> secondDijkstra; + secondDijkstra = new DijkstraGraphSearch<V, Edge<V>>().search(gt, src, dst, modified, ALL_PATHS); + + Path<V, Edge<V>> residualShortPath = null; + if (secondDijkstra.paths().size() == 0) { + dpps.add(new DisjointPathPair<V, E>(shortPath, null)); + continue; + } + + for (Path p2: secondDijkstra.paths()) { + residualShortPath = p2; + + MutableGraph<V, E> roundTrip = mutableCopy(graph); + + List<E> tmp = roundTrip.getEdges().stream().collect(Collectors.toList()); + + tmp.forEach(roundTrip::removeEdge); + + shortPath.edges().forEach(roundTrip::addEdge); + + if (residualShortPath != null) { + for (Edge<V> edge: residualShortPath.edges()) { + if (classE().isInstance(edge)) { + roundTrip.addEdge((E) edge); + } else { + roundTrip.removeEdge(revToEdge.get(edge)); + } + } + } + //Actually build the final result + DefaultResult lastSearch = (DefaultResult) super.search(roundTrip, src, dst, weight, ALL_PATHS); + Path<V, E> path1 = lastSearch.paths().iterator().next(); + path1.edges().forEach(roundTrip::removeEdge); + + Set<Path<V, E>> bckpaths = super.search(roundTrip, src, dst, weight, ALL_PATHS).paths(); + Path<V, E> backup = null; + if (bckpaths.size() != 0) { + backup = bckpaths.iterator().next(); + } + + dpps.add(new DisjointPathPair<>(path1, backup)); + } + } + + for (int i = dpps.size() - 1; i > 0; i--) { + if (dpps.get(i).size() <= 1) { + dpps.remove(i); + } + } + + return new Result<V, E>() { + final DefaultResult search = firstDijkstra; + + public V src() { + return src; + } + public V dst() { + return dst; + } + public Set<Path<V, E>> paths() { + Set<Path<V, E>> pathsD = new HashSet<>(); + int paths = 0; + for (DisjointPathPair<V, E> path: dpps) { + pathsD.add((Path<V, E>) path); + paths++; + if (paths == maxPaths) { + break; + } + } + return pathsD; + } + public Map<V, Double> costs() { + return search.costs(); + } + public Map<V, Set<E>> parents() { + return search.parents(); + } + }; + } + + private Class<?> clazzV; + + public Class<?> classV() { + return clazzV; + } + + private Class<?> clazzE; + + public Class<?> classE() { + return clazzE; + } + /** + * Creates a mutable copy of an immutable graph. + * + * @param graph immutable graph + * @return mutable copy + */ + public MutableGraph mutableCopy(Graph<V, E> graph) { + clazzV = graph.getVertexes().iterator().next().getClass(); + clazzE = graph.getEdges().iterator().next().getClass(); + return new MutableAdjacencyListsGraph<V, E>(graph.getVertexes(), graph.getEdges()); + } +} + diff --git a/framework/src/onos/utils/misc/src/main/java/org/onlab/graph/TarjanGraphSearch.java b/framework/src/onos/utils/misc/src/main/java/org/onlab/graph/TarjanGraphSearch.java new file mode 100644 index 00000000..5bf305e6 --- /dev/null +++ b/framework/src/onos/utils/misc/src/main/java/org/onlab/graph/TarjanGraphSearch.java @@ -0,0 +1,212 @@ +/* + * Copyright 2014 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.graph; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * Tarjan algorithm for searching a graph and producing results describing + * the graph SCC (strongly-connected components). + */ +public class TarjanGraphSearch<V extends Vertex, E extends Edge<V>> + implements GraphSearch<V, E> { + + /** + * {@inheritDoc} + * <p> + * This implementation produces results augmented with information on + * SCCs within the graph. + * </p> + * <p> + * To prevent traversal of an edge, the {@link EdgeWeight#weight} should + * return a negative value as an edge weight. + * </p> + */ + @Override + public SCCResult<V, E> search(Graph<V, E> graph, EdgeWeight<V, E> weight) { + SCCResult<V, E> result = new SCCResult<>(graph); + for (V vertex : graph.getVertexes()) { + VertexData data = result.data(vertex); + if (data == null) { + connect(graph, vertex, weight, result); + } + } + return result.build(); + } + + /** + * Scans the specified graph, using recursion, and produces SCC results. + * + * @param graph graph to search + * @param vertex current vertex to scan and connect + * @param weight optional edge weight + * @param result graph search result + * @return augmentation vertexData for the current vertex + */ + private VertexData<V> connect(Graph<V, E> graph, V vertex, + EdgeWeight<V, E> weight, + SCCResult<V, E> result) { + VertexData<V> data = result.addData(vertex); + + // Scan through all egress edges of the current vertex. + for (E edge : graph.getEdgesFrom(vertex)) { + V nextVertex = edge.dst(); + + // If edge weight is negative, skip it. + if (weight != null && weight.weight(edge) < 0) { + continue; + } + + // Attempt to get the augmentation vertexData for the next vertex. + VertexData<V> nextData = result.data(nextVertex); + if (nextData == null) { + // Next vertex has not been visited yet, so do this now. + nextData = connect(graph, nextVertex, weight, result); + data.lowLink = Math.min(data.lowLink, nextData.lowLink); + + } else if (result.visited(nextData)) { + // Next vertex has been visited, which means it is in the + // same cluster as the current vertex. + data.lowLink = Math.min(data.lowLink, nextData.index); + } + } + + if (data.lowLink == data.index) { + result.addCluster(data); + } + return data; + } + + /** + * Graph search result augmented with SCC vertexData. + */ + public static final class SCCResult<V extends Vertex, E extends Edge<V>> + implements Result { + + private final Graph<V, E> graph; + private List<Set<V>> clusterVertexes = new ArrayList<>(); + private List<Set<E>> clusterEdges = new ArrayList<>(); + + private int index = 0; + private final Map<V, VertexData<V>> vertexData = new HashMap<>(); + private final List<VertexData<V>> visited = new ArrayList<>(); + + private SCCResult(Graph<V, E> graph) { + this.graph = graph; + } + + /** + * Returns the number of SCC clusters in the graph. + * + * @return number of clusters + */ + public int clusterCount() { + return clusterEdges.size(); + } + + /** + * Returns the list of strongly connected vertex clusters. + * + * @return list of strongly connected vertex sets + */ + public List<Set<V>> clusterVertexes() { + return clusterVertexes; + } + + /** + * Returns the list of edges linking strongly connected vertex clusters. + * + * @return list of strongly connected edge sets + */ + public List<Set<E>> clusterEdges() { + return clusterEdges; + } + + // Gets the augmentation vertexData for the specified vertex + private VertexData<V> data(V vertex) { + return vertexData.get(vertex); + } + + // Adds augmentation vertexData for the specified vertex + private VertexData<V> addData(V vertex) { + VertexData<V> d = new VertexData<>(vertex, index); + vertexData.put(vertex, d); + visited.add(0, d); + index++; + return d; + } + + // Indicates whether the given vertex has been visited + private boolean visited(VertexData data) { + return visited.contains(data); + } + + // Adds a new cluster for the specified vertex + private void addCluster(VertexData data) { + Set<V> vertexes = findClusterVertices(data); + clusterVertexes.add(vertexes); + clusterEdges.add(findClusterEdges(vertexes)); + } + + private Set<V> findClusterVertices(VertexData data) { + VertexData<V> nextVertexData; + Set<V> vertexes = new HashSet<>(); + do { + nextVertexData = visited.remove(0); + vertexes.add(nextVertexData.vertex); + } while (data != nextVertexData); + return Collections.unmodifiableSet(vertexes); + } + + private Set<E> findClusterEdges(Set<V> vertexes) { + Set<E> edges = new HashSet<>(); + for (V vertex : vertexes) { + for (E edge : graph.getEdgesFrom(vertex)) { + if (vertexes.contains((edge.dst()))) { + edges.add(edge); + } + } + } + return Collections.unmodifiableSet(edges); + } + + public SCCResult<V, E> build() { + clusterVertexes = Collections.unmodifiableList(clusterVertexes); + clusterEdges = Collections.unmodifiableList(clusterEdges); + return this; + } + } + + // Augments the vertex to assist in determining SCC clusters. + private static final class VertexData<V extends Vertex> { + final V vertex; + int index; + int lowLink; + + private VertexData(V vertex, int index) { + this.vertex = vertex; + this.index = index; + this.lowLink = index; + } + } + +} diff --git a/framework/src/onos/utils/misc/src/main/java/org/onlab/graph/Vertex.java b/framework/src/onos/utils/misc/src/main/java/org/onlab/graph/Vertex.java new file mode 100644 index 00000000..80e3e7df --- /dev/null +++ b/framework/src/onos/utils/misc/src/main/java/org/onlab/graph/Vertex.java @@ -0,0 +1,22 @@ +/* + * Copyright 2014 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.graph; + +/** + * Representation of a graph vertex. + */ +public interface Vertex { +} diff --git a/framework/src/onos/utils/misc/src/main/java/org/onlab/graph/package-info.java b/framework/src/onos/utils/misc/src/main/java/org/onlab/graph/package-info.java new file mode 100644 index 00000000..2ee949d2 --- /dev/null +++ b/framework/src/onos/utils/misc/src/main/java/org/onlab/graph/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2014 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. + */ + +/** + * Graph abstractions and graph path finding algorithms. + */ +package org.onlab.graph; diff --git a/framework/src/onos/utils/misc/src/main/java/org/onlab/metrics/EventMetric.java b/framework/src/onos/utils/misc/src/main/java/org/onlab/metrics/EventMetric.java new file mode 100644 index 00000000..d20ca53f --- /dev/null +++ b/framework/src/onos/utils/misc/src/main/java/org/onlab/metrics/EventMetric.java @@ -0,0 +1,118 @@ +/* + * Copyright 2014 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.metrics; + +import com.codahale.metrics.Gauge; +import com.codahale.metrics.Meter; + +/** + * Metric measurements for events. + */ +public class EventMetric { + private static final String GAUGE_TIMESTAMP_NAME = "Timestamp.EpochMs"; + private static final String METER_RATE_NAME = "Rate"; + + private final MetricsService metricsService; + private final String componentName; + private final String featureName; + + private MetricsComponent metricsComponent; + private MetricsFeature metricsFeature; + + private volatile long lastEventTimestampEpochMs = 0; + private Gauge<Long> lastEventTimestampGauge; + private Meter eventRateMeter; + + /** + * Constructor. + * + * @param metricsService the Metrics Service to use for Metrics + * registration and deregistration + * @param componentName the Metrics Component Name to use for Metrics + * registration and deregistration + * @param featureName the Metrics Feature Name to use for Metrics + * registration and deregistration + */ + public EventMetric(MetricsService metricsService, String componentName, + String featureName) { + this.metricsService = metricsService; + this.componentName = componentName; + this.featureName = featureName; + } + + /** + * Registers the metrics. + */ + public void registerMetrics() { + metricsComponent = metricsService.registerComponent(componentName); + metricsFeature = metricsComponent.registerFeature(featureName); + + lastEventTimestampEpochMs = 0; + lastEventTimestampGauge = + metricsService.registerMetric(metricsComponent, + metricsFeature, + GAUGE_TIMESTAMP_NAME, + new Gauge<Long>() { + @Override + public Long getValue() { + return lastEventTimestampEpochMs; + } + }); + + eventRateMeter = metricsService.createMeter(metricsComponent, + metricsFeature, + METER_RATE_NAME); + } + + /** + * Removes the metrics. + */ + public void removeMetrics() { + lastEventTimestampEpochMs = 0; + metricsService.removeMetric(metricsComponent, + metricsFeature, + GAUGE_TIMESTAMP_NAME); + metricsService.removeMetric(metricsComponent, + metricsFeature, + METER_RATE_NAME); + } + + /** + * Updates the metric measurements for a single event. + */ + public void eventReceived() { + lastEventTimestampEpochMs = System.currentTimeMillis(); + eventRateMeter.mark(1); + } + + /** + * Gets the last event timestamp Gauge (ms from the Epoch). + * + * @return the last event timestamp Gauge (ms from the Epoch) + */ + public Gauge<Long> lastEventTimestampGauge() { + return lastEventTimestampGauge; + } + + /** + * Gets the event rate meter. + * + * @return the event rate meter + */ + public Meter eventRateMeter() { + return eventRateMeter; + } +} diff --git a/framework/src/onos/utils/misc/src/main/java/org/onlab/metrics/MetricsComponent.java b/framework/src/onos/utils/misc/src/main/java/org/onlab/metrics/MetricsComponent.java new file mode 100644 index 00000000..cc8fe83c --- /dev/null +++ b/framework/src/onos/utils/misc/src/main/java/org/onlab/metrics/MetricsComponent.java @@ -0,0 +1,60 @@ +/* + * Copyright 2014 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.metrics; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +/** + * Components to register for metrics. + */ +public class MetricsComponent implements MetricsComponentRegistry { + private final String name; + + /** + * Registry to hold the Features defined in this Component. + */ + private final ConcurrentMap<String, MetricsFeature> featuresRegistry = + new ConcurrentHashMap<>(); + + /** + * Constructs a component from a name. + * + * @param newName name of the component + */ + MetricsComponent(final String newName) { + name = newName; + } + + @Override + public String getName() { + return name; + } + + @Override + public MetricsFeature registerFeature(final String featureName) { + MetricsFeature feature = featuresRegistry.get(featureName); + if (feature == null) { + final MetricsFeature createdFeature = + new MetricsFeature(featureName); + feature = featuresRegistry.putIfAbsent(featureName, createdFeature); + if (feature == null) { + feature = createdFeature; + } + } + return feature; + } +} diff --git a/framework/src/onos/utils/misc/src/main/java/org/onlab/metrics/MetricsComponentRegistry.java b/framework/src/onos/utils/misc/src/main/java/org/onlab/metrics/MetricsComponentRegistry.java new file mode 100644 index 00000000..89f6ec5e --- /dev/null +++ b/framework/src/onos/utils/misc/src/main/java/org/onlab/metrics/MetricsComponentRegistry.java @@ -0,0 +1,36 @@ +/* + * Copyright 2014 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.metrics; + +/** + * Registry Entry for Metrics Components. + */ +public interface MetricsComponentRegistry { + /** + * Fetches the name of the Component. + * + * @return name of the Component + */ + String getName(); + + /** + * Registers a Feature for this component. + * + * @param featureName name of the Feature to register + * @return Feature object that can be used when creating Metrics + */ + MetricsFeature registerFeature(String featureName); +} diff --git a/framework/src/onos/utils/misc/src/main/java/org/onlab/metrics/MetricsFeature.java b/framework/src/onos/utils/misc/src/main/java/org/onlab/metrics/MetricsFeature.java new file mode 100644 index 00000000..bc6753c9 --- /dev/null +++ b/framework/src/onos/utils/misc/src/main/java/org/onlab/metrics/MetricsFeature.java @@ -0,0 +1,41 @@ +/* + * Copyright 2014 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.metrics; + +/** + * Features to tag metrics. + */ +public class MetricsFeature { + private final String name; + + /** + * Constructs a Feature from a name. + * + * @param newName name of the Feature + */ + public MetricsFeature(final String newName) { + name = newName; + } + + /** + * Fetches the name of the Feature. + * + * @return name of the Feature + */ + public String getName() { + return name; + } +} diff --git a/framework/src/onos/utils/misc/src/main/java/org/onlab/metrics/MetricsManager.java b/framework/src/onos/utils/misc/src/main/java/org/onlab/metrics/MetricsManager.java new file mode 100644 index 00000000..6c9314ad --- /dev/null +++ b/framework/src/onos/utils/misc/src/main/java/org/onlab/metrics/MetricsManager.java @@ -0,0 +1,304 @@ +/* + * Copyright 2014 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.metrics; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +import com.codahale.metrics.Counter; +import com.codahale.metrics.Gauge; +import com.codahale.metrics.Histogram; +import com.codahale.metrics.Meter; +import com.codahale.metrics.Metric; +import com.codahale.metrics.MetricFilter; +import com.codahale.metrics.MetricRegistry; +import com.codahale.metrics.Timer; + +/** + * This class holds the Metrics registry for ONOS. + * All metrics (Counter, Histogram, Timer, Meter, Gauge) use a hierarchical + * string-based naming scheme: COMPONENT.FEATURE.NAME. + * Example: "Topology.Counters.TopologyUpdates". + * The COMPONENT and FEATURE names have to be registered in advance before + * a metric can be created. Example: + * <pre> + * <code> + * private final MetricsManager.MetricsComponent COMPONENT = + * MetricsManager.registerComponent("Topology"); + * private final MetricsManager.MetricsFeature FEATURE = + * COMPONENT.registerFeature("Counters"); + * private final Counter counterTopologyUpdates = + * MetricsManager.createCounter(COMPONENT, FEATURE, "TopologyUpdates"); + * </code> + * </pre> + * Gauges are slightly different because they are not created directly in + * this class, but are allocated by the caller and passed in for registration: + * <pre> + * <code> + * private final Gauge<Long> gauge = + * new {@literal Gauge<Long>}() { + * {@literal @}Override + * public Long getValue() { + * return gaugeValue; + * } + * }; + * MetricsManager.registerMetric(COMPONENT, FEATURE, GAUGE_NAME, gauge); + * </code> + * </pre> + */ +public class MetricsManager implements MetricsService { + + /** + * Registry to hold the Components defined in the system. + */ + private ConcurrentMap<String, MetricsComponent> componentsRegistry = + new ConcurrentHashMap<>(); + + /** + * Registry for the Metrics objects created in the system. + */ + private MetricRegistry metricsRegistry = new MetricRegistry(); + + /** + * Clears the internal state. + */ + protected void clear() { + this.componentsRegistry = new ConcurrentHashMap<>(); + this.metricsRegistry = new MetricRegistry(); + } + + /** + * Registers a component. + * + * @param name name of the Component to register + * @return MetricsComponent object that can be used to create Metrics. + */ + @Override + public MetricsComponent registerComponent(final String name) { + MetricsComponent component = componentsRegistry.get(name); + if (component == null) { + final MetricsComponent createdComponent = + new MetricsComponent(name); + component = componentsRegistry.putIfAbsent(name, createdComponent); + if (component == null) { + component = createdComponent; + } + } + return component; + } + + /** + * Generates a name for a Metric from its component and feature. + * + * @param component component the metric is defined in + * @param feature feature the metric is defined in + * @param metricName local name of the metric + * + * @return full name of the metric + */ + private String generateName(final MetricsComponent component, + final MetricsFeature feature, + final String metricName) { + return MetricRegistry.name(component.getName(), + feature.getName(), + metricName); + } + + /** + * Creates a Counter metric. + * + * @param component component the Counter is defined in + * @param feature feature the Counter is defined in + * @param metricName local name of the metric + * @return the created Counter Meteric + */ + @Override + public Counter createCounter(final MetricsComponent component, + final MetricsFeature feature, + final String metricName) { + final String name = generateName(component, feature, metricName); + return metricsRegistry.counter(name); + } + + /** + * Creates a Histogram metric. + * + * @param component component the Histogram is defined in + * @param feature feature the Histogram is defined in + * @param metricName local name of the metric + * @return the created Histogram Metric + */ + @Override + public Histogram createHistogram(final MetricsComponent component, + final MetricsFeature feature, + final String metricName) { + final String name = generateName(component, feature, metricName); + return metricsRegistry.histogram(name); + } + + /** + * Creates a Timer metric. + * + * @param component component the Timer is defined in + * @param feature feature the Timer is defined in + * @param metricName local name of the metric + * @return the created Timer Metric + */ + @Override + public Timer createTimer(final MetricsComponent component, + final MetricsFeature feature, + final String metricName) { + final String name = generateName(component, feature, metricName); + return metricsRegistry.timer(name); + } + + /** + * Creates a Meter metric. + * + * @param component component the Meter is defined in + * @param feature feature the Meter is defined in + * @param metricName local name of the metric + * @return the created Meter Metric + */ + @Override + public Meter createMeter(final MetricsComponent component, + final MetricsFeature feature, + final String metricName) { + final String name = generateName(component, feature, metricName); + return metricsRegistry.meter(name); + } + + /** + * Registers an already created Metric. This is used for situation where a + * caller needs to allocate its own Metric, but still register it with the + * system. + * + * @param <T> Metric type + * @param component component the Metric is defined in + * @param feature feature the Metric is defined in + * @param metricName local name of the metric + * @param metric Metric to register + * @return the registered Metric + */ + @Override + public <T extends Metric> T registerMetric( + final MetricsComponent component, + final MetricsFeature feature, + final String metricName, + final T metric) { + final String name = generateName(component, feature, metricName); + metricsRegistry.register(name, metric); + return metric; + } + + /** + * Removes the metric with the given name. + * + * @param component component the Metric is defined in + * @param feature feature the Metric is defined in + * @param metricName local name of the metric + * @return true if the metric existed and was removed, otherwise false + */ + @Override + public boolean removeMetric(final MetricsComponent component, + final MetricsFeature feature, + final String metricName) { + final String name = generateName(component, feature, metricName); + return metricsRegistry.remove(name); + } + + /** + * Fetches the existing Timers. + * + * @param filter filter to use to select Timers + * @return a map of the Timers that match the filter, with the key as the + * name String to the Timer. + */ + @Override + public Map<String, Timer> getTimers(final MetricFilter filter) { + return metricsRegistry.getTimers(filter); + } + + /** + * Fetches the existing Gauges. + * + * @param filter filter to use to select Gauges + * @return a map of the Gauges that match the filter, with the key as the + * name String to the Gauge. + */ + @Override + public Map<String, Gauge> getGauges(final MetricFilter filter) { + return metricsRegistry.getGauges(filter); + } + + /** + * Fetches the existing Counters. + * + * @param filter filter to use to select Counters + * @return a map of the Counters that match the filter, with the key as the + * name String to the Counter. + */ + @Override + public Map<String, Counter> getCounters(final MetricFilter filter) { + return metricsRegistry.getCounters(filter); + } + + /** + * Fetches the existing Meters. + * + * @param filter filter to use to select Meters + * @return a map of the Meters that match the filter, with the key as the + * name String to the Meter. + */ + @Override + public Map<String, Meter> getMeters(final MetricFilter filter) { + return metricsRegistry.getMeters(filter); + } + + /** + * Fetches the existing Histograms. + * + * @param filter filter to use to select Histograms + * @return a map of the Histograms that match the filter, with the key as + * the name String to the Histogram. + */ + @Override + public Map<String, Histogram> getHistograms(final MetricFilter filter) { + return metricsRegistry.getHistograms(filter); + } + + /** + * Removes all Metrics that match a given filter. + * + * @param filter filter to use to select the Metrics to remove. + */ + @Override + public void removeMatching(final MetricFilter filter) { + metricsRegistry.removeMatching(filter); + } + + /** + * Fetches the existing Meters. + * + * + * @return a map of all metrics with the key as the + * name String to the Meter. + */ + public Map<String, Metric> getMetrics() { + return metricsRegistry.getMetrics(); + } +} diff --git a/framework/src/onos/utils/misc/src/main/java/org/onlab/metrics/MetricsService.java b/framework/src/onos/utils/misc/src/main/java/org/onlab/metrics/MetricsService.java new file mode 100644 index 00000000..4f0d67a8 --- /dev/null +++ b/framework/src/onos/utils/misc/src/main/java/org/onlab/metrics/MetricsService.java @@ -0,0 +1,178 @@ +/* + * Copyright 2014 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.metrics; + +import java.util.Map; + +import com.codahale.metrics.Counter; +import com.codahale.metrics.Gauge; +import com.codahale.metrics.Histogram; +import com.codahale.metrics.Meter; +import com.codahale.metrics.Metric; +import com.codahale.metrics.MetricFilter; +import com.codahale.metrics.Timer; + +/** + * Metrics Service to collect metrics. + */ +public interface MetricsService { + + /** + * Registers a component. + * + * @param name name of the Component to register + * @return MetricsComponent object that can be used to create Metrics. + */ + MetricsComponent registerComponent(String name); + + /** + * Creates a Counter metric. + * + * @param component component the Counter is defined in + * @param feature feature the Counter is defined in + * @param metricName local name of the metric + * @return the created Counter Meteric + */ + Counter createCounter(MetricsComponent component, + MetricsFeature feature, + String metricName); + + /** + * Creates a Histogram metric. + * + * @param component component the Histogram is defined in + * @param feature feature the Histogram is defined in + * @param metricName local name of the metric + * @return the created Histogram Metric + */ + Histogram createHistogram(MetricsComponent component, + MetricsFeature feature, + String metricName); + + /** + * Creates a Timer metric. + * + * @param component component the Timer is defined in + * @param feature feature the Timer is defined in + * @param metricName local name of the metric + * @return the created Timer Metric + */ + Timer createTimer(MetricsComponent component, + MetricsFeature feature, + String metricName); + + /** + * Creates a Meter metric. + * + * @param component component the Meter is defined in + * @param feature feature the Meter is defined in + * @param metricName local name of the metric + * @return the created Meter Metric + */ + Meter createMeter(MetricsComponent component, + MetricsFeature feature, + String metricName); + + /** + * Registers an already created Metric. This is used for situation where a + * caller needs to allocate its own Metric, but still register it with the + * system. + * + * @param <T> Metric type + * @param component component the Metric is defined in + * @param feature feature the Metric is defined in + * @param metricName local name of the metric + * @param metric Metric to register + * @return the registered Metric + */ + <T extends Metric> T registerMetric( + MetricsComponent component, + MetricsFeature feature, + String metricName, + T metric); + + /** + * Removes the metric with the given name. + * + * @param component component the Metric is defined in + * @param feature feature the Metric is defined in + * @param metricName local name of the metric + * @return true if the metric existed and was removed, otherwise false + */ + boolean removeMetric(MetricsComponent component, + MetricsFeature feature, + String metricName); + + /** + * Fetches the existing Timers. + * + * @param filter filter to use to select Timers + * @return a map of the Timers that match the filter, with the key as the + * name String to the Timer. + */ + Map<String, Timer> getTimers(MetricFilter filter); + + /** + * Fetches the existing Gauges. + * + * @param filter filter to use to select Gauges + * @return a map of the Gauges that match the filter, with the key as the + * name String to the Gauge. + */ + Map<String, Gauge> getGauges(MetricFilter filter); + + /** + * Fetches the existing Counters. + * + * @param filter filter to use to select Counters + * @return a map of the Counters that match the filter, with the key as the + * name String to the Counter. + */ + Map<String, Counter> getCounters(MetricFilter filter); + + /** + * Fetches the existing Meters. + * + * @param filter filter to use to select Meters + * @return a map of the Meters that match the filter, with the key as the + * name String to the Meter. + */ + Map<String, Meter> getMeters(MetricFilter filter); + + /** + * Fetches the existing Histograms. + * + * @param filter filter to use to select Histograms + * @return a map of the Histograms that match the filter, with the key as + * the name String to the Histogram. + */ + Map<String, Histogram> getHistograms(MetricFilter filter); + + /** + * Fetches the existing metrics. + * + * @return a map of the Metrics, with the key as + * the name String to the Histogram. + */ + Map<String, Metric> getMetrics(); + + /** + * Removes all Metrics that match a given filter. + * + * @param filter filter to use to select the Metrics to remove. + */ + void removeMatching(MetricFilter filter); +} diff --git a/framework/src/onos/utils/misc/src/main/java/org/onlab/metrics/MetricsUtil.java b/framework/src/onos/utils/misc/src/main/java/org/onlab/metrics/MetricsUtil.java new file mode 100644 index 00000000..cab6c0ea --- /dev/null +++ b/framework/src/onos/utils/misc/src/main/java/org/onlab/metrics/MetricsUtil.java @@ -0,0 +1,56 @@ +/* + * Copyright 2014 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.metrics; + +import com.codahale.metrics.Timer; +import com.codahale.metrics.Timer.Context; + +public final class MetricsUtil { + + /** + * Starts the Metric Timer. + * <p> + * If the given timer was null, it will silently return null. + * </p> + * + * @param timer timer to start + * @return timing context, if timer was not null + */ + public static Context startTimer(Timer timer) { + if (timer != null) { + return timer.time(); + } + return null; + } + + /** + * Stops the Metric Timer context. + * <p> + * If the given context was null, it will silently be ignored. + * </p> + * + * @param context timing context to stop, if not null. + */ + public static void stopTimer(Context context) { + if (context != null) { + context.stop(); + } + } + + // avoid instantiation + private MetricsUtil() {} +} diff --git a/framework/src/onos/utils/misc/src/main/java/org/onlab/metrics/package-info.java b/framework/src/onos/utils/misc/src/main/java/org/onlab/metrics/package-info.java new file mode 100644 index 00000000..0a61f353 --- /dev/null +++ b/framework/src/onos/utils/misc/src/main/java/org/onlab/metrics/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2014 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. + */ + +/** + * Misc utils for various performance metrics. + */ +package org.onlab.metrics; diff --git a/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/ARP.java b/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/ARP.java new file mode 100644 index 00000000..dc3c07f1 --- /dev/null +++ b/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/ARP.java @@ -0,0 +1,439 @@ +/* + * Copyright 2014 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.Arrays; + +import static org.onlab.packet.PacketUtils.*; + +/** + * + * + */ +public class ARP extends BasePacket { + public static final short HW_TYPE_ETHERNET = 0x1; + + public static final short PROTO_TYPE_IP = 0x800; + + public static final short OP_REQUEST = 0x1; + public static final short OP_REPLY = 0x2; + public static final short OP_RARP_REQUEST = 0x3; + public static final short OP_RARP_REPLY = 0x4; + + public static final short INITIAL_HEADER_LENGTH = 8; + + protected short hardwareType; + protected short protocolType; + protected byte hardwareAddressLength; + protected byte protocolAddressLength; + protected short opCode; + protected byte[] senderHardwareAddress; + protected byte[] senderProtocolAddress; + protected byte[] targetHardwareAddress; + protected byte[] targetProtocolAddress; + + /** + * @return the hardwareType + */ + public short getHardwareType() { + return this.hardwareType; + } + + /** + * @param hwType + * the hardwareType to set + * @return this + */ + public ARP setHardwareType(final short hwType) { + this.hardwareType = hwType; + return this; + } + + /** + * @return the protocolType + */ + public short getProtocolType() { + return this.protocolType; + } + + /** + * @param protoType + * the protocolType to set + * @return this + */ + public ARP setProtocolType(final short protoType) { + this.protocolType = protoType; + return this; + } + + /** + * @return the hardwareAddressLength + */ + public byte getHardwareAddressLength() { + return this.hardwareAddressLength; + } + + /** + * @param hwAddressLength + * the hardwareAddressLength to set + * @return this + */ + public ARP setHardwareAddressLength(final byte hwAddressLength) { + this.hardwareAddressLength = hwAddressLength; + return this; + } + + /** + * @return the protocolAddressLength + */ + public byte getProtocolAddressLength() { + return this.protocolAddressLength; + } + + /** + * @param protoAddressLength + * the protocolAddressLength to set + * @return this + */ + public ARP setProtocolAddressLength(final byte protoAddressLength) { + this.protocolAddressLength = protoAddressLength; + return this; + } + + /** + * @return the opCode + */ + public short getOpCode() { + return this.opCode; + } + + /** + * @param op + * the opCode to set + * @return this + */ + public ARP setOpCode(final short op) { + this.opCode = op; + return this; + } + + /** + * @return the senderHardwareAddress + */ + public byte[] getSenderHardwareAddress() { + return this.senderHardwareAddress; + } + + /** + * @param senderHWAddress + * the senderHardwareAddress to set + * @return this + */ + public ARP setSenderHardwareAddress(final byte[] senderHWAddress) { + this.senderHardwareAddress = senderHWAddress; + return this; + } + + /** + * @return the senderProtocolAddress + */ + public byte[] getSenderProtocolAddress() { + return this.senderProtocolAddress; + } + + /** + * @param senderProtoAddress + * the senderProtocolAddress to set + * @return this + */ + public ARP setSenderProtocolAddress(final byte[] senderProtoAddress) { + this.senderProtocolAddress = senderProtoAddress; + return this; + } + + public ARP setSenderProtocolAddress(final int address) { + this.senderProtocolAddress = ByteBuffer.allocate(4).putInt(address) + .array(); + return this; + } + + /** + * @return the targetHardwareAddress + */ + public byte[] getTargetHardwareAddress() { + return this.targetHardwareAddress; + } + + /** + * @param targetHWAddress + * the targetHardwareAddress to set + * @return this + */ + public ARP setTargetHardwareAddress(final byte[] targetHWAddress) { + this.targetHardwareAddress = targetHWAddress; + return this; + } + + /** + * @return the targetProtocolAddress + */ + public byte[] getTargetProtocolAddress() { + return this.targetProtocolAddress; + } + + /** + * @return True if gratuitous ARP (SPA = TPA), false otherwise + */ + public boolean isGratuitous() { + assert this.senderProtocolAddress.length == this.targetProtocolAddress.length; + + int indx = 0; + while (indx < this.senderProtocolAddress.length) { + if (this.senderProtocolAddress[indx] != this.targetProtocolAddress[indx]) { + return false; + } + indx++; + } + + return true; + } + + /** + * @param targetProtoAddress + * the targetProtocolAddress to set + * @return this + */ + public ARP setTargetProtocolAddress(final byte[] targetProtoAddress) { + this.targetProtocolAddress = targetProtoAddress; + return this; + } + + public ARP setTargetProtocolAddress(final int address) { + this.targetProtocolAddress = ByteBuffer.allocate(4).putInt(address) + .array(); + return this; + } + + @Override + public byte[] serialize() { + final int length = 8 + 2 * (0xff & this.hardwareAddressLength) + 2 + * (0xff & this.protocolAddressLength); + final byte[] data = new byte[length]; + final ByteBuffer bb = ByteBuffer.wrap(data); + bb.putShort(this.hardwareType); + bb.putShort(this.protocolType); + bb.put(this.hardwareAddressLength); + bb.put(this.protocolAddressLength); + bb.putShort(this.opCode); + bb.put(this.senderHardwareAddress, 0, 0xff & this.hardwareAddressLength); + bb.put(this.senderProtocolAddress, 0, 0xff & this.protocolAddressLength); + bb.put(this.targetHardwareAddress, 0, 0xff & this.hardwareAddressLength); + bb.put(this.targetProtocolAddress, 0, 0xff & this.protocolAddressLength); + return data; + } + + @Override + public IPacket deserialize(final byte[] data, final int offset, + final int length) { + final ByteBuffer bb = ByteBuffer.wrap(data, offset, length); + this.hardwareType = bb.getShort(); + this.protocolType = bb.getShort(); + this.hardwareAddressLength = bb.get(); + this.protocolAddressLength = bb.get(); + this.opCode = bb.getShort(); + this.senderHardwareAddress = new byte[0xff & this.hardwareAddressLength]; + bb.get(this.senderHardwareAddress, 0, this.senderHardwareAddress.length); + this.senderProtocolAddress = new byte[0xff & this.protocolAddressLength]; + bb.get(this.senderProtocolAddress, 0, this.senderProtocolAddress.length); + this.targetHardwareAddress = new byte[0xff & this.hardwareAddressLength]; + bb.get(this.targetHardwareAddress, 0, this.targetHardwareAddress.length); + this.targetProtocolAddress = new byte[0xff & this.protocolAddressLength]; + bb.get(this.targetProtocolAddress, 0, this.targetProtocolAddress.length); + return this; + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + final int prime = 13121; + int result = super.hashCode(); + result = prime * result + this.hardwareAddressLength; + result = prime * result + this.hardwareType; + result = prime * result + this.opCode; + result = prime * result + this.protocolAddressLength; + result = prime * result + this.protocolType; + result = prime * result + Arrays.hashCode(this.senderHardwareAddress); + result = prime * result + Arrays.hashCode(this.senderProtocolAddress); + result = prime * result + Arrays.hashCode(this.targetHardwareAddress); + result = prime * result + Arrays.hashCode(this.targetProtocolAddress); + return result; + } + + /* + * (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 ARP)) { + return false; + } + final ARP other = (ARP) obj; + if (this.hardwareAddressLength != other.hardwareAddressLength) { + return false; + } + if (this.hardwareType != other.hardwareType) { + return false; + } + if (this.opCode != other.opCode) { + return false; + } + if (this.protocolAddressLength != other.protocolAddressLength) { + return false; + } + if (this.protocolType != other.protocolType) { + return false; + } + if (!Arrays.equals(this.senderHardwareAddress, + other.senderHardwareAddress)) { + return false; + } + if (!Arrays.equals(this.senderProtocolAddress, + other.senderProtocolAddress)) { + return false; + } + if (!Arrays.equals(this.targetHardwareAddress, + other.targetHardwareAddress)) { + return false; + } + if (!Arrays.equals(this.targetProtocolAddress, + other.targetProtocolAddress)) { + return false; + } + return true; + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return "ARP [hardwareType=" + this.hardwareType + ", protocolType=" + + this.protocolType + ", hardwareAddressLength=" + + this.hardwareAddressLength + ", protocolAddressLength=" + + this.protocolAddressLength + ", opCode=" + this.opCode + + ", senderHardwareAddress=" + + Arrays.toString(this.senderHardwareAddress) + + ", senderProtocolAddress=" + + Arrays.toString(this.senderProtocolAddress) + + ", targetHardwareAddress=" + + Arrays.toString(this.targetHardwareAddress) + + ", targetProtocolAddress=" + + Arrays.toString(this.targetProtocolAddress) + "]"; + } + + /** + * Builds an ARP reply based on a request. + * + * @param srcIp the IP address to use as the reply source + * @param srcMac the MAC address to use as the reply source + * @param request the ARP request we got + * @return an Ethernet frame containing the ARP reply + */ + public static Ethernet buildArpReply(Ip4Address srcIp, MacAddress srcMac, + Ethernet request) { + + Ethernet eth = new Ethernet(); + eth.setDestinationMACAddress(request.getSourceMAC()); + eth.setSourceMACAddress(srcMac); + eth.setEtherType(Ethernet.TYPE_ARP); + eth.setVlanID(request.getVlanID()); + + ARP arp = new ARP(); + arp.setOpCode(ARP.OP_REPLY); + arp.setProtocolType(ARP.PROTO_TYPE_IP); + arp.setHardwareType(ARP.HW_TYPE_ETHERNET); + + arp.setProtocolAddressLength((byte) Ip4Address.BYTE_LENGTH); + arp.setHardwareAddressLength((byte) Ethernet.DATALAYER_ADDRESS_LENGTH); + arp.setSenderHardwareAddress(srcMac.toBytes()); + arp.setTargetHardwareAddress(request.getSourceMACAddress()); + + arp.setTargetProtocolAddress(((ARP) request.getPayload()) + .getSenderProtocolAddress()); + arp.setSenderProtocolAddress(srcIp.toInt()); + + eth.setPayload(arp); + return eth; + } + + /** + * Deserializer function for ARP packets. + * + * @return deserializer function + */ + public static Deserializer<ARP> deserializer() { + return (data, offset, length) -> { + checkInput(data, offset, length, INITIAL_HEADER_LENGTH); + + ARP arp = new ARP(); + final ByteBuffer bb = ByteBuffer.wrap(data, offset, length); + arp.setHardwareType(bb.getShort()); + arp.setProtocolType(bb.getShort()); + + byte hwAddressLength = bb.get(); + arp.setHardwareAddressLength(hwAddressLength); + + byte protocolAddressLength = bb.get(); + arp.setProtocolAddressLength(protocolAddressLength); + arp.setOpCode(bb.getShort()); + + // Check we have enough space for the addresses + checkHeaderLength(length, INITIAL_HEADER_LENGTH + + 2 * hwAddressLength + + 2 * protocolAddressLength); + + arp.senderHardwareAddress = new byte[0xff & hwAddressLength]; + bb.get(arp.senderHardwareAddress, 0, arp.senderHardwareAddress.length); + arp.senderProtocolAddress = new byte[0xff & protocolAddressLength]; + bb.get(arp.senderProtocolAddress, 0, arp.senderProtocolAddress.length); + arp.targetHardwareAddress = new byte[0xff & hwAddressLength]; + bb.get(arp.targetHardwareAddress, 0, arp.targetHardwareAddress.length); + arp.targetProtocolAddress = new byte[0xff & protocolAddressLength]; + bb.get(arp.targetProtocolAddress, 0, arp.targetProtocolAddress.length); + + return arp; + }; + } + +} diff --git a/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/BasePacket.java b/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/BasePacket.java new file mode 100644 index 00000000..4aece66f --- /dev/null +++ b/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/BasePacket.java @@ -0,0 +1,127 @@ +/* + * Copyright 2014 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; + +/** + * + * + */ +public abstract class BasePacket implements IPacket { + protected IPacket parent; + protected IPacket payload; + + /** + * @return the parent + */ + @Override + public IPacket getParent() { + return this.parent; + } + + /** + * @param parent + * the parent to set + */ + @Override + public IPacket setParent(final IPacket parent) { + this.parent = parent; + return this; + } + + /** + * @return the payload + */ + @Override + public IPacket getPayload() { + return this.payload; + } + + /** + * @param payload + * the payload to set + */ + @Override + public IPacket setPayload(final IPacket payload) { + this.payload = payload; + return this; + } + + @Override + public void resetChecksum() { + if (this.parent != null) { + this.parent.resetChecksum(); + } + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + final int prime = 6733; + int result = 1; + result = prime * result + + (this.payload == null ? 0 : this.payload.hashCode()); + return result; + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(final Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (!(obj instanceof BasePacket)) { + return false; + } + final BasePacket other = (BasePacket) obj; + if (this.payload == null) { + if (other.payload != null) { + return false; + } + } else if (!this.payload.equals(other.payload)) { + return false; + } + return true; + } + + @Override + public Object clone() { + IPacket pkt; + try { + pkt = this.getClass().newInstance(); + } catch (final Exception e) { + throw new RuntimeException("Could not clone packet"); + } + + final byte[] data = this.serialize(); + pkt.deserialize(this.serialize(), 0, data.length); + pkt.setParent(this.parent); + return pkt; + } +} diff --git a/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/ChassisId.java b/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/ChassisId.java new file mode 100644 index 00000000..23c9859f --- /dev/null +++ b/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/ChassisId.java @@ -0,0 +1,86 @@ +/* + * Copyright 2014 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; + +/** + * The class representing a network device chassisId. + * This class is immutable. + */ +public final class ChassisId { + + private static final long UNKNOWN = 0; + private final long value; + + /** + * Default constructor. + */ + public ChassisId() { + this.value = ChassisId.UNKNOWN; + } + + /** + * Constructor from a long value. + * + * @param value the value to use. + */ + public ChassisId(long value) { + this.value = value; + } + + /** + * Constructor from a string. + * + * @param value the value to use. + */ + public ChassisId(String value) { + this.value = Long.parseLong(value, 16); + } + + /** + * Get the value of the chassis id. + * + * @return the value of the chassis id. + */ + public long value() { + return value; + } + + /** + * Convert the Chassis Id value to a ':' separated hexadecimal string. + * + * @return the Chassis Id value as a ':' separated hexadecimal string. + */ + @Override + public String toString() { + return Long.toHexString(this.value); + } + + @Override + public boolean equals(Object other) { + if (!(other instanceof ChassisId)) { + return false; + } + + ChassisId otherChassisId = (ChassisId) other; + + return value == otherChassisId.value; + } + + @Override + public int hashCode() { + return Long.hashCode(value); + } +} diff --git a/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/DHCP.java b/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/DHCP.java new file mode 100644 index 00000000..de5b43fa --- /dev/null +++ b/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/DHCP.java @@ -0,0 +1,632 @@ +/* + * Copyright 2014 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.io.UnsupportedEncodingException; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.List; +import java.util.ListIterator; + +import static com.google.common.base.Preconditions.checkArgument; +import static org.onlab.packet.PacketUtils.checkInput; + +/** + * + */ +public class DHCP extends BasePacket { + /** + * Dynamic Host Configuration Protocol packet. + * ------------------------------------------ |op (1) | htype(1) | hlen(1) | + * hops(1) | ------------------------------------------ | xid (4) | + * ------------------------------------------ | secs (2) | flags (2) | + * ------------------------------------------ | ciaddr (4) | + * ------------------------------------------ | yiaddr (4) | + * ------------------------------------------ | siaddr (4) | + * ------------------------------------------ | giaddr (4) | + * ------------------------------------------ | chaddr (16) | + * ------------------------------------------ | sname (64) | + * ------------------------------------------ | file (128) | + * ------------------------------------------ | options (312) | + * ------------------------------------------ + * + */ + // Header + magic without options + public static final int MIN_HEADER_LENGTH = 240; + public static final byte OPCODE_REQUEST = 0x1; + public static final byte OPCODE_REPLY = 0x2; + + public static final byte HWTYPE_ETHERNET = 0x1; + + public enum DHCPOptionCode { + OptionCode_SubnetMask((byte) 1), OptionCode_RouterAddress((byte) 3), OptionCode_DomainServer((byte) 6), + OptionCode_HostName((byte) 12), OptionCode_DomainName((byte) 15), OptionCode_BroadcastAddress((byte) 28), + OptionCode_RequestedIP((byte) 50), OptionCode_LeaseTime((byte) 51), OptionCode_MessageType((byte) 53), + OptionCode_DHCPServerIp((byte) 54), OptionCode_RequestedParameters((byte) 55), + OptionCode_RenewalTime((byte) 58), OPtionCode_RebindingTime((byte) 59), OptionCode_ClientID((byte) 61), + OptionCode_END((byte) 255); + + protected byte value; + + private DHCPOptionCode(final byte value) { + this.value = value; + } + + public byte getValue() { + return this.value; + } + } + + protected byte opCode; + protected byte hardwareType; + protected byte hardwareAddressLength; + protected byte hops; + protected int transactionId; + protected short seconds; + protected short flags; + protected int clientIPAddress; + protected int yourIPAddress; + protected int serverIPAddress; + protected int gatewayIPAddress; + protected byte[] clientHardwareAddress; + protected String serverName; + protected String bootFileName; + protected List<DHCPOption> options = new ArrayList<DHCPOption>(); + + /** + * @return the opCode + */ + public byte getOpCode() { + return this.opCode; + } + + /** + * @param opCode + * the opCode to set + * @return this + */ + public DHCP setOpCode(final byte opCode) { + this.opCode = opCode; + return this; + } + + /** + * @return the hardwareType + */ + public byte getHardwareType() { + return this.hardwareType; + } + + /** + * @param hardwareType + * the hardwareType to set + * @return this + */ + public DHCP setHardwareType(final byte hardwareType) { + this.hardwareType = hardwareType; + return this; + } + + /** + * @return the hardwareAddressLength + */ + public byte getHardwareAddressLength() { + return this.hardwareAddressLength; + } + + /** + * @param hardwareAddressLength + * the hardwareAddressLength to set + * @return this + */ + public DHCP setHardwareAddressLength(final byte hardwareAddressLength) { + this.hardwareAddressLength = hardwareAddressLength; + return this; + } + + /** + * @return the hops + */ + public byte getHops() { + return this.hops; + } + + /** + * @param hops + * the hops to set + * @return this + */ + public DHCP setHops(final byte hops) { + this.hops = hops; + return this; + } + + /** + * @return the transactionId + */ + public int getTransactionId() { + return this.transactionId; + } + + /** + * @param transactionId + * the transactionId to set + * @return this + */ + public DHCP setTransactionId(final int transactionId) { + this.transactionId = transactionId; + return this; + } + + /** + * @return the seconds + */ + public short getSeconds() { + return this.seconds; + } + + /** + * @param seconds + * the seconds to set + * @return this + */ + public DHCP setSeconds(final short seconds) { + this.seconds = seconds; + return this; + } + + /** + * @return the flags + */ + public short getFlags() { + return this.flags; + } + + /** + * @param flags + * the flags to set + * @return this + */ + public DHCP setFlags(final short flags) { + this.flags = flags; + return this; + } + + /** + * @return the clientIPAddress + */ + public int getClientIPAddress() { + return this.clientIPAddress; + } + + /** + * @param clientIPAddress + * the clientIPAddress to set + * @return this + */ + public DHCP setClientIPAddress(final int clientIPAddress) { + this.clientIPAddress = clientIPAddress; + return this; + } + + /** + * @return the yourIPAddress + */ + public int getYourIPAddress() { + return this.yourIPAddress; + } + + /** + * @param yourIPAddress + * the yourIPAddress to set + * @return this + */ + public DHCP setYourIPAddress(final int yourIPAddress) { + this.yourIPAddress = yourIPAddress; + return this; + } + + /** + * @return the serverIPAddress + */ + public int getServerIPAddress() { + return this.serverIPAddress; + } + + /** + * @param serverIPAddress + * the serverIPAddress to set + * @return this + */ + public DHCP setServerIPAddress(final int serverIPAddress) { + this.serverIPAddress = serverIPAddress; + return this; + } + + /** + * @return the gatewayIPAddress + */ + public int getGatewayIPAddress() { + return this.gatewayIPAddress; + } + + /** + * @param gatewayIPAddress + * the gatewayIPAddress to set + * @return this + */ + public DHCP setGatewayIPAddress(final int gatewayIPAddress) { + this.gatewayIPAddress = gatewayIPAddress; + return this; + } + + /** + * @return the clientHardwareAddress + */ + public byte[] getClientHardwareAddress() { + return this.clientHardwareAddress; + } + + /** + * @param clientHardwareAddress + * the clientHardwareAddress to set + * @return this + */ + public DHCP setClientHardwareAddress(final byte[] clientHardwareAddress) { + this.clientHardwareAddress = clientHardwareAddress; + return this; + } + + /** + * Gets a specific DHCP option parameter. + * + * @param optionCode + * The option code to get + * @return The value of the option if it exists, null otherwise + */ + public DHCPOption getOption(final DHCPOptionCode optionCode) { + for (final DHCPOption opt : this.options) { + if (opt.code == optionCode.value) { + return opt; + } + } + return null; + } + + /** + * @return the options + */ + public List<DHCPOption> getOptions() { + return this.options; + } + + /** + * @param options + * the options to set + * @return this + */ + public DHCP setOptions(final List<DHCPOption> options) { + this.options = options; + return this; + } + + /** + * @return the packetType base on option 53 + */ + public DHCPPacketType getPacketType() { + final ListIterator<DHCPOption> lit = this.options.listIterator(); + while (lit.hasNext()) { + final DHCPOption option = lit.next(); + // only care option 53 + if (option.getCode() == 53) { + return DHCPPacketType.getType(option.getData()[0]); + } + } + return null; + } + + /** + * @return the serverName + */ + public String getServerName() { + return this.serverName; + } + + /** + * @param server + * the serverName to set + * @return this + */ + public DHCP setServerName(final String server) { + this.serverName = server; + return this; + } + + /** + * @return the bootFileName + */ + public String getBootFileName() { + return this.bootFileName; + } + + /** + * @param bootFile + * the bootFileName to set + * @return this + */ + public DHCP setBootFileName(final String bootFile) { + this.bootFileName = bootFile; + return this; + } + + @Override + public byte[] serialize() { + // not guaranteed to retain length/exact format + this.resetChecksum(); + + // minimum size 240 including magic cookie, options generally padded to + // 300 + int optionsLength = 0; + for (final DHCPOption option : this.options) { + if (option.getCode() == 0 || option.getCode() == ((byte) 255)) { + optionsLength += 1; + } else { + optionsLength += 2 + (0xff & option.getLength()); + } + } + int optionsPadLength = 0; + if (optionsLength < 60) { + optionsPadLength = 60 - optionsLength; + } + + final byte[] data = new byte[240 + optionsLength + optionsPadLength]; + final ByteBuffer bb = ByteBuffer.wrap(data); + bb.put(this.opCode); + bb.put(this.hardwareType); + bb.put(this.hardwareAddressLength); + bb.put(this.hops); + bb.putInt(this.transactionId); + bb.putShort(this.seconds); + bb.putShort(this.flags); + bb.putInt(this.clientIPAddress); + bb.putInt(this.yourIPAddress); + bb.putInt(this.serverIPAddress); + bb.putInt(this.gatewayIPAddress); + checkArgument(this.clientHardwareAddress.length <= 16, + "Hardware address is too long (%s bytes)", this.clientHardwareAddress.length); + bb.put(this.clientHardwareAddress); + if (this.clientHardwareAddress.length < 16) { + for (int i = 0; i < 16 - this.clientHardwareAddress.length; ++i) { + bb.put((byte) 0x0); + } + } + this.writeString(this.serverName, bb, 64); + this.writeString(this.bootFileName, bb, 128); + // magic cookie + bb.put((byte) 0x63); + bb.put((byte) 0x82); + bb.put((byte) 0x53); + bb.put((byte) 0x63); + for (final DHCPOption option : this.options) { + final int code = option.getCode() & 0xff; + bb.put((byte) code); + if (code != 0 && code != 255) { + bb.put(option.getLength()); + bb.put(option.getData()); + } + } + // assume the rest is padded out with zeroes + return data; + } + + @Override + public IPacket deserialize(final byte[] data, final int offset, + final int length) { + final ByteBuffer bb = ByteBuffer.wrap(data, offset, length); + if (bb.remaining() < DHCP.MIN_HEADER_LENGTH) { + return this; + } + + this.opCode = bb.get(); + this.hardwareType = bb.get(); + this.hardwareAddressLength = bb.get(); + this.hops = bb.get(); + this.transactionId = bb.getInt(); + this.seconds = bb.getShort(); + this.flags = bb.getShort(); + this.clientIPAddress = bb.getInt(); + this.yourIPAddress = bb.getInt(); + this.serverIPAddress = bb.getInt(); + this.gatewayIPAddress = bb.getInt(); + final int hardwareAddressLength = 0xff & this.hardwareAddressLength; + this.clientHardwareAddress = new byte[hardwareAddressLength]; + + bb.get(this.clientHardwareAddress); + for (int i = hardwareAddressLength; i < 16; ++i) { + bb.get(); + } + this.serverName = this.readString(bb, 64); + this.bootFileName = this.readString(bb, 128); + // read the magic cookie + // magic cookie + bb.get(); + bb.get(); + bb.get(); + bb.get(); + // read options + while (bb.hasRemaining()) { + final DHCPOption option = new DHCPOption(); + int code = 0xff & bb.get(); // convert signed byte to int in range + // [0,255] + option.setCode((byte) code); + if (code == 0) { + // skip these + continue; + } else if (code != 255) { + if (bb.hasRemaining()) { + final int l = 0xff & bb.get(); // convert signed byte to + // int in range [0,255] + option.setLength((byte) l); + if (bb.remaining() >= l) { + final byte[] optionData = new byte[l]; + bb.get(optionData); + option.setData(optionData); + } else { + // Skip the invalid option and set the END option + code = 0xff; + option.setCode((byte) code); + option.setLength((byte) 0); + } + } else { + // Skip the invalid option and set the END option + code = 0xff; + option.setCode((byte) code); + option.setLength((byte) 0); + } + } + this.options.add(option); + if (code == 255) { + // remaining bytes are supposed to be 0, but ignore them just in + // case + break; + } + } + + return this; + } + + protected void writeString(final String string, final ByteBuffer bb, + final int maxLength) { + if (string == null) { + for (int i = 0; i < maxLength; ++i) { + bb.put((byte) 0x0); + } + } else { + byte[] bytes = null; + try { + bytes = string.getBytes("ascii"); + } catch (final UnsupportedEncodingException e) { + throw new RuntimeException("Failure encoding server name", e); + } + int writeLength = bytes.length; + if (writeLength > maxLength) { + writeLength = maxLength; + } + bb.put(bytes, 0, writeLength); + for (int i = writeLength; i < maxLength; ++i) { + bb.put((byte) 0x0); + } + } + } + + private static String readString(final ByteBuffer bb, final int maxLength) { + final byte[] bytes = new byte[maxLength]; + bb.get(bytes); + String result = null; + try { + result = new String(bytes, "ascii").trim(); + } catch (final UnsupportedEncodingException e) { + throw new RuntimeException("Failure decoding string", e); + } + return result; + } + + /** + * Deserializer function for DHCP packets. + * + * @return deserializer function + */ + public static Deserializer<DHCP> deserializer() { + return (data, offset, length) -> { + checkInput(data, offset, length, MIN_HEADER_LENGTH); + + ByteBuffer bb = ByteBuffer.wrap(data, offset, length); + DHCP dhcp = new DHCP(); + + dhcp.opCode = bb.get(); + dhcp.hardwareType = bb.get(); + dhcp.hardwareAddressLength = bb.get(); + dhcp.hops = bb.get(); + dhcp.transactionId = bb.getInt(); + dhcp.seconds = bb.getShort(); + dhcp.flags = bb.getShort(); + dhcp.clientIPAddress = bb.getInt(); + dhcp.yourIPAddress = bb.getInt(); + dhcp.serverIPAddress = bb.getInt(); + dhcp.gatewayIPAddress = bb.getInt(); + final int hardwareAddressLength = 0xff & dhcp.hardwareAddressLength; + dhcp.clientHardwareAddress = new byte[hardwareAddressLength]; + + bb.get(dhcp.clientHardwareAddress); + for (int i = hardwareAddressLength; i < 16; ++i) { + bb.get(); + } + dhcp.serverName = readString(bb, 64); + dhcp.bootFileName = readString(bb, 128); + // read the magic cookie + // magic cookie + bb.get(); + bb.get(); + bb.get(); + bb.get(); + + // read options + boolean foundEndOptionsMarker = false; + while (bb.hasRemaining()) { + final DHCPOption option = new DHCPOption(); + int code = 0xff & bb.get(); // convert signed byte to int in range + // [0,255] + option.setCode((byte) code); + if (code == 0) { + // skip these + continue; + } else if (code != 255) { + if (bb.hasRemaining()) { + final int l = 0xff & bb.get(); // convert signed byte to + // int in range [0,255] + option.setLength((byte) l); + if (bb.remaining() >= l) { + final byte[] optionData = new byte[l]; + bb.get(optionData); + option.setData(optionData); + dhcp.options.add(option); + } else { + throw new DeserializationException( + "Buffer underflow while reading DHCP option"); + } + } + } else if (code == 255) { + DHCPOption end = new DHCPOption(); + end.setCode((byte) 255); + dhcp.options.add(end); + // remaining bytes are supposed to be 0, but ignore them just in + // case + foundEndOptionsMarker = true; + break; + } + } + + if (!foundEndOptionsMarker) { + throw new DeserializationException("DHCP End options marker was missing"); + } + + return dhcp; + }; + } +} diff --git a/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/DHCPOption.java b/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/DHCPOption.java new file mode 100644 index 00000000..1b6c670e --- /dev/null +++ b/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/DHCPOption.java @@ -0,0 +1,136 @@ +/* + * Copyright 2014 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.util.Arrays; + +/** + * + */ +public class DHCPOption { + protected byte code; + protected byte length; + protected byte[] data; + + /** + * @return the code + */ + public byte getCode() { + return this.code; + } + + /** + * @param code + * the code to set + * @return this + */ + public DHCPOption setCode(final byte code) { + this.code = code; + return this; + } + + /** + * @return the length + */ + public byte getLength() { + return this.length; + } + + /** + * @param length + * the length to set + * @return this + */ + public DHCPOption setLength(final byte length) { + this.length = length; + return this; + } + + /** + * @return the data + */ + public byte[] getData() { + return this.data; + } + + /** + * @param data + * the data to set + * @return this + */ + public DHCPOption setData(final byte[] data) { + this.data = data; + return this; + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + this.code; + result = prime * result + Arrays.hashCode(this.data); + result = prime * result + this.length; + return result; + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(final Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (!(obj instanceof DHCPOption)) { + return false; + } + final DHCPOption other = (DHCPOption) obj; + if (this.code != other.code) { + return false; + } + if (!Arrays.equals(this.data, other.data)) { + return false; + } + if (this.length != other.length) { + return false; + } + return true; + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return "DHCPOption [code=" + this.code + ", length=" + this.length + + ", data=" + Arrays.toString(this.data) + "]"; + } +} diff --git a/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/DHCPPacketType.java b/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/DHCPPacketType.java new file mode 100644 index 00000000..8254c770 --- /dev/null +++ b/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/DHCPPacketType.java @@ -0,0 +1,116 @@ +/* + * Copyright 2014 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; + +public enum DHCPPacketType { + // From RFC 1533 + DHCPDISCOVER(1), DHCPOFFER(2), DHCPREQUEST(3), DHCPDECLINE(4), DHCPACK(5), DHCPNAK( + 6), DHCPRELEASE(7), + + // From RFC2132 + DHCPINFORM(8), + + // From RFC3203 + DHCPFORCERENEW(9), + + // From RFC4388 + DHCPLEASEQUERY(10), DHCPLEASEUNASSIGNED(11), DHCPLEASEUNKNOWN(12), DHCPLEASEACTIVE( + 13); + + protected int value; + + private DHCPPacketType(final int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + @Override + public String toString() { + switch (this.value) { + case 1: + return "DHCPDISCOVER"; + case 2: + return "DHCPOFFER"; + case 3: + return "DHCPREQUEST"; + case 4: + return "DHCPDECLINE"; + case 5: + return "DHCPACK"; + case 6: + return "DHCPNAK"; + case 7: + return "DHCPRELEASE"; + case 8: + return "DHCPINFORM"; + case 9: + return "DHCPFORCERENEW"; + case 10: + return "DHCPLEASEQUERY"; + case 11: + return "DHCPLEASEUNASSIGNED"; + case 12: + return "DHCPLEASEUNKNOWN"; + case 13: + return "DHCPLEASEACTIVE"; + default: + break; + } + + return null; + } + + public static DHCPPacketType getType(final int value) { + switch (value) { + case 1: + return DHCPDISCOVER; + case 2: + return DHCPOFFER; + case 3: + return DHCPREQUEST; + case 4: + return DHCPDECLINE; + case 5: + return DHCPACK; + case 6: + return DHCPNAK; + case 7: + return DHCPRELEASE; + case 8: + return DHCPINFORM; + case 9: + return DHCPFORCERENEW; + case 10: + return DHCPLEASEQUERY; + case 11: + return DHCPLEASEUNASSIGNED; + case 12: + return DHCPLEASEUNKNOWN; + case 13: + return DHCPLEASEACTIVE; + default: + break; + } + + return null; + } +} diff --git a/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/Data.java b/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/Data.java new file mode 100644 index 00000000..79abcba1 --- /dev/null +++ b/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/Data.java @@ -0,0 +1,132 @@ +/* + * Copyright 2014 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.util.Arrays; + +import static org.onlab.packet.PacketUtils.*; + +/** + * + */ +public class Data extends BasePacket { + protected byte[] data; + + /** + * + */ + public Data() { + data = new byte[0]; + } + + /** + * @param data the data + */ + public Data(final byte[] data) { + this.data = data; + } + + /** + * @return the data + */ + public byte[] getData() { + return this.data; + } + + /** + * @param data + * the data to set + * @return self + */ + public Data setData(final byte[] data) { + this.data = data; + return this; + } + + @Override + public byte[] serialize() { + return this.data; + } + + @Override + public IPacket deserialize(final byte[] data, final int offset, + final int length) { + this.data = Arrays.copyOfRange(data, offset, data.length); + return this; + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + final int prime = 1571; + int result = super.hashCode(); + result = prime * result + Arrays.hashCode(this.data); + return result; + } + + /* + * (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 Data)) { + return false; + } + final Data other = (Data) obj; + if (!Arrays.equals(this.data, other.data)) { + return false; + } + return true; + } + + /** + * Deserializer function for generic payload data. + * + * @return deserializer function + */ + public static Deserializer<Data> deserializer() { + return (data, offset, length) -> { + // Allow zero-length data for now + if (length == 0) { + return new Data(); + } + + checkInput(data, offset, length, 1); + + Data dataObject = new Data(); + + dataObject.data = Arrays.copyOfRange(data, offset, data.length); + + return dataObject; + }; + } + +} diff --git a/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/DeserializationException.java b/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/DeserializationException.java new file mode 100644 index 00000000..03a8aa35 --- /dev/null +++ b/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/DeserializationException.java @@ -0,0 +1,32 @@ +/* + * 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; + +/** + * Signals that an error occurred during deserialization of a packet. + */ +public class DeserializationException extends Exception { + + /** + * Creates a new deserialization exception with the given message. + * + * @param message exception message + */ + public DeserializationException(String message) { + super(message); + } +} diff --git a/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/Deserializer.java b/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/Deserializer.java new file mode 100644 index 00000000..e0ef381b --- /dev/null +++ b/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/Deserializer.java @@ -0,0 +1,36 @@ +/* + * 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; + +/** + * Function to deserialize a packet from a byte-based input stream. + */ +@FunctionalInterface +public interface Deserializer<U extends IPacket> { + + /** + * Deserialize a packet object from a byte array. + * + * @param data input array to take packet bytes from + * @param offset index where this packet header begins in the byte array + * @param length length of the packet header + * @return a deserialized packet object + * @throws DeserializationException if the packet cannot be deserialized + * from the input + */ + U deserialize(byte[] data, int offset, int length) throws DeserializationException; +} diff --git a/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/EAP.java b/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/EAP.java new file mode 100644 index 00000000..516938a4 --- /dev/null +++ b/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/EAP.java @@ -0,0 +1,264 @@ +/* + * + * * Copyright 2015 AT&T Foundry + * * + * * 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.checkHeaderLength; +import static org.onlab.packet.PacketUtils.checkInput; + +/** + * EAP (Extensible Authentication Protocol) packet. + */ +public class EAP extends BasePacket { + private static final int HEADER_LENGTH = 4; + + public static final short MIN_LEN = 0x4; + public static final short EAP_HDR_LEN_REQ_RESP = 5; + public static final short EAP_HDR_LEN_SUC_FAIL = 4; + + // EAP Code + public static final byte REQUEST = 0x1; + public static final byte RESPONSE = 0x2; + public static final byte SUCCESS = 0x3; + public static final byte FAILURE = 0x4; + + // EAP Attribute Type + public static final byte ATTR_IDENTITY = 0x1; + public static final byte ATTR_NOTIFICATION = 0x2; + public static final byte ATTR_NAK = 0x3; + public static final byte ATTR_MD5 = 0x4; + public static final byte ATTR_OTP = 0x5; + public static final byte ATTR_GTC = 0x6; + public static final byte ATTR_TLS = 0xd; + + protected byte code; + protected byte identifier; + protected short length; + protected byte type; + protected byte[] data; + + + /** + * Gets the EAP code. + * + * @return EAP code + */ + public byte getCode() { + return this.code; + } + + + /** + * Sets the EAP code. + * + * @param code EAP code + * @return this + */ + public EAP setCode(final byte code) { + this.code = code; + return this; + } + + /** + * Gets the EAP identifier. + * + * @return EAP identifier + */ + public byte getIdentifier() { + return this.identifier; + } + + /** + * Sets the EAP identifier. + * + * @param identifier EAP identifier + * @return this + */ + public EAP setIdentifier(final byte identifier) { + this.identifier = identifier; + return this; + } + + /** + * Gets the get packet length. + * + * @return packet length + */ + public short getLength() { + return this.length; + } + + /** + * Sets the packet length. + * + * @param length packet length + * @return this + */ + public EAP setLength(final short length) { + this.length = length; + return this; + } + + /** + * Gets the data type. + * + * @return data type + */ + public byte getDataType() { + return this.type; + } + + /** + * Sets the data type. + * + * @param type data type + * @return this + */ + public EAP setDataType(final byte type) { + this.type = type; + return this; + } + + /** + * Gets the EAP data. + * + * @return EAP data + */ + public byte[] getData() { + return this.data; + } + + /** + * Sets the EAP data. + * + * @param data EAP data to be set + * @return this + */ + public EAP setData(final byte[] data) { + this.data = data; + return this; + } + + /** + * Default EAP constructor that sets the EAP code to 0. + */ + public EAP() { + this.code = 0; + } + + /** + * EAP constructor that initially sets all fields. + * + * @param code EAP code + * @param identifier EAP identifier + * @param type packet type + * @param data EAP data + */ + public EAP(byte code, byte identifier, byte type, byte[] data) { + this.code = code; + this.identifier = identifier; + if (this.code == REQUEST || this.code == RESPONSE) { + this.length = (short) (5 + (data == null ? 0 : data.length)); + this.type = type; + } else { + this.length = (short) (4 + (data == null ? 0 : data.length)); + } + this.data = data; + } + + /** + * Deserializer for EAP packets. + * + * @return deserializer + */ + public static Deserializer<EAP> deserializer() { + return (data, offset, length) -> { + checkInput(data, offset, length, HEADER_LENGTH); + + EAP eap = new EAP(); + final ByteBuffer bb = ByteBuffer.wrap(data, offset, length); + eap.code = bb.get(); + eap.identifier = bb.get(); + eap.length = bb.getShort(); + + checkHeaderLength(length, eap.length); + + int dataLength; + if (eap.code == REQUEST || eap.code == RESPONSE) { + eap.type = bb.get(); + dataLength = eap.length - 5; + } else { + dataLength = eap.length - 4; + } + + eap.data = new byte[dataLength]; + bb.get(eap.data); + return eap; + }; + } + + @Override + public byte[] serialize() { + final byte[] data = new byte[this.length]; + + final ByteBuffer bb = ByteBuffer.wrap(data); + bb.put(this.code); + bb.put(this.identifier); + bb.putShort(this.length); + if (this.code == REQUEST || this.code == RESPONSE) { + bb.put(this.type); + } + if (this.data != null) { + bb.put(this.data); + } + return data; + } + + @Override + public IPacket deserialize(final byte[] data, final int offset, + final int length) { + final ByteBuffer bb = ByteBuffer.wrap(data, offset, length); + this.code = bb.get(); + this.identifier = bb.get(); + this.length = bb.getShort(); + + int dataLength; + if (this.code == REQUEST || this.code == RESPONSE) { + this.type = bb.get(); + dataLength = this.length - 5; + } else { + dataLength = this.length - 4; + } + this.data = new byte[dataLength]; + bb.get(this.data); + return this; + } + + @Override + public int hashCode() { + final int prime = 3889; + int result = super.hashCode(); + result = prime * result + this.code; + result = prime * result + this.identifier; + result = prime * result + this.length; + result = prime * result + this.type; + return result; + } +} diff --git a/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/EAPOL.java b/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/EAPOL.java new file mode 100644 index 00000000..1820cc31 --- /dev/null +++ b/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/EAPOL.java @@ -0,0 +1,199 @@ +/* + * + * * Copyright 2015 AT&T Foundry + * * + * * 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.checkHeaderLength; +import static org.onlab.packet.PacketUtils.checkInput; + +/** + * EAPOL (Extensible Authentication Protocol over LAN) header. + */ +public class EAPOL extends BasePacket { + + private byte version = 0x01; + private byte eapolType; + private short packetLength; + + private static final int HEADER_LENGTH = 4; + + // EAPOL Packet Type + public static final byte EAPOL_PACKET = 0x0; + public static final byte EAPOL_START = 0x1; + public static final byte EAPOL_LOGOFF = 0x2; + public static final byte EAPOL_KEY = 0x3; + public static final byte EAPOL_ASF = 0x4; + + public static final MacAddress PAE_GROUP_ADDR = MacAddress.valueOf(new byte[] { + (byte) 0x01, (byte) 0x80, (byte) 0xc2, (byte) 0x00, (byte) 0x00, (byte) 0x03 + }); + + /** + * Gets the version. + * + * @return version + */ + public byte getVersion() { + return this.version; + } + + /** + * Sets the version. + * + * @param version EAPOL version + * @return this + */ + public EAPOL setVersion(final byte version) { + this.version = version; + return this; + } + + /** + * Gets the type. + * + * @return EAPOL type + */ + public byte getEapolType() { + return this.eapolType; + } + + /** + * Sets the EAPOL type. + * + * @param eapolType EAPOL type + * @return this + */ + public EAPOL setEapolType(final byte eapolType) { + this.eapolType = eapolType; + return this; + } + + /** + * Gets the packet length. + * + * @return packet length + */ + public short getPacketLength() { + return this.packetLength; + } + + /** + * Sets the packet length. + * + * @param packetLen packet length + * @return this + */ + public EAPOL setPacketLength(final short packetLen) { + this.packetLength = packetLen; + return this; + } + + /** + * Serializes the packet, based on the code/type using the payload + * to compute its length. + * + * @return this + */ + @Override + public byte[] serialize() { + byte[] payloadData = null; + + if (this.payload != null) { + this.payload.setParent(this); + payloadData = this.payload.serialize(); + } + + // prepare the buffer to hold the version (1), packet type (1), + // packet length (2) and the eap payload. + // if there is no payload, packet length is 0 + byte[] data = new byte[4 + this.packetLength]; + final ByteBuffer bb = ByteBuffer.wrap(data); + bb.put(this.version); + bb.put(this.eapolType); + bb.putShort(this.packetLength); + + // put the EAP payload + if (payloadData != null) { + bb.put(payloadData); + } + + return data; + } + + @Override + public int hashCode() { + final int prime = 3889; + int result = super.hashCode(); + result = prime * result + this.version; + result = prime * result + this.eapolType; + result = prime * result + this.packetLength; + return result; + } + + /** + * Deserializer for EAPOL packets. + * + * @return deserializer + */ + public static Deserializer<EAPOL> deserializer() { + return (data, offset, length) -> { + checkInput(data, offset, length, HEADER_LENGTH); + + EAPOL eapol = new EAPOL(); + final ByteBuffer bb = ByteBuffer.wrap(data, offset, length); + eapol.setVersion(bb.get()); + eapol.setEapolType(bb.get()); + eapol.setPacketLength(bb.getShort()); + + if (eapol.packetLength > 0) { + checkHeaderLength(length, HEADER_LENGTH + eapol.packetLength); + // deserialize the EAP Payload + eapol.payload = EAP.deserializer().deserialize(data, + bb.position(), bb.limit() - bb.position()); + + eapol.payload.setParent(eapol); + } + + return eapol; + }; + } + + @Override + public IPacket deserialize(final byte[] data, final int offset, + final int length) { + final ByteBuffer bb = ByteBuffer.wrap(data, offset, length); + + // deserialize the EAPOL header + this.version = bb.get(); + this.eapolType = bb.get(); + this.packetLength = bb.getShort(); + + if (this.packetLength > 0) { + // deserialize the EAP Payload + this.payload = new EAP(); + + this.payload = this.payload.deserialize(data, bb.position(), length - 4); + this.payload.setParent(this); + } + + return this; + } +} + diff --git a/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/EthType.java b/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/EthType.java new file mode 100644 index 00000000..561c9307 --- /dev/null +++ b/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/EthType.java @@ -0,0 +1,159 @@ +/* + * 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; + +/** + * Representation of an Ethertype. + */ +public class EthType { + + /** + * A list of known ethertypes. Adding a fully defined enum here will + * associated the ethertype with a textual representation and a parsing + * class. + */ + public enum EtherType { + + ARP(0x806, "arp", org.onlab.packet.ARP.deserializer()), + RARP(0x8035, "rarp", org.onlab.packet.ARP.deserializer()), + IPV4(0x800, "ipv4", org.onlab.packet.IPv4.deserializer()), + IPV6(0x86dd, "ipv6", org.onlab.packet.IPv6.deserializer()), + LLDP(0x88cc, "lldp", org.onlab.packet.LLDP.deserializer()), + VLAN(0x8100, "vlan", null), + BDDP(0x8942, "bddp", org.onlab.packet.LLDP.deserializer()), + MPLS_UNICAST(0x8847, "mpls_unicast", org.onlab.packet.MPLS.deserializer()), + MPLS_MULTICAST(0x8848, "mpls_unicast", org.onlab.packet.MPLS.deserializer()), + EAPOL(0x888e, "eapol", org.onlab.packet.EAPOL.deserializer()), + UNKNOWN(0, "unknown", null); + + + private final EthType etherType; + private final String type; + private final Deserializer<?> deserializer; + + /** + * Constucts a new ethertype. + * + * @param ethType The actual ethertype + * @param type it's textual representation + * @param deserializer a parser for this ethertype + */ + EtherType(int ethType, String type, Deserializer<?> deserializer) { + this.etherType = new EthType(ethType); + this.type = type; + this.deserializer = deserializer; + } + + public EthType ethType() { + return etherType; + } + + @Override + public String toString() { + return type; + } + + public Deserializer<?> deserializer() { + return deserializer; + } + + public static EtherType lookup(short etherType) { + for (EtherType ethType : EtherType.values()) { + if (ethType.ethType().toShort() == etherType) { + return ethType; + } + } + return UNKNOWN; + } + + } + + + private final short etherType; + + /** + * Builds the EthType. + * + * @param etherType an integer representing an ethtype + */ + public EthType(int etherType) { + this.etherType = (short) (etherType & 0xFFFF); + } + + /** + * Builds the EthType. + * + * @param etherType a short representing the ethtype + */ + public EthType(short etherType) { + this.etherType = etherType; + } + + /** + * Returns the short value for this ethtype. + * + * @return a short value + */ + public short toShort() { + return etherType; + } + + /** + * Looks up the ethertype by it's numerical representation + * and returns it's textual format. + * + * @param etherType the short value of the ethertype + * @return a textual representation + */ + public EtherType lookup(short etherType) { + for (EtherType ethType : EtherType.values()) { + if (ethType.ethType().toShort() == etherType) { + return ethType; + } + } + return null; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + EthType ethType = (EthType) o; + + if (etherType != ethType.etherType) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + return (int) etherType; + } + + public String toString() { + EtherType ethType = lookup(this.etherType); + return (ethType == null ? String.format("0x%04x", etherType) : + ethType.toString()); + } + +} diff --git a/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/Ethernet.java b/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/Ethernet.java new file mode 100644 index 00000000..003c1772 --- /dev/null +++ b/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/Ethernet.java @@ -0,0 +1,625 @@ +/* + * Copyright 2014-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.Arrays; +import java.util.HashMap; +import java.util.Map; + +import static com.google.common.base.Preconditions.checkNotNull; +import static org.onlab.packet.PacketUtils.checkHeaderLength; +import static org.onlab.packet.PacketUtils.checkInput; + +/** + * + */ +public class Ethernet extends BasePacket { + private static final String HEXES = "0123456789ABCDEF"; + + public static final short TYPE_ARP = EthType.EtherType.ARP.ethType().toShort(); + public static final short TYPE_RARP = EthType.EtherType.RARP.ethType().toShort(); + public static final short TYPE_IPV4 = EthType.EtherType.IPV4.ethType().toShort(); + public static final short TYPE_IPV6 = EthType.EtherType.IPV6.ethType().toShort(); + public static final short TYPE_LLDP = EthType.EtherType.LLDP.ethType().toShort(); + public static final short TYPE_VLAN = EthType.EtherType.VLAN.ethType().toShort(); + public static final short TYPE_BSN = EthType.EtherType.BDDP.ethType().toShort(); + + public static final short MPLS_UNICAST = EthType.EtherType.MPLS_UNICAST.ethType().toShort();; + public static final short MPLS_MULTICAST = EthType.EtherType.MPLS_MULTICAST.ethType().toShort(); + + + public static final short VLAN_UNTAGGED = (short) 0xffff; + + public static final short ETHERNET_HEADER_LENGTH = 14; // bytes + public static final short VLAN_HEADER_LENGTH = 4; // bytes + + public static final short DATALAYER_ADDRESS_LENGTH = 6; // bytes + + private static final Map<Short, Deserializer<? extends IPacket>> ETHERTYPE_DESERIALIZER_MAP = + new HashMap<>(); + + static { + for (EthType.EtherType ethType : EthType.EtherType.values()) { + if (ethType.deserializer() != null) { + ETHERTYPE_DESERIALIZER_MAP.put(ethType.ethType().toShort(), ethType.deserializer()); + } + } + } + + protected MacAddress destinationMACAddress; + protected MacAddress sourceMACAddress; + protected byte priorityCode; + protected short vlanID; + protected short etherType; + protected boolean pad = false; + + /** + * By default, set Ethernet to untagged. + */ + public Ethernet() { + super(); + this.vlanID = Ethernet.VLAN_UNTAGGED; + } + + /** + * Gets the destination MAC address. + * + * @return the destination MAC as a byte array + */ + public byte[] getDestinationMACAddress() { + return this.destinationMACAddress.toBytes(); + } + + /** + * Gets the destination MAC address. + * + * @return the destination MAC + */ + public MacAddress getDestinationMAC() { + return this.destinationMACAddress; + } + + /** + * Sets the destination MAC address. + * + * @param destMac the destination MAC to set + * @return the Ethernet frame + */ + public Ethernet setDestinationMACAddress(final MacAddress destMac) { + this.destinationMACAddress = checkNotNull(destMac); + return this; + } + + /** + * Sets the destination MAC address. + * + * @param destMac the destination MAC to set + * @return the Ethernet frame + */ + public Ethernet setDestinationMACAddress(final byte[] destMac) { + this.destinationMACAddress = MacAddress.valueOf(destMac); + return this; + } + + /** + * Sets the destination MAC address. + * + * @param destMac the destination MAC to set + * @return the Ethernet frame + */ + public Ethernet setDestinationMACAddress(final String destMac) { + this.destinationMACAddress = MacAddress.valueOf(destMac); + return this; + } + + /** + * Gets the source MAC address. + * + * @return the source MACAddress as a byte array + */ + public byte[] getSourceMACAddress() { + return this.sourceMACAddress.toBytes(); + } + + /** + * Gets the source MAC address. + * + * @return the source MACAddress + */ + public MacAddress getSourceMAC() { + return this.sourceMACAddress; + } + + /** + * Sets the source MAC address. + * + * @param sourceMac the source MAC to set + * @return the Ethernet frame + */ + public Ethernet setSourceMACAddress(final MacAddress sourceMac) { + this.sourceMACAddress = checkNotNull(sourceMac); + return this; + } + + /** + * Sets the source MAC address. + * + * @param sourceMac the source MAC to set + * @return the Ethernet frame + */ + public Ethernet setSourceMACAddress(final byte[] sourceMac) { + this.sourceMACAddress = MacAddress.valueOf(sourceMac); + return this; + } + + /** + * Sets the source MAC address. + * + * @param sourceMac the source MAC to set + * @return the Ethernet frame + */ + public Ethernet setSourceMACAddress(final String sourceMac) { + this.sourceMACAddress = MacAddress.valueOf(sourceMac); + return this; + } + + /** + * Gets the priority code. + * + * @return the priorityCode + */ + public byte getPriorityCode() { + return this.priorityCode; + } + + /** + * Sets the priority code. + * + * @param priority the priorityCode to set + * @return the Ethernet frame + */ + public Ethernet setPriorityCode(final byte priority) { + this.priorityCode = priority; + return this; + } + + /** + * Gets the VLAN ID. + * + * @return the vlanID + */ + public short getVlanID() { + return this.vlanID; + } + + /** + * Sets the VLAN ID. + * + * @param vlan the vlanID to set + * @return the Ethernet frame + */ + public Ethernet setVlanID(final short vlan) { + this.vlanID = vlan; + return this; + } + + /** + * Gets the Ethernet type. + * + * @return the etherType + */ + public short getEtherType() { + return this.etherType; + } + + /** + * Sets the Ethernet type. + * + * @param ethType the etherType to set + * @return the Ethernet frame + */ + public Ethernet setEtherType(final short ethType) { + this.etherType = ethType; + return this; + } + + /** + * @return True if the Ethernet frame is broadcast, false otherwise + */ + public boolean isBroadcast() { + assert this.destinationMACAddress.length() == 6; + return this.destinationMACAddress.isBroadcast(); + } + + /** + * @return True is the Ethernet frame is multicast, False otherwise + */ + public boolean isMulticast() { + return this.destinationMACAddress.isMulticast(); + } + + /** + * Pad this packet to 60 bytes minimum, filling with zeros? + * + * @return the pad + */ + public boolean isPad() { + return this.pad; + } + + /** + * Pad this packet to 60 bytes minimum, filling with zeros? + * + * @param pd + * the pad to set + * @return this + */ + public Ethernet setPad(final boolean pd) { + this.pad = pd; + return this; + } + + @Override + public byte[] serialize() { + byte[] payloadData = null; + if (this.payload != null) { + this.payload.setParent(this); + payloadData = this.payload.serialize(); + } + int length = 14 + (this.vlanID == Ethernet.VLAN_UNTAGGED ? 0 : 4) + + (payloadData == null ? 0 : payloadData.length); + if (this.pad && length < 60) { + length = 60; + } + final byte[] data = new byte[length]; + final ByteBuffer bb = ByteBuffer.wrap(data); + bb.put(this.destinationMACAddress.toBytes()); + bb.put(this.sourceMACAddress.toBytes()); + if (this.vlanID != Ethernet.VLAN_UNTAGGED) { + bb.putShort(TYPE_VLAN); + bb.putShort((short) (this.priorityCode << 13 | this.vlanID & 0x0fff)); + } + bb.putShort(this.etherType); + if (payloadData != null) { + bb.put(payloadData); + } + if (this.pad) { + Arrays.fill(data, bb.position(), data.length, (byte) 0x0); + } + return data; + } + + @Override + public IPacket deserialize(final byte[] data, final int offset, + final int length) { + if (length <= 0) { + return null; + } + final ByteBuffer bb = ByteBuffer.wrap(data, offset, length); + if (this.destinationMACAddress == null) { + this.destinationMACAddress = MacAddress.valueOf(new byte[6]); + } + final byte[] dstAddr = new byte[MacAddress.MAC_ADDRESS_LENGTH]; + bb.get(dstAddr); + this.destinationMACAddress = MacAddress.valueOf(dstAddr); + + if (this.sourceMACAddress == null) { + this.sourceMACAddress = MacAddress.valueOf(new byte[6]); + } + final byte[] srcAddr = new byte[MacAddress.MAC_ADDRESS_LENGTH]; + bb.get(srcAddr); + this.sourceMACAddress = MacAddress.valueOf(srcAddr); + + short ethType = bb.getShort(); + if (ethType == TYPE_VLAN) { + final short tci = bb.getShort(); + this.priorityCode = (byte) (tci >> 13 & 0x07); + this.vlanID = (short) (tci & 0x0fff); + ethType = bb.getShort(); + } else { + this.vlanID = Ethernet.VLAN_UNTAGGED; + } + this.etherType = ethType; + + IPacket payload; + Deserializer<? extends IPacket> deserializer; + if (Ethernet.ETHERTYPE_DESERIALIZER_MAP.containsKey(ethType)) { + deserializer = Ethernet.ETHERTYPE_DESERIALIZER_MAP.get(ethType); + } else { + deserializer = Data.deserializer(); + } + try { + this.payload = deserializer.deserialize(data, bb.position(), + bb.limit() - bb.position()); + this.payload.setParent(this); + } catch (DeserializationException e) { + return this; + } + return this; + } + + /** + * Checks to see if a string is a valid MAC address. + * + * @param macAddress string to test if it is a valid MAC + * @return True if macAddress is a valid MAC, False otherwise + */ + public static boolean isMACAddress(final String macAddress) { + final String[] macBytes = macAddress.split(":"); + if (macBytes.length != 6) { + return false; + } + for (int i = 0; i < 6; ++i) { + if (Ethernet.HEXES.indexOf(macBytes[i].toUpperCase().charAt(0)) == -1 + || Ethernet.HEXES.indexOf(macBytes[i].toUpperCase().charAt( + 1)) == -1) { + return false; + } + } + return true; + } + + /** + * Accepts a MAC address of the form 00:aa:11:bb:22:cc, case does not + * matter, and returns a corresponding byte[]. + * + * @param macAddress + * The MAC address to convert into a byte array + * @return The macAddress as a byte array + */ + public static byte[] toMACAddress(final String macAddress) { + return MacAddress.valueOf(macAddress).toBytes(); + } + + /** + * Accepts a MAC address and returns the corresponding long, where the MAC + * bytes are set on the lower order bytes of the long. + * + * @param macAddress MAC address as a byte array + * @return a long containing the mac address bytes + */ + public static long toLong(final byte[] macAddress) { + return MacAddress.valueOf(macAddress).toLong(); + } + + /** + * Converts a long MAC address to a byte array. + * + * @param macAddress MAC address set on the lower order bytes of the long + * @return the bytes of the mac address + */ + public static byte[] toByteArray(final long macAddress) { + return MacAddress.valueOf(macAddress).toBytes(); + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + final int prime = 7867; + int result = super.hashCode(); + result = prime * result + this.destinationMACAddress.hashCode(); + result = prime * result + this.etherType; + result = prime * result + this.vlanID; + result = prime * result + this.priorityCode; + result = prime * result + (this.pad ? 1231 : 1237); + result = prime * result + this.sourceMACAddress.hashCode(); + return result; + } + + /* + * (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 Ethernet)) { + return false; + } + final Ethernet other = (Ethernet) obj; + if (!this.destinationMACAddress.equals(other.destinationMACAddress)) { + return false; + } + if (this.priorityCode != other.priorityCode) { + return false; + } + if (this.vlanID != other.vlanID) { + return false; + } + if (this.etherType != other.etherType) { + return false; + } + if (this.pad != other.pad) { + return false; + } + if (!this.sourceMACAddress.equals(other.sourceMACAddress)) { + return false; + } + return true; + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#toString(java.lang.Object) + */ + @Override + public String toString() { + + final StringBuffer sb = new StringBuffer("\n"); + + final IPacket pkt = this.getPayload(); + + if (pkt instanceof ARP) { + sb.append("arp"); + } else if (pkt instanceof LLDP) { + sb.append("lldp"); + } else if (pkt instanceof ICMP) { + sb.append("icmp"); + } else if (pkt instanceof IPv4) { + sb.append("ip"); + } else if (pkt instanceof DHCP) { + sb.append("dhcp"); + } else { + sb.append(this.getEtherType()); + } + + sb.append("\ndl_vlan: "); + if (this.getVlanID() == Ethernet.VLAN_UNTAGGED) { + sb.append("untagged"); + } else { + sb.append(this.getVlanID()); + } + sb.append("\ndl_vlan_pcp: "); + sb.append(this.getPriorityCode()); + sb.append("\ndl_src: "); + sb.append(bytesToHex(this.getSourceMACAddress())); + sb.append("\ndl_dst: "); + sb.append(bytesToHex(this.getDestinationMACAddress())); + + if (pkt instanceof ARP) { + final ARP p = (ARP) pkt; + sb.append("\nnw_src: "); + sb.append(IPv4.fromIPv4Address(IPv4.toIPv4Address(p + .getSenderProtocolAddress()))); + sb.append("\nnw_dst: "); + sb.append(IPv4.fromIPv4Address(IPv4.toIPv4Address(p + .getTargetProtocolAddress()))); + } else if (pkt instanceof LLDP) { + sb.append("lldp packet"); + } else if (pkt instanceof ICMP) { + final ICMP icmp = (ICMP) pkt; + sb.append("\nicmp_type: "); + sb.append(icmp.getIcmpType()); + sb.append("\nicmp_code: "); + sb.append(icmp.getIcmpCode()); + } else if (pkt instanceof IPv4) { + final IPv4 p = (IPv4) pkt; + sb.append("\nnw_src: "); + sb.append(IPv4.fromIPv4Address(p.getSourceAddress())); + sb.append("\nnw_dst: "); + sb.append(IPv4.fromIPv4Address(p.getDestinationAddress())); + sb.append("\nnw_tos: "); + sb.append(p.getDiffServ()); + sb.append("\nnw_proto: "); + sb.append(p.getProtocol()); + + if (pkt instanceof TCP) { + sb.append("\ntp_src: "); + sb.append(((TCP) pkt).getSourcePort()); + sb.append("\ntp_dst: "); + sb.append(((TCP) pkt).getDestinationPort()); + + } else if (pkt instanceof UDP) { + sb.append("\ntp_src: "); + sb.append(((UDP) pkt).getSourcePort()); + sb.append("\ntp_dst: "); + sb.append(((UDP) pkt).getDestinationPort()); + } + + if (pkt instanceof ICMP) { + final ICMP icmp = (ICMP) pkt; + sb.append("\nicmp_type: "); + sb.append(icmp.getIcmpType()); + sb.append("\nicmp_code: "); + sb.append(icmp.getIcmpCode()); + } + + } else if (pkt instanceof DHCP) { + sb.append("\ndhcp packet"); + } else if (pkt instanceof Data) { + sb.append("\ndata packet"); + } else if (pkt instanceof LLC) { + sb.append("\nllc packet"); + } else { + sb.append("\nunknown packet"); + } + + return sb.toString(); + } + + public static String bytesToHex(byte[] in) { + final StringBuilder builder = new StringBuilder(); + for (byte b : in) { + builder.append(String.format("%02x", b)); + } + return builder.toString(); + } + + /** + * Deserializer function for Ethernet packets. + * + * @return deserializer function + */ + public static Deserializer<Ethernet> deserializer() { + return (data, offset, length) -> { + checkInput(data, offset, length, ETHERNET_HEADER_LENGTH); + + byte[] addressBuffer = new byte[DATALAYER_ADDRESS_LENGTH]; + + ByteBuffer bb = ByteBuffer.wrap(data, offset, length); + Ethernet eth = new Ethernet(); + // Read destination MAC address into buffer + bb.get(addressBuffer); + eth.setDestinationMACAddress(addressBuffer); + + // Read source MAC address into buffer + bb.get(addressBuffer); + eth.setSourceMACAddress(addressBuffer); + + short ethType = bb.getShort(); + if (ethType == TYPE_VLAN) { + checkHeaderLength(length, ETHERNET_HEADER_LENGTH + VLAN_HEADER_LENGTH); + final short tci = bb.getShort(); + eth.setPriorityCode((byte) (tci >> 13 & 0x07)); + eth.setVlanID((short) (tci & 0x0fff)); + ethType = bb.getShort(); + } else { + eth.setVlanID(Ethernet.VLAN_UNTAGGED); + } + eth.setEtherType(ethType); + + IPacket payload; + Deserializer<? extends IPacket> deserializer; + if (Ethernet.ETHERTYPE_DESERIALIZER_MAP.containsKey(ethType)) { + deserializer = Ethernet.ETHERTYPE_DESERIALIZER_MAP.get(ethType); + } else { + deserializer = Data.deserializer(); + } + payload = deserializer.deserialize(data, bb.position(), + bb.limit() - bb.position()); + payload.setParent(eth); + eth.setPayload(payload); + + return eth; + }; + } + +} diff --git a/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/ICMP.java b/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/ICMP.java new file mode 100644 index 00000000..d07a9ba4 --- /dev/null +++ b/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/ICMP.java @@ -0,0 +1,223 @@ +/* + * Copyright 2014-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.*; + +/** + * Implements ICMP packet format. + * + */ +public class ICMP extends BasePacket { + protected byte icmpType; + protected byte icmpCode; + protected short checksum; + + public static final byte TYPE_ECHO_REQUEST = 0x08; + public static final byte TYPE_ECHO_REPLY = 0x00; + public static final byte SUBTYPE_ECHO_REPLY = 0x00; + + public static final short ICMP_HEADER_LENGTH = 4; + + /** + * @return the icmpType + */ + public byte getIcmpType() { + return this.icmpType; + } + + /** + * @param icmpType + * to set + * @return this + */ + public ICMP setIcmpType(final byte icmpType) { + this.icmpType = icmpType; + return this; + } + + /** + * @return the icmp code + */ + public byte getIcmpCode() { + return this.icmpCode; + } + + /** + * @param icmpCode + * code to set + * @return this + */ + public ICMP setIcmpCode(final byte icmpCode) { + this.icmpCode = icmpCode; + return this; + } + + /** + * @return the checksum + */ + public short getChecksum() { + return this.checksum; + } + + /** + * @param checksum + * the checksum to set + * @return this + */ + public ICMP setChecksum(final short checksum) { + this.checksum = checksum; + return this; + } + + /** + * Serializes the packet. Will compute and set the following fields if they + * are set to specific values at the time serialize is called: -checksum : 0 + * -length : 0 + */ + @Override + public byte[] serialize() { + int length = 4; + byte[] payloadData = null; + if (this.payload != null) { + this.payload.setParent(this); + payloadData = this.payload.serialize(); + length += payloadData.length; + } + + final byte[] data = new byte[length]; + final ByteBuffer bb = ByteBuffer.wrap(data); + + bb.put(this.icmpType); + bb.put(this.icmpCode); + bb.putShort(this.checksum); + if (payloadData != null) { + bb.put(payloadData); + } + + if (this.parent != null && this.parent instanceof IPv4) { + ((IPv4) this.parent).setProtocol(IPv4.PROTOCOL_ICMP); + } + + // compute checksum if needed + if (this.checksum == 0) { + bb.rewind(); + int accumulation = 0; + + for (int i = 0; i < length / 2; ++i) { + accumulation += 0xffff & bb.getShort(); + } + // pad to an even number of shorts + if (length % 2 > 0) { + accumulation += (bb.get() & 0xff) << 8; + } + + accumulation = (accumulation >> 16 & 0xffff) + + (accumulation & 0xffff); + this.checksum = (short) (~accumulation & 0xffff); + bb.putShort(2, this.checksum); + } + return data; + } + + @Override + public IPacket deserialize(final byte[] data, final int offset, + final int length) { + final ByteBuffer bb = ByteBuffer.wrap(data, offset, length); + this.icmpType = bb.get(); + this.icmpCode = bb.get(); + this.checksum = bb.getShort(); + + this.payload = new Data(); + this.payload = this.payload.deserialize(data, bb.position(), bb.limit() + - bb.position()); + this.payload.setParent(this); + return this; + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + final int prime = 5807; + int result = super.hashCode(); + result = prime * result + this.icmpType; + result = prime * result + this.icmpCode; + result = prime * result + this.checksum; + return result; + } + + /* + * (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 ICMP)) { + return false; + } + final ICMP other = (ICMP) obj; + if (this.icmpType != other.icmpType) { + return false; + } + if (this.icmpCode != other.icmpCode) { + return false; + } + if (this.checksum != other.checksum) { + return false; + } + return true; + } + + /** + * Deserializer function for ICMP packets. + * + * @return deserializer function + */ + public static Deserializer<ICMP> deserializer() { + return (data, offset, length) -> { + checkInput(data, offset, length, ICMP_HEADER_LENGTH); + + ICMP icmp = new ICMP(); + + final ByteBuffer bb = ByteBuffer.wrap(data, offset, length); + icmp.icmpType = bb.get(); + icmp.icmpCode = bb.get(); + icmp.checksum = bb.getShort(); + + icmp.payload = Data.deserializer() + .deserialize(data, bb.position(), bb.limit() + - bb.position()); + icmp.payload.setParent(icmp); + return icmp; + }; + } +} diff --git a/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/ICMP6.java b/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/ICMP6.java new file mode 100644 index 00000000..c8981302 --- /dev/null +++ b/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/ICMP6.java @@ -0,0 +1,366 @@ +/* + * Copyright 2014-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 org.onlab.packet.ipv6.IExtensionHeader; +import org.onlab.packet.ndp.NeighborAdvertisement; +import org.onlab.packet.ndp.NeighborSolicitation; +import org.onlab.packet.ndp.Redirect; +import org.onlab.packet.ndp.RouterAdvertisement; +import org.onlab.packet.ndp.RouterSolicitation; + +import java.nio.ByteBuffer; +import java.util.HashMap; +import java.util.Map; + +import static org.onlab.packet.PacketUtils.checkInput; + +/** + * Implements ICMPv6 packet format. (RFC 4443) + */ +public class ICMP6 extends BasePacket { + public static final byte HEADER_LENGTH = 4; // bytes + + // Type + /** Destination Unreachable. */ + public static final byte DEST_UNREACH = (byte) 0x01; + /** Packet Too Big. */ + public static final byte PKT_TOO_BIG = (byte) 0x02; + /** Time Exceeded. */ + public static final byte TIME_EXCEED = (byte) 0x03; + /** Parameter Problem. */ + public static final byte PARAM_ERR = (byte) 0x04; + /** Echo Request. */ + public static final byte ECHO_REQUEST = (byte) 0x80; + /** Echo Reply. */ + public static final byte ECHO_REPLY = (byte) 0x81; + /** Multicast Listener Query. */ + public static final byte MCAST_QUERY = (byte) 0x82; + /** Multicast Listener Report. */ + public static final byte MCAST_REPORT = (byte) 0x83; + /** Multicast Listener Done. */ + public static final byte MCAST_DONE = (byte) 0x84; + /** Router Solicitation. */ + public static final byte ROUTER_SOLICITATION = (byte) 0x85; + /** Router Advertisement. */ + public static final byte ROUTER_ADVERTISEMENT = (byte) 0x86; + /** Neighbor Solicitation. */ + public static final byte NEIGHBOR_SOLICITATION = (byte) 0x87; + /** Neighbor Advertisement. */ + public static final byte NEIGHBOR_ADVERTISEMENT = (byte) 0x88; + /** Redirect Message. */ + public static final byte REDIRECT = (byte) 0x89; + + // Code for DEST_UNREACH + /** No route to destination. */ + public static final byte NO_ROUTE = (byte) 0x00; + /** Communication with destination administratively prohibited. */ + public static final byte COMM_PROHIBIT = (byte) 0x01; + /** Beyond scope of source address. */ + public static final byte BEYOND_SCOPE = (byte) 0x02; + /** Address unreachable. */ + public static final byte ADDR_UNREACH = (byte) 0x03; + /** Port unreachable. */ + public static final byte PORT_UNREACH = (byte) 0x04; + /** Source address failed ingress/egress policy. */ + public static final byte FAIL_POLICY = (byte) 0x05; + /** Reject route to destination. */ + public static final byte REJECT_ROUTE = (byte) 0x06; + /** Error in Source Routing Header. */ + public static final byte SRC_ROUTING_HEADER_ERR = (byte) 0x07; + + // Code for TIME_EXCEED + /** Hop limit exceeded in transit. */ + public static final byte HOP_LIMIT_EXCEED = (byte) 0x00; + /** Fragment reassembly time exceeded. */ + public static final byte DEFRAG_TIME_EXCEED = (byte) 0x01; + + // Code for PARAM_ERR + /** Erroneous header field encountered. */ + public static final byte HDR_FIELD_ERR = (byte) 0x00; + /** Unrecognized Next Header type encountered. */ + public static final byte NEXT_HEADER_ERR = (byte) 0x01; + /** Unrecognized IPv6 option encountered. */ + public static final byte IPV6_OPT_ERR = (byte) 0x01; + + public static final Map<Byte, Deserializer<? extends IPacket>> TYPE_DESERIALIZER_MAP = + new HashMap<>(); + + static { + ICMP6.TYPE_DESERIALIZER_MAP.put(ICMP6.ROUTER_SOLICITATION, RouterSolicitation.deserializer()); + ICMP6.TYPE_DESERIALIZER_MAP.put(ICMP6.ROUTER_ADVERTISEMENT, RouterAdvertisement.deserializer()); + ICMP6.TYPE_DESERIALIZER_MAP.put(ICMP6.NEIGHBOR_SOLICITATION, NeighborSolicitation.deserializer()); + ICMP6.TYPE_DESERIALIZER_MAP.put(ICMP6.NEIGHBOR_ADVERTISEMENT, NeighborAdvertisement.deserializer()); + ICMP6.TYPE_DESERIALIZER_MAP.put(ICMP6.REDIRECT, Redirect.deserializer()); + } + + protected byte icmpType; + protected byte icmpCode; + protected short checksum; + + private static final byte[] ZERO_ADDRESS = new byte[Ip6Address.BYTE_LENGTH]; + + /** + * Gets ICMP6 type. + * + * @return the ICMP6 type + */ + public byte getIcmpType() { + return this.icmpType; + } + + /** + * Sets ICMP6 type. + * + * @param icmpType the ICMP type to set + * @return this + */ + public ICMP6 setIcmpType(final byte icmpType) { + this.icmpType = icmpType; + return this; + } + + /** + * Gets ICMP6 code. + * + * @return the ICMP6 code + */ + public byte getIcmpCode() { + return this.icmpCode; + } + + /** + * Sets ICMP6 code. + * + * @param icmpCode the ICMP6 code to set + * @return this + */ + public ICMP6 setIcmpCode(final byte icmpCode) { + this.icmpCode = icmpCode; + return this; + } + + /** + * Gets checksum. + * + * @return the checksum + */ + public short getChecksum() { + return this.checksum; + } + + /** + * Sets checksum. + * + * @param checksum the checksum to set + * @return this + */ + public ICMP6 setChecksum(final short checksum) { + this.checksum = checksum; + return this; + } + + @Override + public byte[] serialize() { + byte[] payloadData = null; + if (this.payload != null) { + this.payload.setParent(this); + payloadData = this.payload.serialize(); + } + + int payloadLength = 0; + if (payloadData != null) { + payloadLength = payloadData.length; + } + + final byte[] data = new byte[HEADER_LENGTH + payloadLength]; + final ByteBuffer bbData = ByteBuffer.wrap(data); + + // Creating ByteBuffer for checksum calculation + final byte[] checksumData = + new byte[IPv6.FIXED_HEADER_LENGTH + HEADER_LENGTH + payloadLength]; + final ByteBuffer bbChecksum = ByteBuffer.wrap(checksumData); + + // + // Creating IPv6 Pseudo Header for checksum calculation according + // to RFC 4443 and RFC 2460 + // + IPv6 ipv6Parent = null; + for (IPacket p = this.parent; p != null; p = p.getParent()) { + if (p instanceof IPv6) { + ipv6Parent = (IPv6) p; + break; + } + } + if (ipv6Parent != null) { + bbChecksum.put(ipv6Parent.getSourceAddress()); + bbChecksum.put(ipv6Parent.getDestinationAddress()); + } else { + // NOTE: IPv6 source and destination addresses unknown. Use zeroes. + bbChecksum.put(ZERO_ADDRESS); + bbChecksum.put(ZERO_ADDRESS); + } + bbChecksum.putInt(HEADER_LENGTH + payloadLength); + bbChecksum.put((byte) 0); + bbChecksum.put((byte) 0); + bbChecksum.put((byte) 0); + bbChecksum.put(IPv6.PROTOCOL_ICMP6); + bbChecksum.put(this.icmpType); + bbChecksum.put(this.icmpCode); + bbChecksum.put((byte) 0); + bbChecksum.put((byte) 0); + + bbData.put(this.icmpType); + bbData.put(this.icmpCode); + bbData.putShort(this.checksum); + if (payloadData != null) { + bbData.put(payloadData); + bbChecksum.put(payloadData); + } + + if (this.parent != null) { + if (this.parent instanceof IPv6) { + ((IPv6) this.parent).setNextHeader(IPv6.PROTOCOL_ICMP6); + } else if (this.parent instanceof IExtensionHeader) { + ((IExtensionHeader) this.parent).setNextHeader(IPv6.PROTOCOL_ICMP6); + } + } + + // compute checksum if needed + if (this.checksum == 0) { + bbData.rewind(); + bbChecksum.rewind(); + int accumulation = 0; + + for (int i = 0; i < checksumData.length / 2; ++i) { + accumulation += 0xffff & bbChecksum.getShort(); + } + // pad to an even number of shorts + if (checksumData.length % 2 > 0) { + accumulation += (bbChecksum.get() & 0xff) << 8; + } + + accumulation = (accumulation >> 16 & 0xffff) + + (accumulation & 0xffff); + this.checksum = (short) (~accumulation & 0xffff); + bbData.putShort(2, this.checksum); + } + return data; + } + + @Override + public IPacket deserialize(final byte[] data, final int offset, + final int length) { + final ByteBuffer bb = ByteBuffer.wrap(data, offset, length); + this.icmpType = bb.get(); + this.icmpCode = bb.get(); + this.checksum = bb.getShort(); + + Deserializer<? extends IPacket> deserializer; + if (ICMP6.TYPE_DESERIALIZER_MAP.containsKey(icmpType)) { + deserializer = TYPE_DESERIALIZER_MAP.get(icmpType); + } else { + deserializer = Data.deserializer(); + } + try { + this.payload = deserializer.deserialize(data, bb.position(), + bb.limit() - bb.position()); + this.payload.setParent(this); + } catch (DeserializationException e) { + return this; + } + + return this; + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + final int prime = 5807; + int result = super.hashCode(); + result = prime * result + this.icmpType; + result = prime * result + this.icmpCode; + result = prime * result + this.checksum; + return result; + } + + /* + * (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 ICMP6)) { + return false; + } + final ICMP6 other = (ICMP6) obj; + if (this.icmpType != other.icmpType) { + return false; + } + if (this.icmpCode != other.icmpCode) { + return false; + } + if (this.checksum != other.checksum) { + return false; + } + return true; + } + + /** + * Deserializer function for ICMPv6 packets. + * + * @return deserializer function + */ + public static Deserializer<ICMP6> deserializer() { + return (data, offset, length) -> { + checkInput(data, offset, length, HEADER_LENGTH); + + ICMP6 icmp6 = new ICMP6(); + + ByteBuffer bb = ByteBuffer.wrap(data, offset, length); + + icmp6.icmpType = bb.get(); + icmp6.icmpCode = bb.get(); + icmp6.checksum = bb.getShort(); + + Deserializer<? extends IPacket> deserializer; + if (ICMP6.TYPE_DESERIALIZER_MAP.containsKey(icmp6.icmpType)) { + deserializer = TYPE_DESERIALIZER_MAP.get(icmp6.icmpType); + } else { + deserializer = Data.deserializer(); + } + icmp6.payload = deserializer.deserialize(data, bb.position(), + bb.limit() - bb.position()); + icmp6.payload.setParent(icmp6); + + return icmp6; + }; + } +} diff --git a/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/IGMP.java b/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/IGMP.java new file mode 100644 index 00000000..e7abbd6a --- /dev/null +++ b/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/IGMP.java @@ -0,0 +1,335 @@ +/* + * 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.HashMap; +import java.util.List; +import java.util.Map; +import org.slf4j.Logger; + +import static org.slf4j.LoggerFactory.getLogger; +import static com.google.common.base.Preconditions.checkNotNull; +import static org.onlab.packet.PacketUtils.checkInput; + + +/** + * 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 + */ + @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(); + + ByteBuffer bb = ByteBuffer.wrap(data); + 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; + } +} diff --git a/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/IGMPGroup.java b/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/IGMPGroup.java new file mode 100644 index 00000000..70ff5563 --- /dev/null +++ b/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/IGMPGroup.java @@ -0,0 +1,98 @@ +/* + * 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.List; + +/** + * A class to represent Groups for membership query and reports. + */ +public abstract class IGMPGroup { + + protected int auxInfo; + protected IpAddress gaddr; + protected List<IpAddress> sources = new ArrayList<>(); + + public IGMPGroup() { + } + + /** + * Initialize this object with a multicast group address and additional info. + * + * @param gaddr: the multicast group address for this message type. + * @param auxInfo: additional info potentially used by IGMPQuery + */ + public IGMPGroup(IpAddress gaddr, int auxInfo) { + this.gaddr = gaddr; + this.auxInfo = auxInfo; + } + + /** + * Get the multicast group address. + * + * @return the group address + */ + public IpAddress getGaddr() { + return this.gaddr; + } + + /** + * Get the auxillary info. + * + * @return the auxillary info + */ + public int getAuxInfo() { + return this.auxInfo; + } + + /** + * Add a unicast source address to this message. + * + * @param saddr IPv4 unicast source address + */ + public void addSource(IpAddress saddr) { + sources.add(saddr); + } + + /** + * Return the list of source addresses. + * + * @return list of source addresses + */ + public List<IpAddress> getSources() { + return sources; + } + + /** + * Deserialize an IGMPQuery or IGMPMembership message. + * + * @param bb the ByteBuffer wrapping the serialized message. The position of the + * ByteBuffer should be pointing at the head of either message type. + * @return An object populated with the respective IGMPGroup subclass + * @throws DeserializationException in case deserialization goes wrong + */ + public abstract IGMPGroup deserialize(ByteBuffer bb) throws DeserializationException; + + /** + * Serialize the IGMPGroup subclass. + * + * @param bb the ByteBuffer to write into, positioned at the next spot to be written to. + * @return The serialized message + */ + public abstract byte[] serialize(ByteBuffer bb); +} diff --git a/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/IGMPMembership.java b/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/IGMPMembership.java new file mode 100644 index 00000000..495e283c --- /dev/null +++ b/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/IGMPMembership.java @@ -0,0 +1,158 @@ +/* + * 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 { + + 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(); + } + + /** + * 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; + } +} diff --git a/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/IGMPQuery.java b/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/IGMPQuery.java new file mode 100644 index 00000000..bb53eba5 --- /dev/null +++ b/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/IGMPQuery.java @@ -0,0 +1,202 @@ +/* + * 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; + +public class IGMPQuery extends IGMPGroup { + + // Bits and bytes after the group address + private byte resv = 0; + private boolean sbit = false; + private byte qrv = 2; + private byte qqic = 0x7d; + + /** + * Create IGMP Query message. + * + * @param gaddr initiaze with a group address. + * @param auxInfo auxillary info. + */ + public IGMPQuery(IpAddress gaddr, int auxInfo) { + super(gaddr, auxInfo); + } + + /** + * Create IGMP Query message. + */ + public IGMPQuery() { + super(); + } + + /** + * Is the S flag set? Telling adjacent routers to suppress normal timer updates. + * + * @return true if the flag is set, false otherwise + */ + public boolean isSbit() { + return sbit; + } + + /** + * Set the S flag. Default is false. + * + * @param sbit true or false + */ + public void setSbit(boolean sbit) { + this.sbit = sbit; + } + + /** + * Get the Querier Robustness Variable. + * + * @return querier robustness value + */ + public byte getQrv() { + return qrv; + } + + /** + * Set the Querier Robustness Variable. Default is 2. + * + * @param qrv new querier robustness value + */ + public void setQrv(byte qrv) { + this.qrv = qrv; + } + + /** + * Get the reserved field. Should be zero, but ignored regardless of it's value. + * + * @return the reserved field. + */ + public byte getResv() { + return resv; + } + + /** + * Set the reserved field. Should be 0 and ignored by receivers. + * + * @param resv the reserved field. + */ + public void setResv(byte resv) { + this.resv = resv; + } + + /** + * Serialize this IGMPQuery. + * + * @param bb the ByteBuffer to write into, positioned at the next spot to be written to. + * @return the serialized message + */ + @Override + public byte[] serialize(ByteBuffer bb) { + + bb.put(gaddr.toOctets()); + byte fld = (byte) (0x7 & qrv); + bb.put(fld); + bb.put(qqic); + bb.putShort((short) sources.size()); + for (IpAddress ipaddr : sources) { + bb.put(ipaddr.toOctets()); + } + return bb.array(); + } + + /** + * Deserialize the IGMP Query group structure. + * + * @param bb ByteBuffer pointing at the IGMP Query group address + * @return the IGMP Group object + */ + public IGMPGroup deserialize(ByteBuffer bb) throws DeserializationException { + + gaddr = Ip4Address.valueOf(bb.getInt()); + byte fld = bb.get(); + + // Just ignore the reserved bits + resv = 0; + this.sbit = ((fld & 0x8) == 0x8); + qrv = (byte) (fld & 0x7); + + // QQIC field + qqic = bb.get(); + + // Get the number of sources. + short nsrcs = bb.getShort(); + + // Do a sanity check on the amount of space we have in our buffer. + int lengthNeeded = (Ip4Address.BYTE_LENGTH * nsrcs); + PacketUtils.checkHeaderLength(bb.remaining(), lengthNeeded); + + for (; nsrcs > 0; nsrcs--) { + Ip4Address ipaddr = Ip4Address.valueOf(bb.getInt()); + this.sources.add(ipaddr); + } + return this; + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#equals() + */ + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof IGMPQuery)) { + return false; + } + IGMPQuery other = (IGMPQuery) obj; + + if (this.sbit != other.sbit) { + return false; + } + if (this.qrv != other.qrv) { + return false; + } + if (this.qqic != other.qqic) { + 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.qqic; + result = prime * result + this.qrv; + result = prime * result + this.sources.hashCode(); + return result; + } +} diff --git a/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/IPacket.java b/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/IPacket.java new file mode 100644 index 00000000..64e6ac36 --- /dev/null +++ b/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/IPacket.java @@ -0,0 +1,89 @@ +/* + * Copyright 2014 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; + +/** + * + */ +public interface IPacket { + /** + * + * @return the payload + */ + IPacket getPayload(); + + /** + * + * @param packet new payload + * @return self + */ + IPacket setPayload(IPacket packet); + + /** + * + * @return parent packet + */ + IPacket getParent(); + + /** + * + * @param packet new parent + * @return self + */ + IPacket setParent(IPacket packet); + + /** + * Reset any checksums as needed, and call resetChecksum on all parents. + */ + void resetChecksum(); + + /** + * Sets all payloads parent packet if applicable, then serializes this + * packet and all payloads. + * + * @return a byte[] containing this packet and payloads + */ + byte[] serialize(); + + /** + * Deserializes this packet layer and all possible payloads. + * + * NOTE: This method has been deprecated and will be removed in a future + * release. It is now recommended to use the Deserializer function provided + * by the deserialize() method on each packet to deserialize them. The + * Deserializer functions are robust to malformed input. + * + * @param data bytes to deserialize + * @param offset + * offset to start deserializing from + * @param length + * length of the data to deserialize + * @return the deserialized data + * @deprecated in Cardinal Release + */ + @Deprecated + IPacket deserialize(byte[] data, int offset, int length); + + /** + * Clone this packet and its payload packet but not its parent. + * + * @return the clone + */ + Object clone(); +} diff --git a/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/IPv4.java b/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/IPv4.java new file mode 100644 index 00000000..d75b50a2 --- /dev/null +++ b/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/IPv4.java @@ -0,0 +1,731 @@ +/* + * Copyright 2014-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.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +import static org.onlab.packet.PacketUtils.*; + +/** + * + */ +public class IPv4 extends BasePacket { + public static final byte PROTOCOL_ICMP = 0x1; + public static final byte PROTOCOL_IGMP = 0x2; + public static final byte PROTOCOL_TCP = 0x6; + public static final byte PROTOCOL_UDP = 0x11; + public static final Map<Byte, Deserializer<? extends IPacket>> PROTOCOL_DESERIALIZER_MAP = + new HashMap<>(); + + static { + IPv4.PROTOCOL_DESERIALIZER_MAP.put(IPv4.PROTOCOL_ICMP, ICMP.deserializer()); + IPv4.PROTOCOL_DESERIALIZER_MAP.put(IPv4.PROTOCOL_IGMP, IGMP.deserializer()); + IPv4.PROTOCOL_DESERIALIZER_MAP.put(IPv4.PROTOCOL_TCP, TCP.deserializer()); + IPv4.PROTOCOL_DESERIALIZER_MAP.put(IPv4.PROTOCOL_UDP, UDP.deserializer()); + } + + private static final byte DSCP_MASK = 0x3f; + private static final byte DSCP_OFFSET = 2; + private static final byte ECN_MASK = 0x3; + + private static final short HEADER_LENGTH = 20; + + protected byte version; + protected byte headerLength; + protected byte diffServ; + protected short totalLength; + protected short identification; + protected byte flags; + protected short fragmentOffset; + protected byte ttl; + protected byte protocol; + protected short checksum; + protected int sourceAddress; + protected int destinationAddress; + protected byte[] options; + + protected boolean isTruncated; + + /** + * Default constructor that sets the version to 4. + */ + public IPv4() { + super(); + this.version = 4; + this.isTruncated = false; + } + + /** + * @return the version + */ + public byte getVersion() { + return this.version; + } + + /** + * @param version + * the version to set + * @return this + */ + public IPv4 setVersion(final byte version) { + this.version = version; + return this; + } + + /** + * @return the headerLength + */ + public byte getHeaderLength() { + return this.headerLength; + } + + /** + * Gets the DSCP value (6 bits). + * + * @return the DSCP value (6 bits) + */ + public byte getDscp() { + return (byte) ((this.diffServ >>> DSCP_OFFSET) & DSCP_MASK); + } + + /** + * Sets the DSCP value (6 bits). + * + * @param dscp the DSCP value (6 bits) + * @return this + */ + public IPv4 setDscp(byte dscp) { + this.diffServ &= ~(DSCP_MASK << DSCP_OFFSET); + this.diffServ |= (dscp & DSCP_MASK) << DSCP_OFFSET; + return this; + } + + /** + * Gets the ECN value (2 bits). + * + * @return the ECN value (2 bits) + */ + public byte getEcn() { + return (byte) (this.diffServ & ECN_MASK); + } + + /** + * Sets the ECN value (2 bits). + * + * @param ecn the ECN value (2 bits) + * @return this + */ + public IPv4 setEcn(byte ecn) { + this.diffServ &= ~ECN_MASK; + this.diffServ |= (ecn & ECN_MASK); + return this; + } + + /** + * Gets the DiffServ octet (including the DSCP and ECN bits). + * + * @return the diffServ octet (including the DSCP and ECN bits) + */ + public byte getDiffServ() { + return this.diffServ; + } + + /** + * Sets the DiffServ octet (including the DSCP and ECN bits). + * + * @param diffServ the diffServ octet to set (including the DSCP and ECN + * bits) + * @return this + */ + public IPv4 setDiffServ(final byte diffServ) { + this.diffServ = diffServ; + return this; + } + + /** + * @return the totalLength + */ + public short getTotalLength() { + return this.totalLength; + } + + /** + * @return the identification + */ + public short getIdentification() { + return this.identification; + } + + public boolean isTruncated() { + return this.isTruncated; + } + + public void setTruncated(final boolean isTruncated) { + this.isTruncated = isTruncated; + } + + /** + * @param identification + * the identification to set + * @return this + */ + public IPv4 setIdentification(final short identification) { + this.identification = identification; + return this; + } + + /** + * @return the flags + */ + public byte getFlags() { + return this.flags; + } + + /** + * @param flags + * the flags to set + * @return this +s */ + public IPv4 setFlags(final byte flags) { + this.flags = flags; + return this; + } + + /** + * @return the fragmentOffset + */ + public short getFragmentOffset() { + return this.fragmentOffset; + } + + /** + * @param fragmentOffset + * the fragmentOffset to set + * @return this + */ + public IPv4 setFragmentOffset(final short fragmentOffset) { + this.fragmentOffset = fragmentOffset; + return this; + } + + /** + * @return the ttl + */ + public byte getTtl() { + return this.ttl; + } + + /** + * @param ttl + * the ttl to set + * @return this + */ + public IPv4 setTtl(final byte ttl) { + this.ttl = ttl; + return this; + } + + /** + * @return the protocol + */ + public byte getProtocol() { + return this.protocol; + } + + /** + * @param protocol + * the protocol to set + * @return this + */ + public IPv4 setProtocol(final byte protocol) { + this.protocol = protocol; + return this; + } + + /** + * @return the checksum + */ + public short getChecksum() { + return this.checksum; + } + + /** + * @param checksum + * the checksum to set + * @return this + */ + public IPv4 setChecksum(final short checksum) { + this.checksum = checksum; + return this; + } + + @Override + public void resetChecksum() { + this.checksum = 0; + super.resetChecksum(); + } + + /** + * @return the sourceAddress + */ + public int getSourceAddress() { + return this.sourceAddress; + } + + /** + * @param sourceAddress + * the sourceAddress to set + * @return this + */ + public IPv4 setSourceAddress(final int sourceAddress) { + this.sourceAddress = sourceAddress; + return this; + } + + /** + * @param sourceAddress + * the sourceAddress to set + * @return this + */ + public IPv4 setSourceAddress(final String sourceAddress) { + this.sourceAddress = IPv4.toIPv4Address(sourceAddress); + return this; + } + + /** + * @return the destinationAddress + */ + public int getDestinationAddress() { + return this.destinationAddress; + } + + /** + * @param destinationAddress + * the destinationAddress to set + * @return this + */ + public IPv4 setDestinationAddress(final int destinationAddress) { + this.destinationAddress = destinationAddress; + return this; + } + + /** + * @param destinationAddress + * the destinationAddress to set + * @return this + */ + public IPv4 setDestinationAddress(final String destinationAddress) { + this.destinationAddress = IPv4.toIPv4Address(destinationAddress); + return this; + } + + /** + * @return the options + */ + public byte[] getOptions() { + return this.options; + } + + /** + * @param options + * the options to set + * @return this + */ + public IPv4 setOptions(final byte[] options) { + if (options != null && options.length % 4 > 0) { + throw new IllegalArgumentException( + "Options length must be a multiple of 4"); + } + this.options = options; + return this; + } + + /** + * Serializes the packet. Will compute and set the following fields if they + * are set to specific values at the time serialize is called: -checksum : 0 + * -headerLength : 0 -totalLength : 0 + */ + @Override + public byte[] serialize() { + byte[] payloadData = null; + if (this.payload != null) { + this.payload.setParent(this); + payloadData = this.payload.serialize(); + } + + int optionsLength = 0; + if (this.options != null) { + optionsLength = this.options.length / 4; + } + this.headerLength = (byte) (5 + optionsLength); + + this.totalLength = (short) (this.headerLength * 4 + (payloadData == null ? 0 + : payloadData.length)); + + final byte[] data = new byte[this.totalLength]; + final ByteBuffer bb = ByteBuffer.wrap(data); + + bb.put((byte) ((this.version & 0xf) << 4 | this.headerLength & 0xf)); + bb.put(this.diffServ); + bb.putShort(this.totalLength); + bb.putShort(this.identification); + bb.putShort((short) ((this.flags & 0x7) << 13 | this.fragmentOffset & 0x1fff)); + bb.put(this.ttl); + bb.put(this.protocol); + bb.putShort(this.checksum); + bb.putInt(this.sourceAddress); + bb.putInt(this.destinationAddress); + if (this.options != null) { + bb.put(this.options); + } + if (payloadData != null) { + bb.put(payloadData); + } + + // compute checksum if needed + if (this.checksum == 0) { + bb.rewind(); + int accumulation = 0; + for (int i = 0; i < this.headerLength * 2; ++i) { + accumulation += 0xffff & bb.getShort(); + } + accumulation = (accumulation >> 16 & 0xffff) + + (accumulation & 0xffff); + this.checksum = (short) (~accumulation & 0xffff); + bb.putShort(10, this.checksum); + } + return data; + } + + @Override + public IPacket deserialize(final byte[] data, final int offset, + final int length) { + final ByteBuffer bb = ByteBuffer.wrap(data, offset, length); + short sscratch; + + this.version = bb.get(); + this.headerLength = (byte) (this.version & 0xf); + this.version = (byte) (this.version >> 4 & 0xf); + this.diffServ = bb.get(); + this.totalLength = bb.getShort(); + this.identification = bb.getShort(); + sscratch = bb.getShort(); + this.flags = (byte) (sscratch >> 13 & 0x7); + this.fragmentOffset = (short) (sscratch & 0x1fff); + this.ttl = bb.get(); + this.protocol = bb.get(); + this.checksum = bb.getShort(); + this.sourceAddress = bb.getInt(); + this.destinationAddress = bb.getInt(); + + if (this.headerLength > 5) { + final int optionsLength = (this.headerLength - 5) * 4; + this.options = new byte[optionsLength]; + bb.get(this.options); + } + + if (this.totalLength != length) { + this.isTruncated = true; + } else { + this.isTruncated = false; + } + + Deserializer<? extends IPacket> deserializer; + if (IPv4.PROTOCOL_DESERIALIZER_MAP.containsKey(this.protocol)) { + deserializer = IPv4.PROTOCOL_DESERIALIZER_MAP.get(this.protocol); + } else { + deserializer = Data.deserializer(); + } + try { + this.payload = deserializer.deserialize(data, bb.position(), + bb.limit() - bb.position()); + this.payload.setParent(this); + } catch (DeserializationException e) { + return this; + } + + return this; + } + + /** + * Accepts an IPv4 address of the form xxx.xxx.xxx.xxx, ie 192.168.0.1 and + * returns the corresponding 32 bit integer. + * + * @param ipAddress ip address in string form + * @return int ip address value + */ + public static int toIPv4Address(final String ipAddress) { + if (ipAddress == null) { + throw new IllegalArgumentException("Specified IPv4 address must" + + "contain 4 sets of numerical digits separated by periods"); + } + final String[] octets = ipAddress.split("\\."); + if (octets.length != 4) { + throw new IllegalArgumentException("Specified IPv4 address must" + + "contain 4 sets of numerical digits separated by periods"); + } + + int result = 0; + for (int i = 0; i < 4; ++i) { + result |= Integer.parseInt(octets[i]) << (3 - i) * 8; + } + return result; + } + + /** + * Accepts an IPv4 address in a byte array and returns the corresponding + * 32-bit integer value. + * + * @param ipAddress ip address in byte form + * @return int ip address value + */ + public static int toIPv4Address(final byte[] ipAddress) { + int ip = 0; + for (int i = 0; i < 4; i++) { + final int t = (ipAddress[i] & 0xff) << (3 - i) * 8; + ip |= t; + } + return ip; + } + + /** + * Accepts an IPv4 address and returns of string of the form xxx.xxx.xxx.xxx, + * e.g., 192.168.0.1. + * + * @param ipAddress ip address in form + * @return string form of ip address + */ + public static String fromIPv4Address(final int ipAddress) { + final StringBuffer sb = new StringBuffer(); + int result = 0; + for (int i = 0; i < 4; ++i) { + result = ipAddress >> (3 - i) * 8 & 0xff; + sb.append(result); + if (i != 3) { + sb.append("."); + } + } + return sb.toString(); + } + + /** + * Accepts a collection of IPv4 addresses as integers and returns a single + * String useful in toString method's containing collections of IP + * addresses. + * + * @param ipAddresses + * collection + * @return ip addresses in comma-separated string form + */ + public static String fromIPv4AddressCollection( + final Collection<Integer> ipAddresses) { + if (ipAddresses == null) { + return "null"; + } + final StringBuffer sb = new StringBuffer(); + sb.append("["); + for (final Integer ip : ipAddresses) { + sb.append(IPv4.fromIPv4Address(ip)); + sb.append(","); + } + sb.replace(sb.length() - 1, sb.length(), "]"); + return sb.toString(); + } + + /** + * Accepts an IPv4 address of the form xxx.xxx.xxx.xxx, ie 192.168.0.1 and + * returns the corresponding byte array. + * + * @param ipAddress + * The IP address in the form xx.xxx.xxx.xxx. + * @return The IP address separated into bytes + */ + public static byte[] toIPv4AddressBytes(final String ipAddress) { + final String[] octets = ipAddress.split("\\."); + if (octets.length != 4) { + throw new IllegalArgumentException("Specified IPv4 address must" + + "contain 4 sets of numerical digits separated by periods"); + } + + final byte[] result = new byte[4]; + for (int i = 0; i < 4; ++i) { + result[i] = Integer.valueOf(octets[i]).byteValue(); + } + return result; + } + + /** + * Accepts an IPv4 address in the form of an integer and returns the + * corresponding byte array. + * + * @param ipAddress + * The IP address as an integer. + * @return The IP address separated into bytes. + */ + public static byte[] toIPv4AddressBytes(final int ipAddress) { + return new byte[] {(byte) (ipAddress >>> 24), + (byte) (ipAddress >>> 16), (byte) (ipAddress >>> 8), + (byte) ipAddress}; + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + final int prime = 2521; + int result = super.hashCode(); + result = prime * result + this.checksum; + result = prime * result + this.destinationAddress; + result = prime * result + this.diffServ; + result = prime * result + this.flags; + result = prime * result + this.fragmentOffset; + result = prime * result + this.headerLength; + result = prime * result + this.identification; + result = prime * result + Arrays.hashCode(this.options); + result = prime * result + this.protocol; + result = prime * result + this.sourceAddress; + result = prime * result + this.totalLength; + result = prime * result + this.ttl; + result = prime * result + this.version; + return result; + } + + /* + * (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 IPv4)) { + return false; + } + final IPv4 other = (IPv4) obj; + if (this.checksum != other.checksum) { + return false; + } + if (this.destinationAddress != other.destinationAddress) { + return false; + } + if (this.diffServ != other.diffServ) { + return false; + } + if (this.flags != other.flags) { + return false; + } + if (this.fragmentOffset != other.fragmentOffset) { + return false; + } + if (this.headerLength != other.headerLength) { + return false; + } + if (this.identification != other.identification) { + return false; + } + if (!Arrays.equals(this.options, other.options)) { + return false; + } + if (this.protocol != other.protocol) { + return false; + } + if (this.sourceAddress != other.sourceAddress) { + return false; + } + if (this.totalLength != other.totalLength) { + return false; + } + if (this.ttl != other.ttl) { + return false; + } + if (this.version != other.version) { + return false; + } + return true; + } + + /** + * Deserializer function for IPv4 packets. + * + * @return deserializer function + */ + public static Deserializer<IPv4> deserializer() { + return (data, offset, length) -> { + checkInput(data, offset, length, HEADER_LENGTH); + + IPv4 ipv4 = new IPv4(); + + final ByteBuffer bb = ByteBuffer.wrap(data, offset, length); + + byte versionByte = bb.get(); + ipv4.headerLength = (byte) (versionByte & 0xf); + ipv4.setVersion((byte) (versionByte >> 4 & 0xf)); + ipv4.setDiffServ(bb.get()); + ipv4.totalLength = bb.getShort(); + ipv4.identification = bb.getShort(); + short flagsFragment = bb.getShort(); + ipv4.flags = (byte) (flagsFragment >> 13 & 0x7); + ipv4.fragmentOffset = (short) (flagsFragment & 0x1fff); + ipv4.ttl = bb.get(); + ipv4.protocol = bb.get(); + ipv4.checksum = bb.getShort(); + ipv4.sourceAddress = bb.getInt(); + ipv4.destinationAddress = bb.getInt(); + + if (ipv4.headerLength > 5) { + checkHeaderLength(length, ipv4.headerLength * 4); + + int optionsLength = (ipv4.headerLength - 5) * 4; + ipv4.options = new byte[optionsLength]; + bb.get(ipv4.options); + } + + Deserializer<? extends IPacket> deserializer; + if (IPv4.PROTOCOL_DESERIALIZER_MAP.containsKey(ipv4.protocol)) { + deserializer = IPv4.PROTOCOL_DESERIALIZER_MAP.get(ipv4.protocol); + } else { + deserializer = Data.deserializer(); + } + ipv4.payload = deserializer.deserialize(data, bb.position(), + bb.limit() - bb.position()); + ipv4.payload.setParent(ipv4); + + if (ipv4.totalLength != length) { + ipv4.isTruncated = true; + } else { + ipv4.isTruncated = false; + } + + return ipv4; + }; + } +} diff --git a/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/IPv6.java b/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/IPv6.java new file mode 100644 index 00000000..2e59632a --- /dev/null +++ b/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/IPv6.java @@ -0,0 +1,384 @@ +/* + * Copyright 2014-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 org.onlab.packet.ipv6.Authentication; +import org.onlab.packet.ipv6.DestinationOptions; +import org.onlab.packet.ipv6.EncapSecurityPayload; +import org.onlab.packet.ipv6.Fragment; +import org.onlab.packet.ipv6.HopByHopOptions; +import org.onlab.packet.ipv6.IExtensionHeader; +import org.onlab.packet.ipv6.Routing; + +import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +import static org.onlab.packet.PacketUtils.checkInput; + +/** + * Implements IPv6 packet format. (RFC 2460) + */ +public class IPv6 extends BasePacket implements IExtensionHeader { + public static final byte FIXED_HEADER_LENGTH = 40; // bytes + + public static final byte PROTOCOL_TCP = 0x6; + public static final byte PROTOCOL_UDP = 0x11; + public static final byte PROTOCOL_ICMP6 = 0x3A; + public static final byte PROTOCOL_HOPOPT = 0x00; + public static final byte PROTOCOL_ROUTING = 0x2B; + public static final byte PROTOCOL_FRAG = 0x2C; + public static final byte PROTOCOL_ESP = 0x32; + public static final byte PROTOCOL_AH = 0x33; + public static final byte PROTOCOL_DSTOPT = 0x3C; + + + public static final Map<Byte, Deserializer<? extends IPacket>> PROTOCOL_DESERIALIZER_MAP = + new HashMap<>(); + + static { + IPv6.PROTOCOL_DESERIALIZER_MAP.put(IPv6.PROTOCOL_ICMP6, ICMP6.deserializer()); + IPv6.PROTOCOL_DESERIALIZER_MAP.put(IPv6.PROTOCOL_TCP, TCP.deserializer()); + IPv6.PROTOCOL_DESERIALIZER_MAP.put(IPv6.PROTOCOL_UDP, UDP.deserializer()); + IPv6.PROTOCOL_DESERIALIZER_MAP.put(IPv6.PROTOCOL_HOPOPT, HopByHopOptions.deserializer()); + IPv6.PROTOCOL_DESERIALIZER_MAP.put(IPv6.PROTOCOL_ROUTING, Routing.deserializer()); + IPv6.PROTOCOL_DESERIALIZER_MAP.put(IPv6.PROTOCOL_FRAG, Fragment.deserializer()); + IPv6.PROTOCOL_DESERIALIZER_MAP.put(IPv6.PROTOCOL_ESP, EncapSecurityPayload.deserializer()); + IPv6.PROTOCOL_DESERIALIZER_MAP.put(IPv6.PROTOCOL_AH, Authentication.deserializer()); + IPv6.PROTOCOL_DESERIALIZER_MAP.put(IPv6.PROTOCOL_DSTOPT, DestinationOptions.deserializer()); + } + + protected byte version; + protected byte trafficClass; + protected int flowLabel; + protected short payloadLength; + protected byte nextHeader; + protected byte hopLimit; + protected byte[] sourceAddress = new byte[Ip6Address.BYTE_LENGTH]; + protected byte[] destinationAddress = new byte[Ip6Address.BYTE_LENGTH]; + + /** + * Default constructor that sets the version to 6. + */ + public IPv6() { + super(); + this.version = 6; + } + + /** + * Gets IP version. + * + * @return the IP version + */ + public byte getVersion() { + return this.version; + } + + /** + * Sets IP version. + * + * @param version the IP version to set + * @return this + */ + public IPv6 setVersion(final byte version) { + this.version = version; + return this; + } + + /** + * Gets traffic class. + * + * @return the traffic class + */ + public byte getTrafficClass() { + return this.trafficClass; + } + + /** + * Sets traffic class. + * + * @param trafficClass the traffic class to set + * @return this + */ + public IPv6 setTrafficClass(final byte trafficClass) { + this.trafficClass = trafficClass; + return this; + } + + /** + * Gets flow label. + * + * @return the flow label + */ + public int getFlowLabel() { + return this.flowLabel; + } + + /** + * Sets flow label. + * + * @param flowLabel the flow label to set + * @return this + */ + public IPv6 setFlowLabel(final int flowLabel) { + this.flowLabel = flowLabel; + return this; + } + + @Override + public byte getNextHeader() { + return this.nextHeader; + } + + @Override + public IPv6 setNextHeader(final byte nextHeader) { + this.nextHeader = nextHeader; + return this; + } + + /** + * Gets hop limit. + * + * @return the hop limit + */ + public byte getHopLimit() { + return this.hopLimit; + } + + /** + * Sets hop limit. + * + * @param hopLimit the hop limit to set + * @return this + */ + public IPv6 setHopLimit(final byte hopLimit) { + this.hopLimit = hopLimit; + return this; + } + + /** + * Gets source address. + * + * @return the IPv6 source address + */ + public byte[] getSourceAddress() { + return this.sourceAddress; + } + + /** + * Sets source address. + * + * @param sourceAddress the IPv6 source address to set + * @return this + */ + public IPv6 setSourceAddress(final byte[] sourceAddress) { + this.sourceAddress = Arrays.copyOfRange(sourceAddress, 0, Ip6Address.BYTE_LENGTH); + return this; + } + + /** + * Gets destination address. + * + * @return the IPv6 destination address + */ + public byte[] getDestinationAddress() { + return this.destinationAddress; + } + + /** + * Sets destination address. + * + * @param destinationAddress the IPv6 destination address to set + * @return this + */ + public IPv6 setDestinationAddress(final byte[] destinationAddress) { + this.destinationAddress = Arrays.copyOfRange(destinationAddress, 0, Ip6Address.BYTE_LENGTH); + return this; + } + + @Override + public byte[] serialize() { + byte[] payloadData = null; + if (this.payload != null) { + this.payload.setParent(this); + payloadData = this.payload.serialize(); + } + + this.payloadLength = 0; + if (payloadData != null) { + this.payloadLength = (short) payloadData.length; + } + + final byte[] data = new byte[FIXED_HEADER_LENGTH + payloadLength]; + final ByteBuffer bb = ByteBuffer.wrap(data); + + bb.putInt((this.version & 0xf) << 28 | (this.trafficClass & 0xff) << 20 | this.flowLabel & 0xfffff); + bb.putShort(this.payloadLength); + bb.put(this.nextHeader); + bb.put(this.hopLimit); + bb.put(this.sourceAddress, 0, Ip6Address.BYTE_LENGTH); + bb.put(this.destinationAddress, 0, Ip6Address.BYTE_LENGTH); + + if (payloadData != null) { + bb.put(payloadData); + } + + return data; + } + + @Override + public IPacket deserialize(final byte[] data, final int offset, + final int length) { + final ByteBuffer bb = ByteBuffer.wrap(data, offset, length); + int iscratch; + + iscratch = bb.getInt(); + this.version = (byte) (iscratch >> 28 & 0xf); + this.trafficClass = (byte) (iscratch >> 20 & 0xff); + this.flowLabel = iscratch & 0xfffff; + this.payloadLength = bb.getShort(); + this.nextHeader = bb.get(); + this.hopLimit = bb.get(); + bb.get(this.sourceAddress, 0, Ip6Address.BYTE_LENGTH); + bb.get(this.destinationAddress, 0, Ip6Address.BYTE_LENGTH); + + Deserializer<? extends IPacket> deserializer; + if (IPv6.PROTOCOL_DESERIALIZER_MAP.containsKey(this.nextHeader)) { + deserializer = IPv6.PROTOCOL_DESERIALIZER_MAP.get(this.nextHeader); + } else { + deserializer = Data.deserializer(); + } + try { + this.payload = deserializer.deserialize(data, bb.position(), + bb.limit() - bb.position()); + this.payload.setParent(this); + } catch (DeserializationException e) { + return this; + } + + return this; + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + final int prime = 2521; + int result = super.hashCode(); + ByteBuffer bb; + bb = ByteBuffer.wrap(this.destinationAddress); + for (int i = 0; i < 4; i++) { + result = prime * result + bb.getInt(); + } + result = prime * result + this.trafficClass; + result = prime * result + this.flowLabel; + result = prime * result + this.hopLimit; + result = prime * result + this.nextHeader; + result = prime * result + this.payloadLength; + bb = ByteBuffer.wrap(this.sourceAddress); + for (int i = 0; i < 4; i++) { + result = prime * result + bb.getInt(); + } + result = prime * result + this.version; + return result; + } + + /* + * (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 IPv6)) { + return false; + } + final IPv6 other = (IPv6) obj; + if (!Arrays.equals(this.destinationAddress, other.destinationAddress)) { + return false; + } + if (this.trafficClass != other.trafficClass) { + return false; + } + if (this.flowLabel != other.flowLabel) { + return false; + } + if (this.hopLimit != other.hopLimit) { + return false; + } + if (this.nextHeader != other.nextHeader) { + return false; + } + if (this.payloadLength != other.payloadLength) { + return false; + } + if (!Arrays.equals(this.sourceAddress, other.sourceAddress)) { + return false; + } + return true; + } + + /** + * Deserializer function for IPv6 packets. + * + * @return deserializer function + */ + public static Deserializer<IPv6> deserializer() { + return (data, offset, length) -> { + checkInput(data, offset, length, FIXED_HEADER_LENGTH); + + IPv6 ipv6 = new IPv6(); + + ByteBuffer bb = ByteBuffer.wrap(data, offset, length); + + int iscratch = bb.getInt(); + + ipv6.version = (byte) (iscratch >> 28 & 0xf); + ipv6.trafficClass = (byte) (iscratch >> 20 & 0xff); + ipv6.flowLabel = iscratch & 0xfffff; + ipv6.payloadLength = bb.getShort(); + ipv6.nextHeader = bb.get(); + ipv6.hopLimit = bb.get(); + bb.get(ipv6.sourceAddress, 0, Ip6Address.BYTE_LENGTH); + bb.get(ipv6.destinationAddress, 0, Ip6Address.BYTE_LENGTH); + + Deserializer<? extends IPacket> deserializer; + if (IPv6.PROTOCOL_DESERIALIZER_MAP.containsKey(ipv6.nextHeader)) { + deserializer = IPv6.PROTOCOL_DESERIALIZER_MAP.get(ipv6.nextHeader); + } else { + deserializer = Data.deserializer(); + } + ipv6.payload = deserializer.deserialize(data, bb.position(), + bb.limit() - bb.position()); + ipv6.payload.setParent(ipv6); + + return ipv6; + }; + } +} diff --git a/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/Ip4Address.java b/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/Ip4Address.java new file mode 100644 index 00000000..114f126c --- /dev/null +++ b/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/Ip4Address.java @@ -0,0 +1,174 @@ +/* + * Copyright 2014 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.net.InetAddress; +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.nio.ByteBuffer; +import java.util.Arrays; + +import com.google.common.net.InetAddresses; + +/** + * A class representing an IPv4 address. + * This class is immutable. + */ +public final class Ip4Address extends IpAddress { + public static final IpAddress.Version VERSION = IpAddress.Version.INET; + public static final int BYTE_LENGTH = IpAddress.INET_BYTE_LENGTH; + public static final int BIT_LENGTH = IpAddress.INET_BIT_LENGTH; + + /** + * Constructor for given IP address version and address octets. + * + * @param value the IP address value stored in network byte order + * (i.e., the most significant byte first) + * @throws IllegalArgumentException if the arguments are invalid + */ + private Ip4Address(byte[] value) { + super(VERSION, value); + } + + /** + * Returns the integer value of this IPv4 address. + * + * @return the IPv4 address's value as an integer + */ + public int toInt() { + ByteBuffer bb = ByteBuffer.wrap(super.toOctets()); + return bb.getInt(); + } + + /** + * Converts an integer into an IPv4 address. + * + * @param value an integer representing an IPv4 address value + * @return an IPv4 address + */ + public static Ip4Address valueOf(int value) { + byte[] bytes = + ByteBuffer.allocate(INET_BYTE_LENGTH).putInt(value).array(); + return new Ip4Address(bytes); + } + + /** + * Converts a byte array into an IPv4 address. + * + * @param value the IPv4 address value stored in network byte order + * (i.e., the most significant byte first) + * @return an IPv4 address + * @throws IllegalArgumentException if the argument is invalid + */ + public static Ip4Address valueOf(byte[] value) { + return new Ip4Address(value); + } + + /** + * Converts a byte array and a given offset from the beginning of the + * array into an IPv4 address. + * <p> + * The IP address is stored in network byte order (i.e., the most + * significant byte first). + * </p> + * @param value the value to use + * @param offset the offset in bytes from the beginning of the byte array + * @return an IPv4 address + * @throws IllegalArgumentException if the arguments are invalid + */ + public static Ip4Address valueOf(byte[] value, int offset) { + IpAddress.checkArguments(VERSION, value, offset); + byte[] bc = Arrays.copyOfRange(value, offset, value.length); + return Ip4Address.valueOf(bc); + } + + /** + * Converts an InetAddress into an IPv4 address. + * + * @param inetAddress the InetAddress value to use. It must contain an IPv4 + * address + * @return an IPv4 address + * @throws IllegalArgumentException if the argument is invalid + */ + public static Ip4Address valueOf(InetAddress inetAddress) { + byte[] bytes = inetAddress.getAddress(); + if (inetAddress instanceof Inet4Address) { + return new Ip4Address(bytes); + } + if ((inetAddress instanceof Inet6Address) || + (bytes.length == INET6_BYTE_LENGTH)) { + final String msg = "Invalid IPv4 version address string: " + + inetAddress.toString(); + throw new IllegalArgumentException(msg); + } + // Use the number of bytes as a hint + if (bytes.length == INET_BYTE_LENGTH) { + return new Ip4Address(bytes); + } + final String msg = "Unrecognized IP version address string: " + + inetAddress.toString(); + throw new IllegalArgumentException(msg); + } + + /** + * Converts an IPv4 string literal (e.g., "10.2.3.4") into an IP address. + * + * @param value an IPv4 address value in string form + * @return an IPv4 address + * @throws IllegalArgumentException if the argument is invalid + */ + public static Ip4Address valueOf(String value) { + InetAddress inetAddress = null; + try { + inetAddress = InetAddresses.forString(value); + } catch (IllegalArgumentException e) { + final String msg = "Invalid IP address string: " + value; + throw new IllegalArgumentException(msg); + } + return valueOf(inetAddress); + } + + /** + * Creates an IPv4 network mask prefix. + * + * @param prefixLength the length of the mask prefix. Must be in the + * interval [0, 32] + * @return a new IPv4 address that contains a mask prefix of the + * specified length + * @throws IllegalArgumentException if the argument is invalid + */ + public static Ip4Address makeMaskPrefix(int prefixLength) { + byte[] mask = IpAddress.makeMaskPrefixArray(VERSION, prefixLength); + return new Ip4Address(mask); + } + + /** + * Creates an IPv4 address by masking it with a network mask of given + * mask length. + * + * @param address the address to mask + * @param prefixLength the length of the mask prefix. Must be in the + * interval [0, 32] + * @return a new IPv4 address that is masked with a mask prefix of the + * specified length + * @throws IllegalArgumentException if the prefix length is invalid + */ + public static Ip4Address makeMaskedAddress(final Ip4Address address, + int prefixLength) { + byte[] net = makeMaskedAddressArray(address, prefixLength); + return Ip4Address.valueOf(net); + } +} diff --git a/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/Ip4Prefix.java b/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/Ip4Prefix.java new file mode 100644 index 00000000..fc442c3e --- /dev/null +++ b/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/Ip4Prefix.java @@ -0,0 +1,104 @@ +/* + * Copyright 2014-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; + +/** + * The class representing an IPv4 network address. + * This class is immutable. + */ +public final class Ip4Prefix extends IpPrefix { + public static final IpAddress.Version VERSION = IpAddress.Version.INET; + // Maximum network mask length + public static final int MAX_MASK_LENGTH = IpPrefix.MAX_INET_MASK_LENGTH; + + /** + * Constructor for given IPv4 address, and a prefix length. + * + * @param address the IPv4 address + * @param prefixLength the prefix length + * @throws IllegalArgumentException if the prefix length value is invalid + */ + private Ip4Prefix(Ip4Address address, int prefixLength) { + super(address, prefixLength); + } + + /** + * Returns the IPv4 address value of the prefix. + * + * @return the IPv4 address value of the prefix + */ + public Ip4Address address() { + IpAddress a = super.address(); + return (Ip4Address) a; + } + + /** + * Converts an integer and a prefix length into an IPv4 prefix. + * + * @param address an integer representing the IPv4 address + * @param prefixLength the prefix length + * @return an IPv4 prefix + * @throws IllegalArgumentException if the prefix length value is invalid + */ + public static Ip4Prefix valueOf(int address, int prefixLength) { + return new Ip4Prefix(Ip4Address.valueOf(address), prefixLength); + } + + /** + * Converts a byte array and a prefix length into an IPv4 prefix. + * + * @param address the IPv4 address value stored in network byte order + * @param prefixLength the prefix length + * @return an IPv4 prefix + * @throws IllegalArgumentException if the prefix length value is invalid + */ + public static Ip4Prefix valueOf(byte[] address, int prefixLength) { + return new Ip4Prefix(Ip4Address.valueOf(address), prefixLength); + } + + /** + * Converts an IPv4 address and a prefix length into an IPv4 prefix. + * + * @param address the IPv4 address + * @param prefixLength the prefix length + * @return an IPv4 prefix + * @throws IllegalArgumentException if the prefix length value is invalid + */ + public static Ip4Prefix valueOf(Ip4Address address, int prefixLength) { + return new Ip4Prefix(address, prefixLength); + } + + /** + * Converts a CIDR (slash) notation string (e.g., "10.1.0.0/16") + * into an IPv4 prefix. + * + * @param address an IP prefix in string form (e.g., "10.1.0.0/16") + * @return an IPv4 prefix + * @throws IllegalArgumentException if the arguments are invalid + */ + public static Ip4Prefix valueOf(String address) { + final String[] parts = address.split("/"); + if (parts.length != 2) { + String msg = "Malformed IPv4 prefix string: " + address + ". " + + "Address must take form \"x.x.x.x/y\""; + throw new IllegalArgumentException(msg); + } + Ip4Address ipAddress = Ip4Address.valueOf(parts[0]); + int prefixLength = Integer.parseInt(parts[1]); + + return new Ip4Prefix(ipAddress, prefixLength); + } +} diff --git a/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/Ip6Address.java b/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/Ip6Address.java new file mode 100644 index 00000000..d353422b --- /dev/null +++ b/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/Ip6Address.java @@ -0,0 +1,152 @@ +/* + * Copyright 2014 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.net.InetAddress; +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.util.Arrays; + +import com.google.common.net.InetAddresses; + +/** + * A class representing an IPv6 address. + * This class is immutable. + */ +public final class Ip6Address extends IpAddress { + public static final IpAddress.Version VERSION = IpAddress.Version.INET6; + public static final int BYTE_LENGTH = IpAddress.INET6_BYTE_LENGTH; + public static final int BIT_LENGTH = IpAddress.INET6_BIT_LENGTH; + + /** + * Constructor for given IP address version and address octets. + * + * @param value the IP address value stored in network byte order + * (i.e., the most significant byte first) + * @throws IllegalArgumentException if the arguments are invalid + */ + private Ip6Address(byte[] value) { + super(VERSION, value); + } + + /** + * Converts a byte array into an IPv6 address. + * + * @param value the IPv6 address value stored in network byte order + * (i.e., the most significant byte first) + * @return an IPv6 address + * @throws IllegalArgumentException if the argument is invalid + */ + public static Ip6Address valueOf(byte[] value) { + return new Ip6Address(value); + } + + /** + * Converts a byte array and a given offset from the beginning of the + * array into an IPv6 address. + * <p> + * The IP address is stored in network byte order (i.e., the most + * significant byte first). + * </p> + * @param value the value to use + * @param offset the offset in bytes from the beginning of the byte array + * @return an IPv6 address + * @throws IllegalArgumentException if the arguments are invalid + */ + public static Ip6Address valueOf(byte[] value, int offset) { + IpAddress.checkArguments(VERSION, value, offset); + byte[] bc = Arrays.copyOfRange(value, offset, value.length); + return Ip6Address.valueOf(bc); + } + + /** + * Converts an InetAddress into an IPv6 address. + * + * @param inetAddress the InetAddress value to use. It must contain an IPv6 + * address + * @return an IPv6 address + * @throws IllegalArgumentException if the argument is invalid + */ + public static Ip6Address valueOf(InetAddress inetAddress) { + byte[] bytes = inetAddress.getAddress(); + if (inetAddress instanceof Inet6Address) { + return new Ip6Address(bytes); + } + if ((inetAddress instanceof Inet4Address) || + (bytes.length == INET_BYTE_LENGTH)) { + final String msg = "Invalid IPv6 version address string: " + + inetAddress.toString(); + throw new IllegalArgumentException(msg); + } + // Use the number of bytes as a hint + if (bytes.length == INET6_BYTE_LENGTH) { + return new Ip6Address(bytes); + } + final String msg = "Unrecognized IP version address string: " + + inetAddress.toString(); + throw new IllegalArgumentException(msg); + } + + /** + * Converts an IPv6 string literal (e.g., "1111:2222::8888") into an IP + * address. + * + * @param value an IPv6 address value in string form + * @return an IPv6 address + * @throws IllegalArgumentException if the argument is invalid + */ + public static Ip6Address valueOf(String value) { + InetAddress inetAddress = null; + try { + inetAddress = InetAddresses.forString(value); + } catch (IllegalArgumentException e) { + final String msg = "Invalid IP address string: " + value; + throw new IllegalArgumentException(msg); + } + return valueOf(inetAddress); + } + + /** + * Creates an IPv6 network mask prefix. + * + * @param prefixLength the length of the mask prefix. Must be in the + * interval [0, 128] + * @return a new IPv6 address that contains a mask prefix of the + * specified length + * @throws IllegalArgumentException if the arguments are invalid + */ + public static Ip6Address makeMaskPrefix(int prefixLength) { + byte[] mask = IpAddress.makeMaskPrefixArray(VERSION, prefixLength); + return new Ip6Address(mask); + } + + /** + * Creates an IPv6 address by masking it with a network mask of given + * mask length. + * + * @param address the address to mask + * @param prefixLength the length of the mask prefix. Must be in the + * interval [0, 128] + * @return a new IPv6 address that is masked with a mask prefix of the + * specified length + * @throws IllegalArgumentException if the prefix length is invalid + */ + public static Ip6Address makeMaskedAddress(final Ip6Address address, + int prefixLength) { + byte[] net = makeMaskedAddressArray(address, prefixLength); + return Ip6Address.valueOf(net); + } +} diff --git a/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/Ip6Prefix.java b/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/Ip6Prefix.java new file mode 100644 index 00000000..4ea9099b --- /dev/null +++ b/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/Ip6Prefix.java @@ -0,0 +1,93 @@ +/* + * Copyright 2014-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; + +/** + * The class representing an IPv6 network address. + * This class is immutable. + */ +public final class Ip6Prefix extends IpPrefix { + public static final IpAddress.Version VERSION = IpAddress.Version.INET6; + // Maximum network mask length + public static final int MAX_MASK_LENGTH = IpPrefix.MAX_INET6_MASK_LENGTH; + + /** + * Constructor for given IPv6 address, and a prefix length. + * + * @param address the IPv6 address + * @param prefixLength the prefix length + * @throws IllegalArgumentException if the prefix length value is invalid + */ + private Ip6Prefix(Ip6Address address, int prefixLength) { + super(address, prefixLength); + } + + /** + * Returns the IPv6 address value of the prefix. + * + * @return the IPv6 address value of the prefix + */ + public Ip6Address address() { + IpAddress a = super.address(); + return (Ip6Address) a; + } + + /** + * Converts a byte array and a prefix length into an IPv6 prefix. + * + * @param address the IPv6 address value stored in network byte order + * @param prefixLength the prefix length + * @return an IPv6 prefix + * @throws IllegalArgumentException if the prefix length value is invalid + */ + public static Ip6Prefix valueOf(byte[] address, int prefixLength) { + return new Ip6Prefix(Ip6Address.valueOf(address), prefixLength); + } + + /** + * Converts an IPv6 address and a prefix length into an IPv6 prefix. + * + * @param address the IPv6 address + * @param prefixLength the prefix length + * @return an IPv6 prefix + * @throws IllegalArgumentException if the prefix length value is invalid + */ + public static Ip6Prefix valueOf(Ip6Address address, int prefixLength) { + return new Ip6Prefix(address, prefixLength); + } + + /** + * Converts a CIDR (slash) notation string (e.g., "1111:2222::/64") + * into an IPv6 prefix. + * + * @param address an IP prefix in string form (e.g.,"1111:2222::/64") + * @return an IPv6 prefix + * @throws IllegalArgumentException if the arguments are invalid + */ + public static Ip6Prefix valueOf(String address) { + final String[] parts = address.split("/"); + if (parts.length != 2) { + String msg = "Malformed IPv6 prefix string: " + address + ". " + + "Address must take form " + + "\"xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx/y\""; + throw new IllegalArgumentException(msg); + } + Ip6Address ipAddress = Ip6Address.valueOf(parts[0]); + int prefixLength = Integer.parseInt(parts[1]); + + return new Ip6Prefix(ipAddress, prefixLength); + } +} diff --git a/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/IpAddress.java b/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/IpAddress.java new file mode 100644 index 00000000..5fdd3276 --- /dev/null +++ b/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/IpAddress.java @@ -0,0 +1,559 @@ +/* + * Copyright 2014-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.net.InetAddress; +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.Objects; +import com.google.common.net.InetAddresses; +import com.google.common.primitives.UnsignedBytes; + + +/** + * A class representing an IP address. + * This class is immutable. + */ +public class IpAddress implements Comparable<IpAddress> { + private static final int BIT_MASK = 0x000000ff; + + // IP Versions + public enum Version { INET, INET6 }; + + // lengths of address, in bytes + public static final int INET_BYTE_LENGTH = 4; + public static final int INET_BIT_LENGTH = INET_BYTE_LENGTH * Byte.SIZE; + public static final int INET6_BYTE_LENGTH = 16; + public static final int INET6_BIT_LENGTH = INET6_BYTE_LENGTH * Byte.SIZE; + + private final Version version; + private final byte[] octets; + + /** + * Constructor for given IP address version and address octets. + * + * @param version the IP address version + * @param value the IP address value stored in network byte order + * (i.e., the most significant byte first) + * @throws IllegalArgumentException if the arguments are invalid + */ + protected IpAddress(Version version, byte[] value) { + checkArguments(version, value, 0); + this.version = version; + switch (version) { + case INET: + this.octets = Arrays.copyOf(value, INET_BYTE_LENGTH); + break; + case INET6: + this.octets = Arrays.copyOf(value, INET6_BYTE_LENGTH); + break; + default: + // Should not be reached + this.octets = null; + break; + } + } + + /** + * Returns the IP version of this address. + * + * @return the version + */ + public Version version() { + return this.version; + } + + /** + * Tests whether the IP version of this address is IPv4. + * + * @return true if the IP version of this address is IPv4, otherwise false. + */ + public boolean isIp4() { + return (version() == Ip4Address.VERSION); + } + + /** + * Tests whether the IP version of this address is IPv6. + * + * @return true if the IP version of this address is IPv6, otherwise false. + */ + public boolean isIp6() { + return (version() == Ip6Address.VERSION); + } + + /** + * Gets the {@link Ip4Address} view of the IP address. + * + * @return the {@link Ip4Address} view of the IP address if it is IPv4, + * otherwise null + */ + public Ip4Address getIp4Address() { + if (!isIp4()) { + return null; + } + + // Return this object itself if it is already instance of Ip4Address + if (this instanceof Ip4Address) { + return (Ip4Address) this; + } + return Ip4Address.valueOf(octets); + } + + /** + * Gets the {@link Ip6Address} view of the IP address. + * + * @return the {@link Ip6Address} view of the IP address if it is IPv6, + * otherwise null + */ + public Ip6Address getIp6Address() { + if (!isIp6()) { + return null; + } + + // Return this object itself if it is already instance of Ip6Address + if (this instanceof Ip6Address) { + return (Ip6Address) this; + } + return Ip6Address.valueOf(octets); + } + + /** + * Returns the IP address as a byte array. + * + * @return a byte array + */ + public byte[] toOctets() { + return Arrays.copyOf(octets, octets.length); + } + + /** + * Computes the IP address byte length for a given IP version. + * + * @param version the IP version + * @return the IP address byte length for the IP version + * @throws IllegalArgumentException if the IP version is invalid + */ + public static int byteLength(Version version) { + switch (version) { + case INET: + return INET_BYTE_LENGTH; + case INET6: + return INET6_BYTE_LENGTH; + default: + String msg = "Invalid IP version " + version; + throw new IllegalArgumentException(msg); + } + } + + /** + * Converts an integer into an IPv4 address. + * + * @param value an integer representing an IPv4 address value + * @return an IP address + */ + public static IpAddress valueOf(int value) { + byte[] bytes = + ByteBuffer.allocate(INET_BYTE_LENGTH).putInt(value).array(); + return new IpAddress(Version.INET, bytes); + } + + /** + * Converts a byte array into an IP address. + * + * @param version the IP address version + * @param value the IP address value stored in network byte order + * (i.e., the most significant byte first) + * @return an IP address + * @throws IllegalArgumentException if the arguments are invalid + */ + public static IpAddress valueOf(Version version, byte[] value) { + return new IpAddress(version, value); + } + + /** + * Converts a byte array and a given offset from the beginning of the + * array into an IP address. + * <p> + * The IP address is stored in network byte order (i.e., the most + * significant byte first). + * </p> + * @param version the IP address version + * @param value the value to use + * @param offset the offset in bytes from the beginning of the byte array + * @return an IP address + * @throws IllegalArgumentException if the arguments are invalid + */ + public static IpAddress valueOf(Version version, byte[] value, + int offset) { + checkArguments(version, value, offset); + byte[] bc = Arrays.copyOfRange(value, offset, value.length); + return IpAddress.valueOf(version, bc); + } + + /** + * Converts an InetAddress into an IP address. + * + * @param inetAddress the InetAddress value to use + * @return an IP address + * @throws IllegalArgumentException if the argument is invalid + */ + public static IpAddress valueOf(InetAddress inetAddress) { + byte[] bytes = inetAddress.getAddress(); + if (inetAddress instanceof Inet4Address) { + return new IpAddress(Version.INET, bytes); + } + if (inetAddress instanceof Inet6Address) { + return new IpAddress(Version.INET6, bytes); + } + // Use the number of bytes as a hint + if (bytes.length == INET_BYTE_LENGTH) { + return new IpAddress(Version.INET, bytes); + } + if (bytes.length == INET6_BYTE_LENGTH) { + return new IpAddress(Version.INET6, bytes); + } + final String msg = "Unrecognized IP version address string: " + + inetAddress.toString(); + throw new IllegalArgumentException(msg); + } + + /** + * Converts an IPv4 or IPv6 string literal (e.g., "10.2.3.4" or + * "1111:2222::8888") into an IP address. + * + * @param value an IP address value in string form + * @return an IP address + * @throws IllegalArgumentException if the argument is invalid + */ + public static IpAddress valueOf(String value) { + InetAddress inetAddress = null; + try { + inetAddress = InetAddresses.forString(value); + } catch (IllegalArgumentException e) { + final String msg = "Invalid IP address string: " + value; + throw new IllegalArgumentException(msg); + } + return valueOf(inetAddress); + } + + /** + * Creates an IP network mask prefix. + * + * @param version the IP address version + * @param prefixLength the length of the mask prefix. Must be in the + * interval [0, 32] for IPv4, or [0, 128] for IPv6 + * @return a new IP address that contains a mask prefix of the + * specified length + * @throws IllegalArgumentException if the arguments are invalid + */ + public static IpAddress makeMaskPrefix(Version version, int prefixLength) { + byte[] mask = makeMaskPrefixArray(version, prefixLength); + return new IpAddress(version, mask); + } + + /** + * Creates an IP address by masking it with a network mask of given + * mask length. + * + * @param address the address to mask + * @param prefixLength the length of the mask prefix. Must be in the + * interval [0, 32] for IPv4, or [0, 128] for IPv6 + * @return a new IP address that is masked with a mask prefix of the + * specified length + * @throws IllegalArgumentException if the prefix length is invalid + */ + public static IpAddress makeMaskedAddress(final IpAddress address, + int prefixLength) { + if (address instanceof Ip4Address) { + Ip4Address ip4a = (Ip4Address) address; + return Ip4Address.makeMaskedAddress(ip4a, prefixLength); + } else if (address instanceof Ip6Address) { + Ip6Address ip6a = (Ip6Address) address; + return Ip6Address.makeMaskedAddress(ip6a, prefixLength); + } else { + byte[] net = makeMaskedAddressArray(address, prefixLength); + return IpAddress.valueOf(address.version(), net); + } + } + + /** + * Check if this IP address is zero. + * + * @return true if this address is zero + */ + public boolean isZero() { + for (byte b : octets) { + if (b != 0) { + return false; + } + } + return true; + } + + /** + * Check if this IP address is self-assigned. + * + * @return true if this address is self-assigned + */ + public boolean isSelfAssigned() { + return isIp4() && octets[0] == (byte) 169; + } + + @Override + public int compareTo(IpAddress o) { + // Compare first the version + if (this.version != o.version) { + return this.version.compareTo(o.version); + } + + // Compare the bytes, one-by-one + for (int i = 0; i < this.octets.length; i++) { + if (this.octets[i] != o.octets[i]) { + return UnsignedBytes.compare(this.octets[i], o.octets[i]); + } + } + return 0; // Equal + } + + @Override + public int hashCode() { + return Objects.hash(version, Arrays.hashCode(octets)); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if ((obj == null) || (!(obj instanceof IpAddress))) { + return false; + } + IpAddress other = (IpAddress) obj; + return (version == other.version) && + Arrays.equals(octets, other.octets); + } + + @Override + /* + * (non-Javadoc) + * The string representation of the IP address: "x.x.x.x" for IPv4 + * addresses, or ':' separated string for IPv6 addresses. + * + * @see java.lang.Object#toString() + */ + public String toString() { + // FIXME InetAddress is super slow + switch (version) { + case INET: + return String.format("%d.%d.%d.%d", octets[0] & 0xff, + octets[1] & 0xff, + octets[2] & 0xff, + octets[3] & 0xff); + case INET6: + default: + return ipv6ToStringHelper(); + } + } + + /** + * Generates an IP prefix. + * + * @return the IP prefix of the IP address + */ + public IpPrefix toIpPrefix() { + + if (isIp4()) { + return IpPrefix.valueOf(new IpAddress(Version.INET, octets), + Ip4Address.BIT_LENGTH); + } else { + return IpPrefix.valueOf(new IpAddress(Version.INET6, octets), + Ip6Address.BIT_LENGTH); + } + } + + /** + * Gets the IP address name for the IP address version. + * + * @param version the IP address version + * @return the IP address name for the IP address version + */ + private static String addressName(Version version) { + switch (version) { + case INET: + return "IPv4"; + case INET6: + return "IPv6"; + default: + break; + } + return "UnknownIP(" + version + ")"; + } + + /** + * Checks whether the arguments are valid. + * + * @param version the IP address version + * @param value the IP address value stored in a byte array + * @param offset the offset in bytes from the beginning of the byte + * array with the address + * @throws IllegalArgumentException if any of the arguments is invalid + */ + static void checkArguments(Version version, byte[] value, int offset) { + // Check the offset and byte array length + int addrByteLength = byteLength(version); + if ((offset < 0) || (offset + addrByteLength > value.length)) { + String msg; + if (value.length < addrByteLength) { + msg = "Invalid " + addressName(version) + + " address array: array length: " + value.length + + ". Must be at least " + addrByteLength; + } else { + msg = "Invalid " + addressName(version) + + " address array: array offset: " + offset + + ". Must be in the interval [0, " + + (value.length - addrByteLength) + "]"; + } + throw new IllegalArgumentException(msg); + } + } + + /** + * Creates a byte array for IP network mask prefix. + * + * @param version the IP address version + * @param prefixLength the length of the mask prefix. Must be in the + * interval [0, 32] for IPv4, or [0, 128] for IPv6 + * @return a byte array that contains a mask prefix of the + * specified length + * @throws IllegalArgumentException if the arguments are invalid + */ + static byte[] makeMaskPrefixArray(Version version, int prefixLength) { + int addrByteLength = byteLength(version); + int addrBitLength = addrByteLength * Byte.SIZE; + + // Verify the prefix length + if ((prefixLength < 0) || (prefixLength > addrBitLength)) { + final String msg = "Invalid IP prefix length: " + prefixLength + + ". Must be in the interval [0, " + addrBitLength + "]."; + throw new IllegalArgumentException(msg); + } + + // Number of bytes and extra bits that should be all 1s + int maskBytes = prefixLength / Byte.SIZE; + int maskBits = prefixLength % Byte.SIZE; + byte[] mask = new byte[addrByteLength]; + + // Set the bytes and extra bits to 1s + for (int i = 0; i < maskBytes; i++) { + mask[i] = (byte) 0xff; // Set mask bytes to 1s + } + for (int i = maskBytes; i < addrByteLength; i++) { + mask[i] = 0; // Set remaining bytes to 0s + } + if (maskBits > 0) { + mask[maskBytes] = (byte) (0xff << (Byte.SIZE - maskBits)); + } + return mask; + } + + /** + * Creates a byte array that represents an IP address masked with + * a network mask of given mask length. + * + * @param addr the address to mask + * @param prefixLength the length of the mask prefix. Must be in the + * interval [0, 32] for IPv4, or [0, 128] for IPv6 + * @return a byte array that represents the IP address masked with + * a mask prefix of the specified length + * @throws IllegalArgumentException if the prefix length is invalid + */ + static byte[] makeMaskedAddressArray(final IpAddress addr, + int prefixLength) { + byte[] mask = IpAddress.makeMaskPrefixArray(addr.version(), + prefixLength); + byte[] net = new byte[mask.length]; + + // Mask each byte + for (int i = 0; i < net.length; i++) { + net[i] = (byte) (addr.octets[i] & mask[i]); + } + return net; + } + + /** + * Creates a string based on the IPv6 recommendations for canonical representations found here: + * https://tools.ietf.org/html/rfc5952#section-1. + * @return A properly formatted IPv6 canonical representation. + */ + private String ipv6ToStringHelper() { + //Populate a buffer with the string of the full address with leading zeros stripped + StringBuffer buff = new StringBuffer(); + buff.append(String.format("%x:%x:%x:%x:%x:%x:%x:%x", + (((octets[0] & BIT_MASK) << 8) | (octets[1] & BIT_MASK)), + (((octets[2] & BIT_MASK) << 8) | (octets[3] & BIT_MASK)), + (((octets[4] & BIT_MASK) << 8) | (octets[5] & BIT_MASK)), + (((octets[6] & BIT_MASK) << 8) | (octets[7] & BIT_MASK)), + (((octets[8] & BIT_MASK) << 8) | (octets[9] & BIT_MASK)), + (((octets[10] & BIT_MASK) << 8) | (octets[11] & BIT_MASK)), + (((octets[12] & BIT_MASK) << 8) | (octets[13] & BIT_MASK)), + (((octets[14] & BIT_MASK) << 8) | (octets[15] & BIT_MASK)))); + //Initialize variables for tracking longest zero subsequence, tiebreaking by first occurence + int longestSeqStart, longestSeqLen, currSeqStart, currSeqLen; + longestSeqStart = 0; + longestSeqLen = 0; + currSeqStart = 0; + currSeqLen = 0; + + for (int index = 0; index < buff.length(); index++) { + if (buff.charAt(index) == ':') { + if (currSeqLen != 0 && buff.charAt(index + 1) == '0') { + currSeqLen += 1; + } + } else if (buff.charAt(index) == '0' && ((index == 0) || (buff.charAt(index - 1) == ':'))) { + if (currSeqLen == 0) { + currSeqStart = index; + } + currSeqLen += 1; + } else { + if (currSeqLen > longestSeqLen) { + longestSeqStart = currSeqStart; + longestSeqLen = currSeqLen; + } + currSeqLen = 0; + } + } + + if (currSeqLen > longestSeqLen) { + longestSeqLen = currSeqLen; + longestSeqStart = currSeqStart; + } + if (longestSeqLen > 1) { + if (buff.length() == (longestSeqStart + longestSeqLen)) { + buff.append(':'); + } + + buff.delete(longestSeqStart, longestSeqStart + longestSeqLen); + + if (longestSeqStart == 0) { + buff.insert(0, ':'); + } + } + + return buff.toString(); + } +} diff --git a/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/IpPrefix.java b/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/IpPrefix.java new file mode 100644 index 00000000..14d07fed --- /dev/null +++ b/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/IpPrefix.java @@ -0,0 +1,303 @@ +/* + * Copyright 2014-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.util.Objects; + +/** + * A class representing an IP prefix. A prefix consists of an IP address and + * a subnet mask. + * This class is immutable. + * <p> + * NOTE: The stored IP address in the result IP prefix is masked to + * contain zeroes in all bits after the prefix length. + * </p> + */ +public class IpPrefix { + // Maximum network mask length + public static final int MAX_INET_MASK_LENGTH = IpAddress.INET_BIT_LENGTH; + public static final int MAX_INET6_MASK_LENGTH = IpAddress.INET6_BIT_LENGTH; + + private final IpAddress address; + private final short prefixLength; + + /** + * Constructor for given IP address, and a prefix length. + * + * @param address the IP address + * @param prefixLength the prefix length + * @throws IllegalArgumentException if the prefix length value is invalid + */ + protected IpPrefix(IpAddress address, int prefixLength) { + checkPrefixLength(address.version(), prefixLength); + this.address = IpAddress.makeMaskedAddress(address, prefixLength); + this.prefixLength = (short) prefixLength; + } + + /** + * Returns the IP version of the prefix. + * + * @return the IP version of the prefix + */ + public IpAddress.Version version() { + return address.version(); + } + + /** + * Tests whether the IP version of this prefix is IPv4. + * + * @return true if the IP version of this prefix is IPv4, otherwise false. + */ + public boolean isIp4() { + return address.isIp4(); + } + + /** + * Tests whether the IP version of this prefix is IPv6. + * + * @return true if the IP version of this prefix is IPv6, otherwise false. + */ + public boolean isIp6() { + return address.isIp6(); + } + + /** + * Returns the IP address value of the prefix. + * + * @return the IP address value of the prefix + */ + public IpAddress address() { + return address; + } + + /** + * Returns the IP address prefix length. + * + * @return the IP address prefix length + */ + public int prefixLength() { + return prefixLength; + } + + /** + * Gets the {@link Ip4Prefix} view of the IP prefix. + * + * @return the {@link Ip4Prefix} view of the IP prefix if it is IPv4, + * otherwise null + */ + public Ip4Prefix getIp4Prefix() { + if (!isIp4()) { + return null; + } + + // Return this object itself if it is already instance of Ip4Prefix + if (this instanceof Ip4Prefix) { + return (Ip4Prefix) this; + } + return Ip4Prefix.valueOf(address.getIp4Address(), prefixLength); + } + + /** + * Gets the {@link Ip6Prefix} view of the IP prefix. + * + * @return the {@link Ip6Prefix} view of the IP prefix if it is IPv6, + * otherwise null + */ + public Ip6Prefix getIp6Prefix() { + if (!isIp6()) { + return null; + } + + // Return this object itself if it is already instance of Ip6Prefix + if (this instanceof Ip6Prefix) { + return (Ip6Prefix) this; + } + return Ip6Prefix.valueOf(address.getIp6Address(), prefixLength); + } + + /** + * Converts an integer and a prefix length into an IPv4 prefix. + * + * @param address an integer representing the IPv4 address + * @param prefixLength the prefix length + * @return an IP prefix + * @throws IllegalArgumentException if the prefix length value is invalid + */ + public static IpPrefix valueOf(int address, int prefixLength) { + return new IpPrefix(IpAddress.valueOf(address), prefixLength); + } + + /** + * Converts a byte array and a prefix length into an IP prefix. + * + * @param version the IP address version + * @param address the IP address value stored in network byte order + * @param prefixLength the prefix length + * @return an IP prefix + * @throws IllegalArgumentException if the prefix length value is invalid + */ + public static IpPrefix valueOf(IpAddress.Version version, byte[] address, + int prefixLength) { + return new IpPrefix(IpAddress.valueOf(version, address), prefixLength); + } + + /** + * Converts an IP address and a prefix length into an IP prefix. + * + * @param address the IP address + * @param prefixLength the prefix length + * @return an IP prefix + * @throws IllegalArgumentException if the prefix length value is invalid + */ + public static IpPrefix valueOf(IpAddress address, int prefixLength) { + return new IpPrefix(address, prefixLength); + } + + /** + * Converts a CIDR (slash) notation string (e.g., "10.1.0.0/16" or + * "1111:2222::/64") into an IP prefix. + * + * @param address an IP prefix in string form (e.g. "10.1.0.0/16" or + * "1111:2222::/64") + * @return an IP prefix + * @throws IllegalArgumentException if the arguments are invalid + */ + public static IpPrefix valueOf(String address) { + final String[] parts = address.split("/"); + if (parts.length != 2) { + String msg = "Malformed IP prefix string: " + address + ". " + + "Address must take form \"x.x.x.x/y\" or " + + "\"xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx/y\""; + throw new IllegalArgumentException(msg); + } + IpAddress ipAddress = IpAddress.valueOf(parts[0]); + int prefixLength = Integer.parseInt(parts[1]); + + return new IpPrefix(ipAddress, prefixLength); + } + + /** + * Determines whether a given IP prefix is contained within this prefix. + * + * @param other the IP prefix to test + * @return true if the other IP prefix is contained in this prefix, + * otherwise false + */ + public boolean contains(IpPrefix other) { + if (version() != other.version()) { + return false; + } + + if (this.prefixLength > other.prefixLength) { + return false; // This prefix has smaller prefix size + } + + // + // Mask the other address with my prefix length. + // If the other prefix is within this prefix, the masked address must + // be same as the address of this prefix. + // + IpAddress maskedAddr = + IpAddress.makeMaskedAddress(other.address, this.prefixLength); + return this.address.equals(maskedAddr); + } + + /** + * Determines whether a given IP address is contained within this prefix. + * + * @param other the IP address to test + * @return true if the IP address is contained in this prefix, otherwise + * false + */ + public boolean contains(IpAddress other) { + if (version() != other.version()) { + return false; + } + + // + // Mask the other address with my prefix length. + // If the other prefix is within this prefix, the masked address must + // be same as the address of this prefix. + // + IpAddress maskedAddr = + IpAddress.makeMaskedAddress(other, this.prefixLength); + return this.address.equals(maskedAddr); + } + + @Override + public int hashCode() { + return Objects.hash(address, prefixLength); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if ((obj == null) || (!(obj instanceof IpPrefix))) { + return false; + } + IpPrefix other = (IpPrefix) obj; + return ((prefixLength == other.prefixLength) && + address.equals(other.address)); + } + + @Override + /* + * (non-Javadoc) + * The format is "x.x.x.x/y" for IPv4 prefixes. + * + * @see java.lang.Object#toString() + */ + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append(address.toString()); + builder.append("/"); + builder.append(String.format("%d", prefixLength)); + return builder.toString(); + } + + /** + * Checks whether the prefix length is valid. + * + * @param version the IP address version + * @param prefixLength the prefix length value to check + * @throws IllegalArgumentException if the prefix length value is invalid + */ + private static void checkPrefixLength(IpAddress.Version version, + int prefixLength) { + int maxPrefixLen = 0; + + switch (version) { + case INET: + maxPrefixLen = MAX_INET_MASK_LENGTH; + break; + case INET6: + maxPrefixLen = MAX_INET6_MASK_LENGTH; + break; + default: + String msg = "Invalid IP version " + version; + throw new IllegalArgumentException(msg); + } + + if ((prefixLength < 0) || (prefixLength > maxPrefixLen)) { + String msg = "Invalid prefix length " + prefixLength + ". " + + "The value must be in the interval [0, " + + maxPrefixLen + "]"; + throw new IllegalArgumentException(msg); + } + } +} diff --git a/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/LLC.java b/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/LLC.java new file mode 100644 index 00000000..78b4f3fa --- /dev/null +++ b/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/LLC.java @@ -0,0 +1,102 @@ +/* + * Copyright 2014 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.*; + +/** + * This class represents an Link Local Control header that is used in Ethernet + * 802.3. + * + * + */ +public class LLC extends BasePacket { + + public static final byte LLC_HEADER_LENGTH = 3; + + private byte dsap = 0; + private byte ssap = 0; + private byte ctrl = 0; + + public byte getDsap() { + return this.dsap; + } + + public void setDsap(final byte dsap) { + this.dsap = dsap; + } + + public byte getSsap() { + return this.ssap; + } + + public void setSsap(final byte ssap) { + this.ssap = ssap; + } + + public byte getCtrl() { + return this.ctrl; + } + + public void setCtrl(final byte ctrl) { + this.ctrl = ctrl; + } + + @Override + public byte[] serialize() { + final byte[] data = new byte[3]; + final ByteBuffer bb = ByteBuffer.wrap(data); + bb.put(this.dsap); + bb.put(this.ssap); + bb.put(this.ctrl); + return data; + } + + @Override + public IPacket deserialize(final byte[] data, final int offset, + final int length) { + final ByteBuffer bb = ByteBuffer.wrap(data, offset, length); + this.dsap = bb.get(); + this.ssap = bb.get(); + this.ctrl = bb.get(); + return this; + } + + /** + * Deserializer function for LLC packets. + * + * @return deserializer function + */ + public static Deserializer<LLC> deserializer() { + return (data, offset, length) -> { + checkInput(data, offset, length, LLC_HEADER_LENGTH); + + ByteBuffer bb = ByteBuffer.wrap(data, offset, length); + LLC llc = new LLC(); + + llc.dsap = bb.get(); + llc.ssap = bb.get(); + llc.ctrl = bb.get(); + + return llc; + }; + } +} diff --git a/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/LLDP.java b/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/LLDP.java new file mode 100644 index 00000000..ae9d7173 --- /dev/null +++ b/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/LLDP.java @@ -0,0 +1,300 @@ +/* + * Copyright 2014 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.LinkedList; +import java.util.List; + +import static org.onlab.packet.PacketUtils.*; + +/** + * + */ +public class LLDP extends BasePacket { + public static final byte CHASSIS_TLV_TYPE = 1; + public static final short CHASSIS_TLV_SIZE = 7; + public static final byte CHASSIS_TLV_SUBTYPE = 4; + + public static final byte PORT_TLV_TYPE = 2; + public static final short PORT_TLV_SIZE = 5; + public static final byte PORT_TLV_SUBTYPE = 2; + + public static final byte TTL_TLV_TYPE = 3; + public static final short TTL_TLV_SIZE = 2; + + protected LLDPTLV chassisId; + protected LLDPTLV portId; + protected LLDPTLV ttl; + protected List<LLDPTLV> optionalTLVList; + protected short ethType; + + public LLDP() { + this.optionalTLVList = new LinkedList<>(); + this.ethType = Ethernet.TYPE_LLDP; + } + + /** + * @return the chassisId + */ + public LLDPTLV getChassisId() { + return this.chassisId; + } + + /** + * @param chassis + * the chassisId to set + * @return this + */ + public LLDP setChassisId(final LLDPTLV chassis) { + this.chassisId = chassis; + return this; + } + + /** + * @return the portId + */ + public LLDPTLV getPortId() { + return this.portId; + } + + /** + * @param portId + * the portId to set + * @return this + */ + public LLDP setPortId(final LLDPTLV portId) { + this.portId = portId; + return this; + } + + /** + * @return the ttl + */ + public LLDPTLV getTtl() { + return this.ttl; + } + + /** + * @param ttl + * the ttl to set + * @return this + */ + public LLDP setTtl(final LLDPTLV ttl) { + this.ttl = ttl; + return this; + } + + /** + * @return the optionalTLVList + */ + public List<LLDPTLV> getOptionalTLVList() { + return this.optionalTLVList; + } + + /** + * @param optionalTLVList + * the optionalTLVList to set + * @return this + */ + public LLDP setOptionalTLVList(final List<LLDPTLV> optionalTLVList) { + this.optionalTLVList = optionalTLVList; + return this; + } + + @Override + public byte[] serialize() { + int length = 2 + this.chassisId.getLength() + 2 + + this.portId.getLength() + 2 + this.ttl.getLength() + 2; + for (final LLDPTLV tlv : this.optionalTLVList) { + length += 2 + tlv.getLength(); + } + + final byte[] data = new byte[length]; + final ByteBuffer bb = ByteBuffer.wrap(data); + bb.put(this.chassisId.serialize()); + bb.put(this.portId.serialize()); + bb.put(this.ttl.serialize()); + for (final LLDPTLV tlv : this.optionalTLVList) { + bb.put(tlv.serialize()); + } + bb.putShort((short) 0); // End of LLDPDU + + /* + * if (this.parent != null && this.parent instanceof Ethernet) { + * ((Ethernet) this.parent).setEtherType(this.ethType); } + */ + + return data; + } + + @Override + public IPacket deserialize(final byte[] data, final int offset, + final int length) { + final ByteBuffer bb = ByteBuffer.wrap(data, offset, length); + LLDPTLV tlv; + do { + try { + tlv = new LLDPOrganizationalTLV().deserialize(bb); + } catch (DeserializationException e) { + break; + } + + // if there was a failure to deserialize stop processing TLVs + if (tlv == null) { + break; + } + switch (tlv.getType()) { + case 0x0: + // can throw this one away, its just an end delimiter + break; + case 0x1: + this.chassisId = tlv; + break; + case 0x2: + this.portId = tlv; + break; + case 0x3: + this.ttl = tlv; + break; + + default: + this.optionalTLVList.add(tlv); + break; + } + } while (tlv.getType() != 0 && bb.hasRemaining()); + return this; + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + final int prime = 883; + int result = super.hashCode(); + result = prime * result + + (this.chassisId == null ? 0 : this.chassisId.hashCode()); + result = prime * result + this.optionalTLVList.hashCode(); + result = prime * result + + (this.portId == null ? 0 : this.portId.hashCode()); + result = prime * result + (this.ttl == null ? 0 : this.ttl.hashCode()); + return result; + } + + /* + * (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 LLDP)) { + return false; + } + final LLDP other = (LLDP) obj; + if (this.chassisId == null) { + if (other.chassisId != null) { + return false; + } + } else if (!this.chassisId.equals(other.chassisId)) { + return false; + } + if (!this.optionalTLVList.equals(other.optionalTLVList)) { + return false; + } + if (this.portId == null) { + if (other.portId != null) { + return false; + } + } else if (!this.portId.equals(other.portId)) { + return false; + } + if (this.ttl == null) { + if (other.ttl != null) { + return false; + } + } else if (!this.ttl.equals(other.ttl)) { + return false; + } + return true; + } + + /** + * Deserializer function for LLDP packets. + * + * @return deserializer function + */ + public static Deserializer<LLDP> deserializer() { + return (data, offset, length) -> { + checkInput(data, offset, length, 0); + + LLDP lldp = new LLDP(); + + int currentIndex = 0; + + ByteBuffer bb = ByteBuffer.wrap(data, offset, length); + LLDPTLV tlv; + do { + // Each new TLV must be a minimum of 2 bytes + // (containing the type and length fields). + currentIndex += 2; + checkHeaderLength(length, currentIndex); + + tlv = new LLDPOrganizationalTLV().deserialize(bb); + + // if there was a failure to deserialize stop processing TLVs + if (tlv == null) { + break; + } + switch (tlv.getType()) { + case 0x0: + // can throw this one away, it's just an end delimiter + break; + case 0x1: + lldp.chassisId = tlv; + break; + case 0x2: + lldp.portId = tlv; + break; + case 0x3: + lldp.ttl = tlv; + break; + default: + lldp.optionalTLVList.add(tlv); + break; + } + + currentIndex += tlv.getLength(); + } while (tlv.getType() != 0); + + return lldp; + }; + } + +} diff --git a/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/LLDPOrganizationalTLV.java b/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/LLDPOrganizationalTLV.java new file mode 100644 index 00000000..bedf439f --- /dev/null +++ b/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/LLDPOrganizationalTLV.java @@ -0,0 +1,225 @@ +/* + * Copyright 2014 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. + */ +/** + * 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.nio.charset.Charset; +import java.util.Arrays; + +/** + * The class representing LLDP Organizationally Specific TLV. + * + */ +public class LLDPOrganizationalTLV extends LLDPTLV { + public static final int OUI_LENGTH = 3; + public static final int SUBTYPE_LENGTH = 1; + public static final byte ORGANIZATIONAL_TLV_TYPE = 127; + public static final int MAX_INFOSTRING_LENGTH = 507; + + protected byte[] oui; + protected byte subType; + private byte[] infoString; + + public LLDPOrganizationalTLV() { + this.type = LLDPOrganizationalTLV.ORGANIZATIONAL_TLV_TYPE; + } + + /** + * Set the value of OUI. + * + * @param oui + * The value of OUI to be set. + * @return This LLDP Organizationally Specific TLV. + */ + public LLDPOrganizationalTLV setOUI(final byte[] oui) { + if (oui.length != LLDPOrganizationalTLV.OUI_LENGTH) { + throw new IllegalArgumentException("The length of OUI must be " + + LLDPOrganizationalTLV.OUI_LENGTH + ", but it is " + + oui.length); + } + this.oui = Arrays.copyOf(oui, oui.length); + return this; + } + + /** + * Returns the value of the OUI. + * + * @return The value of the OUI . + */ + public byte[] getOUI() { + return Arrays.copyOf(this.oui, this.oui.length); + } + + /** + * Set the value of sub type. + * + * @param subType + * The value of sub type to be set. + * @return This LLDP Organizationally Specific TLV. + */ + public LLDPOrganizationalTLV setSubType(final byte subType) { + this.subType = subType; + return this; + } + + /** + * Returns the value of the sub type. + * + * @return The value of the sub type. + */ + public byte getSubType() { + return this.subType; + } + + /** + * Set the value of information string. + * + * @param infoString + * the byte array of the value of information string. + * @return This LLDP Organizationally Specific TLV. + */ + public LLDPOrganizationalTLV setInfoString(final byte[] infoString) { + if (infoString.length > LLDPOrganizationalTLV.MAX_INFOSTRING_LENGTH) { + throw new IllegalArgumentException( + "The length of infoString cannot exceed " + + LLDPOrganizationalTLV.MAX_INFOSTRING_LENGTH); + } + this.infoString = Arrays.copyOf(infoString, infoString.length); + return this; + } + + /** + * Set the value of information string. The String value is automatically + * converted into byte array with UTF-8 encoding. + * + * @param infoString + * the String value of information string. + * @return This LLDP Organizationally Specific TLV. + */ + public LLDPOrganizationalTLV setInfoString(final String infoString) { + final byte[] infoStringBytes = infoString.getBytes(Charset + .forName("UTF-8")); + return this.setInfoString(infoStringBytes); + } + + /** + * Returns the value of information string. + * + * @return the value of information string. + */ + public byte[] getInfoString() { + return Arrays.copyOf(this.infoString, this.infoString.length); + } + + @Override + public byte[] serialize() { + if (this.type != LLDPOrganizationalTLV.ORGANIZATIONAL_TLV_TYPE) { + return super.serialize(); + } + final int valueLength = LLDPOrganizationalTLV.OUI_LENGTH + + LLDPOrganizationalTLV.SUBTYPE_LENGTH + this.infoString.length; + this.value = new byte[valueLength]; + final ByteBuffer bb = ByteBuffer.wrap(this.value); + bb.put(this.oui); + bb.put(this.subType); + bb.put(this.infoString); + return super.serialize(); + } + + @Override + public LLDPTLV deserialize(final ByteBuffer bb) throws DeserializationException { + super.deserialize(bb); + if (this.getType() != LLDPOrganizationalTLV.ORGANIZATIONAL_TLV_TYPE) { + return this; + } + + if (this.getLength() <= OUI_LENGTH + SUBTYPE_LENGTH) { + throw new DeserializationException( + "TLV length is less than required for organizational TLV"); + } + + final ByteBuffer optionalField = ByteBuffer.wrap(this.value); + + final byte[] oui = new byte[LLDPOrganizationalTLV.OUI_LENGTH]; + optionalField.get(oui); + this.setOUI(oui); + + this.setSubType(optionalField.get()); + + final byte[] infoString = new byte[this.getLength() + - LLDPOrganizationalTLV.OUI_LENGTH + - LLDPOrganizationalTLV.SUBTYPE_LENGTH]; + optionalField.get(infoString); + this.setInfoString(infoString); + return this; + } + + @Override + public int hashCode() { + final int prime = 1423; + int result = 1; + result = prime * result + this.type; + result = prime * result + this.length; + result = prime * result + Arrays.hashCode(this.oui); + result = prime * result + this.subType; + result = prime * result + Arrays.hashCode(this.infoString); + return result; + } + + @Override + public boolean equals(final Object o) { + if (o == this) { + return true; + } + + if (!(o instanceof LLDPOrganizationalTLV)) { + return false; + } + + final LLDPOrganizationalTLV other = (LLDPOrganizationalTLV) o; + if (this.type != other.type) { + return false; + } + if (this.length != other.length) { + return false; + } + if (!Arrays.equals(this.oui, other.oui)) { + return false; + } + if (this.subType != other.subType) { + return false; + } + if (!Arrays.equals(this.infoString, other.infoString)) { + return false; + } + + return true; + } +} diff --git a/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/LLDPTLV.java b/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/LLDPTLV.java new file mode 100644 index 00000000..77efe1b7 --- /dev/null +++ b/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/LLDPTLV.java @@ -0,0 +1,165 @@ +/* + * Copyright 2014 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.Arrays; + +/** + * + * + */ +public class LLDPTLV { + protected byte type; + protected short length; + protected byte[] value; + + /** + * @return the type + */ + public byte getType() { + return this.type; + } + + /** + * @param type + * the type to set + * @return this + */ + public LLDPTLV setType(final byte type) { + this.type = type; + return this; + } + + /** + * @return the length + */ + public short getLength() { + return this.length; + } + + /** + * @param length + * the length to set + * @return this + */ + public LLDPTLV setLength(final short length) { + this.length = length; + return this; + } + + /** + * @return the value + */ + public byte[] getValue() { + return this.value; + } + + /** + * @param value + * the value to set + * @return this + */ + public LLDPTLV setValue(final byte[] value) { + this.value = value; + return this; + } + + public byte[] serialize() { + // type = 7 bits + // info string length 9 bits, each value == byte + // info string + final short scratch = (short) ((0x7f & this.type) << 9 | 0x1ff & this.length); + final byte[] data = new byte[2 + this.length]; + final ByteBuffer bb = ByteBuffer.wrap(data); + bb.putShort(scratch); + if (this.value != null) { + bb.put(this.value); + } + return data; + } + + public LLDPTLV deserialize(final ByteBuffer bb) throws DeserializationException { + if (bb.remaining() < 2) { + throw new DeserializationException( + "Not enough bytes to deserialize TLV type and length"); + } + short typeLength; + typeLength = bb.getShort(); + this.type = (byte) (typeLength >> 9 & 0x7f); + this.length = (short) (typeLength & 0x1ff); + + if (this.length > 0) { + this.value = new byte[this.length]; + + // if there is an underrun just toss the TLV + if (bb.remaining() < this.length) { + throw new DeserializationException( + "Remaining bytes are less then the length of the TLV"); + } + bb.get(this.value); + } + + return this; + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + final int prime = 1423; + int result = 1; + result = prime * result + this.length; + result = prime * result + this.type; + result = prime * result + Arrays.hashCode(this.value); + return result; + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(final Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (!(obj instanceof LLDPTLV)) { + return false; + } + final LLDPTLV other = (LLDPTLV) obj; + if (this.length != other.length) { + return false; + } + if (this.type != other.type) { + return false; + } + if (!Arrays.equals(this.value, other.value)) { + return false; + } + return true; + } +} diff --git a/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/MPLS.java b/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/MPLS.java new file mode 100644 index 00000000..47dbeed2 --- /dev/null +++ b/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/MPLS.java @@ -0,0 +1,147 @@ +package org.onlab.packet; + +import java.nio.ByteBuffer; +import java.util.HashMap; +import java.util.Map; + +import static org.onlab.packet.PacketUtils.checkInput; + +public class MPLS extends BasePacket { + public static final int HEADER_LENGTH = 4; + + public static final byte PROTOCOL_IPV4 = 0x1; + public static final byte PROTOCOL_MPLS = 0x6; + static Map<Byte, Deserializer<? extends IPacket>> protocolDeserializerMap + = new HashMap<>(); + + static { + protocolDeserializerMap.put(PROTOCOL_IPV4, IPv4.deserializer()); + protocolDeserializerMap.put(PROTOCOL_MPLS, MPLS.deserializer()); + } + + protected int label; //20bits + protected byte bos; //1bit + protected byte ttl; //8bits + protected byte protocol; + + /** + * Default constructor that sets the version to 4. + */ + public MPLS() { + super(); + this.bos = 1; + this.protocol = PROTOCOL_IPV4; + } + + @Override + public byte[] serialize() { + byte[] payloadData = null; + if (payload != null) { + payload.setParent(this); + payloadData = payload.serialize(); + } + + byte[] data = new byte[(4 + ((payloadData != null) ? payloadData.length : 0)) ]; + ByteBuffer bb = ByteBuffer.wrap(data); + + bb.putInt(((this.label & 0x000fffff) << 12) | ((this.bos & 0x1) << 8 | (this.ttl & 0xff))); + if (payloadData != null) { + bb.put(payloadData); + } + + return data; + } + + @Override + public IPacket deserialize(byte[] data, int offset, int length) { + ByteBuffer bb = ByteBuffer.wrap(data, offset, length); + + int mplsheader = bb.getInt(); + this.label = ((mplsheader & 0xfffff000) >> 12); + this.bos = (byte) ((mplsheader & 0x00000100) >> 8); + this.bos = (byte) (mplsheader & 0x000000ff); + this.protocol = (this.bos == 1) ? PROTOCOL_IPV4 : PROTOCOL_MPLS; + + Deserializer<? extends IPacket> deserializer; + if (protocolDeserializerMap.containsKey(this.protocol)) { + deserializer = protocolDeserializerMap.get(this.protocol); + } else { + deserializer = Data.deserializer(); + } + try { + this.payload = deserializer.deserialize(data, bb.position(), bb.limit() - bb.position()); + this.payload.setParent(this); + } catch (DeserializationException e) { + return this; + } + + return this; + } + + /** + * Returns the MPLS label. + * + * @return MPLS label + */ + public int getLabel() { + return label; + } + + /** + * Sets the MPLS label. + * + * @param label MPLS label + */ + public void setLabel(int label) { + this.label = label; + } + + /** + * Returns the MPLS TTL of the packet. + * + * @return MPLS TTL of the packet + */ + public byte getTtl() { + return ttl; + } + + /** + * Sets the MPLS TTL of the packet. + * + * @param ttl MPLS TTL + */ + public void setTtl(byte ttl) { + this.ttl = ttl; + } + + /** + * Deserializer function for MPLS packets. + * + * @return deserializer function + */ + public static Deserializer<MPLS> deserializer() { + return (data, offset, length) -> { + checkInput(data, offset, length, HEADER_LENGTH); + + MPLS mpls = new MPLS(); + ByteBuffer bb = ByteBuffer.wrap(data, offset, length); + + int mplsheader = bb.getInt(); + mpls.label = ((mplsheader & 0xfffff000) >>> 12); + mpls.bos = (byte) ((mplsheader & 0x00000100) >> 8); + mpls.ttl = (byte) (mplsheader & 0x000000ff); + mpls.protocol = (mpls.bos == 1) ? PROTOCOL_IPV4 : PROTOCOL_MPLS; + + Deserializer<? extends IPacket> deserializer; + if (protocolDeserializerMap.containsKey(mpls.protocol)) { + deserializer = protocolDeserializerMap.get(mpls.protocol); + } else { + deserializer = Data.deserializer(); + } + mpls.payload = deserializer.deserialize(data, bb.position(), bb.limit() - bb.position()); + mpls.payload.setParent(mpls); + + return mpls; + }; + } +} diff --git a/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/MacAddress.java b/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/MacAddress.java new file mode 100644 index 00000000..89cddbae --- /dev/null +++ b/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/MacAddress.java @@ -0,0 +1,217 @@ +/* + * Copyright 2014-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.util.Arrays; + +/** + * The class representing MAC address. + */ +public class MacAddress { + + public static final MacAddress ZERO = valueOf("00:00:00:00:00:00"); + public static final MacAddress BROADCAST = valueOf("ff:ff:ff:ff:ff:ff"); + + private static final byte[] LL = new byte[]{ + 0x01, (byte) 0x80, (byte) 0xc2, 0x00, 0x00, + 0x00, 0x0e, 0x03 + }; + + public static final int MAC_ADDRESS_LENGTH = 6; + private byte[] address = new byte[MacAddress.MAC_ADDRESS_LENGTH]; + + public MacAddress(final byte[] address) { + this.address = Arrays.copyOf(address, MacAddress.MAC_ADDRESS_LENGTH); + } + + /** + * Returns a MAC address instance representing the value of the specified + * {@code String}. + * + * @param address the String representation of the MAC Address to be parsed. + * @return a MAC Address instance representing the value of the specified + * {@code String}. + * @throws IllegalArgumentException if the string cannot be parsed as a MAC address. + */ + public static MacAddress valueOf(final String address) { + final String[] elements = address.split(":"); + if (elements.length != MacAddress.MAC_ADDRESS_LENGTH) { + throw new IllegalArgumentException( + "Specified MAC Address must contain 12 hex digits" + + " separated pairwise by :'s."); + } + + final byte[] addressInBytes = new byte[MacAddress.MAC_ADDRESS_LENGTH]; + for (int i = 0; i < MacAddress.MAC_ADDRESS_LENGTH; i++) { + final String element = elements[i]; + addressInBytes[i] = (byte) Integer.parseInt(element, 16); + } + + return new MacAddress(addressInBytes); + } + + /** + * Returns a MAC address instance representing the specified {@code byte} + * array. + * + * @param address the byte array to be parsed. + * @return a MAC address instance representing the specified {@code byte} + * array. + * @throws IllegalArgumentException if the byte array cannot be parsed as a MAC address. + */ + public static MacAddress valueOf(final byte[] address) { + if (address.length != MacAddress.MAC_ADDRESS_LENGTH) { + throw new IllegalArgumentException("the length is not " + + MacAddress.MAC_ADDRESS_LENGTH); + } + + return new MacAddress(address); + } + + /** + * Returns a MAC address instance representing the specified {@code long} + * value. The lower 48 bits of the long value are used to parse as a MAC + * address. + * + * @param address the long value to be parsed. The lower 48 bits are used for a + * MAC address. + * @return a MAC address instance representing the specified {@code long} + * value. + * @throws IllegalArgumentException if the long value cannot be parsed as a MAC address. + */ + public static MacAddress valueOf(final long address) { + final byte[] addressInBytes = new byte[]{ + (byte) (address >> 40 & 0xff), (byte) (address >> 32 & 0xff), + (byte) (address >> 24 & 0xff), (byte) (address >> 16 & 0xff), + (byte) (address >> 8 & 0xff), (byte) (address >> 0 & 0xff)}; + + return new MacAddress(addressInBytes); + } + + /** + * Returns the length of the {@code MACAddress}. + * + * @return the length of the {@code MACAddress}. + */ + public int length() { + return this.address.length; + } + + /** + * Returns the value of the {@code MACAddress} as a {@code byte} array. + * + * @return the numeric value represented by this object after conversion to + * type {@code byte} array. + */ + public byte[] toBytes() { + return Arrays.copyOf(this.address, this.address.length); + } + + /** + * Returns the value of the {@code MACAddress} as a {@code long}. + * + * @return the numeric value represented by this object after conversion to + * type {@code long}. + */ + public long toLong() { + long mac = 0; + for (int i = 0; i < 6; i++) { + final long t = (this.address[i] & 0xffL) << (5 - i) * 8; + mac |= t; + } + return mac; + } + + /** + * Returns {@code true} if the MAC address is the broadcast address. + * + * @return {@code true} if the MAC address is the broadcast address. + */ + public boolean isBroadcast() { + for (final byte b : this.address) { + if (b != -1) { + return false; + } + } + return true; + } + + /** + * Returns {@code true} if the MAC address is the multicast address. + * + * @return {@code true} if the MAC address is the multicast address. + */ + public boolean isMulticast() { + if (this.isBroadcast()) { + return false; + } + return (this.address[0] & 0x01) != 0; + } + + /** + * Returns true if this MAC address is link local. + * + * @return true if link local + */ + public boolean isLinkLocal() { + return LL[0] == address[0] && LL[1] == address[1] && LL[2] == address[2] && + LL[3] == address[3] && LL[4] == address[4] && + (LL[5] == address[5] || LL[6] == address[5] || LL[7] == address[5]); + } + + @Override + public boolean equals(final Object o) { + if (o == this) { + return true; + } + + if (!(o instanceof MacAddress)) { + return false; + } + + final MacAddress other = (MacAddress) o; + return Arrays.equals(this.address, other.address); + } + + @Override + public int hashCode() { + return Long.hashCode(toLong()); + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + for (final byte b : this.address) { + if (builder.length() > 0) { + builder.append(":"); + } + builder.append(String.format("%02X", b & 0xFF)); + } + return builder.toString(); + } + + /** + * @return MAC address in string representation without colons (useful for + * radix tree storage) + */ + public String toStringNoColon() { + final StringBuilder builder = new StringBuilder(); + for (final byte b : this.address) { + builder.append(String.format("%02X", b & 0xFF)); + } + return builder.toString(); + } +} diff --git a/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/MplsLabel.java b/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/MplsLabel.java new file mode 100644 index 00000000..09a939fc --- /dev/null +++ b/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/MplsLabel.java @@ -0,0 +1,74 @@ +package org.onlab.packet; + +/* + * Copyright 2014-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. + */ + +/** + * Representation of a MPLS label. + */ +public class MplsLabel { + + private final int mplsLabel; + + // An MPLS Label maximum 20 bits. + public static final int MAX_MPLS = 0xFFFFF; + + protected MplsLabel(int value) { + this.mplsLabel = value; + } + + public static MplsLabel mplsLabel(int value) { + + if (value < 0 || value > MAX_MPLS) { + String errorMsg = "MPLS label value " + value + + " is not in the interval [0, 0xFFFFF]"; + throw new IllegalArgumentException(errorMsg); + } + return new MplsLabel(value); + } + + public int toInt() { + return this.mplsLabel; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + + if (obj instanceof MplsLabel) { + + MplsLabel other = (MplsLabel) obj; + + if (this.mplsLabel == other.mplsLabel) { + return true; + } + } + + return false; + } + + @Override + public int hashCode() { + return this.mplsLabel; + } + + @Override + public String toString() { + return String.valueOf(this.mplsLabel); + } +} diff --git a/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/ONOSLLDP.java b/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/ONOSLLDP.java new file mode 100644 index 00000000..5b3902a8 --- /dev/null +++ b/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/ONOSLLDP.java @@ -0,0 +1,185 @@ +/* + * Copyright 2014 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 com.google.common.collect.Lists; +import org.apache.commons.lang.ArrayUtils; + +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; + +/** + * ONOS LLDP containing organizational TLV for ONOS device dicovery. + */ +public class ONOSLLDP extends LLDP { + + public static final byte[] ONLAB_OUI = {(byte) 0xa4, 0x23, 0x05}; + public static final String DEFAULT_DEVICE = "INVALID"; + public static final String DEFAULT_NAME = "ONOS Discovery"; + + public static final byte[] LLDP_NICIRA = {0x01, 0x23, 0x20, 0x00, 0x00, + 0x01}; + public static final byte[] LLDP_MULTICAST = {0x01, (byte) 0x80, + (byte) 0xc2, 0x00, 0x00, 0x0e}; + public static final byte[] BDDP_MULTICAST = {(byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff}; + + private static final byte NAME_SUBTYPE = 1; + private static final byte DEVICE_SUBTYPE = 2; + private static final short NAME_LENGTH = 4; //1 for subtype + 3 for OUI + private static final short DEVICE_LENGTH = 4; //1 for subtype + 3 for OUI + private final LLDPOrganizationalTLV nameTLV = new LLDPOrganizationalTLV(); + private final LLDPOrganizationalTLV deviceTLV = new LLDPOrganizationalTLV(); + + // TLV constants: type, size and subtype + // Organizationally specific TLV also have packet offset and contents of TLV + // header + private static final byte CHASSIS_TLV_TYPE = 1; + private static final byte CHASSIS_TLV_SIZE = 7; + private static final byte CHASSIS_TLV_SUBTYPE = 4; + + private static final byte PORT_TLV_TYPE = 2; + private static final byte PORT_TLV_SIZE = 5; + private static final byte PORT_TLV_SUBTYPE = 2; + + private static final byte TTL_TLV_TYPE = 3; + + + private final byte[] ttlValue = new byte[] {0, 0x78}; + + public ONOSLLDP() { + super(); + setName(DEFAULT_NAME); + setDevice(DEFAULT_DEVICE); + setOptionalTLVList(Lists.<LLDPTLV>newArrayList(nameTLV, deviceTLV)); + setTtl(new LLDPTLV().setType(TTL_TLV_TYPE) + .setLength((short) ttlValue.length) + .setValue(ttlValue)); + + } + + private ONOSLLDP(LLDP lldp) { + this.portId = lldp.getPortId(); + this.chassisId = lldp.getChassisId(); + this.ttl = lldp.getTtl(); + this.optionalTLVList = lldp.getOptionalTLVList(); + } + + public void setName(String name) { + nameTLV.setLength((short) (name.length() + NAME_LENGTH)); + nameTLV.setInfoString(name); + nameTLV.setSubType(NAME_SUBTYPE); + nameTLV.setOUI(ONLAB_OUI); + } + + public void setDevice(String device) { + deviceTLV.setInfoString(device); + deviceTLV.setLength((short) (device.length() + DEVICE_LENGTH)); + deviceTLV.setSubType(DEVICE_SUBTYPE); + deviceTLV.setOUI(ONLAB_OUI); + } + + public void setChassisId(final ChassisId chassisId) { + MacAddress chassisMac = MacAddress.valueOf(chassisId.value()); + byte[] chassis = ArrayUtils.addAll(new byte[] {CHASSIS_TLV_SUBTYPE}, + chassisMac.toBytes()); + + LLDPTLV chassisTLV = new LLDPTLV(); + chassisTLV.setLength(CHASSIS_TLV_SIZE); + chassisTLV.setType(CHASSIS_TLV_TYPE); + chassisTLV.setValue(chassis); + this.setChassisId(chassisTLV); + } + + public void setPortId(final int portNumber) { + byte[] port = ArrayUtils.addAll(new byte[] {PORT_TLV_SUBTYPE}, + ByteBuffer.allocate(4).putInt(portNumber).array()); + + LLDPTLV portTLV = new LLDPTLV(); + portTLV.setLength(PORT_TLV_SIZE); + portTLV.setType(PORT_TLV_TYPE); + portTLV.setValue(port); + this.setPortId(portTLV); + } + + public LLDPOrganizationalTLV getNameTLV() { + for (LLDPTLV tlv : this.getOptionalTLVList()) { + if (tlv.getType() == LLDPOrganizationalTLV.ORGANIZATIONAL_TLV_TYPE) { + LLDPOrganizationalTLV orgTLV = (LLDPOrganizationalTLV) tlv; + if (orgTLV.getSubType() == NAME_SUBTYPE) { + return orgTLV; + } + } + } + return null; + } + + public LLDPOrganizationalTLV getDeviceTLV() { + for (LLDPTLV tlv : this.getOptionalTLVList()) { + if (tlv.getType() == LLDPOrganizationalTLV.ORGANIZATIONAL_TLV_TYPE) { + LLDPOrganizationalTLV orgTLV = (LLDPOrganizationalTLV) tlv; + if (orgTLV.getSubType() == DEVICE_SUBTYPE) { + return orgTLV; + } + } + } + return null; + } + + public String getNameString() { + LLDPOrganizationalTLV tlv = getNameTLV(); + if (tlv != null) { + return new String(tlv.getInfoString(), StandardCharsets.UTF_8); + } + return null; + } + + public String getDeviceString() { + LLDPOrganizationalTLV tlv = getDeviceTLV(); + if (tlv != null) { + return new String(tlv.getInfoString(), StandardCharsets.UTF_8); + } + return null; + } + + public Integer getPort() { + ByteBuffer portBB = ByteBuffer.wrap(this.getPortId().getValue()); + portBB.position(1); + return portBB.getInt(); + } + + /** + * Given an ethernet packet, determines if this is an LLDP from + * ONOS and returns the device the LLDP came from. + * @param eth an ethernet packet + * @return a the lldp packet or null + */ + public static ONOSLLDP parseONOSLLDP(Ethernet eth) { + if (eth.getEtherType() == Ethernet.TYPE_LLDP || + eth.getEtherType() == Ethernet.TYPE_BSN) { + ONOSLLDP onosLldp = new ONOSLLDP((LLDP) eth.getPayload()); //(ONOSLLDP) eth.getPayload(); + if (ONOSLLDP.DEFAULT_NAME.equals(onosLldp.getNameString())) { + return onosLldp; + } + } + return null; + } + + + + + +} diff --git a/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/PacketUtils.java b/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/PacketUtils.java new file mode 100644 index 00000000..c3bede2f --- /dev/null +++ b/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/PacketUtils.java @@ -0,0 +1,84 @@ +/* + * 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 static com.google.common.base.Preconditions.checkNotNull; + +/** + * Utilities for working with packet headers. + */ +public final class PacketUtils { + + private PacketUtils() { + } + + /** + * Check the length of the input buffer is appropriate given the offset and + * length parameters. + * + * @param byteLength length of the input buffer array + * @param offset offset given to begin reading bytes from + * @param length length given to read up until + * @throws DeserializationException if the input parameters don't match up (i.e + * we can't read that many bytes from the buffer at the given offest) + */ + public static void checkBufferLength(int byteLength, int offset, int length) + throws DeserializationException { + boolean ok = (offset >= 0 && offset < byteLength); + ok = ok & (length >= 0 && offset + length <= byteLength); + + if (!ok) { + throw new DeserializationException("Unable to read " + length + " bytes from a " + + byteLength + " byte array starting at offset " + offset); + } + } + + /** + * Check that there are enough bytes in the buffer to read some number of + * bytes that we need to read a full header. + * + * @param givenLength given size of the buffer + * @param requiredLength number of bytes we need to read some header fully + * @throws DeserializationException if there aren't enough bytes + */ + public static void checkHeaderLength(int givenLength, int requiredLength) + throws DeserializationException { + if (requiredLength > givenLength) { + throw new DeserializationException(requiredLength + + " bytes are needed to continue deserialization, however only " + + givenLength + " remain in buffer"); + } + } + + /** + * Check the input parameters are sane and there's enough bytes to read + * the required length. + * + * @param data input byte buffer + * @param offset offset of the start of the header + * @param length length given to deserialize the header + * @param requiredLength length needed to deserialize header + * @throws DeserializationException if we're unable to deserialize the + * packet based on the input parameters + */ + public static void checkInput(byte[] data, int offset, int length, int requiredLength) + throws DeserializationException { + checkNotNull(data); + checkBufferLength(data.length, offset, length); + checkHeaderLength(length, requiredLength); + } +} diff --git a/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/RADIUS.java b/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/RADIUS.java new file mode 100644 index 00000000..297fee7c --- /dev/null +++ b/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/RADIUS.java @@ -0,0 +1,423 @@ +/* + * + * * Copyright 2015 AT&T Foundry + * * + * * 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 org.slf4j.Logger; + +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.security.SecureRandom; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import static org.onlab.packet.PacketUtils.checkHeaderLength; +import static org.onlab.packet.PacketUtils.checkInput; +import static org.slf4j.LoggerFactory.getLogger; + +/** + * RADIUS packet. + */ +public class RADIUS extends BasePacket { + protected byte code; + protected byte identifier; + protected short length = RADIUS_MIN_LENGTH; + protected byte[] authenticator = new byte[16]; + protected List<RADIUSAttribute> attributes = new ArrayList<>(); + + // RADIUS parameters + public static final short RADIUS_MIN_LENGTH = 20; + public static final short MAX_ATTR_VALUE_LENGTH = 253; + public static final short RADIUS_MAX_LENGTH = 4096; + + // RADIUS packet types + public static final byte RADIUS_CODE_ACCESS_REQUEST = 0x01; + public static final byte RADIUS_CODE_ACCESS_ACCEPT = 0x02; + public static final byte RADIUS_CODE_ACCESS_REJECT = 0x03; + public static final byte RADIUS_CODE_ACCOUNTING_REQUEST = 0x04; + public static final byte RADIUS_CODE_ACCOUNTING_RESPONSE = 0x05; + public static final byte RADIUS_CODE_ACCESS_CHALLENGE = 0x0b; + + private final Logger log = getLogger(getClass()); + + /** + * Default constructor. + */ + public RADIUS() { + } + + /** + * Constructs a RADIUS packet with the given code and identifier. + * + * @param code code + * @param identifier identifier + */ + public RADIUS(byte code, byte identifier) { + this.code = code; + this.identifier = identifier; + } + + /** + * Gets the code. + * + * @return code + */ + public byte getCode() { + return this.code; + } + + /** + * Sets the code. + * + * @param code code + */ + public void setCode(byte code) { + this.code = code; + } + + /** + * Gets the identifier. + * + * @return identifier + */ + public byte getIdentifier() { + return this.identifier; + } + + /** + * Sets the identifier. + * + * @param identifier identifier + */ + public void setIdentifier(byte identifier) { + this.identifier = identifier; + } + + /** + * Gets the authenticator. + * + * @return authenticator + */ + public byte[] getAuthenticator() { + return this.authenticator; + } + + /** + * Sets the authenticator. + * + * @param authenticator authenticator + */ + public void setAuthenticator(byte[] authenticator) { + this.authenticator = authenticator; + } + + /** + * Generates an authenticator code. + * + * @return the authenticator + */ + public byte[] generateAuthCode() { + new SecureRandom().nextBytes(this.authenticator); + return this.authenticator; + } + + /** + * Checks if the packet's code field is valid. + * + * @return whether the code is valid + */ + public boolean isValidCode() { + return this.code == RADIUS_CODE_ACCESS_REQUEST || + this.code == RADIUS_CODE_ACCESS_ACCEPT || + this.code == RADIUS_CODE_ACCESS_REJECT || + this.code == RADIUS_CODE_ACCOUNTING_REQUEST || + this.code == RADIUS_CODE_ACCOUNTING_RESPONSE || + this.code == RADIUS_CODE_ACCESS_CHALLENGE; + } + + /** + * Adds a message authenticator to the packet based on the given key. + * + * @param key key to generate message authenticator + * @return the messgae authenticator RADIUS attribute + */ + public RADIUSAttribute addMessageAuthenticator(String key) { + // Message-Authenticator = HMAC-MD5 (Type, Identifier, Length, + // Request Authenticator, Attributes) + // When the message integrity check is calculated the signature string + // should be considered to be sixteen octets of zero. + byte[] hashOutput = new byte[16]; + Arrays.fill(hashOutput, (byte) 0); + + RADIUSAttribute authAttribute = this.getAttribute(RADIUSAttribute.RADIUS_ATTR_MESSAGE_AUTH); + if (authAttribute != null) { + // If Message-Authenticator was already present, override it + this.log.warn("Attempted to add duplicate Message-Authenticator"); + authAttribute = this.updateAttribute(RADIUSAttribute.RADIUS_ATTR_MESSAGE_AUTH, hashOutput); + } else { + // Else generate a new attribute padded with zeroes + authAttribute = this.setAttribute(RADIUSAttribute.RADIUS_ATTR_MESSAGE_AUTH, hashOutput); + } + // Calculate the MD5 HMAC based on the message + try { + SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(), "HmacMD5"); + Mac mac = Mac.getInstance("HmacMD5"); + mac.init(keySpec); + hashOutput = mac.doFinal(this.serialize()); + // Update HMAC in Message-Authenticator + authAttribute = this.updateAttribute(RADIUSAttribute.RADIUS_ATTR_MESSAGE_AUTH, hashOutput); + } catch (Exception e) { + this.log.error("Failed to generate message authenticator: {}", e.getMessage()); + } + + return authAttribute; + } + + /** + * Checks the message authenticator in the packet with one generated from + * the given key. + * + * @param key key to generate message authenticator + * @return whether the message authenticators match or not + */ + public boolean checkMessageAuthenticator(String key) { + byte[] newHash = new byte[16]; + Arrays.fill(newHash, (byte) 0); + byte[] messageAuthenticator = this.getAttribute(RADIUSAttribute.RADIUS_ATTR_MESSAGE_AUTH).getValue(); + this.updateAttribute(RADIUSAttribute.RADIUS_ATTR_MESSAGE_AUTH, newHash); + // Calculate the MD5 HMAC based on the message + try { + SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(), "HmacMD5"); + Mac mac = Mac.getInstance("HmacMD5"); + mac.init(keySpec); + newHash = mac.doFinal(this.serialize()); + } catch (Exception e) { + log.error("Failed to generate message authenticator: {}", e.getMessage()); + } + this.updateAttribute(RADIUSAttribute.RADIUS_ATTR_MESSAGE_AUTH, messageAuthenticator); + // Compare the calculated Message-Authenticator with the one in the message + return Arrays.equals(newHash, messageAuthenticator); + } + + /** + * Encapsulates an EAP packet in this RADIUS packet. + * + * @param message EAP message object to be embedded in the RADIUS + * EAP-Message attributed + */ + public void encapsulateMessage(EAP message) { + if (message.length <= MAX_ATTR_VALUE_LENGTH) { + // Use the regular serialization method as it fits into one EAP-Message attribute + this.setAttribute(RADIUSAttribute.RADIUS_ATTR_EAP_MESSAGE, + message.serialize()); + } else { + // Segment the message into chucks and embed them in several EAP-Message attributes + short remainingLength = message.length; + byte[] messageBuffer = message.serialize(); + final ByteBuffer bb = ByteBuffer.wrap(messageBuffer); + while (bb.hasRemaining()) { + byte[] messageAttributeData; + if (remainingLength > MAX_ATTR_VALUE_LENGTH) { + // The remaining data is still too long to fit into one attribute, keep going + messageAttributeData = new byte[MAX_ATTR_VALUE_LENGTH]; + bb.get(messageAttributeData, 0, MAX_ATTR_VALUE_LENGTH); + remainingLength -= MAX_ATTR_VALUE_LENGTH; + } else { + // The remaining data fits, this will be the last chunk + messageAttributeData = new byte[remainingLength]; + bb.get(messageAttributeData, 0, remainingLength); + } + this.attributes.add(new RADIUSAttribute(RADIUSAttribute.RADIUS_ATTR_EAP_MESSAGE, + (byte) (messageAttributeData.length + 2), messageAttributeData)); + + // Adding the size of the data to the total RADIUS length + this.length += (short) (messageAttributeData.length & 0xFF); + // Adding the size of the overhead attribute type and length + this.length += 2; + } + } + } + + /** + * Decapsulates an EAP packet from the RADIUS packet. + * + * @return An EAP object containing the reassembled EAP message + */ + public EAP decapsulateMessage() { + EAP message = new EAP(); + ByteArrayOutputStream messageStream = new ByteArrayOutputStream(); + // Iterating through EAP-Message attributes to concatenate their value + for (RADIUSAttribute ra : this.getAttributeList(RADIUSAttribute.RADIUS_ATTR_EAP_MESSAGE)) { + try { + messageStream.write(ra.getValue()); + } catch (IOException e) { + log.error("Error while reassembling EAP message: {}", e.getMessage()); + } + } + // Assembling EAP object from the concatenated stream + message.deserialize(messageStream.toByteArray(), 0, messageStream.size()); + return message; + } + + /** + * Gets a list of attributes from the RADIUS packet. + * + * @param attrType the type field of the required attributes + * @return List of the attributes that matches the type or an empty list if there is none + */ + public ArrayList<RADIUSAttribute> getAttributeList(byte attrType) { + ArrayList<RADIUSAttribute> attrList = new ArrayList<>(); + for (int i = 0; i < this.attributes.size(); i++) { + if (this.attributes.get(i).getType() == attrType) { + attrList.add(this.attributes.get(i)); + } + } + return attrList; + } + + /** + * Gets an attribute from the RADIUS packet. + * + * @param attrType the type field of the required attribute + * @return the first attribute that matches the type or null if does not exist + */ + public RADIUSAttribute getAttribute(byte attrType) { + for (int i = 0; i < this.attributes.size(); i++) { + if (this.attributes.get(i).getType() == attrType) { + return this.attributes.get(i); + } + } + return null; + } + + /** + * Sets an attribute in the RADIUS packet. + * + * @param attrType the type field of the attribute to set + * @param value value to be set + * @return reference to the attribute object + */ + public RADIUSAttribute setAttribute(byte attrType, byte[] value) { + byte attrLength = (byte) (value.length + 2); + RADIUSAttribute newAttribute = new RADIUSAttribute(attrType, attrLength, value); + this.attributes.add(newAttribute); + this.length += (short) (attrLength & 0xFF); + return newAttribute; + } + + /** + * Updates an attribute in the RADIUS packet. + * + * @param attrType the type field of the attribute to update + * @param value the value to update to + * @return reference to the attribute object + */ + public RADIUSAttribute updateAttribute(byte attrType, byte[] value) { + for (int i = 0; i < this.attributes.size(); i++) { + if (this.attributes.get(i).getType() == attrType) { + this.length -= (short) (this.attributes.get(i).getLength() & 0xFF); + RADIUSAttribute newAttr = new RADIUSAttribute(attrType, (byte) (value.length + 2), value); + this.attributes.set(i, newAttr); + this.length += (short) (newAttr.getLength() & 0xFF); + return newAttr; + } + } + return null; + } + + /** + * Deserializer for RADIUS packets. + * + * @return deserializer + */ + public static Deserializer<RADIUS> deserializer() { + return (data, offset, length) -> { + checkInput(data, offset, length, RADIUS_MIN_LENGTH); + + final ByteBuffer bb = ByteBuffer.wrap(data, offset, length); + RADIUS radius = new RADIUS(); + radius.code = bb.get(); + radius.identifier = bb.get(); + radius.length = bb.getShort(); + bb.get(radius.authenticator, 0, 16); + + checkHeaderLength(length, radius.length); + + int remainingLength = radius.length - RADIUS_MIN_LENGTH; + while (remainingLength > 0 && bb.hasRemaining()) { + + RADIUSAttribute attr = new RADIUSAttribute(); + attr.setType(bb.get()); + attr.setLength(bb.get()); + short attrLength = (short) (attr.length & 0xff); + attr.value = new byte[attrLength - 2]; + bb.get(attr.value, 0, attrLength - 2); + radius.attributes.add(attr); + remainingLength -= attr.length; + } + return radius; + }; + } + + @Override + public byte[] serialize() { + final byte[] data = new byte[this.length]; + final ByteBuffer bb = ByteBuffer.wrap(data); + + bb.put(this.code); + bb.put(this.identifier); + bb.putShort(this.length); + bb.put(this.authenticator); + for (int i = 0; i < this.attributes.size(); i++) { + RADIUSAttribute attr = this.attributes.get(i); + bb.put(attr.getType()); + bb.put(attr.getLength()); + bb.put(attr.getValue()); + } + + return data; + } + + @Override + public IPacket deserialize(final byte[] data, final int offset, + final int length) { + final ByteBuffer bb = ByteBuffer.wrap(data, offset, length); + this.code = bb.get(); + this.identifier = bb.get(); + this.length = bb.getShort(); + bb.get(this.authenticator, 0, 16); + + int remainingLength = this.length - RADIUS_MIN_LENGTH; + while (remainingLength > 0 && bb.hasRemaining()) { + RADIUSAttribute attr = new RADIUSAttribute(); + attr.setType(bb.get()); + attr.setLength(bb.get()); + short attrLength = (short) (attr.length & 0xff); + attr.value = new byte[attrLength - 2]; + bb.get(attr.value, 0, attrLength - 2); + this.attributes.add(attr); + remainingLength -= attr.length; + } + return this; + } + +} diff --git a/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/RADIUSAttribute.java b/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/RADIUSAttribute.java new file mode 100644 index 00000000..9687e377 --- /dev/null +++ b/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/RADIUSAttribute.java @@ -0,0 +1,142 @@ +/* + * + * * Copyright 2015 AT&T Foundry + * * + * * 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; + +/** + * An attribute in a RADIUS packet. + */ +public class RADIUSAttribute { + protected byte type; + protected byte length; + protected byte[] value; + + // RADIUS attribute types + public static final byte RADIUS_ATTR_USERNAME = 1; + public static final byte RADIUS_ATTR_NAS_IP = 4; + public static final byte RADIUS_ATTR_NAS_PORT = 5; + public static final byte RADIUS_ATTR_FRAMED_MTU = 12; + public static final byte RADIUS_ATTR_STATE = 24; + public static final byte RADIUS_ATTR_VENDOR_SPECIFIC = 26; + public static final byte RADIUS_ATTR_CALLING_STATION_ID = 31; + public static final byte RADIUS_ATTR_NAS_ID = 32; + public static final byte RADIUS_ATTR_ACCT_SESSION_ID = 44; + public static final byte RADIUS_ATTR_NAS_PORT_TYPE = 61; + public static final byte RADIUS_ATTR_EAP_MESSAGE = 79; + public static final byte RADIUS_ATTR_MESSAGE_AUTH = 80; + public static final byte RADIUS_ATTR_NAS_PORT_ID = 87; + + /** + * Default constructor. + */ + public RADIUSAttribute() { + } + + /** + * Constructs a RADIUS attribute with the give type, length and value. + * + * @param type type + * @param length length + * @param value value + */ + public RADIUSAttribute(final byte type, final byte length, final byte[] value) { + this.type = type; + this.length = length; + this.value = value; + } + + /** + * Checks if the attribute type is valid. + * + * @return whether the type is valid or not + */ + public boolean isValidType() { + return this.type == RADIUS_ATTR_USERNAME || + this.type == RADIUS_ATTR_NAS_IP || + this.type == RADIUS_ATTR_NAS_PORT || + this.type == RADIUS_ATTR_VENDOR_SPECIFIC || + this.type == RADIUS_ATTR_CALLING_STATION_ID || + this.type == RADIUS_ATTR_NAS_ID || + this.type == RADIUS_ATTR_ACCT_SESSION_ID || + this.type == RADIUS_ATTR_NAS_PORT_TYPE || + this.type == RADIUS_ATTR_EAP_MESSAGE || + this.type == RADIUS_ATTR_MESSAGE_AUTH || + this.type == RADIUS_ATTR_NAS_PORT_ID; + } + + /** + * Gets the attribute type. + * + * @return the type + */ + public byte getType() { + return this.type; + } + + /** + * Sets the attribute type. + * + * @param type the code to set + * @return this + */ + public RADIUSAttribute setType(final byte type) { + this.type = type; + return this; + } + + /** + * Gets the attribute length. + * + * @return the length + */ + public byte getLength() { + return this.length; + } + + /** + * Sets the attribute length. + * + * @param length the length to set + * @return this + */ + public RADIUSAttribute setLength(final byte length) { + this.length = length; + return this; + } + + /** + * Gets the attribute value. + * + * @return the value + */ + public byte[] getValue() { + return this.value; + } + + /** + * Sets the attribute value. + * + * @param value the data to set + * @return this + */ + public RADIUSAttribute setValue(final byte[] value) { + this.value = value; + return this; + } + +} diff --git a/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/TCP.java b/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/TCP.java new file mode 100644 index 00000000..e089f272 --- /dev/null +++ b/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/TCP.java @@ -0,0 +1,462 @@ +/* + * Copyright 2014-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.Arrays; + +import static org.onlab.packet.PacketUtils.*; + +/** + * Implements TCP packet format. + */ + +public class TCP extends BasePacket { + + private static final short TCP_HEADER_LENGTH = 20; + + protected int sourcePort; + protected int destinationPort; + protected int sequence; + protected int acknowledge; + protected byte dataOffset; + protected short flags; + protected short windowSize; + protected short checksum; + protected short urgentPointer; + protected byte[] options; + + /** + * Gets TCP source port. + * + * @return TCP source port + */ + public int getSourcePort() { + return this.sourcePort; + } + + /** + * Sets TCP source port. + * + * @param sourcePort the sourcePort to set (unsigned 16 bits integer) + * @return this + */ + public TCP setSourcePort(final int sourcePort) { + this.sourcePort = sourcePort; + return this; + } + + /** + * Gets TCP destination port. + * + * @return the destinationPort + */ + public int getDestinationPort() { + return this.destinationPort; + } + + /** + * Sets TCP destination port. + * + * @param destinationPort the destinationPort to set (unsigned 16 bits integer) + * @return this + */ + public TCP setDestinationPort(final int destinationPort) { + this.destinationPort = destinationPort; + return this; + } + + /** + * Gets checksum. + * + * @return the checksum + */ + public short getChecksum() { + return this.checksum; + } + + /** + * Sets checksum. + * + * @param checksum the checksum to set + * @return this + */ + public TCP setChecksum(final short checksum) { + this.checksum = checksum; + return this; + } + + /** + * Gets sequence number. + * + * @return the sequence number + */ + public int getSequence() { + return this.sequence; + } + + /** + * Sets sequence number. + * + * @param seq the sequence number to set + * @return this + */ + public TCP setSequence(final int seq) { + this.sequence = seq; + return this; + } + + /** + * Gets acknowledge number. + * + * @return the acknowledge number + */ + public int getAcknowledge() { + return this.acknowledge; + } + + /** + * Sets acknowledge number. + * + * @param ack the acknowledge number to set + * @return this + */ + public TCP setAcknowledge(final int ack) { + this.acknowledge = ack; + return this; + } + + /** + * Gets offset. + * + * @return the offset + */ + public byte getDataOffset() { + return this.dataOffset; + } + + /** + * Sets offset. + * + * @param offset the offset to set + * @return this + */ + public TCP setDataOffset(final byte offset) { + this.dataOffset = offset; + return this; + } + + /** + * Gets TCP flags. + * + * @return the TCP flags + */ + public short getFlags() { + return this.flags; + } + + /** + * Sets TCP flags. + * + * @param flags the TCP flags to set + * @return this + */ + public TCP setFlags(final short flags) { + this.flags = flags; + return this; + } + + /** + * Gets TCP window size. + * + * @return the TCP window size + */ + public short getWindowSize() { + return this.windowSize; + } + + /** + * Sets TCP window size. + * + * @param windowSize the TCP window size to set + * @return this + */ + public TCP setWindowSize(final short windowSize) { + this.windowSize = windowSize; + return this; + } + + @Override + public void resetChecksum() { + this.checksum = 0; + super.resetChecksum(); + } + + /** + * Gets urgent pointer. + * + * @return the urgent pointer + */ + public short getUrgentPointer() { + return this.urgentPointer; + } + + /** + * Sets urgent pointer. + * + * @param urgentPointer the urgent pointer to set + * @return this + */ + public TCP setUrgentPointer(final short urgentPointer) { + this.urgentPointer = urgentPointer; + return this; + } + + /** + * Gets TCP options. + * + * @return the TCP options + */ + public byte[] getOptions() { + return this.options; + } + + /** + * Sets TCP options. + * + * @param options the options to set + * @return this + */ + public TCP setOptions(final byte[] options) { + this.options = options; + this.dataOffset = (byte) (20 + options.length + 3 >> 2); + return this; + } + + /** + * Serializes the packet. Will compute and set the following fields if they + * are set to specific values at the time serialize is called: -checksum : 0 + * -length : 0 + */ + @Override + public byte[] serialize() { + int length; + if (this.dataOffset == 0) { + this.dataOffset = 5; // default header length + } + length = this.dataOffset << 2; + byte[] payloadData = null; + if (this.payload != null) { + this.payload.setParent(this); + payloadData = this.payload.serialize(); + length += payloadData.length; + } + + final byte[] data = new byte[length]; + final ByteBuffer bb = ByteBuffer.wrap(data); + + bb.putShort((short) (this.sourcePort & 0xffff)); + bb.putShort((short) (this.destinationPort & 0xffff)); + bb.putInt(this.sequence); + bb.putInt(this.acknowledge); + bb.putShort((short) (this.flags | this.dataOffset << 12)); + bb.putShort(this.windowSize); + bb.putShort(this.checksum); + bb.putShort(this.urgentPointer); + if (this.dataOffset > 5) { + int padding; + bb.put(this.options); + padding = (this.dataOffset << 2) - 20 - this.options.length; + for (int i = 0; i < padding; i++) { + bb.put((byte) 0); + } + } + if (payloadData != null) { + bb.put(payloadData); + } + + if (this.parent != null && this.parent instanceof IPv4) { + ((IPv4) this.parent).setProtocol(IPv4.PROTOCOL_TCP); + } + + // compute checksum if needed + if (this.checksum == 0) { + bb.rewind(); + int accumulation = 0; + + // compute pseudo header mac + if (this.parent != null) { + if (this.parent instanceof IPv4) { + final IPv4 ipv4 = (IPv4) this.parent; + accumulation += (ipv4.getSourceAddress() >> 16 & 0xffff) + + (ipv4.getSourceAddress() & 0xffff); + accumulation += (ipv4.getDestinationAddress() >> 16 & 0xffff) + + (ipv4.getDestinationAddress() & 0xffff); + accumulation += ipv4.getProtocol() & 0xff; + accumulation += length & 0xffff; + } else if (this.parent instanceof IPv6) { + final IPv6 ipv6 = (IPv6) this.parent; + final int bbLength = + Ip6Address.BYTE_LENGTH * 2 // IPv6 src, dst + + 2 // nextHeader (with padding) + + 4; // length + final ByteBuffer bbChecksum = ByteBuffer.allocate(bbLength); + bbChecksum.put(ipv6.getSourceAddress()); + bbChecksum.put(ipv6.getDestinationAddress()); + bbChecksum.put((byte) 0); // padding + bbChecksum.put(ipv6.getNextHeader()); + bbChecksum.putInt(length); + bbChecksum.rewind(); + for (int i = 0; i < bbLength / 2; ++i) { + accumulation += 0xffff & bbChecksum.getShort(); + } + } + } + + for (int i = 0; i < length / 2; ++i) { + accumulation += 0xffff & bb.getShort(); + } + // pad to an even number of shorts + if (length % 2 > 0) { + accumulation += (bb.get() & 0xff) << 8; + } + + accumulation = (accumulation >> 16 & 0xffff) + + (accumulation & 0xffff); + this.checksum = (short) (~accumulation & 0xffff); + bb.putShort(16, this.checksum); + } + return data; + } + + @Override + public IPacket deserialize(final byte[] data, final int offset, + final int length) { + final ByteBuffer bb = ByteBuffer.wrap(data, offset, length); + this.sourcePort = (bb.getShort() & 0xffff); + this.destinationPort = (bb.getShort() & 0xffff); + this.sequence = bb.getInt(); + this.acknowledge = bb.getInt(); + this.flags = bb.getShort(); + this.dataOffset = (byte) (this.flags >> 12 & 0xf); + this.flags = (short) (this.flags & 0x1ff); + this.windowSize = bb.getShort(); + this.checksum = bb.getShort(); + this.urgentPointer = bb.getShort(); + if (this.dataOffset > 5) { + int optLength = (this.dataOffset << 2) - 20; + if (bb.limit() < bb.position() + optLength) { + optLength = bb.limit() - bb.position(); + } + try { + this.options = new byte[optLength]; + bb.get(this.options, 0, optLength); + } catch (final IndexOutOfBoundsException e) { + this.options = null; + } + } + + this.payload = new Data(); + this.payload = this.payload.deserialize(data, bb.position(), bb.limit() + - bb.position()); + this.payload.setParent(this); + return this; + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + final int prime = 5807; + int result = super.hashCode(); + result = prime * result + this.checksum; + result = prime * result + this.destinationPort; + result = prime * result + this.sourcePort; + return result; + } + + /* + * (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 TCP)) { + return false; + } + final TCP other = (TCP) obj; + // May want to compare fields based on the flags set + return this.checksum == other.checksum + && this.destinationPort == other.destinationPort + && this.sourcePort == other.sourcePort + && this.sequence == other.sequence + && this.acknowledge == other.acknowledge + && this.dataOffset == other.dataOffset + && this.flags == other.flags + && this.windowSize == other.windowSize + && this.urgentPointer == other.urgentPointer + && (this.dataOffset == 5 || Arrays.equals(this.options, other.options)); + } + + /** + * Deserializer function for TCP packets. + * + * @return deserializer function + */ + public static Deserializer<TCP> deserializer() { + return (data, offset, length) -> { + checkInput(data, offset, length, TCP_HEADER_LENGTH); + + TCP tcp = new TCP(); + + final ByteBuffer bb = ByteBuffer.wrap(data, offset, length); + tcp.sourcePort = (bb.getShort() & 0xffff); + tcp.destinationPort = (bb.getShort() & 0xffff); + tcp.sequence = bb.getInt(); + tcp.acknowledge = bb.getInt(); + tcp.flags = bb.getShort(); + tcp.dataOffset = (byte) (tcp.flags >> 12 & 0xf); + tcp.flags = (short) (tcp.flags & 0x1ff); + tcp.windowSize = bb.getShort(); + tcp.checksum = bb.getShort(); + tcp.urgentPointer = bb.getShort(); + if (tcp.dataOffset > 5) { + int optLength = (tcp.dataOffset << 2) - 20; + checkHeaderLength(length, TCP_HEADER_LENGTH + tcp.dataOffset); + tcp.options = new byte[optLength]; + bb.get(tcp.options, 0, optLength); + } + + tcp.payload = Data.deserializer() + .deserialize(data, bb.position(), bb.limit() - bb.position()); + tcp.payload.setParent(tcp); + return tcp; + }; + } +} diff --git a/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/TpPort.java b/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/TpPort.java new file mode 100644 index 00000000..9b86a816 --- /dev/null +++ b/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/TpPort.java @@ -0,0 +1,104 @@ +package org.onlab.packet; + +/* + * Copyright 2014-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. + */ + +/** + * Representation of a transport layer port. + */ +public class TpPort { + + private final int port; + + // Transport layer port is unsigned 16 bits integer. + public static final int MAX_PORT = 0xFFFF; + public static final int MIN_PORT = 0; + + /** + * Constructs a new TpPort. + * + * @param value the transport layer port + */ + protected TpPort(int value) { + this.port = value; + } + + /** + * Converts an integer into a TpPort. + * + * @param value an integer representing the transport layer port + * @return a TpPort + * @throws IllegalArgumentException if the value is invalid + */ + public static TpPort tpPort(int value) { + if (value < MIN_PORT || value > MAX_PORT) { + throw new IllegalArgumentException( + "Transport layer port value " + value + "is not in the interval [0, 0xFFFF]"); + } + return new TpPort(value); + } + + /** + * Returns the integer value for this transport port. + * + * @return an integer value + */ + public int toInt() { + return this.port; + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + + if (obj instanceof TpPort) { + + TpPort other = (TpPort) obj; + + if (this.port == other.port) { + return true; + } + } + return false; + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + return this.port; + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return String.valueOf(this.port); + } +} diff --git a/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/UDP.java b/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/UDP.java new file mode 100644 index 00000000..a30c9a92 --- /dev/null +++ b/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/UDP.java @@ -0,0 +1,306 @@ +/* + * Copyright 2014-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.HashMap; +import java.util.Map; + +import static org.onlab.packet.PacketUtils.*; + +/** + * Representation of a UDP packet. + */ +public class UDP extends BasePacket { + public static final Map<Integer, Deserializer<? extends IPacket>> PORT_DESERIALIZER_MAP = + new HashMap<>(); + public static final int DHCP_SERVER_PORT = 67; + public static final int DHCP_CLIENT_PORT = 68; + + private static final short UDP_HEADER_LENGTH = 8; + + static { + /* + * Disable DHCP until the deserialize code is hardened to deal with + * garbage input + */ + UDP.PORT_DESERIALIZER_MAP.put(UDP.DHCP_SERVER_PORT, DHCP.deserializer()); + UDP.PORT_DESERIALIZER_MAP.put(UDP.DHCP_CLIENT_PORT, DHCP.deserializer()); + + } + + protected int sourcePort; + protected int destinationPort; + protected short length; + protected short checksum; + + /** + * @return the sourcePort + */ + public int getSourcePort() { + return this.sourcePort; + } + + /** + * @param sourcePort + * the sourcePort to set (16 bits unsigned integer) + * @return this + */ + public UDP setSourcePort(final int sourcePort) { + this.sourcePort = sourcePort; + return this; + } + + /** + * @return the destinationPort + */ + public int getDestinationPort() { + return this.destinationPort; + } + + /** + * @param destinationPort + * the destinationPort to set (16 bits unsigned integer) + * @return this + */ + public UDP setDestinationPort(final int destinationPort) { + this.destinationPort = destinationPort; + return this; + } + + /** + * @return the length + */ + public short getLength() { + return this.length; + } + + /** + * @return the checksum + */ + public short getChecksum() { + return this.checksum; + } + + /** + * @param checksum + * the checksum to set + * @return this + */ + public UDP setChecksum(final short checksum) { + this.checksum = checksum; + return this; + } + + @Override + public void resetChecksum() { + this.checksum = 0; + super.resetChecksum(); + } + + /** + * Serializes the packet. Will compute and set the following fields if they + * are set to specific values at the time serialize is called: -checksum : 0 + * -length : 0 + */ + @Override + public byte[] serialize() { + byte[] payloadData = null; + if (this.payload != null) { + this.payload.setParent(this); + payloadData = this.payload.serialize(); + } + + this.length = (short) (8 + (payloadData == null ? 0 + : payloadData.length)); + + final byte[] data = new byte[this.length]; + final ByteBuffer bb = ByteBuffer.wrap(data); + + bb.putShort((short) (this.sourcePort & 0xffff)); + bb.putShort((short) (this.destinationPort & 0xffff)); + bb.putShort(this.length); + bb.putShort(this.checksum); + if (payloadData != null) { + bb.put(payloadData); + } + + if (this.parent != null && this.parent instanceof IPv4) { + ((IPv4) this.parent).setProtocol(IPv4.PROTOCOL_UDP); + } + + // compute checksum if needed + if (this.checksum == 0) { + bb.rewind(); + int accumulation = 0; + + // compute pseudo header mac + if (this.parent != null) { + if (this.parent instanceof IPv4) { + final IPv4 ipv4 = (IPv4) this.parent; + accumulation += (ipv4.getSourceAddress() >> 16 & 0xffff) + + (ipv4.getSourceAddress() & 0xffff); + accumulation += (ipv4.getDestinationAddress() >> 16 & 0xffff) + + (ipv4.getDestinationAddress() & 0xffff); + accumulation += ipv4.getProtocol() & 0xff; + accumulation += length & 0xffff; + } else if (this.parent instanceof IPv6) { + final IPv6 ipv6 = (IPv6) this.parent; + final int bbLength = + Ip6Address.BYTE_LENGTH * 2 // IPv6 src, dst + + 2 // nextHeader (with padding) + + 4; // length + final ByteBuffer bbChecksum = ByteBuffer.allocate(bbLength); + bbChecksum.put(ipv6.getSourceAddress()); + bbChecksum.put(ipv6.getDestinationAddress()); + bbChecksum.put((byte) 0); // padding + bbChecksum.put(ipv6.getNextHeader()); + bbChecksum.putInt(length); + bbChecksum.rewind(); + for (int i = 0; i < bbLength / 2; ++i) { + accumulation += 0xffff & bbChecksum.getShort(); + } + } + } + + for (int i = 0; i < this.length / 2; ++i) { + accumulation += 0xffff & bb.getShort(); + } + // pad to an even number of shorts + if (this.length % 2 > 0) { + accumulation += (bb.get() & 0xff) << 8; + } + + accumulation = (accumulation >> 16 & 0xffff) + + (accumulation & 0xffff); + this.checksum = (short) (~accumulation & 0xffff); + bb.putShort(6, this.checksum); + } + return data; + } + + @Override + public IPacket deserialize(final byte[] data, final int offset, + final int length) { + final ByteBuffer bb = ByteBuffer.wrap(data, offset, length); + this.sourcePort = (bb.getShort() & 0xffff); + this.destinationPort = (bb.getShort() & 0xffff); + this.length = bb.getShort(); + this.checksum = bb.getShort(); + + Deserializer<? extends IPacket> deserializer; + if (UDP.PORT_DESERIALIZER_MAP.containsKey(this.destinationPort)) { + deserializer = UDP.PORT_DESERIALIZER_MAP.get(this.destinationPort); + } else if (UDP.PORT_DESERIALIZER_MAP.containsKey(this.sourcePort)) { + deserializer = UDP.PORT_DESERIALIZER_MAP.get(this.sourcePort); + } else { + deserializer = Data.deserializer(); + } + + try { + this.payload = deserializer.deserialize(data, bb.position(), + bb.limit() - bb.position()); + this.payload.setParent(this); + } catch (DeserializationException e) { + return this; + } + return this; + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + final int prime = 5807; + int result = super.hashCode(); + result = prime * result + this.checksum; + result = prime * result + this.destinationPort; + result = prime * result + this.length; + result = prime * result + this.sourcePort; + return result; + } + + /* + * (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 UDP)) { + return false; + } + final UDP other = (UDP) obj; + if (this.checksum != other.checksum) { + return false; + } + if (this.destinationPort != other.destinationPort) { + return false; + } + if (this.length != other.length) { + return false; + } + if (this.sourcePort != other.sourcePort) { + return false; + } + return true; + } + + /** + * Deserializer function for UDP packets. + * + * @return deserializer function + */ + public static Deserializer<UDP> deserializer() { + return (data, offset, length) -> { + checkInput(data, offset, length, UDP_HEADER_LENGTH); + + UDP udp = new UDP(); + + ByteBuffer bb = ByteBuffer.wrap(data, offset, length); + udp.sourcePort = (bb.getShort() & 0xffff); + udp.destinationPort = (bb.getShort() & 0xffff); + udp.length = bb.getShort(); + udp.checksum = bb.getShort(); + + Deserializer<? extends IPacket> deserializer; + if (UDP.PORT_DESERIALIZER_MAP.containsKey(udp.destinationPort)) { + deserializer = UDP.PORT_DESERIALIZER_MAP.get(udp.destinationPort); + } else if (UDP.PORT_DESERIALIZER_MAP.containsKey(udp.sourcePort)) { + deserializer = UDP.PORT_DESERIALIZER_MAP.get(udp.sourcePort); + } else { + deserializer = Data.deserializer(); + } + + udp.payload = deserializer.deserialize(data, bb.position(), + bb.limit() - bb.position()); + udp.payload.setParent(udp); + return udp; + }; + } +} diff --git a/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/VlanId.java b/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/VlanId.java new file mode 100644 index 00000000..4b38308b --- /dev/null +++ b/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/VlanId.java @@ -0,0 +1,102 @@ +/* + * Copyright 2014-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; + +/** + * Representation of a VLAN ID. + */ +public class VlanId { + + private final short value; + + // Based on convention used elsewhere? Check and change if needed + public static final short UNTAGGED = (short) 0xffff; + + // In a traffic selector, this means that a VLAN ID must be present, but + // can have any value. We use the same value as OpenFlow, but this is not + // required. + public static final short ANY_VALUE = (short) 0x1000; + + public static final VlanId NONE = VlanId.vlanId(UNTAGGED); + public static final VlanId ANY = VlanId.vlanId(ANY_VALUE); + + // A VLAN ID is actually 12 bits of a VLAN tag. + public static final short MAX_VLAN = 4095; + + protected VlanId() { + this.value = UNTAGGED; + } + + protected VlanId(short value) { + this.value = value; + } + + public static VlanId vlanId() { + return new VlanId(UNTAGGED); + } + + public static VlanId vlanId(short value) { + if (value == UNTAGGED) { + return new VlanId(); + } + + if (value == ANY_VALUE) { + return new VlanId(ANY_VALUE); + } + + if (value > MAX_VLAN) { + throw new IllegalArgumentException( + "value exceeds allowed maximum VLAN ID value (4095)"); + } + return new VlanId(value); + } + + public short toShort() { + return this.value; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + + if (obj instanceof VlanId) { + + VlanId other = (VlanId) obj; + + if (this.value == other.value) { + return true; + } + } + + return false; + } + + @Override + public int hashCode() { + return this.value; + } + + @Override + public String toString() { + if (this.value == ANY_VALUE) { + return "Any"; + } + return String.valueOf(this.value); + } +} + diff --git a/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/ipv6/Authentication.java b/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/ipv6/Authentication.java new file mode 100644 index 00000000..ec04a812 --- /dev/null +++ b/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/ipv6/Authentication.java @@ -0,0 +1,300 @@ +/* + * Copyright 2014-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.ipv6; + +import org.onlab.packet.BasePacket; +import org.onlab.packet.Data; +import org.onlab.packet.DeserializationException; +import org.onlab.packet.Deserializer; +import org.onlab.packet.IPacket; +import org.onlab.packet.IPv6; + +import java.nio.ByteBuffer; +import java.util.Arrays; + +import static org.onlab.packet.PacketUtils.checkInput; + +/** + * Implements IPv6 authentication extension header format. (RFC 4302) + */ +public class Authentication extends BasePacket implements IExtensionHeader { + public static final byte FIXED_HEADER_LENGTH = 12; // bytes + public static final byte LENGTH_UNIT = 4; // bytes per unit + public static final byte MINUS = 2; + + protected byte nextHeader; + protected byte payloadLength; + protected int securityParamIndex; + protected int sequence; + protected byte[] integrityCheck; + + @Override + public byte getNextHeader() { + return this.nextHeader; + } + + @Override + public Authentication setNextHeader(final byte nextHeader) { + this.nextHeader = nextHeader; + return this; + } + + /** + * Gets the payload length of this header. + * + * @return the payload length + */ + public byte getPayloadLength() { + return this.payloadLength; + } + + /** + * Sets the payload length of this header. + * + * @param payloadLength the payload length to set + * @return this + */ + public Authentication setPayloadLength(final byte payloadLength) { + this.payloadLength = payloadLength; + return this; + } + + /** + * Gets the security parameter index of this header. + * + * @return the security parameter index + */ + public int getSecurityParamIndex() { + return this.securityParamIndex; + } + + /** + * Sets the security parameter index of this header. + * + * @param securityParamIndex the security parameter index to set + * @return this + */ + public Authentication setSecurityParamIndex(final int securityParamIndex) { + this.securityParamIndex = securityParamIndex; + return this; + } + + /** + * Gets the sequence number of this header. + * + * @return the sequence number + */ + public int getSequence() { + return this.sequence; + } + + /** + * Sets the sequence number of this header. + * + * @param sequence the sequence number to set + * @return this + */ + public Authentication setSequence(final int sequence) { + this.sequence = sequence; + return this; + } + + /** + * Gets the integrity check value of this header. + * + * @return the integrity check value + */ + public byte[] getIntegrityCheck() { + return this.integrityCheck; + } + + /** + * Sets the integrity check value of this header. + * + * @param integrityCheck the integrity check value to set + * @return this + */ + public Authentication setIngegrityCheck(final byte[] integrityCheck) { + this.integrityCheck = + Arrays.copyOfRange(integrityCheck, 0, integrityCheck.length); + return this; + } + + /** + * Gets the total length of this header. + * According to spec, payload length should be the total length of this AH + * in 4-octet unit, minus 2 + * + * @return the total length + */ + public int getTotalLength() { + return (this.payloadLength + MINUS) * LENGTH_UNIT; + } + + @Override + public byte[] serialize() { + byte[] payloadData = null; + if (this.payload != null) { + this.payload.setParent(this); + payloadData = this.payload.serialize(); + } + + int headerLength = FIXED_HEADER_LENGTH + integrityCheck.length; + int payloadLength = 0; + if (payloadData != null) { + payloadLength = payloadData.length; + } + + final byte[] data = new byte[headerLength + payloadLength]; + final ByteBuffer bb = ByteBuffer.wrap(data); + + bb.put(this.nextHeader); + bb.put(this.payloadLength); + bb.putShort((short) 0); + bb.putInt(this.securityParamIndex); + bb.putInt(this.sequence); + bb.put(this.integrityCheck, 0, integrityCheck.length); + + if (payloadData != null) { + bb.put(payloadData); + } + + if (this.parent != null && this.parent instanceof IExtensionHeader) { + ((IExtensionHeader) this.parent).setNextHeader(IPv6.PROTOCOL_AH); + } + return data; + } + + @Override + public IPacket deserialize(byte[] data, int offset, int length) { + final ByteBuffer bb = ByteBuffer.wrap(data, offset, length); + this.nextHeader = bb.get(); + this.payloadLength = bb.get(); + bb.getShort(); + this.securityParamIndex = bb.getInt(); + this.sequence = bb.getInt(); + int icvLength = getTotalLength() - FIXED_HEADER_LENGTH; + this.integrityCheck = new byte[icvLength]; + bb.get(this.integrityCheck, 0, icvLength); + + Deserializer<? extends IPacket> deserializer; + if (IPv6.PROTOCOL_DESERIALIZER_MAP.containsKey(this.nextHeader)) { + deserializer = IPv6.PROTOCOL_DESERIALIZER_MAP.get(this.nextHeader); + } else { + deserializer = Data.deserializer(); + } + + try { + this.payload = deserializer.deserialize(data, bb.position(), + bb.limit() - bb.position()); + this.payload.setParent(this); + } catch (DeserializationException e) { + return this; + } + + return this; + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + final int prime = 5807; + int result = super.hashCode(); + result = prime * result + this.nextHeader; + result = prime * result + this.payloadLength; + result = prime * result + this.securityParamIndex; + result = prime * result + this.sequence; + for (byte b : this.integrityCheck) { + result = prime * result + b; + } + return result; + } + + /* + * (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 Authentication)) { + return false; + } + final Authentication other = (Authentication) obj; + if (this.nextHeader != other.nextHeader) { + return false; + } + if (this.payloadLength != other.payloadLength) { + return false; + } + if (this.securityParamIndex != other.securityParamIndex) { + return false; + } + if (this.sequence != other.sequence) { + return false; + } + if (!Arrays.equals(this.integrityCheck, other.integrityCheck)) { + return false; + } + return true; + } + + /** + * Deserializer function for authentication headers. + * + * @return deserializer function + */ + public static Deserializer<Authentication> deserializer() { + return (data, offset, length) -> { + checkInput(data, offset, length, FIXED_HEADER_LENGTH); + + Authentication authentication = new Authentication(); + + ByteBuffer bb = ByteBuffer.wrap(data, offset, length); + authentication.nextHeader = bb.get(); + authentication.payloadLength = bb.get(); + bb.getShort(); + authentication.securityParamIndex = bb.getInt(); + authentication.sequence = bb.getInt(); + int icvLength = (authentication.payloadLength + MINUS) * LENGTH_UNIT - FIXED_HEADER_LENGTH; + authentication.integrityCheck = new byte[icvLength]; + bb.get(authentication.integrityCheck, 0, icvLength); + + Deserializer<? extends IPacket> deserializer; + if (IPv6.PROTOCOL_DESERIALIZER_MAP.containsKey(authentication.nextHeader)) { + deserializer = IPv6.PROTOCOL_DESERIALIZER_MAP.get(authentication.nextHeader); + } else { + deserializer = Data.deserializer(); + } + authentication.payload = deserializer.deserialize(data, bb.position(), + bb.limit() - bb.position()); + authentication.payload.setParent(authentication); + + return authentication; + }; + } +} diff --git a/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/ipv6/BaseOptions.java b/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/ipv6/BaseOptions.java new file mode 100644 index 00000000..f57b756e --- /dev/null +++ b/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/ipv6/BaseOptions.java @@ -0,0 +1,260 @@ +/* + * Copyright 2014-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.ipv6; + +import org.onlab.packet.BasePacket; +import org.onlab.packet.Data; +import org.onlab.packet.DeserializationException; +import org.onlab.packet.Deserializer; +import org.onlab.packet.IPacket; +import org.onlab.packet.IPv6; + +import java.nio.ByteBuffer; +import java.util.Arrays; + +import static org.onlab.packet.PacketUtils.checkHeaderLength; +import static org.onlab.packet.PacketUtils.checkInput; + +/** + * Base class for hop-by-hop options and destination options. + */ +public class BaseOptions extends BasePacket implements IExtensionHeader { + public static final byte FIXED_HEADER_LENGTH = 2; // bytes + public static final byte FIXED_OPTIONS_LENGTH = 6; // bytes + public static final byte LENGTH_UNIT = 8; // bytes per unit + + protected byte nextHeader; + protected byte headerExtLength; + protected byte[] options; + protected byte type; + + @Override + public byte getNextHeader() { + return this.nextHeader; + } + + @Override + public BaseOptions setNextHeader(final byte nextHeader) { + this.nextHeader = nextHeader; + return this; + } + + /** + * Gets the extension length of this header. + * + * @return header length + */ + public byte getHeaderExtLength() { + return this.headerExtLength; + } + + /** + * Sets the extension length of this header. + * + * @param headerExtLength the header length to set + * @return this + */ + public BaseOptions setHeaderExtLength(final byte headerExtLength) { + this.headerExtLength = headerExtLength; + return this; + } + + /** + * Gets the options. + * + * @return the options + */ + public byte[] getOptions() { + return this.options; + } + + /** + * Sets the options. + * + * @param options the options to set + * @return this + */ + public BaseOptions setOptions(final byte[] options) { + this.options = + Arrays.copyOfRange(options, 0, options.length); + return this; + } + + /** + * Gets the type of this option. + * + * @return the type + */ + protected byte getType() { + return this.type; + } + + /** + * Sets the type of this option. + * Must be either IPv6.PROTOCOL_HOPOPT or IPv6.PROTOCOL_DSTOPT + * + * @param type the type to set + * @return this + */ + protected BaseOptions setType(final byte type) { + this.type = type; + return this; + } + + @Override + public byte[] serialize() { + byte[] payloadData = null; + if (this.payload != null) { + this.payload.setParent(this); + payloadData = this.payload.serialize(); + } + + int headerLength = FIXED_HEADER_LENGTH + options.length; + int payloadLength = 0; + if (payloadData != null) { + payloadLength = payloadData.length; + } + + final byte[] data = new byte[headerLength + payloadLength]; + final ByteBuffer bb = ByteBuffer.wrap(data); + + bb.put(this.nextHeader); + bb.put(this.headerExtLength); + bb.put(this.options, 0, options.length); + + if (payloadData != null) { + bb.put(payloadData); + } + + if (this.parent != null && this.parent instanceof IExtensionHeader) { + ((IExtensionHeader) this.parent).setNextHeader(this.type); + } + return data; + } + + @Override + public IPacket deserialize(byte[] data, int offset, int length) { + final ByteBuffer bb = ByteBuffer.wrap(data, offset, length); + this.nextHeader = bb.get(); + this.headerExtLength = bb.get(); + int optionLength = + FIXED_OPTIONS_LENGTH + LENGTH_UNIT * this.headerExtLength; + this.options = new byte[optionLength]; + bb.get(this.options, 0, optionLength); + + Deserializer<? extends IPacket> deserializer; + if (IPv6.PROTOCOL_DESERIALIZER_MAP.containsKey(this.nextHeader)) { + deserializer = IPv6.PROTOCOL_DESERIALIZER_MAP.get(this.nextHeader); + } else { + deserializer = Data.deserializer(); + } + try { + this.payload = deserializer.deserialize(data, bb.position(), + bb.limit() - bb.position()); + this.payload.setParent(this); + } catch (DeserializationException e) { + return this; + } + + return this; + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + final int prime = 5807; + int result = super.hashCode(); + result = prime * result + this.nextHeader; + result = prime * result + this.headerExtLength; + for (byte b : this.options) { + result = prime * result + b; + } + return result; + } + + /* + * (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 BaseOptions)) { + return false; + } + final BaseOptions other = (BaseOptions) obj; + if (this.nextHeader != other.nextHeader) { + return false; + } + if (this.headerExtLength != other.headerExtLength) { + return false; + } + if (!Arrays.equals(this.options, other.options)) { + return false; + } + if (this.type != other.type) { + return false; + } + return true; + } + + /** + * Deserializer function for IPv6 base options. + * + * @return deserializer function + */ + public static Deserializer<BaseOptions> deserializer() { + return (data, offset, length) -> { + checkInput(data, offset, length, FIXED_HEADER_LENGTH); + + BaseOptions baseOptions = new BaseOptions(); + + ByteBuffer bb = ByteBuffer.wrap(data, offset, length); + baseOptions.nextHeader = bb.get(); + baseOptions.headerExtLength = bb.get(); + int optionLength = + FIXED_OPTIONS_LENGTH + LENGTH_UNIT * baseOptions.headerExtLength; + + checkHeaderLength(bb.remaining(), optionLength); + + baseOptions.options = new byte[optionLength]; + bb.get(baseOptions.options, 0, optionLength); + + Deserializer<? extends IPacket> deserializer; + if (IPv6.PROTOCOL_DESERIALIZER_MAP.containsKey(baseOptions.nextHeader)) { + deserializer = IPv6.PROTOCOL_DESERIALIZER_MAP.get(baseOptions.nextHeader); + } else { + deserializer = Data.deserializer(); + } + baseOptions.payload = deserializer.deserialize(data, bb.position(), + bb.limit() - bb.position()); + baseOptions.payload.setParent(baseOptions); + + return baseOptions; + }; + } +} diff --git a/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/ipv6/DestinationOptions.java b/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/ipv6/DestinationOptions.java new file mode 100644 index 00000000..208bdd7e --- /dev/null +++ b/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/ipv6/DestinationOptions.java @@ -0,0 +1,29 @@ +/* + * Copyright 2014-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.ipv6; + +import org.onlab.packet.IPv6; + +/** + * Implements IPv6 Destination Options extension header format. (RFC 2460) + */ +public class DestinationOptions extends BaseOptions { + public DestinationOptions() { + super(); + this.setType(IPv6.PROTOCOL_DSTOPT); + } +}
\ No newline at end of file diff --git a/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/ipv6/EncapSecurityPayload.java b/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/ipv6/EncapSecurityPayload.java new file mode 100644 index 00000000..e46a1261 --- /dev/null +++ b/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/ipv6/EncapSecurityPayload.java @@ -0,0 +1,188 @@ +/* + * Copyright 2014-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.ipv6; + +import org.onlab.packet.BasePacket; +import org.onlab.packet.Data; +import org.onlab.packet.Deserializer; +import org.onlab.packet.IPacket; +import org.onlab.packet.IPv6; + +import java.nio.ByteBuffer; + +import static org.onlab.packet.PacketUtils.checkInput; + +/** + * Implements IPv6 Encapsulating Security Payload (ESP) extension header format. + * (RFC 4303) + */ +public class EncapSecurityPayload extends BasePacket { + public static final byte HEADER_LENGTH = 8; // bytes + + protected int securityParamIndex; + protected int sequence; + // + // NOTE: The remaining fields including payload data, padding length and + // next header are encrypted and all considered as a payload of ESP. + // + + /** + * Gets the security parameter index of this header. + * + * @return the security parameter index + */ + public int getSecurityParamIndex() { + return this.securityParamIndex; + } + + /** + * Sets the security parameter index of this header. + * + * @param securityParamIndex the security parameter index to set + * @return this + */ + public EncapSecurityPayload setSecurityParamIndex(final int securityParamIndex) { + this.securityParamIndex = securityParamIndex; + return this; + } + + /** + * Gets the sequence number of this header. + * + * @return the sequence number + */ + public int getSequence() { + return this.sequence; + } + + /** + * Sets the sequence number of this header. + * + * @param sequence the sequence number to set + * @return this + */ + public EncapSecurityPayload setSequence(final int sequence) { + this.sequence = sequence; + return this; + } + + @Override + public byte[] serialize() { + byte[] payloadData = null; + if (this.payload != null) { + this.payload.setParent(this); + payloadData = this.payload.serialize(); + } + + int payloadLength = 0; + if (payloadData != null) { + payloadLength = payloadData.length; + } + + final byte[] data = new byte[HEADER_LENGTH + payloadLength]; + final ByteBuffer bb = ByteBuffer.wrap(data); + + bb.putInt(this.securityParamIndex); + bb.putInt(this.sequence); + + if (payloadData != null) { + bb.put(payloadData); + } + + if (this.parent != null && this.parent instanceof IExtensionHeader) { + ((IExtensionHeader) this.parent).setNextHeader(IPv6.PROTOCOL_ESP); + } + return data; + } + + @Override + public IPacket deserialize(byte[] data, int offset, int length) { + final ByteBuffer bb = ByteBuffer.wrap(data, offset, length); + this.securityParamIndex = bb.getInt(); + this.sequence = bb.getInt(); + + this.payload = new Data(); + this.payload.deserialize(data, bb.position(), + bb.limit() - bb.position()); + this.payload.setParent(this); + + return this; + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + final int prime = 5807; + int result = super.hashCode(); + result = prime * result + this.securityParamIndex; + result = prime * result + this.sequence; + return result; + } + + /* + * (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 EncapSecurityPayload)) { + return false; + } + final EncapSecurityPayload other = (EncapSecurityPayload) obj; + if (this.securityParamIndex != other.securityParamIndex) { + return false; + } + if (this.sequence != other.sequence) { + return false; + } + return true; + } + + /** + * Deserializer function for encapsulated security payload headers. + * + * @return deserializer function + */ + public static Deserializer<EncapSecurityPayload> deserializer() { + return (data, offset, length) -> { + checkInput(data, offset, length, HEADER_LENGTH); + + EncapSecurityPayload encapSecurityPayload = new EncapSecurityPayload(); + + ByteBuffer bb = ByteBuffer.wrap(data, offset, length); + encapSecurityPayload.securityParamIndex = bb.getInt(); + encapSecurityPayload.sequence = bb.getInt(); + + encapSecurityPayload.payload = Data.deserializer().deserialize( + data, bb.position(), bb.limit() - bb.position()); + encapSecurityPayload.payload.setParent(encapSecurityPayload); + + return encapSecurityPayload; + }; + } +} diff --git a/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/ipv6/Fragment.java b/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/ipv6/Fragment.java new file mode 100644 index 00000000..68015d31 --- /dev/null +++ b/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/ipv6/Fragment.java @@ -0,0 +1,253 @@ +/* + * Copyright 2014-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.ipv6; + +import org.onlab.packet.BasePacket; +import org.onlab.packet.Data; +import org.onlab.packet.DeserializationException; +import org.onlab.packet.Deserializer; +import org.onlab.packet.IPacket; +import org.onlab.packet.IPv6; + +import java.nio.ByteBuffer; + +import static org.onlab.packet.PacketUtils.checkInput; + +/** + * Implements IPv6 fragment extension header format. (RFC 2460) + */ +public class Fragment extends BasePacket implements IExtensionHeader { + public static final byte HEADER_LENGTH = 8; // bytes + + protected byte nextHeader; + protected short fragmentOffset; + protected byte moreFragment; + protected int identification; + + @Override + public byte getNextHeader() { + return this.nextHeader; + } + + @Override + public Fragment setNextHeader(final byte nextHeader) { + this.nextHeader = nextHeader; + return this; + } + + /** + * Gets the fragment offset of this header. + * + * @return fragment offset + */ + public short getFragmentOffset() { + return this.fragmentOffset; + } + + /** + * Sets the fragment offset of this header. + * + * @param fragmentOffset the fragment offset to set + * @return this + */ + public Fragment setFragmentOffset(final short fragmentOffset) { + this.fragmentOffset = fragmentOffset; + return this; + } + + /** + * Gets the more fragment flag of this header. + * + * @return more fragment flag + */ + public byte getMoreFragment() { + return this.moreFragment; + } + + /** + * Sets the more fragment flag of this header. + * + * @param moreFragment the more fragment flag to set + * @return this + */ + public Fragment setMoreFragment(final byte moreFragment) { + this.moreFragment = moreFragment; + return this; + } + + /** + * Gets the identification of this header. + * + * @return identification + */ + public int getIdentification() { + return this.identification; + } + + /** + * Sets the identification of this header. + * + * @param identification the identification to set + * @return this + */ + public Fragment setIdentification(final int identification) { + this.identification = identification; + return this; + } + + @Override + public byte[] serialize() { + byte[] payloadData = null; + if (this.payload != null) { + this.payload.setParent(this); + payloadData = this.payload.serialize(); + } + + int payloadLength = 0; + if (payloadData != null) { + payloadLength = payloadData.length; + } + + final byte[] data = new byte[HEADER_LENGTH + payloadLength]; + final ByteBuffer bb = ByteBuffer.wrap(data); + + bb.put(this.nextHeader); + bb.put((byte) 0); + bb.putShort((short) ( + (this.fragmentOffset & 0x1fff) << 3 | + this.moreFragment & 0x1 + )); + bb.putInt(this.identification); + + if (payloadData != null) { + bb.put(payloadData); + } + + if (this.parent != null && this.parent instanceof IExtensionHeader) { + ((IExtensionHeader) this.parent).setNextHeader(IPv6.PROTOCOL_FRAG); + } + return data; + } + + @Override + public IPacket deserialize(byte[] data, int offset, int length) { + final ByteBuffer bb = ByteBuffer.wrap(data, offset, length); + this.nextHeader = bb.get(); + bb.get(); + short sscratch = bb.getShort(); + this.fragmentOffset = (short) (sscratch >> 3 & 0x1fff); + this.moreFragment = (byte) (sscratch & 0x1); + this.identification = bb.getInt(); + + Deserializer<? extends IPacket> deserializer; + if (IPv6.PROTOCOL_DESERIALIZER_MAP.containsKey(this.nextHeader)) { + deserializer = IPv6.PROTOCOL_DESERIALIZER_MAP.get(this.nextHeader); + } else { + deserializer = Data.deserializer(); + } + try { + this.payload = deserializer.deserialize(data, bb.position(), + bb.limit() - bb.position()); + this.payload.setParent(this); + } catch (DeserializationException e) { + return this; + } + + return this; + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + final int prime = 5807; + int result = super.hashCode(); + result = prime * result + this.nextHeader; + result = prime * result + this.fragmentOffset; + result = prime * result + this.moreFragment; + result = prime * result + this.identification; + return result; + } + + /* + * (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 Fragment)) { + return false; + } + final Fragment other = (Fragment) obj; + if (this.nextHeader != other.nextHeader) { + return false; + } + if (this.fragmentOffset != other.fragmentOffset) { + return false; + } + if (this.moreFragment != other.moreFragment) { + return false; + } + if (this.identification != other.identification) { + return false; + } + return true; + } + + /** + * Deserializer function for fragment headers. + * + * @return deserializer function + */ + public static Deserializer<Fragment> deserializer() { + return (data, offset, length) -> { + checkInput(data, offset, length, HEADER_LENGTH); + + Fragment fragment = new Fragment(); + + ByteBuffer bb = ByteBuffer.wrap(data, offset, length); + fragment.nextHeader = bb.get(); + bb.get(); + short sscratch = bb.getShort(); + fragment.fragmentOffset = (short) (sscratch >> 3 & 0x1fff); + fragment.moreFragment = (byte) (sscratch & 0x1); + fragment.identification = bb.getInt(); + + Deserializer<? extends IPacket> deserializer; + if (IPv6.PROTOCOL_DESERIALIZER_MAP.containsKey(fragment.nextHeader)) { + deserializer = IPv6.PROTOCOL_DESERIALIZER_MAP.get(fragment.nextHeader); + } else { + deserializer = Data.deserializer(); + } + fragment.payload = deserializer.deserialize(data, bb.position(), + bb.limit() - bb.position()); + fragment.payload.setParent(fragment); + + return fragment; + }; + } +} diff --git a/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/ipv6/HopByHopOptions.java b/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/ipv6/HopByHopOptions.java new file mode 100644 index 00000000..cd8c141c --- /dev/null +++ b/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/ipv6/HopByHopOptions.java @@ -0,0 +1,29 @@ +/* + * Copyright 2014-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.ipv6; + +import org.onlab.packet.IPv6; + +/** + * Implements IPv6 Hop-by-hop Options extension header format. (RFC 2460) + */ +public class HopByHopOptions extends BaseOptions { + public HopByHopOptions() { + super(); + this.setType(IPv6.PROTOCOL_HOPOPT); + } +}
\ No newline at end of file diff --git a/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/ipv6/IExtensionHeader.java b/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/ipv6/IExtensionHeader.java new file mode 100644 index 00000000..252f1a3c --- /dev/null +++ b/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/ipv6/IExtensionHeader.java @@ -0,0 +1,37 @@ +/* + * Copyright 2014-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.ipv6; + +/** + * Interface for IPv6 extension header. + */ +public interface IExtensionHeader { + /** + * Gets the type of next header. + * + * @return next header + */ + byte getNextHeader(); + + /** + * Sets the type of next header. + * + * @param nextHeader the next header to set + * @return this + */ + IExtensionHeader setNextHeader(final byte nextHeader); +} diff --git a/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/ipv6/Routing.java b/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/ipv6/Routing.java new file mode 100644 index 00000000..d7d204a9 --- /dev/null +++ b/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/ipv6/Routing.java @@ -0,0 +1,291 @@ +/* + * Copyright 2014-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.ipv6; + +import org.onlab.packet.BasePacket; +import org.onlab.packet.Data; +import org.onlab.packet.DeserializationException; +import org.onlab.packet.Deserializer; +import org.onlab.packet.IPacket; +import org.onlab.packet.IPv6; + +import java.nio.ByteBuffer; +import java.util.Arrays; + +import static org.onlab.packet.PacketUtils.checkHeaderLength; +import static org.onlab.packet.PacketUtils.checkInput; + +/** + * Implements IPv6 routing extension header format. (RFC 2460) + */ +public class Routing extends BasePacket implements IExtensionHeader { + public static final byte FIXED_HEADER_LENGTH = 4; // bytes + public static final byte FIXED_ROUTING_DATA_LENGTH = 4; // bytes + public static final byte LENGTH_UNIT = 8; // bytes per unit + + protected byte nextHeader; + protected byte headerExtLength; + protected byte routingType; + protected byte segmentsLeft; + protected byte[] routingData; + + @Override + public byte getNextHeader() { + return this.nextHeader; + } + + @Override + public Routing setNextHeader(final byte nextHeader) { + this.nextHeader = nextHeader; + return this; + } + + /** + * Gets the extension length of this header. + * + * @return header length + */ + public byte getHeaderExtLength() { + return this.headerExtLength; + } + + /** + * Sets the extension length of this header. + * + * @param headerExtLength the header length to set + * @return this + */ + public Routing setHeaderExtLength(final byte headerExtLength) { + this.headerExtLength = headerExtLength; + return this; + } + + /** + * Gets the routing type of this header. + * + * @return routing type + */ + public byte getRoutingType() { + return this.routingType; + } + + /** + * Sets the routing type of this header. + * + * @param routingType the routing type to set + * @return this + */ + public Routing setRoutingType(final byte routingType) { + this.routingType = routingType; + return this; + } + + /** + * Gets the number of remaining route segments of this header. + * + * @return number of remaining route segments + */ + public byte getSegmentsLeft() { + return this.segmentsLeft; + } + + /** + * Sets the number of remaining route segments of this header. + * + * @param segmentsLeft the number of remaining route segments to set + * @return this + */ + public Routing setSegmntsLeft(final byte segmentsLeft) { + this.segmentsLeft = segmentsLeft; + return this; + } + + /** + * Gets the routing data. + * + * @return the routing data + */ + public byte[] getRoutingData() { + return this.routingData; + } + + /** + * Sets the routing data. + * + * @param routingData the routing data to set + * @return this + */ + public Routing setRoutingData(final byte[] routingData) { + this.routingData = + Arrays.copyOfRange(routingData, 0, routingData.length); + return this; + } + + @Override + public byte[] serialize() { + byte[] payloadData = null; + if (this.payload != null) { + this.payload.setParent(this); + payloadData = this.payload.serialize(); + } + + int headerLength = FIXED_HEADER_LENGTH + routingData.length; + int payloadLength = 0; + if (payloadData != null) { + payloadLength = payloadData.length; + } + + final byte[] data = new byte[headerLength + payloadLength]; + final ByteBuffer bb = ByteBuffer.wrap(data); + + bb.put(this.nextHeader); + bb.put(this.headerExtLength); + bb.put(this.routingType); + bb.put(this.segmentsLeft); + bb.put(this.routingData, 0, routingData.length); + + if (payloadData != null) { + bb.put(payloadData); + } + + if (this.parent != null && this.parent instanceof IExtensionHeader) { + ((IExtensionHeader) this.parent).setNextHeader(IPv6.PROTOCOL_ROUTING); + } + return data; + } + + @Override + public IPacket deserialize(byte[] data, int offset, int length) { + final ByteBuffer bb = ByteBuffer.wrap(data, offset, length); + this.nextHeader = bb.get(); + this.headerExtLength = bb.get(); + this.routingType = bb.get(); + this.segmentsLeft = bb.get(); + int dataLength = + FIXED_ROUTING_DATA_LENGTH + LENGTH_UNIT * this.headerExtLength; + this.routingData = new byte[dataLength]; + bb.get(this.routingData, 0, dataLength); + + Deserializer<? extends IPacket> deserializer; + if (IPv6.PROTOCOL_DESERIALIZER_MAP.containsKey(this.nextHeader)) { + deserializer = IPv6.PROTOCOL_DESERIALIZER_MAP.get(this.nextHeader); + } else { + deserializer = new Data().deserializer(); + } + try { + this.payload = deserializer.deserialize(data, bb.position(), + bb.limit() - bb.position()); + this.payload.setParent(this); + } catch (DeserializationException e) { + return this; + } + + return this; + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + final int prime = 5807; + int result = super.hashCode(); + result = prime * result + this.nextHeader; + result = prime * result + this.headerExtLength; + result = prime * result + this.routingType; + result = prime * result + this.segmentsLeft; + for (byte b : this.routingData) { + result = prime * result + b; + } + return result; + } + + /* + * (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 Routing)) { + return false; + } + final Routing other = (Routing) obj; + if (this.nextHeader != other.nextHeader) { + return false; + } + if (this.headerExtLength != other.headerExtLength) { + return false; + } + if (this.routingType != other.routingType) { + return false; + } + if (this.segmentsLeft != other.segmentsLeft) { + return false; + } + if (!Arrays.equals(this.routingData, other.routingData)) { + return false; + } + return true; + } + + /** + * Deserializer function for routing headers. + * + * @return deserializer function + */ + public static Deserializer<Routing> deserializer() { + return (data, offset, length) -> { + checkInput(data, offset, length, FIXED_HEADER_LENGTH); + + Routing routing = new Routing(); + + ByteBuffer bb = ByteBuffer.wrap(data, offset, length); + routing.nextHeader = bb.get(); + routing.headerExtLength = bb.get(); + routing.routingType = bb.get(); + routing.segmentsLeft = bb.get(); + int dataLength = + FIXED_ROUTING_DATA_LENGTH + LENGTH_UNIT * routing.headerExtLength; + + checkHeaderLength(bb.remaining(), dataLength); + + routing.routingData = new byte[dataLength]; + bb.get(routing.routingData, 0, dataLength); + + Deserializer<? extends IPacket> deserializer; + if (IPv6.PROTOCOL_DESERIALIZER_MAP.containsKey(routing.nextHeader)) { + deserializer = IPv6.PROTOCOL_DESERIALIZER_MAP.get(routing.nextHeader); + } else { + deserializer = new Data().deserializer(); + } + routing.payload = deserializer.deserialize(data, bb.position(), + bb.limit() - bb.position()); + routing.payload.setParent(routing); + + return routing; + }; + } +} diff --git a/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/ipv6/package-info.java b/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/ipv6/package-info.java new file mode 100644 index 00000000..714fd1b2 --- /dev/null +++ b/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/ipv6/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2014-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. + */ + +/** + * Utilities for decoding and encoding IPv6 extension headers. + */ +package org.onlab.packet.ipv6; diff --git a/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/ndp/NeighborAdvertisement.java b/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/ndp/NeighborAdvertisement.java new file mode 100644 index 00000000..99fa0dd6 --- /dev/null +++ b/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/ndp/NeighborAdvertisement.java @@ -0,0 +1,278 @@ +/* + * Copyright 2014-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.ndp; + +import org.onlab.packet.BasePacket; +import org.onlab.packet.Deserializer; +import org.onlab.packet.IPacket; +import org.onlab.packet.Ip6Address; + +import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.List; + +import static org.onlab.packet.PacketUtils.checkInput; + +/** + * Implements ICMPv6 Neighbor Advertisement packet format (RFC 4861). + */ +public class NeighborAdvertisement extends BasePacket { + public static final byte HEADER_LENGTH = 20; // bytes + + protected byte routerFlag; + protected byte solicitedFlag; + protected byte overrideFlag; + protected byte[] targetAddress = new byte[Ip6Address.BYTE_LENGTH]; + + private final NeighborDiscoveryOptions options = + new NeighborDiscoveryOptions(); + + /** + * Gets router flag. + * + * @return the router flag + */ + public byte getRouterFlag() { + return this.routerFlag; + } + + /** + * Sets router flag. + * + * @param routerFlag the router flag to set + * @return this + */ + public NeighborAdvertisement setRouterFlag(final byte routerFlag) { + this.routerFlag = routerFlag; + return this; + } + + /** + * Gets solicited flag. + * + * @return the solicited flag + */ + public byte getSolicitedFlag() { + return this.solicitedFlag; + } + + /** + * Sets solicited flag. + * + * @param solicitedFlag the solicited flag to set + * @return this + */ + public NeighborAdvertisement setSolicitedFlag(final byte solicitedFlag) { + this.solicitedFlag = solicitedFlag; + return this; + } + + /** + * Gets override flag. + * + * @return the override flag + */ + public byte getOverrideFlag() { + return this.overrideFlag; + } + + /** + * Sets override flag. + * + * @param overrideFlag the override flag to set + * @return this + */ + public NeighborAdvertisement setOverrideFlag(final byte overrideFlag) { + this.overrideFlag = overrideFlag; + return this; + } + + /** + * Gets target address. + * + * @return the target IPv6 address + */ + public byte[] getTargetAddress() { + return this.targetAddress; + } + + /** + * Sets target address. + * + * @param targetAddress the target IPv6 address to set + * @return this + */ + public NeighborAdvertisement setTargetAddress(final byte[] targetAddress) { + this.targetAddress = + Arrays.copyOfRange(targetAddress, 0, Ip6Address.BYTE_LENGTH); + return this; + } + + /** + * Gets the Neighbor Discovery Protocol packet options. + * + * @return the Neighbor Discovery Protocol packet options + */ + public List<NeighborDiscoveryOptions.Option> getOptions() { + return this.options.options(); + } + + /** + * Adds a Neighbor Discovery Protocol packet option. + * + * @param type the option type + * @param data the option data + * @return this + */ + public NeighborAdvertisement addOption(final byte type, + final byte[] data) { + this.options.addOption(type, data); + return this; + } + + @Override + public byte[] serialize() { + byte[] optionsData = null; + if (this.options.hasOptions()) { + optionsData = this.options.serialize(); + } + + int optionsLength = 0; + if (optionsData != null) { + optionsLength = optionsData.length; + } + + final byte[] data = new byte[HEADER_LENGTH + optionsLength]; + final ByteBuffer bb = ByteBuffer.wrap(data); + + bb.putInt((this.routerFlag & 0x1) << 31 | + (this.solicitedFlag & 0x1) << 30 | + (this.overrideFlag & 0x1) << 29); + bb.put(this.targetAddress, 0, Ip6Address.BYTE_LENGTH); + if (optionsData != null) { + bb.put(optionsData); + } + + return data; + } + + @Override + public IPacket deserialize(byte[] data, int offset, int length) { + final ByteBuffer bb = ByteBuffer.wrap(data, offset, length); + int iscratch; + + iscratch = bb.getInt(); + this.routerFlag = (byte) (iscratch >> 31 & 0x1); + this.solicitedFlag = (byte) (iscratch >> 30 & 0x1); + this.overrideFlag = (byte) (iscratch >> 29 & 0x1); + bb.get(this.targetAddress, 0, Ip6Address.BYTE_LENGTH); + + this.options.deserialize(data, bb.position(), + bb.limit() - bb.position()); + + return this; + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + final int prime = 5807; + int result = super.hashCode(); + ByteBuffer bb; + result = prime * result + this.routerFlag; + result = prime * result + this.solicitedFlag; + result = prime * result + this.overrideFlag; + bb = ByteBuffer.wrap(this.targetAddress); + for (int i = 0; i < this.targetAddress.length / 4; i++) { + result = prime * result + bb.getInt(); + } + result = prime * result + this.options.hashCode(); + return result; + } + + /* + * (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 NeighborAdvertisement)) { + return false; + } + final NeighborAdvertisement other = (NeighborAdvertisement) obj; + if (this.routerFlag != other.routerFlag) { + return false; + } + if (this.solicitedFlag != other.solicitedFlag) { + return false; + } + if (this.overrideFlag != other.overrideFlag) { + return false; + } + if (!Arrays.equals(this.targetAddress, other.targetAddress)) { + return false; + } + if (!this.options.equals(other.options)) { + return false; + } + return true; + } + + /** + * Deserializer function for neighbor advertisement packets. + * + * @return deserializer function + */ + public static Deserializer<NeighborAdvertisement> deserializer() { + return (data, offset, length) -> { + checkInput(data, offset, length, HEADER_LENGTH); + + NeighborAdvertisement neighborAdvertisement = new NeighborAdvertisement(); + + ByteBuffer bb = ByteBuffer.wrap(data, offset, length); + + int iscratch; + + iscratch = bb.getInt(); + neighborAdvertisement.routerFlag = (byte) (iscratch >> 31 & 0x1); + neighborAdvertisement.solicitedFlag = (byte) (iscratch >> 30 & 0x1); + neighborAdvertisement.overrideFlag = (byte) (iscratch >> 29 & 0x1); + bb.get(neighborAdvertisement.targetAddress, 0, Ip6Address.BYTE_LENGTH); + + if (bb.limit() - bb.position() > 0) { + NeighborDiscoveryOptions options = NeighborDiscoveryOptions.deserializer() + .deserialize(data, bb.position(), bb.limit() - bb.position()); + + for (NeighborDiscoveryOptions.Option option : options.options()) { + neighborAdvertisement.addOption(option.type(), option.data()); + } + } + + return neighborAdvertisement; + }; + } +} diff --git a/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/ndp/NeighborDiscoveryOptions.java b/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/ndp/NeighborDiscoveryOptions.java new file mode 100644 index 00000000..00a26068 --- /dev/null +++ b/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/ndp/NeighborDiscoveryOptions.java @@ -0,0 +1,281 @@ +/* + * 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.ndp; + +import org.onlab.packet.BasePacket; +import org.onlab.packet.DeserializationException; +import org.onlab.packet.Deserializer; +import org.onlab.packet.IPacket; + +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import static org.onlab.packet.PacketUtils.checkInput; + +/** + * Neighbor Discovery Protocol packet options. + */ +public class NeighborDiscoveryOptions extends BasePacket { + public static final byte TYPE_SOURCE_LL_ADDRESS = 1; + public static final byte TYPE_TARGET_LL_ADDRESS = 2; + public static final byte TYPE_PREFIX_INFORMATION = 3; + public static final byte TYPE_REDIRECTED_HEADER = 4; + public static final byte TYPE_MTU = 5; + + public static final byte INITIAL_HEADER_REQUIRED = 2; + + private static final String BUFFER_UNDERFLOW_ERROR = + "Not enough bytes in buffer to read option"; + + private final List<Option> options = new ArrayList<>(); + + /** + * Packet option. + */ + public final class Option { + private final byte type; + private final byte[] data; + + /** + * Constructor. + * + * @param type the option type + * @param data the option data + */ + private Option(byte type, byte[] data) { + this.type = type; + this.data = Arrays.copyOfRange(data, 0, data.length); + } + + /** + * Gets the option type. + * + * @return the option type + */ + public byte type() { + return this.type; + } + + /** + * Gets the option data. + * + * @return the option data + */ + public byte[] data() { + return this.data; + } + + /** + * Gets the option data length (in number of octets). + * + * @return the option data length (in number of octets) + */ + public int dataLength() { + return data.length; + } + + /** + * Gets the option length (in number of octets), including the type and + * length fields (one octet each). + * + * @return the option length (in number of octets), including the type + * and length fields + */ + private int optionLength() { + return 2 + dataLength(); + } + + /** + * Gets the option length field value (in units of 8 octets). + * + * @return the option length field value (in units of 8 octets) + */ + private byte optionLengthField() { + return (byte) ((optionLength() + 7) / 8); + } + + /** + * Gets the option length on the wire (in number of octets). + * + * @return the option length on the wire (in number of octets) + */ + private int optionWireLength() { + return 8 * optionLengthField(); + } + } + + /** + * Adds a Neighbor Discovery Protocol packet option. + * + * @param type the option type + * @param data the option data + * @return this + */ + public NeighborDiscoveryOptions addOption(byte type, byte[] data) { + options.add(new Option(type, data)); + return this; + } + + /** + * Gets the Neighbor Discovery Protocol packet options. + * + * @return the Neighbor Discovery Protocol packet options + */ + public List<NeighborDiscoveryOptions.Option> options() { + return this.options; + } + + /** + * Checks whether any options are included. + * + * @return true if options are included, otherwise false + */ + public boolean hasOptions() { + return !this.options.isEmpty(); + } + + @Override + public byte[] serialize() { + // Compute first the total length on the wire for all options + + int wireLength = 0; + + for (Option option : this.options) { + wireLength += option.optionWireLength(); + } + + final byte[] data = new byte[wireLength]; + final ByteBuffer bb = ByteBuffer.wrap(data); + + // + // Serialize all options + // + for (Option option : this.options) { + bb.put(option.type()); + bb.put(option.optionLengthField()); + bb.put(option.data()); + // Add the padding + int paddingLength = + option.optionWireLength() - option.optionLength(); + for (int i = 0; i < paddingLength; i++) { + bb.put((byte) 0); + } + } + + return data; + } + + @Override + public IPacket deserialize(byte[] data, int offset, int length) { + final ByteBuffer bb = ByteBuffer.wrap(data, offset, length); + + options.clear(); + + // + // Deserialize all options + // + while (bb.hasRemaining()) { + byte type = bb.get(); + if (!bb.hasRemaining()) { + break; + } + byte lengthField = bb.get(); + int dataLength = lengthField * 8; // The data length field is in + // unit of 8 octets + + // Exclude the type and length fields + if (dataLength < 2) { + break; + } + dataLength -= 2; + + if (bb.remaining() < dataLength) { + break; + } + byte[] optionData = new byte[dataLength]; + bb.get(optionData, 0, optionData.length); + addOption(type, optionData); + } + + return this; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + + for (Option option : this.options) { + result = prime * result + option.type(); + result = prime * result + Arrays.hashCode(option.data()); + } + return result; + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) { + return true; + } + if (obj instanceof NeighborDiscoveryOptions) { + NeighborDiscoveryOptions other = (NeighborDiscoveryOptions) obj; + return this.options.equals(other.options); + } + return false; + } + + public static Deserializer<NeighborDiscoveryOptions> deserializer() { + return (data, offset, length) -> { + checkInput(data, offset, length, INITIAL_HEADER_REQUIRED); + + final ByteBuffer bb = ByteBuffer.wrap(data, offset, length); + + NeighborDiscoveryOptions ndo = new NeighborDiscoveryOptions(); + + ndo.options.clear(); + + // + // Deserialize all options + // + while (bb.hasRemaining()) { + byte type = bb.get(); + if (!bb.hasRemaining()) { + throw new DeserializationException(BUFFER_UNDERFLOW_ERROR); + } + byte lengthField = bb.get(); + int dataLength = lengthField * 8; // The data length field is in + // unit of 8 octets + + // Exclude the type and length fields + if (dataLength < 2) { + break; + } + dataLength -= 2; + + if (bb.remaining() < dataLength) { + throw new DeserializationException(BUFFER_UNDERFLOW_ERROR); + } + byte[] optionData = new byte[dataLength]; + bb.get(optionData, 0, optionData.length); + ndo.addOption(type, optionData); + } + + return ndo; + }; + } +} diff --git a/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/ndp/NeighborSolicitation.java b/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/ndp/NeighborSolicitation.java new file mode 100644 index 00000000..77c119a0 --- /dev/null +++ b/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/ndp/NeighborSolicitation.java @@ -0,0 +1,192 @@ +/* + * Copyright 2014-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.ndp; + +import org.onlab.packet.BasePacket; +import org.onlab.packet.Deserializer; +import org.onlab.packet.IPacket; +import org.onlab.packet.Ip6Address; + +import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.List; + +import static org.onlab.packet.PacketUtils.checkInput; + +/** + * Implements ICMPv6 Neighbor Solicitation packet format. (RFC 4861) + */ +public class NeighborSolicitation extends BasePacket { + public static final byte HEADER_LENGTH = 20; // bytes + + protected byte[] targetAddress = new byte[Ip6Address.BYTE_LENGTH]; + + private final NeighborDiscoveryOptions options = + new NeighborDiscoveryOptions(); + + /** + * Gets target address. + * + * @return the target IPv6 address + */ + public byte[] getTargetAddress() { + return this.targetAddress; + } + + /** + * Sets target address. + * + * @param targetAddress the target IPv6 address to set + * @return this + */ + public NeighborSolicitation setTargetAddress(final byte[] targetAddress) { + this.targetAddress = + Arrays.copyOfRange(targetAddress, 0, Ip6Address.BYTE_LENGTH); + return this; + } + + /** + * Gets the Neighbor Discovery Protocol packet options. + * + * @return the Neighbor Discovery Protocol packet options + */ + public List<NeighborDiscoveryOptions.Option> getOptions() { + return this.options.options(); + } + + /** + * Adds a Neighbor Discovery Protocol packet option. + * + * @param type the option type + * @param data the option data + * @return this + */ + public NeighborSolicitation addOption(final byte type, + final byte[] data) { + this.options.addOption(type, data); + return this; + } + + @Override + public byte[] serialize() { + byte[] optionsData = null; + if (this.options.hasOptions()) { + optionsData = this.options.serialize(); + } + + int optionsLength = 0; + if (optionsData != null) { + optionsLength = optionsData.length; + } + + final byte[] data = new byte[HEADER_LENGTH + optionsLength]; + final ByteBuffer bb = ByteBuffer.wrap(data); + + bb.putInt(0); + bb.put(this.targetAddress, 0, Ip6Address.BYTE_LENGTH); + if (optionsData != null) { + bb.put(optionsData); + } + + return data; + } + + @Override + public IPacket deserialize(byte[] data, int offset, int length) { + final ByteBuffer bb = ByteBuffer.wrap(data, offset, length); + + bb.getInt(); + bb.get(this.targetAddress, 0, Ip6Address.BYTE_LENGTH); + + this.options.deserialize(data, bb.position(), + bb.limit() - bb.position()); + + return this; + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + final int prime = 5807; + int result = super.hashCode(); + ByteBuffer bb; + bb = ByteBuffer.wrap(this.targetAddress); + for (int i = 0; i < this.targetAddress.length / 4; i++) { + result = prime * result + bb.getInt(); + } + result = prime * result + this.options.hashCode(); + return result; + } + + /* + * (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 NeighborSolicitation)) { + return false; + } + final NeighborSolicitation other = (NeighborSolicitation) obj; + if (!Arrays.equals(this.targetAddress, other.targetAddress)) { + return false; + } + if (!this.options.equals(other.options)) { + return false; + } + return true; + } + + /** + * Deserializer function for neighbor solicitation packets. + * + * @return deserializer function + */ + public static Deserializer<NeighborSolicitation> deserializer() { + return (data, offset, length) -> { + checkInput(data, offset, length, HEADER_LENGTH); + + NeighborSolicitation neighborSolicitation = new NeighborSolicitation(); + + ByteBuffer bb = ByteBuffer.wrap(data, offset, length); + + bb.getInt(); + bb.get(neighborSolicitation.targetAddress, 0, Ip6Address.BYTE_LENGTH); + + if (bb.limit() - bb.position() > 0) { + NeighborDiscoveryOptions options = NeighborDiscoveryOptions.deserializer() + .deserialize(data, bb.position(), bb.limit() - bb.position()); + + for (NeighborDiscoveryOptions.Option option : options.options()) { + neighborSolicitation.addOption(option.type(), option.data()); + } + } + + return neighborSolicitation; + }; + } +} diff --git a/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/ndp/Redirect.java b/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/ndp/Redirect.java new file mode 100644 index 00000000..51256d41 --- /dev/null +++ b/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/ndp/Redirect.java @@ -0,0 +1,225 @@ +/* + * Copyright 2014-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.ndp; + +import org.onlab.packet.BasePacket; +import org.onlab.packet.Deserializer; +import org.onlab.packet.IPacket; +import org.onlab.packet.Ip6Address; + +import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.List; + +import static org.onlab.packet.PacketUtils.checkInput; + +/** + * Implements ICMPv6 Redirect packet format. (RFC 4861) + */ +public class Redirect extends BasePacket { + public static final byte HEADER_LENGTH = 36; // bytes + + protected byte[] targetAddress = new byte[Ip6Address.BYTE_LENGTH]; + protected byte[] destinationAddress = new byte[Ip6Address.BYTE_LENGTH]; + + private final NeighborDiscoveryOptions options = + new NeighborDiscoveryOptions(); + + /** + * Gets target address. + * + * @return the target IPv6 address + */ + public byte[] getTargetAddress() { + return this.targetAddress; + } + + /** + * Sets target address. + * + * @param targetAddress the target IPv6 address to set + * @return this + */ + public Redirect setTargetAddress(final byte[] targetAddress) { + this.targetAddress = + Arrays.copyOfRange(targetAddress, 0, Ip6Address.BYTE_LENGTH); + return this; + } + + /** + * Gets destination address. + * + * @return the destination IPv6 address + */ + public byte[] getDestinationAddress() { + return this.destinationAddress; + } + + /** + * Sets destination address. + * + * @param destinationAddress the destination IPv6 address to set + * @return this + */ + public Redirect setDestinationAddress(final byte[] destinationAddress) { + this.destinationAddress = + Arrays.copyOfRange(destinationAddress, 0, Ip6Address.BYTE_LENGTH); + return this; + } + + /** + * Gets the Neighbor Discovery Protocol packet options. + * + * @return the Neighbor Discovery Protocol packet options + */ + public List<NeighborDiscoveryOptions.Option> getOptions() { + return this.options.options(); + } + + /** + * Adds a Neighbor Discovery Protocol packet option. + * + * @param type the option type + * @param data the option data + * @return this + */ + public Redirect addOption(final byte type, final byte[] data) { + this.options.addOption(type, data); + return this; + } + + @Override + public byte[] serialize() { + byte[] optionsData = null; + if (this.options.hasOptions()) { + optionsData = this.options.serialize(); + } + + int optionsLength = 0; + if (optionsData != null) { + optionsLength = optionsData.length; + } + + final byte[] data = new byte[HEADER_LENGTH + optionsLength]; + final ByteBuffer bb = ByteBuffer.wrap(data); + + bb.putInt(0); + bb.put(this.targetAddress, 0, Ip6Address.BYTE_LENGTH); + bb.put(this.destinationAddress, 0, Ip6Address.BYTE_LENGTH); + if (optionsData != null) { + bb.put(optionsData); + } + + return data; + } + + @Override + public IPacket deserialize(byte[] data, int offset, int length) { + final ByteBuffer bb = ByteBuffer.wrap(data, offset, length); + + bb.getInt(); + bb.get(this.targetAddress, 0, Ip6Address.BYTE_LENGTH); + bb.get(this.destinationAddress, 0, Ip6Address.BYTE_LENGTH); + + this.options.deserialize(data, bb.position(), + bb.limit() - bb.position()); + + return this; + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + final int prime = 5807; + int result = super.hashCode(); + ByteBuffer bb; + bb = ByteBuffer.wrap(this.targetAddress); + for (int i = 0; i < this.targetAddress.length / 4; i++) { + result = prime * result + bb.getInt(); + } + bb = ByteBuffer.wrap(this.destinationAddress); + for (int i = 0; i < this.destinationAddress.length / 4; i++) { + result = prime * result + bb.getInt(); + } + result = prime * result + this.options.hashCode(); + return result; + } + + /* + * (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 Redirect)) { + return false; + } + final Redirect other = (Redirect) obj; + if (!Arrays.equals(this.targetAddress, other.targetAddress)) { + return false; + } + if (!Arrays.equals(this.destinationAddress, + other.destinationAddress)) { + return false; + } + if (!this.options.equals(other.options)) { + return false; + } + return true; + } + + /** + * Deserializer function for redirect packets. + * + * @return deserializer function + */ + public static Deserializer<Redirect> deserializer() { + return (data, offset, length) -> { + checkInput(data, offset, length, HEADER_LENGTH); + + Redirect redirect = new Redirect(); + + ByteBuffer bb = ByteBuffer.wrap(data, offset, length); + + bb.getInt(); + + bb.get(redirect.targetAddress, 0, Ip6Address.BYTE_LENGTH); + bb.get(redirect.destinationAddress, 0, Ip6Address.BYTE_LENGTH); + + if (bb.limit() - bb.position() > 0) { + NeighborDiscoveryOptions options = NeighborDiscoveryOptions.deserializer() + .deserialize(data, bb.position(), bb.limit() - bb.position()); + + for (NeighborDiscoveryOptions.Option option : options.options()) { + redirect.addOption(option.type(), option.data()); + } + } + + return redirect; + }; + } +} diff --git a/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/ndp/RouterAdvertisement.java b/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/ndp/RouterAdvertisement.java new file mode 100644 index 00000000..597fc9f8 --- /dev/null +++ b/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/ndp/RouterAdvertisement.java @@ -0,0 +1,325 @@ +/* + * Copyright 2014-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.ndp; + +import org.onlab.packet.BasePacket; +import org.onlab.packet.Deserializer; +import org.onlab.packet.IPacket; + +import java.nio.ByteBuffer; +import java.util.List; + +import static org.onlab.packet.PacketUtils.checkInput; + +/** + * Implements ICMPv6 Router Advertisement packet format. (RFC 4861) + */ +public class RouterAdvertisement extends BasePacket { + public static final byte HEADER_LENGTH = 12; // bytes + + protected byte currentHopLimit; + protected byte mFlag; + protected byte oFlag; + protected short routerLifetime; + protected int reachableTime; + protected int retransmitTimer; + + private final NeighborDiscoveryOptions options = + new NeighborDiscoveryOptions(); + + /** + * Gets current hop limit. + * + * @return the current hop limit + */ + public byte getCurrentHopLimit() { + return this.currentHopLimit; + } + + /** + * Sets current hop limit. + * + * @param currentHopLimit the current hop limit to set + * @return this + */ + public RouterAdvertisement setCurrentHopLimit(final byte currentHopLimit) { + this.currentHopLimit = currentHopLimit; + return this; + } + + /** + * Gets managed address configuration flag. + * + * @return the managed address configuration flag + */ + public byte getMFlag() { + return this.mFlag; + } + + /** + * Sets managed address configuration flag. + * + * @param mFlag the managed address configuration flag to set + * @return this + */ + public RouterAdvertisement setMFlag(final byte mFlag) { + this.mFlag = mFlag; + return this; + } + + /** + * Gets other configuration flag. + * + * @return the other configuration flag + */ + public byte getOFlag() { + return this.oFlag; + } + + /** + * Sets other configuration flag. + * + * @param oFlag the other configuration flag to set + * @return this + */ + public RouterAdvertisement setOFlag(final byte oFlag) { + this.oFlag = oFlag; + return this; + } + + /** + * Gets router lifetime. + * + * @return the router lifetime + */ + public short getRouterLifetime() { + return this.routerLifetime; + } + + /** + * Sets router lifetime. + * + * @param routerLifetime the router lifetime to set + * @return this + */ + public RouterAdvertisement setRouterLifetime(final short routerLifetime) { + this.routerLifetime = routerLifetime; + return this; + } + + /** + * Gets reachable time. + * + * @return the reachable time + */ + public int getReachableTime() { + return this.reachableTime; + } + + /** + * Sets reachable time. + * + * @param reachableTime the reachable time to set + * @return this + */ + public RouterAdvertisement setReachableTime(final int reachableTime) { + this.reachableTime = reachableTime; + return this; + } + + /** + * Gets retransmission timer. + * + * @return the retransmission timer + */ + public int getRetransmitTimer() { + return this.retransmitTimer; + } + + /** + * Sets retransmission timer. + * + * @param retransmitTimer the retransmission timer to set + * @return this + */ + public RouterAdvertisement setRetransmitTimer(final int retransmitTimer) { + this.retransmitTimer = retransmitTimer; + return this; + } + + /** + * Gets the Neighbor Discovery Protocol packet options. + * + * @return the Neighbor Discovery Protocol packet options + */ + public List<NeighborDiscoveryOptions.Option> getOptions() { + return this.options.options(); + } + + /** + * Adds a Neighbor Discovery Protocol packet option. + * + * @param type the option type + * @param data the option data + * @return this + */ + public RouterAdvertisement addOption(final byte type, final byte[] data) { + this.options.addOption(type, data); + return this; + } + + @Override + public byte[] serialize() { + byte[] optionsData = null; + if (this.options.hasOptions()) { + optionsData = this.options.serialize(); + } + + int optionsLength = 0; + if (optionsData != null) { + optionsLength = optionsData.length; + } + + final byte[] data = new byte[HEADER_LENGTH + optionsLength]; + final ByteBuffer bb = ByteBuffer.wrap(data); + + bb.put(this.currentHopLimit); + bb.put((byte) ((this.mFlag & 0x1) << 7 | (this.oFlag & 0x1) << 6)); + bb.putShort(routerLifetime); + bb.putInt(reachableTime); + bb.putInt(retransmitTimer); + + if (optionsData != null) { + bb.put(optionsData); + } + + return data; + } + + @Override + public IPacket deserialize(byte[] data, int offset, int length) { + final ByteBuffer bb = ByteBuffer.wrap(data, offset, length); + int bscratch; + + this.currentHopLimit = bb.get(); + bscratch = bb.get(); + this.mFlag = (byte) ((bscratch >> 7) & 0x1); + this.oFlag = (byte) ((bscratch >> 6) & 0x1); + this.routerLifetime = bb.getShort(); + this.reachableTime = bb.getInt(); + this.retransmitTimer = bb.getInt(); + + this.options.deserialize(data, bb.position(), + bb.limit() - bb.position()); + + return this; + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + final int prime = 5807; + int result = super.hashCode(); + result = prime * result + this.currentHopLimit; + result = prime * result + this.mFlag; + result = prime * result + this.oFlag; + result = prime * result + this.routerLifetime; + result = prime * result + this.reachableTime; + result = prime * result + this.retransmitTimer; + result = prime * result + this.options.hashCode(); + return result; + } + + /* + * (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 RouterAdvertisement)) { + return false; + } + final RouterAdvertisement other = (RouterAdvertisement) obj; + if (this.currentHopLimit != other.currentHopLimit) { + return false; + } + if (this.mFlag != other.mFlag) { + return false; + } + if (this.oFlag != other.oFlag) { + return false; + } + if (this.routerLifetime != other.routerLifetime) { + return false; + } + if (this.reachableTime != other.reachableTime) { + return false; + } + if (this.retransmitTimer != other.retransmitTimer) { + return false; + } + if (!this.options.equals(other.options)) { + return false; + } + return true; + } + + /** + * Deserializer function for router advertisement packets. + * + * @return deserializer function + */ + public static Deserializer<RouterAdvertisement> deserializer() { + return (data, offset, length) -> { + checkInput(data, offset, length, HEADER_LENGTH); + + RouterAdvertisement routerAdvertisement = new RouterAdvertisement(); + + ByteBuffer bb = ByteBuffer.wrap(data, offset, length); + int bscratch; + + routerAdvertisement.currentHopLimit = bb.get(); + bscratch = bb.get(); + routerAdvertisement.mFlag = (byte) ((bscratch >> 7) & 0x1); + routerAdvertisement.oFlag = (byte) ((bscratch >> 6) & 0x1); + routerAdvertisement.routerLifetime = bb.getShort(); + routerAdvertisement.reachableTime = bb.getInt(); + routerAdvertisement.retransmitTimer = bb.getInt(); + + if (bb.limit() - bb.position() > 0) { + NeighborDiscoveryOptions options = NeighborDiscoveryOptions.deserializer() + .deserialize(data, bb.position(), bb.limit() - bb.position()); + + for (NeighborDiscoveryOptions.Option option : options.options()) { + routerAdvertisement.addOption(option.type(), option.data()); + } + } + + return routerAdvertisement; + }; + } +} diff --git a/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/ndp/RouterSolicitation.java b/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/ndp/RouterSolicitation.java new file mode 100644 index 00000000..e279a404 --- /dev/null +++ b/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/ndp/RouterSolicitation.java @@ -0,0 +1,155 @@ +/* + * Copyright 2014-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.ndp; + +import org.onlab.packet.BasePacket; +import org.onlab.packet.Deserializer; +import org.onlab.packet.IPacket; + +import java.nio.ByteBuffer; +import java.util.List; + +import static org.onlab.packet.PacketUtils.checkInput; + +/** + * Implements ICMPv6 Router Solicitation packet format. (RFC 4861) + */ +public class RouterSolicitation extends BasePacket { + public static final byte HEADER_LENGTH = 4; // bytes + + private final NeighborDiscoveryOptions options = new NeighborDiscoveryOptions(); + + /** + * Gets the Neighbor Discovery Protocol packet options. + * + * @return the Neighbor Discovery Protocol packet options + */ + public List<NeighborDiscoveryOptions.Option> getOptions() { + return this.options.options(); + } + + /** + * Adds a Neighbor Discovery Protocol packet option. + * + * @param type the option type + * @param data the option data + * @return this + */ + public RouterSolicitation addOption(final byte type, final byte[] data) { + this.options.addOption(type, data); + return this; + } + + @Override + public byte[] serialize() { + byte[] optionsData = null; + if (this.options.hasOptions()) { + optionsData = this.options.serialize(); + } + + int optionsLength = 0; + if (optionsData != null) { + optionsLength = optionsData.length; + } + + final byte[] data = new byte[HEADER_LENGTH + optionsLength]; + final ByteBuffer bb = ByteBuffer.wrap(data); + + bb.putInt(0); + + if (optionsData != null) { + bb.put(optionsData); + } + + return data; + } + + @Override + public IPacket deserialize(byte[] data, int offset, int length) { + final ByteBuffer bb = ByteBuffer.wrap(data, offset, length); + + bb.getInt(); + + this.options.deserialize(data, bb.position(), + bb.limit() - bb.position()); + + return this; + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + final int prime = 5807; + int result = super.hashCode(); + result = prime * result + this.options.hashCode(); + return result; + } + + /* + * (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 RouterSolicitation)) { + return false; + } + final RouterSolicitation other = (RouterSolicitation) obj; + if (!this.options.equals(other.options)) { + return false; + } + return true; + } + + /** + * Deserializer function for router solicitation packets. + * + * @return deserializer function + */ + public static Deserializer<RouterSolicitation> deserializer() { + return (data, offset, length) -> { + checkInput(data, offset, length, HEADER_LENGTH); + + RouterSolicitation routerSolicitation = new RouterSolicitation(); + + ByteBuffer bb = ByteBuffer.wrap(data, offset, length); + + bb.getInt(); + + if (bb.limit() - bb.position() > 0) { + NeighborDiscoveryOptions options = NeighborDiscoveryOptions.deserializer() + .deserialize(data, bb.position(), bb.limit() - bb.position()); + + for (NeighborDiscoveryOptions.Option option : options.options()) { + routerSolicitation.addOption(option.type(), option.data()); + } + } + + return routerSolicitation; + }; + } +} diff --git a/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/ndp/package-info.java b/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/ndp/package-info.java new file mode 100644 index 00000000..c62b1fba --- /dev/null +++ b/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/ndp/package-info.java @@ -0,0 +1,21 @@ +/* + * Copyright 2014-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. + */ + +/** + * Utilities for decoding and encoding packets of Neighbor Discovery Protocol + * for IPv6 (RFC 4861). + */ +package org.onlab.packet.ndp; diff --git a/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/package-info.java b/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/package-info.java new file mode 100644 index 00000000..e8e0cb5e --- /dev/null +++ b/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/package-info.java @@ -0,0 +1,21 @@ +/* + * Copyright 2014 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. + */ + +/** + * Utilities for decoding and encoding packets of various network protocols + * and encapsulations. + */ +package org.onlab.packet; diff --git a/framework/src/onos/utils/misc/src/main/java/org/onlab/util/AbstractAccumulator.java b/framework/src/onos/utils/misc/src/main/java/org/onlab/util/AbstractAccumulator.java new file mode 100644 index 00000000..500f8d60 --- /dev/null +++ b/framework/src/onos/utils/misc/src/main/java/org/onlab/util/AbstractAccumulator.java @@ -0,0 +1,214 @@ +/* + * 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.util; + +import com.google.common.collect.Lists; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; +import java.util.Timer; +import java.util.TimerTask; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Base implementation of an item accumulator. It allows triggering based on + * item inter-arrival time threshold, maximum batch life threshold and maximum + * batch size. + */ +public abstract class AbstractAccumulator<T> implements Accumulator<T> { + + private Logger log = LoggerFactory.getLogger(AbstractAccumulator.class); + + private final Timer timer; + private final int maxItems; + private final int maxBatchMillis; + private final int maxIdleMillis; + + private volatile TimerTask idleTask = new ProcessorTask(); + private volatile TimerTask maxTask = new ProcessorTask(); + + private List<T> items = Lists.newArrayList(); + + /** + * Creates an item accumulator capable of triggering on the specified + * thresholds. + * + * @param timer timer to use for scheduling check-points + * @param maxItems maximum number of items to accumulate before + * processing is triggered + * @param maxBatchMillis maximum number of millis allowed since the first + * item before processing is triggered + * @param maxIdleMillis maximum number millis between items before + * processing is triggered + */ + protected AbstractAccumulator(Timer timer, int maxItems, + int maxBatchMillis, int maxIdleMillis) { + this.timer = checkNotNull(timer, "Timer cannot be null"); + + checkArgument(maxItems > 1, "Maximum number of items must be > 1"); + checkArgument(maxBatchMillis > 0, "Maximum millis must be positive"); + checkArgument(maxIdleMillis > 0, "Maximum idle millis must be positive"); + + this.maxItems = maxItems; + this.maxBatchMillis = maxBatchMillis; + this.maxIdleMillis = maxIdleMillis; + } + + @Override + public synchronized void add(T item) { + idleTask = cancelIfActive(idleTask); + items.add(checkNotNull(item, "Item cannot be null")); + + // Did we hit the max item threshold? + if (items.size() >= maxItems) { + maxTask = cancelIfActive(maxTask); + scheduleNow(); + } else { + // Otherwise, schedule idle task and if this is a first item + // also schedule the max batch age task. + idleTask = schedule(maxIdleMillis); + if (items.size() == 1) { + maxTask = schedule(maxBatchMillis); + } + } + } + + /** + * Finalizes the current batch, if ready, and schedules a new processor + * in the immediate future. + */ + private void scheduleNow() { + if (isReady()) { + TimerTask task = new ProcessorTask(finalizeCurrentBatch()); + timer.schedule(task, 1); + } + } + + /** + * Schedules a new processor task given number of millis in the future. + * Batch finalization is deferred to time of execution. + */ + private TimerTask schedule(int millis) { + TimerTask task = new ProcessorTask(); + timer.schedule(task, millis); + return task; + } + + /** + * Cancels the specified task if it is active. + */ + private TimerTask cancelIfActive(TimerTask task) { + if (task != null) { + task.cancel(); + } + return task; + } + + // Task for triggering processing of accumulated items + private class ProcessorTask extends TimerTask { + + private final List<T> items; + + // Creates a new processor task with deferred batch finalization. + ProcessorTask() { + this.items = null; + } + + // Creates a new processor task with pre-emptive batch finalization. + ProcessorTask(List<T> items) { + this.items = items; + } + + @Override + public void run() { + synchronized (AbstractAccumulator.this) { + idleTask = cancelIfActive(idleTask); + } + if (isReady()) { + try { + synchronized (AbstractAccumulator.this) { + maxTask = cancelIfActive(maxTask); + } + List<T> batch = items != null ? items : finalizeCurrentBatch(); + if (!batch.isEmpty()) { + processItems(batch); + } + } catch (Exception e) { + log.warn("Unable to process batch due to", e); + } + } else { + synchronized (AbstractAccumulator.this) { + idleTask = schedule(maxIdleMillis); + } + } + } + } + + // Demotes and returns the current batch of items and promotes a new one. + private synchronized List<T> finalizeCurrentBatch() { + List<T> toBeProcessed = items; + items = Lists.newArrayList(); + return toBeProcessed; + } + + @Override + public boolean isReady() { + return true; + } + + /** + * Returns the backing timer. + * + * @return backing timer + */ + public Timer timer() { + return timer; + } + + /** + * Returns the maximum number of items allowed to accumulate before + * processing is triggered. + * + * @return max number of items + */ + public int maxItems() { + return maxItems; + } + + /** + * Returns the maximum number of millis allowed to expire since the first + * item before processing is triggered. + * + * @return max number of millis a batch is allowed to last + */ + public int maxBatchMillis() { + return maxBatchMillis; + } + + /** + * Returns the maximum number of millis allowed to expire since the last + * item arrival before processing is triggered. + * + * @return max number of millis since the last item + */ + public int maxIdleMillis() { + return maxIdleMillis; + } + +} diff --git a/framework/src/onos/utils/misc/src/main/java/org/onlab/util/Accumulator.java b/framework/src/onos/utils/misc/src/main/java/org/onlab/util/Accumulator.java new file mode 100644 index 00000000..20b7a481 --- /dev/null +++ b/framework/src/onos/utils/misc/src/main/java/org/onlab/util/Accumulator.java @@ -0,0 +1,49 @@ +/* + * 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.util; + +import java.util.List; + +/** + * Abstraction of an accumulator capable of collecting items and at some + * point in time triggers processing of all previously accumulated items. + * + * @param <T> item type + */ +public interface Accumulator<T> { + + /** + * Adds an item to the current batch. This operation may, or may not + * trigger processing of the current batch of items. + * + * @param item item to be added to the current batch + */ + void add(T item); + + /** + * Processes the specified list of accumulated items. + * + * @param items list of accumulated items + */ + void processItems(List<T> items); + + /** + * Indicates whether the accumulator is ready to process items. + * + * @return true if ready to process + */ + boolean isReady(); +} diff --git a/framework/src/onos/utils/misc/src/main/java/org/onlab/util/Bandwidth.java b/framework/src/onos/utils/misc/src/main/java/org/onlab/util/Bandwidth.java new file mode 100644 index 00000000..349e660f --- /dev/null +++ b/framework/src/onos/utils/misc/src/main/java/org/onlab/util/Bandwidth.java @@ -0,0 +1,138 @@ +/* + * 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.util; + +import com.google.common.collect.ComparisonChain; + +import java.util.Objects; + +/** + * Representation of bandwidth. + * Use the static factory method corresponding to the unit (like Kbps) you desire on instantiation. + */ +public final class Bandwidth implements RichComparable<Bandwidth> { + + private final double bps; + + /** + * Creates a new instance with given bandwidth. + * + * @param bps bandwidth value to be assigned + */ + private Bandwidth(double bps) { + this.bps = bps; + } + + // Constructor for serialization + private Bandwidth() { + this.bps = 0; + } + + /** + * Creates a new instance with given bandwidth in bps. + * + * @param bps bandwidth value to be assigned + * @return {@link Bandwidth} instance with given bandwidth + */ + public static Bandwidth bps(double bps) { + return new Bandwidth(bps); + } + + /** + * Creates a new instance with given bandwidth in Kbps. + * + * @param kbps bandwidth value to be assigned + * @return {@link Bandwidth} instance with given bandwidth + */ + public static Bandwidth kbps(double kbps) { + return bps(kbps * 1_000L); + } + + /** + * Creates a new instance with given bandwidth in Mbps. + * + * @param mbps bandwidth value to be assigned + * @return {@link Bandwidth} instance with given bandwidth + */ + public static Bandwidth mbps(double mbps) { + return bps(mbps * 1_000_000L); + } + + /** + * Creates a new instance with given bandwidth in Gbps. + * + * @param gbps bandwidth value to be assigned + * @return {@link Bandwidth} instance with given bandwidth + */ + public static Bandwidth gbps(double gbps) { + return bps(gbps * 1_000_000_000L); + } + + /** + * Returns bandwidth in bps. + * + * @return bandwidth in bps. + */ + public double bps() { + return bps; + } + + /** + * Returns a Bandwidth whose value is (this + value). + * + * @param value value to be added to this Frequency + * @return this + value + */ + public Bandwidth add(Bandwidth value) { + return bps(this.bps + value.bps); + } + + /** + * Returns a Bandwidth whose value is (this - value). + * + * @param value value to be added to this Frequency + * @return this - value + */ + public Bandwidth subtract(Bandwidth value) { + return bps(this.bps - value.bps); + } + + @Override + public int compareTo(Bandwidth other) { + return ComparisonChain.start() + .compare(this.bps, other.bps) + .result(); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof Bandwidth) { + Bandwidth that = (Bandwidth) obj; + return Objects.equals(this.bps, that.bps); + } + return false; + } + + @Override + public int hashCode() { + return Long.hashCode(Double.doubleToLongBits(bps)); + } + + @Override + public String toString() { + return String.valueOf(this.bps); + } +} diff --git a/framework/src/onos/utils/misc/src/main/java/org/onlab/util/BlockingBoolean.java b/framework/src/onos/utils/misc/src/main/java/org/onlab/util/BlockingBoolean.java new file mode 100644 index 00000000..f3049c31 --- /dev/null +++ b/framework/src/onos/utils/misc/src/main/java/org/onlab/util/BlockingBoolean.java @@ -0,0 +1,97 @@ +/* + * 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.util; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.AbstractQueuedSynchronizer; + +/** + * Mutable boolean that allows threads to wait for a specified value. + */ +public class BlockingBoolean extends AbstractQueuedSynchronizer { + + private static final int TRUE = 1; + private static final int FALSE = 0; + + /** + * Creates a new blocking boolean with the specified value. + * + * @param value the starting value + */ + public BlockingBoolean(boolean value) { + setState(value ? TRUE : FALSE); + } + + /** + * Causes the current thread to wait until the boolean equals the specified + * value unless the thread is {@linkplain Thread#interrupt interrupted}. + * + * @param value specified value + * @throws InterruptedException if interrupted while waiting + */ + public void await(boolean value) throws InterruptedException { + acquireSharedInterruptibly(value ? TRUE : FALSE); + } + + /** + * Causes the current thread to wait until the boolean equals the specified + * value unless the thread is {@linkplain Thread#interrupt interrupted}, + * or the specified waiting time elapses. + * + * @param value specified value + * @param timeout the maximum time to wait + * @param unit the time unit of the {@code timeout} argument + * @return {@code true} if the count reached zero and {@code false} + * if the waiting time elapsed before the count reached zero + * @throws InterruptedException if interrupted while waiting + */ + public boolean await(boolean value, long timeout, TimeUnit unit) + throws InterruptedException { + return tryAcquireSharedNanos(value ? TRUE : FALSE, unit.toNanos(timeout)); + } + + protected int tryAcquireShared(int acquires) { + return (getState() == acquires) ? 1 : -1; + } + + /** + * Sets the value of the blocking boolean. + * + * @param value new value + */ + public void set(boolean value) { + releaseShared(value ? TRUE : FALSE); + } + + /** + * Gets the value of the blocking boolean. + * + * @return current value + */ + public boolean get() { + return getState() == TRUE; + } + + protected boolean tryReleaseShared(int releases) { + // Signal on state change only + int state = getState(); + if (state == releases) { + return false; + } + return compareAndSetState(state, releases); + } + +} diff --git a/framework/src/onos/utils/misc/src/main/java/org/onlab/util/BoundedThreadPool.java b/framework/src/onos/utils/misc/src/main/java/org/onlab/util/BoundedThreadPool.java new file mode 100644 index 00000000..9eef6609 --- /dev/null +++ b/framework/src/onos/utils/misc/src/main/java/org/onlab/util/BoundedThreadPool.java @@ -0,0 +1,176 @@ +/* + * 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.util; + +import org.slf4j.LoggerFactory; + +import java.util.concurrent.Callable; +import java.util.concurrent.Future; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.RejectedExecutionHandler; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicLong; + +/** + * Implementation of ThreadPoolExecutor that bounds the work queue. + * <p> + * When a new job would exceed the queue bound, the job is run on the caller's + * thread rather than on a thread from the pool. + * </p> + */ +public final class BoundedThreadPool extends ThreadPoolExecutor { + + private static final org.slf4j.Logger log = LoggerFactory.getLogger(BoundedThreadPool.class); + + protected static int maxQueueSize = 80_000; //TODO tune this value + //private static final RejectedExecutionHandler DEFAULT_HANDLER = new CallerFeedbackPolicy(); + private static final long STATS_INTERVAL = 5_000; //ms + + private final BlockingBoolean underHighLoad; + + private BoundedThreadPool(int numberOfThreads, + ThreadFactory threadFactory) { + super(numberOfThreads, numberOfThreads, + 0L, TimeUnit.MILLISECONDS, + new LinkedBlockingQueue<>(maxQueueSize), + threadFactory, + new CallerFeedbackPolicy()); + underHighLoad = ((CallerFeedbackPolicy) getRejectedExecutionHandler()).load(); + } + + /** + * Returns a single-thread, bounded executor service. + * + * @param threadFactory thread factory for the worker thread. + * @return the bounded thread pool + */ + public static BoundedThreadPool newSingleThreadExecutor(ThreadFactory threadFactory) { + return new BoundedThreadPool(1, threadFactory); + } + + /** + * Returns a fixed-size, bounded executor service. + * + * @param numberOfThreads number of threads in the pool + * @param threadFactory thread factory for the worker threads. + * @return the bounded thread pool + */ + public static BoundedThreadPool newFixedThreadPool(int numberOfThreads, ThreadFactory threadFactory) { + return new BoundedThreadPool(numberOfThreads, threadFactory); + } + + //TODO Might want to switch these to use Metrics class Meter and/or Gauge instead. + private final Counter submitted = new Counter(); + private final Counter taken = new Counter(); + + @Override + public Future<?> submit(Runnable task) { + submitted.add(1); + return super.submit(task); + } + + @Override + public <T> Future<T> submit(Runnable task, T result) { + submitted.add(1); + return super.submit(task, result); + } + + @Override + public void execute(Runnable command) { + submitted.add(1); + super.execute(command); + } + + @Override + public <T> Future<T> submit(Callable<T> task) { + submitted.add(1); + return super.submit(task); + } + + + @Override + protected void beforeExecute(Thread t, Runnable r) { + super.beforeExecute(t, r); + taken.add(1); + periodicallyPrintStats(); + updateLoad(); + } + + // TODO schedule this with a fixed delay from a scheduled executor + private final AtomicLong lastPrinted = new AtomicLong(0L); + + private void periodicallyPrintStats() { + long now = System.currentTimeMillis(); + long prev = lastPrinted.get(); + if (now - prev > STATS_INTERVAL) { + if (lastPrinted.compareAndSet(prev, now)) { + log.debug("queue size: {} jobs, submitted: {} jobs/s, taken: {} jobs/s", + getQueue().size(), + submitted.throughput(), taken.throughput()); + submitted.reset(); + taken.reset(); + } + } + } + + // TODO consider updating load whenever queue changes + private void updateLoad() { + underHighLoad.set(getQueue().remainingCapacity() / (double) maxQueueSize < 0.2); + } + + /** + * Feedback policy that delays the caller's thread until the executor's work + * queue falls below a threshold, then runs the job on the caller's thread. + */ + private static final class CallerFeedbackPolicy implements RejectedExecutionHandler { + + private final BlockingBoolean underLoad = new BlockingBoolean(false); + + public BlockingBoolean load() { + return underLoad; + } + + /** + * Executes task r in the caller's thread, unless the executor + * has been shut down, in which case the task is discarded. + * + * @param r the runnable task requested to be executed + * @param e the executor attempting to execute this task + */ + public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { + if (!e.isShutdown()) { + // Wait for up to 1 second while the queue drains... + boolean notified = false; + try { + notified = underLoad.await(false, 1, TimeUnit.SECONDS); + } catch (InterruptedException exception) { + log.debug("Got exception waiting for notification:", exception); + } finally { + if (!notified) { + log.info("Waited for 1 second on {}. Proceeding with work...", + Thread.currentThread().getName()); + } else { + log.info("FIXME we got a notice"); + } + } + // Do the work on the submitter's thread + r.run(); + } + } + } +}
\ No newline at end of file diff --git a/framework/src/onos/utils/misc/src/main/java/org/onlab/util/ByteArraySizeHashPrinter.java b/framework/src/onos/utils/misc/src/main/java/org/onlab/util/ByteArraySizeHashPrinter.java new file mode 100644 index 00000000..cc39ce28 --- /dev/null +++ b/framework/src/onos/utils/misc/src/main/java/org/onlab/util/ByteArraySizeHashPrinter.java @@ -0,0 +1,69 @@ +/* + * Copyright 2014 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.util; + +import java.util.Arrays; + +import com.google.common.base.MoreObjects; +import com.google.common.base.MoreObjects.ToStringHelper; + +/** + * Helper to print byte[] length and hashCode. + */ +public final class ByteArraySizeHashPrinter { + + private final byte[] bytes; + + /** + * Returns ByteArraySizeHashPrinter wrapping given byte[]. + * + * @param bytes bytes to wrap around + * @return ByteArraySizeHashPrinter + */ + public static ByteArraySizeHashPrinter of(byte[] bytes) { + return new ByteArraySizeHashPrinter(bytes); + } + + /** + * Returns ByteArraySizeHashPrinter wrapping given byte[]. + * + * @param bytes bytes to wrap around + * @return null if {@code bytes == null}, ByteArraySizeHashPrinter otherwise + */ + public static ByteArraySizeHashPrinter orNull(byte[] bytes) { + if (bytes == null) { + return null; + } + return new ByteArraySizeHashPrinter(bytes); + } + + public ByteArraySizeHashPrinter(byte[] bytes) { + this.bytes = bytes; + } + + @Override + public String toString() { + ToStringHelper helper = MoreObjects.toStringHelper("byte[]"); + if (bytes != null) { + helper.add("length", bytes.length) + .add("hash", Arrays.hashCode(bytes)); + } else { + helper.addValue(bytes); + } + return helper.toString(); + } +} diff --git a/framework/src/onos/utils/misc/src/main/java/org/onlab/util/Counter.java b/framework/src/onos/utils/misc/src/main/java/org/onlab/util/Counter.java new file mode 100644 index 00000000..bde28783 --- /dev/null +++ b/framework/src/onos/utils/misc/src/main/java/org/onlab/util/Counter.java @@ -0,0 +1,139 @@ +/* + * Copyright 2014 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.util; + +import java.util.Objects; + +import static com.google.common.base.MoreObjects.toStringHelper; +import static com.google.common.base.Preconditions.checkArgument; + +/** + * Counting mechanism capable of tracking occurrences and rates. + */ +public class Counter { + + private long total = 0; + private long start = System.currentTimeMillis(); + private long end = 0; + + /** + * Creates a new counter. + */ + public Counter() { + } + + /** + * Creates a new counter in a specific state. If non-zero end time is + * specified, the counter will be frozen. + * + * @param start start time + * @param total total number of items to start with + * @param end end time; if non-ze + */ + public Counter(long start, long total, long end) { + checkArgument(start <= end, "Malformed interval: start > end"); + checkArgument(total >= 0, "Total must be non-negative"); + this.start = start; + this.total = total; + this.end = end; + } + + /** + * Resets the counter, by zeroing out the count and restarting the timer. + */ + public synchronized void reset() { + end = 0; + total = 0; + start = System.currentTimeMillis(); + } + + /** + * Freezes the counter in the current state including the counts and times. + */ + public synchronized void freeze() { + end = System.currentTimeMillis(); + } + + /** + * Adds the specified number of occurrences to the counter. No-op if the + * counter has been frozen. + * + * @param count number of occurrences + */ + public synchronized void add(long count) { + checkArgument(count >= 0, "Count must be non-negative"); + if (end == 0L) { + total += count; + } + } + + /** + * Returns the number of occurrences per second. + * + * @return throughput in occurrences per second + */ + public synchronized double throughput() { + return total / duration(); + } + + /** + * Returns the total number of occurrences counted. + * + * @return number of counted occurrences + */ + public synchronized long total() { + return total; + } + + /** + * Returns the duration expressed in fractional number of seconds. + * + * @return fractional number of seconds since the last reset + */ + public synchronized double duration() { + // Protect against 0 return by artificially setting duration to 1ms + long duration = (end == 0L ? System.currentTimeMillis() : end) - start; + return (duration == 0 ? 1 : duration) / 1000.0; + } + + @Override + public int hashCode() { + return Objects.hash(total, start, end); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj instanceof Counter) { + final Counter other = (Counter) obj; + return Objects.equals(this.total, other.total) && + Objects.equals(this.start, other.start) && + Objects.equals(this.end, other.end); + } + return false; + } + + @Override + public String toString() { + return toStringHelper(this) + .add("total", total) + .add("start", start) + .add("end", end) + .toString(); + } +} diff --git a/framework/src/onos/utils/misc/src/main/java/org/onlab/util/Frequency.java b/framework/src/onos/utils/misc/src/main/java/org/onlab/util/Frequency.java new file mode 100644 index 00000000..5669abdc --- /dev/null +++ b/framework/src/onos/utils/misc/src/main/java/org/onlab/util/Frequency.java @@ -0,0 +1,183 @@ +/* + * 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.util; + +import com.google.common.base.MoreObjects; +import com.google.common.collect.ComparisonChain; + +import java.util.Objects; + +/** + * Class representing frequency. This class is intended to be used for a value whose unit is Hz + * and its family (KHz, MHz, etc.). + * + * <p> + * Note: this class is mainly intended to be used for lambda, which + * represents THz order. Long has enough space to represent over THz frequency as Hz, + * and the underlying value is long as Hz. This means this class can't represent + * sub-Hz accuracy. + * </p> + */ +public final class Frequency implements RichComparable<Frequency> { + + private static final long KHZ = 1_000L; + private static final long MHZ = 1_000_000L; + private static final long GHZ = 1_000_000_000L; + private static final long THZ = 1_000_000_000_000L; + + private final long frequency; // frequency in Hz + + /** + * Creates an instance representing the specified frequency in Hz. + * + * @param frequency frequency in Hz + */ + private Frequency(long frequency) { + this.frequency = frequency; + } + + /** + * Return the value this instance represents as Hz. + * + * @return frequency in Hz + */ + public long asHz() { + return frequency; + } + + /** + * Returns an instance representing the specified value in Hz. + * + * @param value frequency in Hz + * @return instance representing the given frequency + */ + public static Frequency ofHz(long value) { + return new Frequency(value); + } + + /** + * Returns an instance representing the specified value in KHz. + * + * @param value frequency in KHz + * @return instance representing the given frequency + */ + public static Frequency ofKHz(double value) { + return new Frequency((long) (value * KHZ)); + } + + /** + * Returns an instance representing the specified value in MHz. + * + * @param value frequency in MHz + * @return instance representing the given frequency + */ + public static Frequency ofMHz(double value) { + return new Frequency((long) (value * MHZ)); + } + + /** + * Returns an instance representing the specified value in GHz. + * + * @param value frequency in GHz + * @return instance representing the given frequency + */ + public static Frequency ofGHz(double value) { + return new Frequency((long) (value * GHZ)); + } + + /** + * Returns an instance representing the specified value in THz. + * + * @param value frequency in THz + * @return instance representing the given frequency + */ + public static Frequency ofTHz(double value) { + return new Frequency((long) (value * THZ)); + } + + /** + * Returns a Frequency whose value is (this + value). + * + * @param value value to be added to this Frequency + * @return this + value + */ + public Frequency add(Frequency value) { + return new Frequency(this.frequency + value.frequency); + } + + /** + * Returns a Frequency whose value is (this - value). + * + * @param value value to be subtracted from this Frequency + * @return this - value + */ + public Frequency subtract(Frequency value) { + return new Frequency(this.frequency - value.frequency); + } + + /** + * Returns a Frequency whose value is (this * value). + * + * @param value value to be multiplied by this Frequency + * @return this * value + */ + public Frequency multiply(long value) { + return new Frequency(this.frequency * value); + } + + /** + * Returns a Frequency whose value is Math.floorDiv(this, value). + * + * @param value value to be divided by this Frequency + * @return Math.floorDiv(this, value) + */ + public Frequency floorDivision(long value) { + return new Frequency(Math.floorDiv(this.frequency, value)); + } + + @Override + public int compareTo(Frequency other) { + return ComparisonChain.start() + .compare(this.frequency, other.frequency) + .result(); + } + + @Override + public int hashCode() { + return Objects.hash(frequency); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + + if (!(obj instanceof Frequency)) { + return false; + } + + final Frequency other = (Frequency) obj; + return this.frequency == other.frequency; + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("frequency", frequency + "Hz") + .toString(); + } +} diff --git a/framework/src/onos/utils/misc/src/main/java/org/onlab/util/GroupedThreadFactory.java b/framework/src/onos/utils/misc/src/main/java/org/onlab/util/GroupedThreadFactory.java new file mode 100644 index 00000000..9001cf5f --- /dev/null +++ b/framework/src/onos/utils/misc/src/main/java/org/onlab/util/GroupedThreadFactory.java @@ -0,0 +1,88 @@ +/* + * 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.util; + +import org.apache.commons.lang3.concurrent.ConcurrentUtils; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ThreadFactory; + +import static com.google.common.base.MoreObjects.toStringHelper; + +/** + * Thread factory for creating threads that belong to the specified thread group. + */ +public final class GroupedThreadFactory implements ThreadFactory { + + public static final String DELIMITER = "/"; + + private final ThreadGroup group; + + // Cache of created thread factories. + private static final ConcurrentHashMap<String, GroupedThreadFactory> FACTORIES = + new ConcurrentHashMap<>(); + + /** + * Returns thread factory for producing threads associated with the specified + * group name. The group name-space is hierarchical, based on slash-delimited + * name segments, e.g. {@code onos/intent}. + * + * @param groupName group name + * @return thread factory + */ + public static GroupedThreadFactory groupedThreadFactory(String groupName) { + GroupedThreadFactory factory = FACTORIES.get(groupName); + if (factory != null) { + return factory; + } + + // Find the parent group or root the group hierarchy under default group. + int i = groupName.lastIndexOf(DELIMITER); + if (i > 0) { + String name = groupName.substring(0, i); + ThreadGroup parentGroup = groupedThreadFactory(name).threadGroup(); + factory = new GroupedThreadFactory(new ThreadGroup(parentGroup, groupName)); + } else { + factory = new GroupedThreadFactory(new ThreadGroup(groupName)); + } + + return ConcurrentUtils.putIfAbsent(FACTORIES, groupName, factory); + } + + // Creates a new thread group + private GroupedThreadFactory(ThreadGroup group) { + this.group = group; + } + + /** + * Returns the thread group associated with the factory. + * + * @return thread group + */ + public ThreadGroup threadGroup() { + return group; + } + + @Override + public Thread newThread(Runnable r) { + return new Thread(group, r); + } + + @Override + public String toString() { + return toStringHelper(this).add("group", group).toString(); + } +} diff --git a/framework/src/onos/utils/misc/src/main/java/org/onlab/util/HexString.java b/framework/src/onos/utils/misc/src/main/java/org/onlab/util/HexString.java new file mode 100644 index 00000000..a1aba93b --- /dev/null +++ b/framework/src/onos/utils/misc/src/main/java/org/onlab/util/HexString.java @@ -0,0 +1,112 @@ +/* + * Copyright 2014 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.util; + +public final class HexString { + + private HexString() { + + } + + /** + * Convert a string of bytes to a ':' separated hex string. + * + * @param bytes string of bytes to convert + * @return "0f:ca:fe:de:ad:be:ef" + */ + public static String toHexString(final byte[] bytes) { + if (bytes == null) { + return "(null)"; + } + int i; + StringBuilder ret = new StringBuilder(bytes.length * 3 - 1); + String tmp; + for (i = 0; i < bytes.length; i++) { + if (i > 0) { + ret.append(':'); + } + tmp = Integer.toHexString((bytes[i] & 0xff)); + if (tmp.length() == 1) { + ret.append('0'); + } + ret.append(tmp); + } + return ret.toString(); + } + + public static String toHexString(final long val, final int padTo) { + char[] arr = Long.toHexString(val).toCharArray(); + StringBuilder ret = new StringBuilder(padTo * 3 - 1); + // prepend the right number of leading zeros + int i = 0; + for (; i < (padTo * 2 - arr.length); i++) { + ret.append('0'); + if ((i % 2) != 0) { + ret.append(':'); + } + } + for (int j = 0; j < arr.length; j++) { + ret.append(arr[j]); + if ((((i + j) % 2) != 0) && (j < (arr.length - 1))) { + ret.append(':'); + } + } + return ret.toString(); + } + + public static String toHexString(final long val) { + return toHexString(val, 8); + } + + /** + * Convert a string of hex values into a string of bytes. + * + * @param values + * "0f:ca:fe:de:ad:be:ef" + * @return [15, 5 ,2, 5, 17] + * @throws NumberFormatException + * If the string can not be parsed + */ + public static byte[] fromHexString(final String values) { + String[] octets = values.split(":"); + byte[] ret = new byte[octets.length]; + + for (int i = 0; i < octets.length; i++) { + if (octets[i].length() > 2) { + throw new NumberFormatException("Invalid octet length"); + } + ret[i] = Integer.valueOf(octets[i], 16).byteValue(); + } + return ret; + } + + public static long toLong(String value) { + String[] octets = value.split(":"); + if (octets.length > 8) { + throw new NumberFormatException("Input string is too big to fit in long: " + value); + } + long l = 0; + for (String octet: octets) { + if (octet.length() > 2) { + throw new NumberFormatException( + "Each colon-separated byte component must consist of 1 or 2 hex digits: " + value); + } + short s = Short.parseShort(octet, 16); + l = (l << 8) + s; + } + return l; + } +} diff --git a/framework/src/onos/utils/misc/src/main/java/org/onlab/util/ItemNotFoundException.java b/framework/src/onos/utils/misc/src/main/java/org/onlab/util/ItemNotFoundException.java new file mode 100644 index 00000000..01440abf --- /dev/null +++ b/framework/src/onos/utils/misc/src/main/java/org/onlab/util/ItemNotFoundException.java @@ -0,0 +1,46 @@ +/* + * Copyright 2014 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.util; + +/** + * Represents condition where an item is not found or not available. + */ +public class ItemNotFoundException extends RuntimeException { + + /** + * Creates a new exception with no message. + */ + public ItemNotFoundException() { + } + + /** + * Creates a new exception with the supplied message. + * @param message error message + */ + public ItemNotFoundException(String message) { + super(message); + } + + /** + * Creates a new exception with the supplied message and cause. + * @param message error message + * @param cause cause of the error + */ + public ItemNotFoundException(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/framework/src/onos/utils/misc/src/main/java/org/onlab/util/KryoNamespace.java b/framework/src/onos/utils/misc/src/main/java/org/onlab/util/KryoNamespace.java new file mode 100644 index 00000000..9977e35d --- /dev/null +++ b/framework/src/onos/utils/misc/src/main/java/org/onlab/util/KryoNamespace.java @@ -0,0 +1,437 @@ +/* + * Copyright 2014-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.util; + +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.lang3.tuple.Pair; + +import com.esotericsoftware.kryo.Kryo; +import com.esotericsoftware.kryo.Serializer; +import com.esotericsoftware.kryo.io.ByteBufferInput; +import com.esotericsoftware.kryo.io.ByteBufferOutput; +import com.esotericsoftware.kryo.io.Input; +import com.esotericsoftware.kryo.pool.KryoCallback; +import com.esotericsoftware.kryo.pool.KryoFactory; +import com.esotericsoftware.kryo.pool.KryoPool; +import com.google.common.base.MoreObjects; +import com.google.common.collect.ImmutableList; + +/** + * Pool of Kryo instances, with classes pre-registered. + */ +//@ThreadSafe +public final class KryoNamespace implements KryoFactory, KryoPool { + + /** + * Default buffer size used for serialization. + * + * @see #serialize(Object) + */ + public static final int DEFAULT_BUFFER_SIZE = 4096; + public static final int MAX_BUFFER_SIZE = 100 * 1000 * 1000; + + /** + * ID to use if this KryoNamespace does not define registration id. + */ + public static final int FLOATING_ID = -1; + + /** + * Smallest ID free to use for user defined registrations. + */ + public static final int INITIAL_ID = 11; + + + private final KryoPool pool = new KryoPool.Builder(this) + .softReferences() + .build(); + + private final ImmutableList<RegistrationBlock> registeredBlocks; + + private final boolean registrationRequired; + + + /** + * KryoNamespace builder. + */ + //@NotThreadSafe + public static final class Builder { + + private int blockHeadId = INITIAL_ID; + private List<Pair<Class<?>, Serializer<?>>> types = new ArrayList<>(); + private List<RegistrationBlock> blocks = new ArrayList<>(); + private boolean registrationRequired = true; + + /** + * Builds a {@link KryoNamespace} instance. + * + * @return KryoNamespace + */ + public KryoNamespace build() { + if (!types.isEmpty()) { + blocks.add(new RegistrationBlock(this.blockHeadId, types)); + } + return new KryoNamespace(blocks, registrationRequired).populate(1); + } + + /** + * Sets the next Kryo registration Id for following register entries. + * + * @param id Kryo registration Id + * @return this + * + * @see Kryo#register(Class, Serializer, int) + */ + public Builder nextId(final int id) { + if (!types.isEmpty()) { + blocks.add(new RegistrationBlock(this.blockHeadId, types)); + types = new ArrayList<>(); + } + this.blockHeadId = id; + return this; + } + + /** + * Registers classes to be serialized using Kryo default serializer. + * + * @param expectedTypes list of classes + * @return this + */ + public Builder register(final Class<?>... expectedTypes) { + for (Class<?> clazz : expectedTypes) { + types.add(Pair.of(clazz, null)); + } + return this; + } + + /** + * Registers a class and it's serializer. + * + * @param classes list of classes to register + * @param serializer serializer to use for the class + * @return this + */ + public Builder register(Serializer<?> serializer, final Class<?>... classes) { + for (Class<?> clazz : classes) { + types.add(Pair.of(clazz, serializer)); + } + return this; + } + + private Builder register(RegistrationBlock block) { + if (block.begin() != FLOATING_ID) { + // flush pending types + nextId(block.begin()); + blocks.add(block); + nextId(block.begin() + block.types().size()); + } else { + // flush pending types + final int addedBlockBegin = blockHeadId + types.size(); + nextId(addedBlockBegin); + blocks.add(new RegistrationBlock(addedBlockBegin, block.types())); + nextId(addedBlockBegin + block.types().size()); + } + return this; + } + + /** + * Registers all the class registered to given KryoNamespace. + * + * @param ns KryoNamespace + * @return this + */ + public Builder register(final KryoNamespace ns) { + for (RegistrationBlock block : ns.registeredBlocks) { + this.register(block); + } + return this; + } + + /** + * Sets the registrationRequired flag. + * + * @param registrationRequired Kryo's registrationRequired flag + * @return this + * + * @see Kryo#setRegistrationRequired(boolean) + */ + public Builder setRegistrationRequired(boolean registrationRequired) { + this.registrationRequired = registrationRequired; + return this; + } + } + + /** + * Creates a new {@link KryoNamespace} builder. + * + * @return builder + */ + public static Builder newBuilder() { + return new Builder(); + } + + /** + * Creates a Kryo instance pool. + * + * @param registeredTypes types to register + * @param registrationRequired + */ + private KryoNamespace(final List<RegistrationBlock> registeredTypes, boolean registrationRequired) { + this.registeredBlocks = ImmutableList.copyOf(registeredTypes); + this.registrationRequired = registrationRequired; + } + + /** + * Populates the Kryo pool. + * + * @param instances to add to the pool + * @return this + */ + public KryoNamespace populate(int instances) { + + for (int i = 0; i < instances; ++i) { + release(create()); + } + return this; + } + + /** + * Serializes given object to byte array using Kryo instance in pool. + * <p> + * Note: Serialized bytes must be smaller than {@link #MAX_BUFFER_SIZE}. + * + * @param obj Object to serialize + * @return serialized bytes + */ + public byte[] serialize(final Object obj) { + return serialize(obj, DEFAULT_BUFFER_SIZE); + } + + /** + * Serializes given object to byte array using Kryo instance in pool. + * + * @param obj Object to serialize + * @param bufferSize maximum size of serialized bytes + * @return serialized bytes + */ + public byte[] serialize(final Object obj, final int bufferSize) { + ByteBufferOutput out = new ByteBufferOutput(bufferSize, MAX_BUFFER_SIZE); + try { + Kryo kryo = borrow(); + try { + kryo.writeClassAndObject(out, obj); + out.flush(); + return out.toBytes(); + } finally { + release(kryo); + } + } finally { + out.release(); + } + } + + /** + * Serializes given object to byte buffer using Kryo instance in pool. + * + * @param obj Object to serialize + * @param buffer to write to + */ + public void serialize(final Object obj, final ByteBuffer buffer) { + ByteBufferOutput out = new ByteBufferOutput(buffer); + Kryo kryo = borrow(); + try { + kryo.writeClassAndObject(out, obj); + out.flush(); + } finally { + release(kryo); + } + } + + /** + * Serializes given object to OutputStream using Kryo instance in pool. + * + * @param obj Object to serialize + * @param stream to write to + */ + public void serialize(final Object obj, final OutputStream stream) { + serialize(obj, stream, DEFAULT_BUFFER_SIZE); + } + + /** + * Serializes given object to OutputStream using Kryo instance in pool. + * + * @param obj Object to serialize + * @param stream to write to + * @param bufferSize size of the buffer in front of the stream + */ + public void serialize(final Object obj, final OutputStream stream, final int bufferSize) { + ByteBufferOutput out = new ByteBufferOutput(stream, bufferSize); + Kryo kryo = borrow(); + try { + kryo.writeClassAndObject(out, obj); + out.flush(); + } finally { + release(kryo); + } + } + + /** + * Deserializes given byte array to Object using Kryo instance in pool. + * + * @param bytes serialized bytes + * @param <T> deserialized Object type + * @return deserialized Object + */ + public <T> T deserialize(final byte[] bytes) { + Input in = new Input(bytes); + Kryo kryo = borrow(); + try { + @SuppressWarnings("unchecked") + T obj = (T) kryo.readClassAndObject(in); + return obj; + } finally { + release(kryo); + } + } + + /** + * Deserializes given byte buffer to Object using Kryo instance in pool. + * + * @param buffer input with serialized bytes + * @param <T> deserialized Object type + * @return deserialized Object + */ + public <T> T deserialize(final ByteBuffer buffer) { + ByteBufferInput in = new ByteBufferInput(buffer); + Kryo kryo = borrow(); + try { + @SuppressWarnings("unchecked") + T obj = (T) kryo.readClassAndObject(in); + return obj; + } finally { + release(kryo); + } + } + + /** + * Deserializes given InputStream to an Object using Kryo instance in pool. + * + * @param stream input stream + * @param <T> deserialized Object type + * @return deserialized Object + */ + public <T> T deserialize(final InputStream stream) { + return deserialize(stream, DEFAULT_BUFFER_SIZE); + } + + /** + * Deserializes given InputStream to an Object using Kryo instance in pool. + * + * @param stream input stream + * @param <T> deserialized Object type + * @return deserialized Object + * @param bufferSize size of the buffer in front of the stream + */ + public <T> T deserialize(final InputStream stream, final int bufferSize) { + ByteBufferInput in = new ByteBufferInput(stream, bufferSize); + Kryo kryo = borrow(); + try { + @SuppressWarnings("unchecked") + T obj = (T) kryo.readClassAndObject(in); + return obj; + } finally { + release(kryo); + } + } + + /** + * Creates a Kryo instance. + * + * @return Kryo instance + */ + @Override + public Kryo create() { + Kryo kryo = new Kryo(); + kryo.setRegistrationRequired(registrationRequired); + for (RegistrationBlock block : registeredBlocks) { + int id = block.begin(); + if (id == FLOATING_ID) { + id = kryo.getNextRegistrationId(); + } + for (Pair<Class<?>, Serializer<?>> entry : block.types()) { + final Serializer<?> serializer = entry.getRight(); + if (serializer == null) { + kryo.register(entry.getLeft(), id++); + } else { + kryo.register(entry.getLeft(), serializer, id++); + } + } + } + return kryo; + } + + @Override + public Kryo borrow() { + return pool.borrow(); + } + + @Override + public void release(Kryo kryo) { + pool.release(kryo); + } + + @Override + public <T> T run(KryoCallback<T> callback) { + return pool.run(callback); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(getClass()) + .add("registeredBlocks", registeredBlocks) + .toString(); + } + + static final class RegistrationBlock { + private final int begin; + private final ImmutableList<Pair<Class<?>, Serializer<?>>> types; + + public RegistrationBlock(int begin, List<Pair<Class<?>, Serializer<?>>> types) { + this.begin = begin; + this.types = ImmutableList.copyOf(types); + } + + public int begin() { + return begin; + } + + public ImmutableList<Pair<Class<?>, Serializer<?>>> types() { + return types; + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(getClass()) + .add("begin", begin) + .add("types", types) + .toString(); + } + } +} diff --git a/framework/src/onos/utils/misc/src/main/java/org/onlab/util/NewConcurrentHashMap.java b/framework/src/onos/utils/misc/src/main/java/org/onlab/util/NewConcurrentHashMap.java new file mode 100644 index 00000000..2d222eac --- /dev/null +++ b/framework/src/onos/utils/misc/src/main/java/org/onlab/util/NewConcurrentHashMap.java @@ -0,0 +1,47 @@ +/* + * Copyright 2014 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.util; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +import org.apache.commons.lang3.concurrent.ConcurrentInitializer; + +/** + * Creates an instance of new ConcurrentHashMap on each {@link #get()} call. + * <p> + * To be used with + * {@link org.apache.commons.lang3.concurrent.ConcurrentUtils#createIfAbsent} + * </p> + * + * @param <K> ConcurrentHashMap key type + * @param <V> ConcurrentHashMap value type + */ +public final class NewConcurrentHashMap<K, V> + implements ConcurrentInitializer<ConcurrentMap<K, V>> { + + public static final NewConcurrentHashMap<?, ?> INSTANCE = new NewConcurrentHashMap<>(); + + @SuppressWarnings("unchecked") + public static <K, V> NewConcurrentHashMap<K, V> ifNeeded() { + return (NewConcurrentHashMap<K, V>) INSTANCE; + } + + @Override + public ConcurrentMap<K, V> get() { + return new ConcurrentHashMap<>(); + } +} diff --git a/framework/src/onos/utils/misc/src/main/java/org/onlab/util/PositionalParameterStringFormatter.java b/framework/src/onos/utils/misc/src/main/java/org/onlab/util/PositionalParameterStringFormatter.java new file mode 100644 index 00000000..647e0c0d --- /dev/null +++ b/framework/src/onos/utils/misc/src/main/java/org/onlab/util/PositionalParameterStringFormatter.java @@ -0,0 +1,48 @@ +/* + * 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.util; + +/** + * Allows slf4j style formatting of parameters into a string. + */ +public final class PositionalParameterStringFormatter { + + /** + * Hide default constructor. + */ + private PositionalParameterStringFormatter() { + } + + /** + * Formats a string using slf4j style positional parameter replacement. + * Instances of "{}" in the source string are replaced in order by the + * specified parameter values as strings. + * + * @param source original string to format + * @param parameters list of parameters that will be substituted + * @return formatted string + */ + public static String format(String source, Object... parameters) { + String current = source; + for (Object parameter : parameters) { + if (!current.contains("{}")) { + return current; + } + current = current.replaceFirst("\\{\\}", String.valueOf(parameter)); + } + return current; + } +} diff --git a/framework/src/onos/utils/misc/src/main/java/org/onlab/util/RetryingFunction.java b/framework/src/onos/utils/misc/src/main/java/org/onlab/util/RetryingFunction.java new file mode 100644 index 00000000..484e236f --- /dev/null +++ b/framework/src/onos/utils/misc/src/main/java/org/onlab/util/RetryingFunction.java @@ -0,0 +1,60 @@ +/* + * 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.util; + +import java.util.function.Function; + +import com.google.common.base.Throwables; + +/** + * Function that retries execution on failure. + * + * @param <U> input type + * @param <V> output type + */ +public class RetryingFunction<U, V> implements Function<U, V> { + + private final Function<U, V> baseFunction; + private final Class<? extends Throwable> exceptionClass; + private final int maxRetries; + private final int maxDelayBetweenRetries; + + public RetryingFunction(Function<U, V> baseFunction, + Class<? extends Throwable> exceptionClass, + int maxRetries, + int maxDelayBetweenRetries) { + this.baseFunction = baseFunction; + this.exceptionClass = exceptionClass; + this.maxRetries = maxRetries; + this.maxDelayBetweenRetries = maxDelayBetweenRetries; + } + + @Override + public V apply(U input) { + int retryAttempts = 0; + while (true) { + try { + return baseFunction.apply(input); + } catch (Throwable t) { + if (!exceptionClass.isAssignableFrom(t.getClass()) || retryAttempts == maxRetries) { + Throwables.propagate(t); + } + Tools.randomDelay(maxDelayBetweenRetries); + retryAttempts++; + } + } + } +} diff --git a/framework/src/onos/utils/misc/src/main/java/org/onlab/util/RichComparable.java b/framework/src/onos/utils/misc/src/main/java/org/onlab/util/RichComparable.java new file mode 100644 index 00000000..8a49bba9 --- /dev/null +++ b/framework/src/onos/utils/misc/src/main/java/org/onlab/util/RichComparable.java @@ -0,0 +1,45 @@ +/* + * 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.util; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Extends useful methods for comparison to {@link Comparable} interface. + * + * @param <T> type of instance to be compared + */ +public interface RichComparable<T> extends Comparable<T> { + /** + * Compares if this object is less than the specified object. + * + * @param other the object to be compared + * @return true if this object is less than the specified object + */ + default boolean isLessThan(T other) { + return compareTo(checkNotNull(other)) < 0; + } + + /** + * Compares if this object is greater than the specified object. + * + * @param other the object to be compared + * @return true if this object is less thant the specified object + */ + default boolean isGreaterThan(T other) { + return compareTo(checkNotNull(other)) > 0; + } +} diff --git a/framework/src/onos/utils/misc/src/main/java/org/onlab/util/SharedExecutorService.java b/framework/src/onos/utils/misc/src/main/java/org/onlab/util/SharedExecutorService.java new file mode 100644 index 00000000..051155ce --- /dev/null +++ b/framework/src/onos/utils/misc/src/main/java/org/onlab/util/SharedExecutorService.java @@ -0,0 +1,138 @@ +/* + * 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.util; + +import java.util.Collection; +import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +/** + * Executor service wrapper for shared executors with safeguards on shutdown + * to prevent inadvertent shutdown. + */ +class SharedExecutorService implements ExecutorService { + + private static final String NOT_ALLOWED = "Shutdown of shared executor is not allowed"; + + private ExecutorService executor; + + /** + * Creates a wrapper for the given executor service. + * + * @param executor executor service to wrap + */ + SharedExecutorService(ExecutorService executor) { + this.executor = executor; + } + + /** + * Returns the backing executor service. + * + * @return backing executor service + */ + ExecutorService backingExecutor() { + return executor; + } + + /** + * Swaps the backing executor with a new one and shuts down the old one. + * + * @param executor new executor service + */ + void setBackingExecutor(ExecutorService executor) { + ExecutorService oldExecutor = this.executor; + this.executor = executor; + oldExecutor.shutdown(); + } + + @Override + public void shutdown() { + throw new UnsupportedOperationException(NOT_ALLOWED); + } + + @Override + public List<Runnable> shutdownNow() { + throw new UnsupportedOperationException(NOT_ALLOWED); + } + + @Override + public boolean isShutdown() { + return executor.isShutdown(); + } + + @Override + public boolean isTerminated() { + return executor.isTerminated(); + } + + @Override + public boolean awaitTermination(long timeout, TimeUnit unit) + throws InterruptedException { + return executor.awaitTermination(timeout, unit); + } + + @Override + public <T> Future<T> submit(Callable<T> task) { + return executor.submit(task); + } + + @Override + public <T> Future<T> submit(Runnable task, T result) { + return executor.submit(task, result); + } + + @Override + public Future<?> submit(Runnable task) { + return executor.submit(task); + } + + @Override + public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) + throws InterruptedException { + return executor.invokeAll(tasks); + } + + @Override + public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, + long timeout, TimeUnit unit) + throws InterruptedException { + return executor.invokeAll(tasks, timeout, unit); + } + + @Override + public <T> T invokeAny(Collection<? extends Callable<T>> tasks) + throws InterruptedException, ExecutionException { + return executor.invokeAny(tasks); + } + + @Override + public <T> T invokeAny(Collection<? extends Callable<T>> tasks, + long timeout, TimeUnit unit) + throws InterruptedException, ExecutionException, TimeoutException { + return executor.invokeAny(tasks, timeout, unit); + } + + @Override + public void execute(Runnable command) { + executor.execute(command); + } + +} diff --git a/framework/src/onos/utils/misc/src/main/java/org/onlab/util/SharedExecutors.java b/framework/src/onos/utils/misc/src/main/java/org/onlab/util/SharedExecutors.java new file mode 100644 index 00000000..0dadce85 --- /dev/null +++ b/framework/src/onos/utils/misc/src/main/java/org/onlab/util/SharedExecutors.java @@ -0,0 +1,123 @@ +/* + * 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.util; + +import java.util.Timer; +import java.util.concurrent.ExecutorService; + +import static com.google.common.base.Preconditions.checkArgument; +import static java.util.concurrent.Executors.newFixedThreadPool; +import static java.util.concurrent.Executors.newSingleThreadExecutor; +import static org.onlab.util.Tools.groupedThreads; + +/** + * Utility for managing a set of shared execution resources, such as a timer, + * single thread executor and thread pool executor for use by various parts of + * the platform or by applications. + * <p> + * Whenever possible, use of these shared resources is encouraged over creating + * separate ones. + * </p> + */ +public final class SharedExecutors { + + public static final int DEFAULT_POOL_SIZE = 30; + + private static SharedExecutorService singleThreadExecutor = + new SharedExecutorService( + newSingleThreadExecutor(groupedThreads("onos/shared", + "onos-single-executor"))); + + private static SharedExecutorService poolThreadExecutor = + new SharedExecutorService( + newFixedThreadPool(DEFAULT_POOL_SIZE, + groupedThreads("onos/shared", + "onos-pool-executor-%d"))); + + private static SharedTimer sharedTimer = new SharedTimer(); + + // Ban public construction + private SharedExecutors() { + } + + /** + * Returns the shared single thread executor. + * + * @return shared single thread executor + */ + public static ExecutorService getSingleThreadExecutor() { + return singleThreadExecutor; + } + + /** + * Returns the shared thread pool executor. + * + * @return shared executor pool + */ + public static ExecutorService getPoolThreadExecutor() { + return poolThreadExecutor; + } + + /** + * Returns the shared timer. + * + * @return shared timer + */ + public static Timer getTimer() { + return sharedTimer; + } + + /** + * Sets the shared thread pool size. + * + * @param poolSize new pool size + */ + public static void setPoolSize(int poolSize) { + checkArgument(poolSize > 0, "Shared pool size size must be greater than 0"); + poolThreadExecutor.setBackingExecutor( + newFixedThreadPool(poolSize, groupedThreads("onos/shared", + "onos-pool-executor-%d"))); + } + + /** + * Shuts down all shared timers and executors and therefore should be + * called only by the framework. + */ + public static void shutdown() { + sharedTimer.shutdown(); + singleThreadExecutor.backingExecutor().shutdown(); + poolThreadExecutor.backingExecutor().shutdown(); + } + + // Timer extension which does not allow outside cancel method. + private static class SharedTimer extends Timer { + + public SharedTimer() { + super("onos-shared-timer"); + } + + @Override + public void cancel() { + throw new UnsupportedOperationException("Cancel of shared timer is not allowed"); + } + + private void shutdown() { + super.cancel(); + } + } + +} diff --git a/framework/src/onos/utils/misc/src/main/java/org/onlab/util/SlidingWindowCounter.java b/framework/src/onos/utils/misc/src/main/java/org/onlab/util/SlidingWindowCounter.java new file mode 100644 index 00000000..4f0093c0 --- /dev/null +++ b/framework/src/onos/utils/misc/src/main/java/org/onlab/util/SlidingWindowCounter.java @@ -0,0 +1,129 @@ +/* + * 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.util; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicLong; +import java.util.stream.Collectors; + +import static com.google.common.base.Preconditions.checkArgument; + +/** + * Maintains a sliding window of value counts. The sliding window counter is + * initialized with a number of window slots. Calls to #incrementCount() will + * increment the value in the current window slot. Periodically the window + * slides and the oldest value count is dropped. Calls to #get() will get the + * total count for the last N window slots. + */ +public final class SlidingWindowCounter { + + private volatile int headSlot; + private final int windowSlots; + + private final List<AtomicLong> counters; + + private final ScheduledExecutorService background; + + private static final int SLIDE_WINDOW_PERIOD_SECONDS = 1; + + /** + * Creates a new sliding window counter with the given total number of + * window slots. + * + * @param windowSlots total number of window slots + */ + public SlidingWindowCounter(int windowSlots) { + checkArgument(windowSlots > 0, "Window size must be a positive integer"); + + this.windowSlots = windowSlots; + this.headSlot = 0; + + // Initialize each item in the list to an AtomicLong of 0 + this.counters = Collections.nCopies(windowSlots, 0) + .stream() + .map(AtomicLong::new) + .collect(Collectors.toCollection(ArrayList::new)); + + background = Executors.newSingleThreadScheduledExecutor(); + background.scheduleWithFixedDelay(this::advanceHead, 0, + SLIDE_WINDOW_PERIOD_SECONDS, TimeUnit.SECONDS); + } + + /** + * Releases resources used by the SlidingWindowCounter. + */ + public void destroy() { + background.shutdownNow(); + } + + /** + * Increments the count of the current window slot by 1. + */ + public void incrementCount() { + incrementCount(headSlot, 1); + } + + /** + * Increments the count of the current window slot by the given value. + * + * @param value value to increment by + */ + public void incrementCount(long value) { + incrementCount(headSlot, value); + } + + private void incrementCount(int slot, long value) { + counters.get(slot).addAndGet(value); + } + + /** + * Gets the total count for the last N window slots. + * + * @param slots number of slots to include in the count + * @return total count for last N slots + */ + public long get(int slots) { + checkArgument(slots <= windowSlots, + "Requested window must be less than the total window slots"); + + long sum = 0; + + for (int i = 0; i < slots; i++) { + int currentIndex = headSlot - i; + if (currentIndex < 0) { + currentIndex = counters.size() + currentIndex; + } + sum += counters.get(currentIndex).get(); + } + + return sum; + } + + void advanceHead() { + counters.get(slotAfter(headSlot)).set(0); + headSlot = slotAfter(headSlot); + } + + private int slotAfter(int slot) { + return (slot + 1) % windowSlots; + } + +} diff --git a/framework/src/onos/utils/misc/src/main/java/org/onlab/util/Spectrum.java b/framework/src/onos/utils/misc/src/main/java/org/onlab/util/Spectrum.java new file mode 100644 index 00000000..42d76090 --- /dev/null +++ b/framework/src/onos/utils/misc/src/main/java/org/onlab/util/Spectrum.java @@ -0,0 +1,51 @@ +/* + * 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.util; + +/** + * Telecom optical wavelength bands: O, E, S, C, L and U bands. + * + * See ITU-T G-Series Recommendations, Supplement 39 + */ +public final class Spectrum { + + private Spectrum() { + } + + // O band (original): 1260 to 1360 nm + public static final Frequency O_BAND_MIN = Frequency.ofTHz(220.436); + public static final Frequency O_BAND_MAX = Frequency.ofTHz(237.931); + + // E band (extended): 1360 to 1460 nm + public static final Frequency E_BAND_MIN = Frequency.ofTHz(205.337); + public static final Frequency E_BAND_MAX = Frequency.ofTHz(220.436); + + // S band (short wavelength): 1460 to 1530 nm + public static final Frequency S_BAND_MIN = Frequency.ofTHz(195.943); + public static final Frequency S_BAND_MAX = Frequency.ofTHz(205.337); + + // C band (conventional): 1530 to 1565 nm + public static final Frequency C_BAND_MIN = Frequency.ofTHz(191.561); + public static final Frequency C_BAND_MAX = Frequency.ofTHz(195.943); + + // L band (long wavelength): 1565 to 1625 nm + public static final Frequency L_BAND_MIN = Frequency.ofTHz(184.488); + public static final Frequency L_BAND_MAX = Frequency.ofTHz(191.561); + + // U band (ultra-long wavelength): 1625 to 1675 nm + public static final Frequency U_BAND_MIN = Frequency.ofTHz(178.981); + public static final Frequency U_BAND_MAX = Frequency.ofTHz(184.488); +} diff --git a/framework/src/onos/utils/misc/src/main/java/org/onlab/util/Timer.java b/framework/src/onos/utils/misc/src/main/java/org/onlab/util/Timer.java new file mode 100644 index 00000000..fe508839 --- /dev/null +++ b/framework/src/onos/utils/misc/src/main/java/org/onlab/util/Timer.java @@ -0,0 +1,52 @@ +/* + * Copyright 2014 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.util; + +import org.jboss.netty.util.HashedWheelTimer; + +/** + * Hashed-wheel timer singleton. Care must be taken to shutdown the timer + * only when the VM is ready to exit. + */ +public final class Timer { + + private static volatile HashedWheelTimer timer; + + // Ban public construction + private Timer() { + } + + /** + * Returns the singleton hashed-wheel timer. + * + * @return hashed-wheel timer + */ + public static HashedWheelTimer getTimer() { + if (Timer.timer == null) { + initTimer(); + } + return Timer.timer; + } + + private static synchronized void initTimer() { + if (Timer.timer == null) { + HashedWheelTimer hwTimer = new HashedWheelTimer(); + hwTimer.start(); + Timer.timer = hwTimer; + } + } + +} diff --git a/framework/src/onos/utils/misc/src/main/java/org/onlab/util/Tools.java b/framework/src/onos/utils/misc/src/main/java/org/onlab/util/Tools.java new file mode 100644 index 00000000..abc48ccf --- /dev/null +++ b/framework/src/onos/utils/misc/src/main/java/org/onlab/util/Tools.java @@ -0,0 +1,557 @@ +/* + * Copyright 2014-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.util; + +import static java.nio.file.Files.delete; +import static java.nio.file.Files.walkFileTree; +import static org.onlab.util.GroupedThreadFactory.groupedThreadFactory; +import static org.slf4j.LoggerFactory.getLogger; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.StandardCopyOption; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Dictionary; +import java.util.List; +import java.util.Random; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; + +import org.slf4j.Logger; + +import com.google.common.base.Strings; +import com.google.common.primitives.UnsignedLongs; +import com.google.common.util.concurrent.ThreadFactoryBuilder; + +/** + * Miscellaneous utility methods. + */ +public abstract class Tools { + + private Tools() { + } + + private static final Logger log = getLogger(Tools.class); + + private static Random random = new Random(); + + /** + * Returns a thread factory that produces threads named according to the + * supplied name pattern. + * + * @param pattern name pattern + * @return thread factory + */ + public static ThreadFactory namedThreads(String pattern) { + return new ThreadFactoryBuilder() + .setNameFormat(pattern) + .setUncaughtExceptionHandler((t, e) -> log.error("Uncaught exception on " + t.getName(), e)) + .build(); + } + + /** + * Returns a thread factory that produces threads named according to the + * supplied name pattern and from the specified thread-group. The thread + * group name is expected to be specified in slash-delimited format, e.g. + * {@code onos/intent}. The thread names will be produced by converting + * the thread group name into dash-delimited format and pre-pended to the + * specified pattern. + * + * @param groupName group name in slash-delimited format to indicate hierarchy + * @param pattern name pattern + * @return thread factory + */ + public static ThreadFactory groupedThreads(String groupName, String pattern) { + return new ThreadFactoryBuilder() + .setThreadFactory(groupedThreadFactory(groupName)) + .setNameFormat(groupName.replace(GroupedThreadFactory.DELIMITER, "-") + "-" + pattern) + .setUncaughtExceptionHandler((t, e) -> log.error("Uncaught exception on " + t.getName(), e)) + .build(); + } + + /** + * Returns a thread factory that produces threads with MIN_PRIORITY. + * + * @param factory backing ThreadFactory + * @return thread factory + */ + public static ThreadFactory minPriority(ThreadFactory factory) { + return new ThreadFactoryBuilder() + .setThreadFactory(factory) + .setPriority(Thread.MIN_PRIORITY) + .build(); + } + + /** + * Returns true if the collection is null or is empty. + * + * @param collection collection to test + * @return true if null or empty; false otherwise + */ + public static boolean isNullOrEmpty(Collection collection) { + return collection == null || collection.isEmpty(); + } + + /** + * Returns the specified item if that item is not null; otherwise throws + * not found exception. + * + * @param item item to check + * @param message not found message + * @param <T> item type + * @return item if not null + * @throws org.onlab.util.ItemNotFoundException if item is null + */ + public static <T> T nullIsNotFound(T item, String message) { + if (item == null) { + throw new ItemNotFoundException(message); + } + return item; + } + + /** + * Returns the specified item if that item is not null; otherwise throws + * bad argument exception. + * + * @param item item to check + * @param message not found message + * @param <T> item type + * @return item if not null + * @throws IllegalArgumentException if item is null + */ + public static <T> T nullIsIllegal(T item, String message) { + if (item == null) { + throw new IllegalArgumentException(message); + } + return item; + } + + /** + * Converts a string from hex to long. + * + * @param string hex number in string form; sans 0x + * @return long value + */ + public static long fromHex(String string) { + return UnsignedLongs.parseUnsignedLong(string, 16); + } + + /** + * Converts a long value to hex string; 16 wide and sans 0x. + * + * @param value long value + * @return hex string + */ + public static String toHex(long value) { + return Strings.padStart(UnsignedLongs.toString(value, 16), 16, '0'); + } + + /** + * Converts a long value to hex string; 16 wide and sans 0x. + * + * @param value long value + * @param width string width; zero padded + * @return hex string + */ + public static String toHex(long value, int width) { + return Strings.padStart(UnsignedLongs.toString(value, 16), width, '0'); + } + + /** + * Returns a copy of the input byte array. + * + * @param original input + * @return copy of original + */ + public static byte[] copyOf(byte[] original) { + return Arrays.copyOf(original, original.length); + } + + /** + * Get property as a string value. + * + * @param properties properties to be looked up + * @param propertyName the name of the property to look up + * @return value when the propertyName is defined or return null + */ + public static String get(Dictionary<?, ?> properties, String propertyName) { + Object v = properties.get(propertyName); + String s = (v instanceof String) ? (String) v : + v != null ? v.toString() : null; + return Strings.isNullOrEmpty(s) ? null : s.trim(); + } + + /** + * Suspends the current thread for a specified number of millis. + * + * @param ms number of millis + */ + public static void delay(int ms) { + try { + Thread.sleep(ms); + } catch (InterruptedException e) { + throw new RuntimeException("Interrupted", e); + } + } + + /** + * Returns a function that retries execution on failure. + * @param base base function + * @param exceptionClass type of exception for which to retry + * @param maxRetries max number of retries before giving up + * @param maxDelayBetweenRetries max delay between successive retries. The actual delay is randomly picked from + * the interval (0, maxDelayBetweenRetries] + * @return function + * @param <U> type of function input + * @param <V> type of function output + */ + public static <U, V> Function<U, V> retryable(Function<U, V> base, + Class<? extends Throwable> exceptionClass, + int maxRetries, + int maxDelayBetweenRetries) { + return new RetryingFunction<>(base, exceptionClass, maxRetries, maxDelayBetweenRetries); + } + + /** + * Returns a Supplier that retries execution on failure. + * @param base base supplier + * @param exceptionClass type of exception for which to retry + * @param maxRetries max number of retries before giving up + * @param maxDelayBetweenRetries max delay between successive retries. The actual delay is randomly picked from + * the interval (0, maxDelayBetweenRetries] + * @return supplier + * @param <V> type of supplied result + */ + public static <V> Supplier<V> retryable(Supplier<V> base, + Class<? extends Throwable> exceptionClass, + int maxRetries, + int maxDelayBetweenRetries) { + return () -> new RetryingFunction<>(v -> base.get(), + exceptionClass, + maxRetries, + maxDelayBetweenRetries).apply(null); + } + + /** + * Suspends the current thread for a random number of millis between 0 and + * the indicated limit. + * + * @param ms max number of millis + */ + public static void randomDelay(int ms) { + try { + Thread.sleep(random.nextInt(ms)); + } catch (InterruptedException e) { + throw new RuntimeException("Interrupted", e); + } + } + + /** + * Suspends the current thread for a specified number of millis and nanos. + * + * @param ms number of millis + * @param nanos number of nanos + */ + public static void delay(int ms, int nanos) { + try { + Thread.sleep(ms, nanos); + } catch (InterruptedException e) { + throw new RuntimeException("Interrupted", e); + } + } + + /** + * Slurps the contents of a file into a list of strings, one per line. + * + * @param path file path + * @return file contents + */ + public static List<String> slurp(File path) { + try { + BufferedReader br = new BufferedReader( + new InputStreamReader(new FileInputStream(path), StandardCharsets.UTF_8)); + + List<String> lines = new ArrayList<>(); + String line; + while ((line = br.readLine()) != null) { + lines.add(line); + } + return lines; + + } catch (IOException e) { + return null; + } + } + + /** + * Purges the specified directory path. Use with great caution since + * no attempt is made to check for symbolic links, which could result in + * deletion of unintended files. + * + * @param path directory to be removed + * @throws java.io.IOException if unable to remove contents + */ + public static void removeDirectory(String path) throws IOException { + DirectoryDeleter visitor = new DirectoryDeleter(); + File dir = new File(path); + if (dir.exists() && dir.isDirectory()) { + walkFileTree(Paths.get(path), visitor); + if (visitor.exception != null) { + throw visitor.exception; + } + } + } + + /** + * Purges the specified directory path. Use with great caution since + * no attempt is made to check for symbolic links, which could result in + * deletion of unintended files. + * + * @param dir directory to be removed + * @throws java.io.IOException if unable to remove contents + */ + public static void removeDirectory(File dir) throws IOException { + DirectoryDeleter visitor = new DirectoryDeleter(); + if (dir.exists() && dir.isDirectory()) { + walkFileTree(Paths.get(dir.getAbsolutePath()), visitor); + if (visitor.exception != null) { + throw visitor.exception; + } + } + } + + // Auxiliary path visitor for recursive directory structure removal. + private static class DirectoryDeleter extends SimpleFileVisitor<Path> { + + private IOException exception; + + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attributes) + throws IOException { + if (attributes.isRegularFile()) { + delete(file); + } + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult postVisitDirectory(Path directory, IOException ioe) + throws IOException { + delete(directory); + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult visitFileFailed(Path file, IOException ioe) + throws IOException { + this.exception = ioe; + return FileVisitResult.TERMINATE; + } + } + + /** + * Returns a human friendly time ago string for a specified system time. + * + * @param unixTime system time in millis + * @return human friendly time ago + */ + public static String timeAgo(long unixTime) { + long deltaMillis = System.currentTimeMillis() - unixTime; + long secondsSince = (long) (deltaMillis / 1000.0); + long minsSince = (long) (deltaMillis / (1000.0 * 60)); + long hoursSince = (long) (deltaMillis / (1000.0 * 60 * 60)); + long daysSince = (long) (deltaMillis / (1000.0 * 60 * 60 * 24)); + if (daysSince > 0) { + return String.format("%dd ago", daysSince); + } else if (hoursSince > 0) { + return String.format("%dh ago", hoursSince); + } else if (minsSince > 0) { + return String.format("%dm ago", minsSince); + } else if (secondsSince > 0) { + return String.format("%ds ago", secondsSince); + } else { + return "just now"; + } + } + + /** + * Copies the specified directory path. Use with great caution since + * no attempt is made to check for symbolic links, which could result in + * copy of unintended files. + * + * @param src directory to be copied + * @param dst destination directory to be removed + * @throws java.io.IOException if unable to remove contents + */ + public static void copyDirectory(String src, String dst) throws IOException { + walkFileTree(Paths.get(src), new DirectoryCopier(src, dst)); + } + + /** + * Copies the specified directory path. Use with great caution since + * no attempt is made to check for symbolic links, which could result in + * copy of unintended files. + * + * @param src directory to be copied + * @param dst destination directory to be removed + * @throws java.io.IOException if unable to remove contents + */ + public static void copyDirectory(File src, File dst) throws IOException { + walkFileTree(Paths.get(src.getAbsolutePath()), + new DirectoryCopier(src.getAbsolutePath(), + dst.getAbsolutePath())); + } + + /** + * Returns the future value when complete or if future + * completes exceptionally returns the defaultValue. + * + * @param future future + * @param defaultValue default value + * @param <T> future value type + * @return future value when complete or if future + * completes exceptionally returns the defaultValue. + */ + public static <T> T futureGetOrElse(Future<T> future, T defaultValue) { + try { + return future.get(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + return defaultValue; + } catch (ExecutionException e) { + return defaultValue; + } + } + + /** + * Returns the future value when complete or if future + * completes exceptionally returns the defaultValue. + * + * @param future future + * @param timeout time to wait for successful completion + * @param timeUnit time unit + * @param defaultValue default value + * @param <T> future value type + * @return future value when complete or if future + * completes exceptionally returns the defaultValue. + */ + public static <T> T futureGetOrElse(Future<T> future, + long timeout, + TimeUnit timeUnit, + T defaultValue) { + try { + return future.get(timeout, timeUnit); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + return defaultValue; + } catch (ExecutionException | TimeoutException e) { + return defaultValue; + } + } + + /** + * Returns a future that is completed exceptionally. + * + * @param t exception + * @param <T> future value type + * @return future + */ + public static <T> CompletableFuture<T> exceptionalFuture(Throwable t) { + CompletableFuture<T> future = new CompletableFuture<>(); + future.completeExceptionally(t); + return future; + } + + /** + * Returns the contents of {@code ByteBuffer} as byte array. + * <p> + * WARNING: There is a performance cost due to array copy + * when using this method. + * + * @param buffer byte buffer + * @return byte array containing the byte buffer contents + */ + public static byte[] byteBuffertoArray(ByteBuffer buffer) { + int length = buffer.remaining(); + if (buffer.hasArray()) { + int offset = buffer.arrayOffset() + buffer.position(); + return Arrays.copyOfRange(buffer.array(), offset, offset + length); + } + byte[] bytes = new byte[length]; + buffer.duplicate().get(bytes); + return bytes; + } + + /** + * Converts an iterable to a stream. + * + * @param it iterable to convert + * @param <T> type if item + * @return iterable as a stream + */ + public static <T> Stream<T> stream(Iterable<T> it) { + return StreamSupport.stream(it.spliterator(), false); + } + + // Auxiliary path visitor for recursive directory structure copying. + private static class DirectoryCopier extends SimpleFileVisitor<Path> { + private Path src; + private Path dst; + private StandardCopyOption copyOption = StandardCopyOption.REPLACE_EXISTING; + + DirectoryCopier(String src, String dst) { + this.src = Paths.get(src); + this.dst = Paths.get(dst); + } + + @Override + public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { + Path targetPath = dst.resolve(src.relativize(dir)); + if (!Files.exists(targetPath)) { + Files.createDirectory(targetPath); + } + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + Files.copy(file, dst.resolve(src.relativize(file)), copyOption); + return FileVisitResult.CONTINUE; + } + } + +} diff --git a/framework/src/onos/utils/misc/src/main/java/org/onlab/util/TriConsumer.java b/framework/src/onos/utils/misc/src/main/java/org/onlab/util/TriConsumer.java new file mode 100644 index 00000000..d1963c46 --- /dev/null +++ b/framework/src/onos/utils/misc/src/main/java/org/onlab/util/TriConsumer.java @@ -0,0 +1,35 @@ +/* + * 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.util; + +/** + * A consumer that accepts three arguments. + * + * @param <U> type of first argument + * @param <V> type of second argument + * @param <W> type of third argument + */ +public interface TriConsumer<U, V, W> { + + /** + * Applies the given arguments to the function. + * @param arg1 first argument + * @param arg2 second argument + * @param arg3 third argument + */ + void accept(U arg1, V arg2, W arg3); + +}
\ No newline at end of file diff --git a/framework/src/onos/utils/misc/src/main/java/org/onlab/util/package-info.java b/framework/src/onos/utils/misc/src/main/java/org/onlab/util/package-info.java new file mode 100644 index 00000000..06cc394f --- /dev/null +++ b/framework/src/onos/utils/misc/src/main/java/org/onlab/util/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2014 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. + */ + +/** + * Miscellaneous domain-agnostic utilities. + */ +package org.onlab.util; diff --git a/framework/src/onos/utils/misc/src/test/java/org/onlab/graph/AbstractEdgeTest.java b/framework/src/onos/utils/misc/src/test/java/org/onlab/graph/AbstractEdgeTest.java new file mode 100644 index 00000000..6e8635dd --- /dev/null +++ b/framework/src/onos/utils/misc/src/test/java/org/onlab/graph/AbstractEdgeTest.java @@ -0,0 +1,37 @@ +/* + * Copyright 2014 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.graph; + +import com.google.common.testing.EqualsTester; +import org.junit.Test; + +/** + * Test of the base edge implementation. + */ +public class AbstractEdgeTest { + + @Test + public void equality() { + TestVertex v1 = new TestVertex("1"); + TestVertex v2 = new TestVertex("2"); + new EqualsTester() + .addEqualityGroup(new TestEdge(v1, v2, 1), + new TestEdge(v1, v2, 1)) + .addEqualityGroup(new TestEdge(v2, v1, 1)) + .testEquals(); + } + +} diff --git a/framework/src/onos/utils/misc/src/test/java/org/onlab/graph/AbstractGraphPathSearchTest.java b/framework/src/onos/utils/misc/src/test/java/org/onlab/graph/AbstractGraphPathSearchTest.java new file mode 100644 index 00000000..b42cacf6 --- /dev/null +++ b/framework/src/onos/utils/misc/src/test/java/org/onlab/graph/AbstractGraphPathSearchTest.java @@ -0,0 +1,61 @@ +/* + * Copyright 2014-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.graph; + +import org.junit.Test; + +import static com.google.common.collect.ImmutableSet.of; +import static org.junit.Assert.assertEquals; + +/** + * Base for all graph search tests. + */ +public abstract class AbstractGraphPathSearchTest extends GraphTest { + + /** + * Creates a test-specific graph search to exercise. + * + * @return graph search + */ + protected abstract AbstractGraphPathSearch<TestVertex, TestEdge> graphSearch(); + + @Test(expected = IllegalArgumentException.class) + public void noSuchSourceArgument() { + graphSearch().search(new AdjacencyListsGraph<>(of(B, C), + of(new TestEdge(B, C, 1))), + A, H, weight, 1); + } + + @Test(expected = NullPointerException.class) + public void nullGraphArgument() { + graphSearch().search(null, A, H, weight, 1); + } + + @Test(expected = NullPointerException.class) + public void nullSourceArgument() { + graphSearch().search(new AdjacencyListsGraph<>(of(B, C), + of(new TestEdge(B, C, 1))), + null, H, weight, 1); + } + + @Test + public void samenessThreshold() { + AbstractGraphPathSearch<TestVertex, TestEdge> search = graphSearch(); + search.setSamenessThreshold(0.3); + assertEquals("incorrect threshold", 0.3, search.samenessThreshold(), 0.01); + } + +} diff --git a/framework/src/onos/utils/misc/src/test/java/org/onlab/graph/AdjacencyListsGraphTest.java b/framework/src/onos/utils/misc/src/test/java/org/onlab/graph/AdjacencyListsGraphTest.java new file mode 100644 index 00000000..1f22b5c4 --- /dev/null +++ b/framework/src/onos/utils/misc/src/test/java/org/onlab/graph/AdjacencyListsGraphTest.java @@ -0,0 +1,72 @@ +/* + * Copyright 2014 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.graph; + +import com.google.common.collect.ImmutableSet; +import com.google.common.testing.EqualsTester; +import org.junit.Test; + +import java.util.Set; + +import static org.junit.Assert.assertEquals; + +/** + * Tests of the graph implementation. + */ +public class AdjacencyListsGraphTest { + + private static final TestVertex A = new TestVertex("A"); + private static final TestVertex B = new TestVertex("B"); + private static final TestVertex C = new TestVertex("C"); + private static final TestVertex D = new TestVertex("D"); + private static final TestVertex E = new TestVertex("E"); + private static final TestVertex F = new TestVertex("F"); + private static final TestVertex G = new TestVertex("G"); + + private final Set<TestEdge> edges = + ImmutableSet.of(new TestEdge(A, B, 1), new TestEdge(B, C, 1), + new TestEdge(C, D, 1), new TestEdge(D, A, 1), + new TestEdge(B, D, 1)); + + @Test + public void equality() { + Set<TestVertex> vertexes = ImmutableSet.of(A, B, C, D, E, F); + Set<TestVertex> vertexes2 = ImmutableSet.of(A, B, C, D, E, F, G); + + AdjacencyListsGraph<TestVertex, TestEdge> graph = new AdjacencyListsGraph<>(vertexes, edges); + AdjacencyListsGraph<TestVertex, TestEdge> same = new AdjacencyListsGraph<>(vertexes, edges); + AdjacencyListsGraph<TestVertex, TestEdge> different = new AdjacencyListsGraph<>(vertexes2, edges); + + new EqualsTester() + .addEqualityGroup(graph, same) + .addEqualityGroup(different) + .testEquals(); + } + + @Test + public void basics() { + Set<TestVertex> vertexes = ImmutableSet.of(A, B, C, D, E, F); + AdjacencyListsGraph<TestVertex, TestEdge> graph = new AdjacencyListsGraph<>(vertexes, edges); + assertEquals("incorrect vertex count", 6, graph.getVertexes().size()); + assertEquals("incorrect edge count", 5, graph.getEdges().size()); + + assertEquals("incorrect egress edge count", 1, graph.getEdgesFrom(A).size()); + assertEquals("incorrect ingress edge count", 1, graph.getEdgesTo(A).size()); + assertEquals("incorrect ingress edge count", 1, graph.getEdgesTo(C).size()); + assertEquals("incorrect egress edge count", 2, graph.getEdgesFrom(B).size()); + assertEquals("incorrect ingress edge count", 2, graph.getEdgesTo(D).size()); + } +} diff --git a/framework/src/onos/utils/misc/src/test/java/org/onlab/graph/BellmanFordGraphSearchTest.java b/framework/src/onos/utils/misc/src/test/java/org/onlab/graph/BellmanFordGraphSearchTest.java new file mode 100644 index 00000000..ff363bfb --- /dev/null +++ b/framework/src/onos/utils/misc/src/test/java/org/onlab/graph/BellmanFordGraphSearchTest.java @@ -0,0 +1,77 @@ +/* + * Copyright 2014-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.graph; + +import org.junit.Test; + +import java.util.HashSet; +import java.util.Set; + +import static org.junit.Assert.assertEquals; +import static org.onlab.graph.GraphPathSearch.ALL_PATHS; + +/** + * Test of the Bellman-Ford algorithm. + */ +public class BellmanFordGraphSearchTest extends BreadthFirstSearchTest { + + @Override + protected AbstractGraphPathSearch<TestVertex, TestEdge> graphSearch() { + return new BellmanFordGraphSearch<>(); + } + + @Test + @Override + public void defaultGraphTest() { + executeDefaultTest(7, 5, 5.0); + } + + @Test + public void defaultHopCountWeight() { + weight = null; + executeDefaultTest(10, 3, 3.0); + } + + @Test + public void searchGraphWithNegativeCycles() { + Set<TestVertex> vertexes = new HashSet<>(vertexes()); + vertexes.add(Z); + + Set<TestEdge> edges = new HashSet<>(edges()); + edges.add(new TestEdge(G, Z, 1.0)); + edges.add(new TestEdge(Z, G, -2.0)); + + graph = new AdjacencyListsGraph<>(vertexes, edges); + + GraphPathSearch<TestVertex, TestEdge> search = graphSearch(); + Set<Path<TestVertex, TestEdge>> paths = search.search(graph, A, H, weight, ALL_PATHS).paths(); + assertEquals("incorrect paths count", 1, paths.size()); + + Path p = paths.iterator().next(); + assertEquals("incorrect src", A, p.src()); + assertEquals("incorrect dst", H, p.dst()); + assertEquals("incorrect path length", 5, p.edges().size()); + assertEquals("incorrect path cost", 5.0, p.cost(), 0.1); + + paths = search.search(graph, A, G, weight, ALL_PATHS).paths(); + assertEquals("incorrect paths count", 0, paths.size()); + + paths = search.search(graph, A, null, weight, ALL_PATHS).paths(); + printPaths(paths); + assertEquals("incorrect paths count", 6, paths.size()); + } + +} diff --git a/framework/src/onos/utils/misc/src/test/java/org/onlab/graph/BreadthFirstSearchTest.java b/framework/src/onos/utils/misc/src/test/java/org/onlab/graph/BreadthFirstSearchTest.java new file mode 100644 index 00000000..0b574aff --- /dev/null +++ b/framework/src/onos/utils/misc/src/test/java/org/onlab/graph/BreadthFirstSearchTest.java @@ -0,0 +1,100 @@ +/* + * Copyright 2014-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.graph; + +import org.junit.Test; + +import java.util.Set; + +import static org.junit.Assert.assertEquals; +import static org.onlab.graph.GraphPathSearch.ALL_PATHS; + +/** + * Test of the BFS and similar path search algorithms. + */ +public class BreadthFirstSearchTest extends AbstractGraphPathSearchTest { + + @Override + protected AbstractGraphPathSearch<TestVertex, TestEdge> graphSearch() { + return new BreadthFirstSearch<>(); + } + + @Test + public void defaultGraphTest() { + executeDefaultTest(7, 3, 8.0); + } + + @Test + public void defaultHopCountWeight() { + weight = null; + executeDefaultTest(7, 3, 3.0); + } + + // Executes the default test + protected void executeDefaultTest(int pathCount, int pathLength, double pathCost) { + graph = new AdjacencyListsGraph<>(vertexes(), edges()); + + GraphPathSearch<TestVertex, TestEdge> search = graphSearch(); + Set<Path<TestVertex, TestEdge>> paths = + search.search(graph, A, H, weight, ALL_PATHS).paths(); + assertEquals("incorrect paths count", 1, paths.size()); + + Path p = paths.iterator().next(); + assertEquals("incorrect src", A, p.src()); + assertEquals("incorrect dst", H, p.dst()); + assertEquals("incorrect path length", pathLength, p.edges().size()); + assertEquals("incorrect path cost", pathCost, p.cost(), 0.1); + + paths = search.search(graph, A, null, weight, ALL_PATHS).paths(); + printPaths(paths); + assertEquals("incorrect paths count", pathCount, paths.size()); + } + + // Executes the search and validates its results. + protected void executeSearch(GraphPathSearch<TestVertex, TestEdge> search, + Graph<TestVertex, TestEdge> graph, + TestVertex src, TestVertex dst, + EdgeWeight<TestVertex, TestEdge> weight, + int pathCount, double pathCost) { + GraphPathSearch.Result<TestVertex, TestEdge> result = + search.search(graph, src, dst, weight, ALL_PATHS); + Set<Path<TestVertex, TestEdge>> paths = result.paths(); + printPaths(paths); + assertEquals("incorrect paths count", pathCount, paths.size()); + if (pathCount > 0) { + Path<TestVertex, TestEdge> path = paths.iterator().next(); + assertEquals("incorrect path cost", pathCost, path.cost(), 0.1); + } + } + + // Executes the single-path search and validates its results. + protected void executeSinglePathSearch(GraphPathSearch<TestVertex, TestEdge> search, + Graph<TestVertex, TestEdge> graph, + TestVertex src, TestVertex dst, + EdgeWeight<TestVertex, TestEdge> weight, + int pathCount, double pathCost) { + GraphPathSearch.Result<TestVertex, TestEdge> result = + search.search(graph, src, dst, weight, 1); + Set<Path<TestVertex, TestEdge>> paths = result.paths(); + printPaths(paths); + assertEquals("incorrect paths count", Math.min(pathCount, 1), paths.size()); + if (pathCount > 0) { + Path<TestVertex, TestEdge> path = paths.iterator().next(); + assertEquals("incorrect path cost", pathCost, path.cost(), 0.1); + } + } + +} diff --git a/framework/src/onos/utils/misc/src/test/java/org/onlab/graph/DefaultMutablePathTest.java b/framework/src/onos/utils/misc/src/test/java/org/onlab/graph/DefaultMutablePathTest.java new file mode 100644 index 00000000..8eb09df6 --- /dev/null +++ b/framework/src/onos/utils/misc/src/test/java/org/onlab/graph/DefaultMutablePathTest.java @@ -0,0 +1,110 @@ +/* + * Copyright 2014 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.graph; + +import com.google.common.testing.EqualsTester; +import org.junit.Test; + +import static com.google.common.collect.ImmutableList.of; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +/** + * Test of the default mutable path. + */ +public class DefaultMutablePathTest extends DefaultPathTest { + + @Test + public void equality() { + DefaultPath<TestVertex, TestEdge> p1 = + new DefaultPath<>(of(new TestEdge(A, B, 1), + new TestEdge(B, C, 1)), 2.0); + DefaultPath<TestVertex, TestEdge> p2 = + new DefaultPath<>(of(new TestEdge(A, B, 1), + new TestEdge(B, D, 1)), 2.0); + new EqualsTester().addEqualityGroup(new DefaultMutablePath<>(p1), + new DefaultMutablePath<>(p1)) + .addEqualityGroup(new DefaultMutablePath<>(p2)) + .testEquals(); + } + + @Test + public void empty() { + MutablePath<TestVertex, TestEdge> p = new DefaultMutablePath<>(); + assertNull("src should be null", p.src()); + assertNull("dst should be null", p.dst()); + assertEquals("incorrect edge count", 0, p.edges().size()); + assertEquals("incorrect path cost", 0.0, p.cost(), 0.1); + } + + @Test + public void pathCost() { + MutablePath<TestVertex, TestEdge> p = new DefaultMutablePath<>(); + p.setCost(4); + assertEquals("incorrect path cost", 4.0, p.cost(), 0.1); + } + + private void validatePath(Path<TestVertex, TestEdge> p, + TestVertex src, TestVertex dst, int length) { + validatePath(p, src, dst, length, 0.0); + } + + @Test + public void insertEdge() { + MutablePath<TestVertex, TestEdge> p = new DefaultMutablePath<>(); + p.insertEdge(new TestEdge(B, C, 1)); + p.insertEdge(new TestEdge(A, B, 1)); + validatePath(p, A, C, 2); + } + + @Test + public void appendEdge() { + MutablePath<TestVertex, TestEdge> p = new DefaultMutablePath<>(); + p.appendEdge(new TestEdge(A, B, 1)); + p.appendEdge(new TestEdge(B, C, 1)); + validatePath(p, A, C, 2); + } + + @Test + public void removeEdge() { + MutablePath<TestVertex, TestEdge> p = new DefaultMutablePath<>(); + p.appendEdge(new TestEdge(A, B, 1)); + p.appendEdge(new TestEdge(B, C, 1)); + p.appendEdge(new TestEdge(C, C, 2)); + p.appendEdge(new TestEdge(C, D, 1)); + validatePath(p, A, D, 4); + + p.removeEdge(new TestEdge(A, B, 1)); + validatePath(p, B, D, 3); + + p.removeEdge(new TestEdge(C, C, 2)); + validatePath(p, B, D, 2); + + p.removeEdge(new TestEdge(C, D, 1)); + validatePath(p, B, C, 1); + } + + @Test + public void toImmutable() { + MutablePath<TestVertex, TestEdge> p = new DefaultMutablePath<>(); + p.appendEdge(new TestEdge(A, B, 1)); + p.appendEdge(new TestEdge(B, C, 1)); + validatePath(p, A, C, 2); + + assertEquals("immutables should equal", p.toImmutable(), p.toImmutable()); + validatePath(p.toImmutable(), A, C, 2); + } +} diff --git a/framework/src/onos/utils/misc/src/test/java/org/onlab/graph/DefaultPathTest.java b/framework/src/onos/utils/misc/src/test/java/org/onlab/graph/DefaultPathTest.java new file mode 100644 index 00000000..92befbfe --- /dev/null +++ b/framework/src/onos/utils/misc/src/test/java/org/onlab/graph/DefaultPathTest.java @@ -0,0 +1,57 @@ +/* + * Copyright 2014 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.graph; + +import com.google.common.testing.EqualsTester; +import org.junit.Test; + +import java.util.List; + +import static com.google.common.collect.ImmutableList.of; +import static org.junit.Assert.assertEquals; + +/** + * Test of the default path. + */ +public class DefaultPathTest extends GraphTest { + + @Test + public void equality() { + List<TestEdge> edges = of(new TestEdge(A, B, 1), new TestEdge(B, C, 1)); + new EqualsTester().addEqualityGroup(new DefaultPath<>(edges, 2.0), + new DefaultPath<>(edges, 2.0)) + .addEqualityGroup(new DefaultPath<>(edges, 3.0)) + .testEquals(); + } + + @Test + public void basics() { + Path<TestVertex, TestEdge> p = new DefaultPath<>(of(new TestEdge(A, B, 1), + new TestEdge(B, C, 1)), 2.0); + validatePath(p, A, C, 2, 2.0); + } + + // Validates the path against expected attributes + protected void validatePath(Path<TestVertex, TestEdge> p, + TestVertex src, TestVertex dst, + int length, double cost) { + assertEquals("incorrect path length", length, p.edges().size()); + assertEquals("incorrect source", src, p.src()); + assertEquals("incorrect destination", dst, p.dst()); + assertEquals("incorrect path cost", cost, p.cost(), 0.1); + } + +} diff --git a/framework/src/onos/utils/misc/src/test/java/org/onlab/graph/DepthFirstSearchTest.java b/framework/src/onos/utils/misc/src/test/java/org/onlab/graph/DepthFirstSearchTest.java new file mode 100644 index 00000000..3977ebf1 --- /dev/null +++ b/framework/src/onos/utils/misc/src/test/java/org/onlab/graph/DepthFirstSearchTest.java @@ -0,0 +1,97 @@ +/* + * Copyright 2014-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.graph; + +import org.junit.Test; + +import java.util.Set; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.onlab.graph.DepthFirstSearch.EdgeType; +import static org.onlab.graph.GraphPathSearch.ALL_PATHS; + +/** + * Test of the DFS algorithm. + */ +public class DepthFirstSearchTest extends AbstractGraphPathSearchTest { + + @Override + protected DepthFirstSearch<TestVertex, TestEdge> graphSearch() { + return new DepthFirstSearch<>(); + } + + @Test + public void defaultGraphTest() { + executeDefaultTest(3, 6, 5.0, 12.0); + executeBroadSearch(); + } + + @Test + public void defaultHopCountWeight() { + weight = null; + executeDefaultTest(3, 6, 3.0, 6.0); + executeBroadSearch(); + } + + protected void executeDefaultTest(int minLength, int maxLength, + double minCost, double maxCost) { + graph = new AdjacencyListsGraph<>(vertexes(), edges()); + DepthFirstSearch<TestVertex, TestEdge> search = graphSearch(); + + DepthFirstSearch<TestVertex, TestEdge>.SpanningTreeResult result = + search.search(graph, A, H, weight, 1); + Set<Path<TestVertex, TestEdge>> paths = result.paths(); + assertEquals("incorrect path count", 1, paths.size()); + + Path path = paths.iterator().next(); + System.out.println(path); + assertEquals("incorrect src", A, path.src()); + assertEquals("incorrect dst", H, path.dst()); + + int l = path.edges().size(); + assertTrue("incorrect path length " + l, + minLength <= l && l <= maxLength); + assertTrue("incorrect path cost " + path.cost(), + minCost <= path.cost() && path.cost() <= maxCost); + + System.out.println(result.edges()); + printPaths(paths); + } + + public void executeBroadSearch() { + graph = new AdjacencyListsGraph<>(vertexes(), edges()); + DepthFirstSearch<TestVertex, TestEdge> search = graphSearch(); + + // Perform narrow path search to a specific destination. + DepthFirstSearch<TestVertex, TestEdge>.SpanningTreeResult result = + search.search(graph, A, null, weight, ALL_PATHS); + assertEquals("incorrect paths count", 7, result.paths().size()); + + int[] types = new int[]{0, 0, 0, 0}; + for (EdgeType t : result.edges().values()) { + types[t.ordinal()] += 1; + } + assertEquals("incorrect tree-edge count", 7, + types[EdgeType.TREE_EDGE.ordinal()]); + assertEquals("incorrect back-edge count", 1, + types[EdgeType.BACK_EDGE.ordinal()]); + assertEquals("incorrect cross-edge & forward-edge count", 4, + types[EdgeType.FORWARD_EDGE.ordinal()] + + types[EdgeType.CROSS_EDGE.ordinal()]); + } + +} diff --git a/framework/src/onos/utils/misc/src/test/java/org/onlab/graph/DijkstraGraphSearchTest.java b/framework/src/onos/utils/misc/src/test/java/org/onlab/graph/DijkstraGraphSearchTest.java new file mode 100644 index 00000000..17f08225 --- /dev/null +++ b/framework/src/onos/utils/misc/src/test/java/org/onlab/graph/DijkstraGraphSearchTest.java @@ -0,0 +1,165 @@ +/* + * Copyright 2014-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.graph; + +import org.junit.Test; + +import java.text.DecimalFormat; +import java.util.HashSet; +import java.util.Set; + +import static com.google.common.collect.ImmutableSet.of; +import static org.junit.Assert.assertEquals; + +/** + * Test of the Dijkstra algorithm. + */ +public class DijkstraGraphSearchTest extends BreadthFirstSearchTest { + + @Override + protected AbstractGraphPathSearch<TestVertex, TestEdge> graphSearch() { + return new DijkstraGraphSearch<>(); + } + + @Test + @Override + public void defaultGraphTest() { + executeDefaultTest(7, 5, 5.0); + } + + @Test + @Override + public void defaultHopCountWeight() { + weight = null; + executeDefaultTest(10, 3, 3.0); + } + + @Test + public void noPath() { + graph = new AdjacencyListsGraph<>(of(A, B, C, D), + of(new TestEdge(A, B, 1), + new TestEdge(B, A, 1), + new TestEdge(C, D, 1), + new TestEdge(D, C, 1))); + GraphPathSearch<TestVertex, TestEdge> gs = graphSearch(); + Set<Path<TestVertex, TestEdge>> paths = gs.search(graph, A, B, weight, 1).paths(); + printPaths(paths); + assertEquals("incorrect paths count", 1, paths.size()); + assertEquals("incorrect path cost", 1.0, paths.iterator().next().cost(), 0.1); + + paths = gs.search(graph, A, D, weight, 1).paths(); + printPaths(paths); + assertEquals("incorrect paths count", 0, paths.size()); + + paths = gs.search(graph, A, null, weight, 1).paths(); + printPaths(paths); + assertEquals("incorrect paths count", 1, paths.size()); + assertEquals("incorrect path cost", 1.0, paths.iterator().next().cost(), 0.1); + } + + @Test + public void simpleMultiplePath() { + graph = new AdjacencyListsGraph<>(of(A, B, C, D), + of(new TestEdge(A, B, 1), + new TestEdge(A, C, 1), + new TestEdge(B, D, 1), + new TestEdge(C, D, 1))); + executeSearch(graphSearch(), graph, A, D, weight, 2, 2.0); + executeSinglePathSearch(graphSearch(), graph, A, D, weight, 1, 2.0); + } + + @Test + public void denseMultiplePath() { + graph = new AdjacencyListsGraph<>(of(A, B, C, D, E, F, G), + of(new TestEdge(A, B, 1), + new TestEdge(A, C, 1), + new TestEdge(B, D, 1), + new TestEdge(C, D, 1), + new TestEdge(D, E, 1), + new TestEdge(D, F, 1), + new TestEdge(E, G, 1), + new TestEdge(F, G, 1), + new TestEdge(A, G, 4))); + executeSearch(graphSearch(), graph, A, G, weight, 5, 4.0); + executeSinglePathSearch(graphSearch(), graph, A, G, weight, 1, 4.0); + } + + @Test + public void dualEdgeMultiplePath() { + graph = new AdjacencyListsGraph<>(of(A, B, C, D, E, F, G, H), + of(new TestEdge(A, B, 1), new TestEdge(A, C, 3), + new TestEdge(B, D, 2), new TestEdge(B, C, 1), + new TestEdge(B, E, 4), new TestEdge(C, E, 1), + new TestEdge(D, H, 5), new TestEdge(D, E, 1), + new TestEdge(E, F, 1), new TestEdge(F, D, 1), + new TestEdge(F, G, 1), new TestEdge(F, H, 1), + new TestEdge(A, E, 3), new TestEdge(B, D, 1))); + executeSearch(graphSearch(), graph, A, E, weight, 3, 3.0); + executeSinglePathSearch(graphSearch(), graph, A, E, weight, 1, 3.0); + } + + @Test + public void negativeWeights() { + graph = new AdjacencyListsGraph<>(of(A, B, C, D, E, F, G), + of(new TestEdge(A, B, 1), + new TestEdge(A, C, -1), + new TestEdge(B, D, 1), + new TestEdge(D, A, -2), + new TestEdge(C, D, 1), + new TestEdge(D, E, 1), + new TestEdge(D, F, 1), + new TestEdge(E, G, 1), + new TestEdge(F, G, 1), + new TestEdge(G, A, -5), + new TestEdge(A, G, 4))); + executeSearch(graphSearch(), graph, A, G, weight, 3, 4.0); + executeSinglePathSearch(graphSearch(), graph, A, G, weight, 1, 4.0); + } + + @Test + public void disconnectedPerf() { + disconnected(); + disconnected(); + disconnected(); + disconnected(); + disconnected(); + disconnected(); + disconnected(); + disconnected(); + disconnected(); + disconnected(); + } + + + @Test + public void disconnected() { + Set<TestVertex> vertexes = new HashSet<>(); + for (int i = 0; i < 200; i++) { + vertexes.add(new TestVertex("v" + i)); + } + + graph = new AdjacencyListsGraph<>(vertexes, of()); + + long start = System.nanoTime(); + for (TestVertex src : vertexes) { + executeSearch(graphSearch(), graph, src, null, null, 0, 0); + } + long end = System.nanoTime(); + DecimalFormat fmt = new DecimalFormat("#,###"); + System.out.println("Compute cost is " + fmt.format(end - start) + " nanos"); + } + +} diff --git a/framework/src/onos/utils/misc/src/test/java/org/onlab/graph/GraphTest.java b/framework/src/onos/utils/misc/src/test/java/org/onlab/graph/GraphTest.java new file mode 100644 index 00000000..d29282fc --- /dev/null +++ b/framework/src/onos/utils/misc/src/test/java/org/onlab/graph/GraphTest.java @@ -0,0 +1,66 @@ +/* + * Copyright 2014 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.graph; + +import java.util.Set; + +import static com.google.common.collect.ImmutableSet.of; + +/** + * Base class for various graph-related tests. + */ +public class GraphTest { + + static final TestVertex A = new TestVertex("A"); + static final TestVertex B = new TestVertex("B"); + static final TestVertex C = new TestVertex("C"); + static final TestVertex D = new TestVertex("D"); + static final TestVertex E = new TestVertex("E"); + static final TestVertex F = new TestVertex("F"); + static final TestVertex G = new TestVertex("G"); + static final TestVertex H = new TestVertex("H"); + static final TestVertex Z = new TestVertex("Z"); + + protected Graph<TestVertex, TestEdge> graph; + + protected EdgeWeight<TestVertex, TestEdge> weight = + new EdgeWeight<TestVertex, TestEdge>() { + @Override + public double weight(TestEdge edge) { + return edge.weight(); + } + }; + + protected void printPaths(Set<Path<TestVertex, TestEdge>> paths) { + for (Path p : paths) { + System.out.println(p); + } + } + + protected Set<TestVertex> vertexes() { + return of(A, B, C, D, E, F, G, H); + } + + protected Set<TestEdge> edges() { + return of(new TestEdge(A, B, 1), new TestEdge(A, C, 3), + new TestEdge(B, D, 2), new TestEdge(B, C, 1), + new TestEdge(B, E, 4), new TestEdge(C, E, 1), + new TestEdge(D, H, 5), new TestEdge(D, E, 1), + new TestEdge(E, F, 1), new TestEdge(F, D, 1), + new TestEdge(F, G, 1), new TestEdge(F, H, 1)); + } + +} diff --git a/framework/src/onos/utils/misc/src/test/java/org/onlab/graph/HeapTest.java b/framework/src/onos/utils/misc/src/test/java/org/onlab/graph/HeapTest.java new file mode 100644 index 00000000..f34185e2 --- /dev/null +++ b/framework/src/onos/utils/misc/src/test/java/org/onlab/graph/HeapTest.java @@ -0,0 +1,97 @@ +/* + * Copyright 2014 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.graph; + +import com.google.common.collect.Ordering; +import com.google.common.testing.EqualsTester; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.Comparator; + +import static com.google.common.collect.ImmutableList.of; +import static org.junit.Assert.*; + +/** + * Heap data structure tests. + */ +public class HeapTest { + + private ArrayList<Integer> data = + new ArrayList<>(of(6, 4, 5, 9, 8, 3, 2, 1, 7, 0)); + + private static final Comparator<Integer> MIN = Ordering.natural().reverse(); + private static final Comparator<Integer> MAX = Ordering.natural(); + + @Test + public void equality() { + new EqualsTester() + .addEqualityGroup(new Heap<>(data, MIN), + new Heap<>(data, MIN)) + .addEqualityGroup(new Heap<>(data, MAX)) + .testEquals(); + } + + @Test + public void empty() { + Heap<Integer> h = new Heap<>(new ArrayList<Integer>(), MIN); + assertTrue("should be empty", h.isEmpty()); + assertEquals("incorrect size", 0, h.size()); + assertNull("no item expected", h.extreme()); + assertNull("no item expected", h.extractExtreme()); + } + + @Test + public void insert() { + Heap<Integer> h = new Heap<>(data, MIN); + assertEquals("incorrect size", 10, h.size()); + h.insert(3); + assertEquals("incorrect size", 11, h.size()); + } + + @Test + public void minQueue() { + Heap<Integer> h = new Heap<>(data, MIN); + assertFalse("should not be empty", h.isEmpty()); + assertEquals("incorrect size", 10, h.size()); + assertEquals("incorrect extreme", (Integer) 0, h.extreme()); + + for (int i = 0, n = h.size(); i < n; i++) { + assertEquals("incorrect element", (Integer) i, h.extractExtreme()); + } + assertTrue("should be empty", h.isEmpty()); + } + + @Test + public void maxQueue() { + Heap<Integer> h = new Heap<>(data, MAX); + assertFalse("should not be empty", h.isEmpty()); + assertEquals("incorrect size", 10, h.size()); + assertEquals("incorrect extreme", (Integer) 9, h.extreme()); + + for (int i = h.size(); i > 0; i--) { + assertEquals("incorrect element", (Integer) (i - 1), h.extractExtreme()); + } + assertTrue("should be empty", h.isEmpty()); + } + + @Test + public void iterator() { + Heap<Integer> h = new Heap<>(data, MIN); + assertTrue("should have next element", h.iterator().hasNext()); + } + +} diff --git a/framework/src/onos/utils/misc/src/test/java/org/onlab/graph/KshortestPathSearchTest.java b/framework/src/onos/utils/misc/src/test/java/org/onlab/graph/KshortestPathSearchTest.java new file mode 100644 index 00000000..3e8900b8 --- /dev/null +++ b/framework/src/onos/utils/misc/src/test/java/org/onlab/graph/KshortestPathSearchTest.java @@ -0,0 +1,197 @@ +/* + * Copyright 2014 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.graph; + +import static com.google.common.collect.ImmutableSet.of; +import static org.junit.Assert.*; + +import java.io.ByteArrayOutputStream; +//import java.io.PrintStream; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +public class KshortestPathSearchTest extends BreadthFirstSearchTest { + + private final ByteArrayOutputStream outContent = new ByteArrayOutputStream(); + + @Test + public void noPath() { + graph = new AdjacencyListsGraph<>(of(A, B, C, D), + of(new TestEdge(A, B, 1), + new TestEdge(B, A, 1), + new TestEdge(C, D, 1), + new TestEdge(D, C, 1))); + KshortestPathSearch<TestVertex, TestEdge> gs = new KshortestPathSearch<TestVertex, TestEdge>(graph); + List<List<TestEdge>> result = gs.search(A, D, weight, 1); + List<Path> paths = new ArrayList<>(); + Iterator<List<TestEdge>> itr = result.iterator(); + while (itr.hasNext()) { + System.out.println(itr.next().toString()); + } + assertEquals("incorrect paths count", 0, result.size()); + } + + @Test + public void test2Path() { + graph = new AdjacencyListsGraph<>(of(A, B, C, D), + of(new TestEdge(A, B, 1), + new TestEdge(B, A, 1), + new TestEdge(B, D, 1), + new TestEdge(D, B, 1), + new TestEdge(A, C, 1), + new TestEdge(C, A, 1), + new TestEdge(C, D, 1), + new TestEdge(D, C, 1))); + KshortestPathSearch<TestVertex, TestEdge> gs = new KshortestPathSearch<TestVertex, TestEdge>(graph); + List<List<TestEdge>> result = gs.search(A, D, weight, 2); + List<Path> paths = new ArrayList<>(); + Iterator<List<TestEdge>> itr = result.iterator(); + while (itr.hasNext()) { + System.out.println(itr.next().toString()); + } + assertEquals("incorrect paths count", 2, result.size()); + // assertEquals("printing the paths", outContent.toString()); + } + + @Test + public void test3Path() { + graph = new AdjacencyListsGraph<>(of(A, B, C, D), + of(new TestEdge(A, B, 1), + new TestEdge(B, A, 1), + new TestEdge(A, D, 1), + new TestEdge(D, A, 1), + new TestEdge(B, D, 1), + new TestEdge(D, B, 1), + new TestEdge(A, C, 1), + new TestEdge(C, A, 1), + new TestEdge(C, D, 1), + new TestEdge(D, C, 1))); + KshortestPathSearch<TestVertex, TestEdge> gs = new KshortestPathSearch<TestVertex, TestEdge>(graph); + List<List<TestEdge>> result = gs.search(A, D, weight, 3); + List<Path> paths = new ArrayList<>(); + Iterator<List<TestEdge>> itr = result.iterator(); + while (itr.hasNext()) { + System.out.println(itr.next().toString()); + } + assertEquals("incorrect paths count", 3, result.size()); + // assertEquals("printing the paths", outContent.toString()); + } + + @Test + public void test4Path() { + graph = new AdjacencyListsGraph<>(of(A, B, C, D, E, F), + of(new TestEdge(A, B, 1), + new TestEdge(B, A, 1), + new TestEdge(A, C, 1), + new TestEdge(C, A, 1), + new TestEdge(B, D, 1), + new TestEdge(D, B, 1), + new TestEdge(C, E, 1), + new TestEdge(E, C, 1), + new TestEdge(D, F, 1), + new TestEdge(F, D, 1), + new TestEdge(F, E, 1), + new TestEdge(E, F, 1), + new TestEdge(C, D, 1), + new TestEdge(D, C, 1))); + KshortestPathSearch<TestVertex, TestEdge> gs = new KshortestPathSearch<TestVertex, TestEdge>(graph); + List<List<TestEdge>> result = gs.search(A, F, weight, 4); + List<Path> paths = new ArrayList<>(); + Iterator<List<TestEdge>> itr = result.iterator(); + while (itr.hasNext()) { + System.out.println(itr.next().toString()); + } + assertEquals("incorrect paths count", 4, result.size()); + // assertEquals("printing the paths", outContent.toString()); + } + + @Test + public void test6Path() { + graph = new AdjacencyListsGraph<>(of(A, B, C, D, E, F), + of(new TestEdge(A, B, 1), + new TestEdge(B, A, 1), + new TestEdge(A, C, 1), + new TestEdge(C, A, 1), + new TestEdge(B, D, 1), + new TestEdge(D, B, 1), + new TestEdge(B, C, 1), + new TestEdge(C, B, 1), + new TestEdge(D, E, 1), + new TestEdge(E, D, 1), + new TestEdge(C, E, 1), + new TestEdge(E, C, 1), + new TestEdge(D, F, 1), + new TestEdge(F, D, 1), + new TestEdge(E, F, 1), + new TestEdge(F, E, 1))); + KshortestPathSearch<TestVertex, TestEdge> gs = new KshortestPathSearch<TestVertex, TestEdge>(graph); + List<List<TestEdge>> result = gs.search(A, F, weight, 6); + List<Path> paths = new ArrayList<>(); + Iterator<List<TestEdge>> itr = result.iterator(); + while (itr.hasNext()) { + System.out.println(itr.next().toString()); + } + assertEquals("incorrect paths count", 6, result.size()); + // assertEquals("printing the paths", outContent.toString()); + } + + @Test + public void dualEdgePath() { + graph = new AdjacencyListsGraph<>(of(A, B, C, D, E, F, G, H), + of(new TestEdge(A, B, 1), new TestEdge(A, C, 3), + new TestEdge(B, D, 2), new TestEdge(B, C, 1), + new TestEdge(B, E, 4), new TestEdge(C, E, 1), + new TestEdge(D, H, 5), new TestEdge(D, E, 1), + new TestEdge(E, F, 1), new TestEdge(F, D, 1), + new TestEdge(F, G, 1), new TestEdge(F, H, 1), + new TestEdge(A, E, 3), new TestEdge(B, D, 1))); + KshortestPathSearch<TestVertex, TestEdge> gs = new KshortestPathSearch<TestVertex, TestEdge>(graph); + List<List<TestEdge>> result = gs.search(A, G, weight, 6); + List<Path> paths = new ArrayList<>(); + Iterator<List<TestEdge>> itr = result.iterator(); + while (itr.hasNext()) { + System.out.println(itr.next().toString()); + } + assertEquals("incorrect paths count", 6, result.size()); + // assertEquals("printing the paths", outContent.toString()); + } + + @BeforeClass + public static void setUpBeforeClass() throws Exception { + } + + @AfterClass + public static void tearDownAfterClass() throws Exception { + } + + @Before + public void setUp() throws Exception { + // System.setOut(new PrintStream(outContent)); + } + + @After + public void tearDown() throws Exception { + // System.setOut(null); + } + +} diff --git a/framework/src/onos/utils/misc/src/test/java/org/onlab/graph/SRLGGraphSearchTest.java b/framework/src/onos/utils/misc/src/test/java/org/onlab/graph/SRLGGraphSearchTest.java new file mode 100644 index 00000000..885fbe5c --- /dev/null +++ b/framework/src/onos/utils/misc/src/test/java/org/onlab/graph/SRLGGraphSearchTest.java @@ -0,0 +1,183 @@ +/* + * 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.graph; + +import org.junit.Test; +import java.util.Set; +import java.util.HashSet; +import java.util.Map; +import java.util.HashMap; + +import static com.google.common.collect.ImmutableSet.of; +import static org.junit.Assert.assertTrue; + + + +/** + * Test of the Suurballe backup path algorithm. + */ +public class SRLGGraphSearchTest extends BreadthFirstSearchTest { + @Override + protected AbstractGraphPathSearch<TestVertex, TestEdge> graphSearch() { + return new SRLGGraphSearch<TestVertex, TestEdge>(null); + } + + public void setWeights() { + weight = new EdgeWeight<TestVertex, TestEdge>() { + @Override + public double weight(TestEdge edge) { + return edge.weight(); + } + }; + } + public void setDefaultWeights() { + weight = null; + } + @Override + public void defaultGraphTest() { + + } + + @Override + public void defaultHopCountWeight() { + + } + + @Test + public void onePathPair() { + setDefaultWeights(); + TestEdge aB = new TestEdge(A, B, 1); + TestEdge bC = new TestEdge(B, C, 1); + TestEdge aD = new TestEdge(A, D, 1); + TestEdge dC = new TestEdge(D, C, 1); + Graph<TestVertex, TestEdge> graph = new AdjacencyListsGraph<>(of(A, B, C, D), + of(aB, bC, aD, dC)); + Map<TestEdge, Integer> riskProfile = new HashMap<TestEdge, Integer>(); + riskProfile.put(aB, 0); + riskProfile.put(bC, 0); + riskProfile.put(aD, 1); + riskProfile.put(dC, 1); + SRLGGraphSearch<TestVertex, TestEdge> search = + new SRLGGraphSearch<TestVertex, TestEdge>(2, riskProfile); + Set<Path<TestVertex, TestEdge>> paths = search.search(graph, A, C, weight, GraphPathSearch.ALL_PATHS).paths(); + System.out.println("\n\n\n" + paths + "\n\n\n"); + assertTrue("one disjoint path pair found", paths.size() == 1); + checkIsDisjoint(paths.iterator().next(), riskProfile); + } + public void checkIsDisjoint(Path<TestVertex, TestEdge> p, Map<TestEdge, Integer> risks) { + assertTrue("The path is not a DisjointPathPair", (p instanceof DisjointPathPair)); + DisjointPathPair<TestVertex, TestEdge> q = (DisjointPathPair) p; + Set<Integer> p1Risks = new HashSet<Integer>(); + Set<Integer> p2Risks = new HashSet<Integer>(); + for (TestEdge e: q.edges()) { + p1Risks.add(risks.get(e)); + } + if (!q.hasBackup()) { + return; + } + Path<TestVertex, TestEdge> pq = q.path2; + for (TestEdge e: pq.edges()) { + assertTrue("The paths are not disjoint", !p1Risks.contains(risks.get(e))); + } + } + @Test + public void complexGraphTest() { + setDefaultWeights(); + TestEdge aB = new TestEdge(A, B, 1); + TestEdge bC = new TestEdge(B, C, 1); + TestEdge aD = new TestEdge(A, D, 1); + TestEdge dC = new TestEdge(D, C, 1); + TestEdge cE = new TestEdge(C, E, 1); + TestEdge bE = new TestEdge(B, E, 1); + Graph<TestVertex, TestEdge> graph = new AdjacencyListsGraph<>(of(A, B, C, D, E), + of(aB, bC, aD, dC, cE, bE)); + Map<TestEdge, Integer> riskProfile = new HashMap<TestEdge, Integer>(); + riskProfile.put(aB, 0); + riskProfile.put(bC, 0); + riskProfile.put(aD, 1); + riskProfile.put(dC, 1); + riskProfile.put(cE, 2); + riskProfile.put(bE, 3); + SRLGGraphSearch<TestVertex, TestEdge> search = + new SRLGGraphSearch<TestVertex, TestEdge>(4, riskProfile); + Set<Path<TestVertex, TestEdge>> paths = search.search(graph, A, E, weight, GraphPathSearch.ALL_PATHS).paths(); + } + + @Test + public void multiplePathGraphTest() { + setDefaultWeights(); + TestEdge aB = new TestEdge(A, B, 1); + TestEdge bE = new TestEdge(B, E, 1); + TestEdge aD = new TestEdge(A, D, 1); + TestEdge dE = new TestEdge(D, E, 1); + TestEdge aC = new TestEdge(A, C, 1); + TestEdge cE = new TestEdge(C, E, 1); + Graph<TestVertex, TestEdge> graph = new AdjacencyListsGraph<>(of(A, B, C, D, E), + of(aB, bE, aD, dE, aC, cE)); + Map<TestEdge, Integer> riskProfile = new HashMap<TestEdge, Integer>(); + riskProfile.put(aB, 0); + riskProfile.put(bE, 1); + riskProfile.put(aD, 2); + riskProfile.put(dE, 3); + riskProfile.put(aC, 4); + riskProfile.put(cE, 5); + SRLGGraphSearch<TestVertex, TestEdge> search = + new SRLGGraphSearch<TestVertex, TestEdge>(6, riskProfile); + Set<Path<TestVertex, TestEdge>> paths = search.search(graph, A, E, weight, GraphPathSearch.ALL_PATHS).paths(); + assertTrue("> one disjoint path pair found", paths.size() >= 1); + checkIsDisjoint(paths.iterator().next(), riskProfile); + } + @Test + public void onePath() { + setDefaultWeights(); + TestEdge aB = new TestEdge(A, B, 1); + TestEdge bC = new TestEdge(B, C, 1); + TestEdge aD = new TestEdge(A, D, 1); + TestEdge dC = new TestEdge(D, C, 1); + Graph<TestVertex, TestEdge> graph = new AdjacencyListsGraph<>(of(A, B, C, D), + of(aB, bC, aD, dC)); + Map<TestEdge, Integer> riskProfile = new HashMap<TestEdge, Integer>(); + riskProfile.put(aB, 0); + riskProfile.put(bC, 0); + riskProfile.put(aD, 1); + riskProfile.put(dC, 0); + SRLGGraphSearch<TestVertex, TestEdge> search = + new SRLGGraphSearch<TestVertex, TestEdge>(2, riskProfile); + Set<Path<TestVertex, TestEdge>> paths = search.search(graph, A, C, weight, GraphPathSearch.ALL_PATHS).paths(); + System.out.println(paths); + assertTrue("no disjoint path pairs found", paths.size() == 0); + } + @Test + public void noPath() { + setDefaultWeights(); + TestEdge aB = new TestEdge(A, B, 1); + TestEdge bC = new TestEdge(B, C, 1); + TestEdge aD = new TestEdge(A, D, 1); + TestEdge dC = new TestEdge(D, C, 1); + Graph<TestVertex, TestEdge> graph = new AdjacencyListsGraph<>(of(A, B, C, D, E), + of(aB, bC, aD, dC)); + Map<TestEdge, Integer> riskProfile = new HashMap<>(); + riskProfile.put(aB, 0); + riskProfile.put(bC, 0); + riskProfile.put(aD, 1); + riskProfile.put(dC, 0); + SRLGGraphSearch<TestVertex, TestEdge> search = + new SRLGGraphSearch<>(2, riskProfile); + Set<Path<TestVertex, TestEdge>> paths = search.search(graph, A, E, weight, GraphPathSearch.ALL_PATHS).paths(); + assertTrue("no disjoint path pairs found", paths.size() == 0); + } +} diff --git a/framework/src/onos/utils/misc/src/test/java/org/onlab/graph/SuurballeGraphSearchTest.java b/framework/src/onos/utils/misc/src/test/java/org/onlab/graph/SuurballeGraphSearchTest.java new file mode 100644 index 00000000..0d2d13b0 --- /dev/null +++ b/framework/src/onos/utils/misc/src/test/java/org/onlab/graph/SuurballeGraphSearchTest.java @@ -0,0 +1,154 @@ +/* + * Copyright 2014 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.graph; + +import org.junit.Test; +import java.util.Set; + +import static com.google.common.collect.ImmutableSet.of; +import static org.junit.Assert.assertEquals; +//import static org.junit.Assert.assertTrue; + + + +/** + * Test of the Suurballe backup path algorithm. + */ +public class SuurballeGraphSearchTest extends BreadthFirstSearchTest { + + @Override + protected AbstractGraphPathSearch<TestVertex, TestEdge> graphSearch() { + return new SuurballeGraphSearch<>(); + } + + public void setWeights() { + weight = new EdgeWeight<TestVertex, TestEdge>() { + @Override + public double weight(TestEdge edge) { + return edge.weight(); + } + }; + } + public void setDefaultWeights() { + weight = null; + } + @Override + public void defaultGraphTest() { + + } + + @Override + public void defaultHopCountWeight() { + + } + + @Test + public void basicGraphTest() { + setDefaultWeights(); + Graph<TestVertex, TestEdge> graph = new AdjacencyListsGraph<>(of(A, B, C, D), + of(new TestEdge(A, B, 1), + new TestEdge(B, C, 1), + new TestEdge(A, D, 1), + new TestEdge(D, C, 1))); + executeSearch(graphSearch(), graph, A, C, weight, 1, 4.0); + } + + @Test + public void multiplePathOnePairGraphTest() { + setWeights(); + Graph<TestVertex, TestEdge> graph = new AdjacencyListsGraph<>(of(A, B, C, D, E), + of(new TestEdge(A, B, 1), + new TestEdge(B, C, 1), + new TestEdge(A, D, 1), + new TestEdge(D, C, 1), + new TestEdge(B, E, 2), + new TestEdge(C, E, 1))); + executeSearch(graphSearch(), graph, A, E, weight, 1, 6.0); + } + + @Test + public void multiplePathsMultiplePairs() { + setWeights(); + Graph<TestVertex, TestEdge> graph = new AdjacencyListsGraph<>(of(A, B, C, D, E), + of(new TestEdge(A, B, 1), + new TestEdge(B, E, 1), + new TestEdge(A, C, 1), + new TestEdge(C, E, 1), + new TestEdge(A, D, 1), + new TestEdge(D, E, 1), + new TestEdge(A, E, 2))); + GraphPathSearch.Result<TestVertex, TestEdge> result = + graphSearch().search(graph, A, E, weight, GraphPathSearch.ALL_PATHS); + Set<Path<TestVertex, TestEdge>> paths = result.paths(); + System.out.println("\n\n" + paths + "\n\n\ndone\n"); + assertEquals("incorrect paths count", 3, paths.size()); + DisjointPathPair<TestVertex, TestEdge> dpp = (DisjointPathPair<TestVertex, TestEdge>) paths.iterator().next(); + assertEquals("incorrect disjoint paths per path", 2, dpp.size()); + } + + @Test + public void differingPrimaryAndBackupPathLengths() { + setWeights(); + Graph<TestVertex, TestEdge> graph = new AdjacencyListsGraph<>(of(A, B, C, D, E), + of(new TestEdge(A, B, 1), + new TestEdge(B, C, 1), + new TestEdge(A, D, 1), + new TestEdge(D, C, 1), + new TestEdge(B, E, 1), + new TestEdge(C, E, 1))); + executeSearch(graphSearch(), graph, A, E, weight, 1, 5.0); + } + + @Test + public void onePath() { + setWeights(); + Graph<TestVertex, TestEdge> graph = new AdjacencyListsGraph<>(of(A, B, C, D), + of(new TestEdge(A, B, 1), + new TestEdge(B, C, 1), + new TestEdge(A, C, 4), + new TestEdge(C, D, 1))); + GraphPathSearch.Result<TestVertex, TestEdge> result = + graphSearch().search(graph, A, D, weight, GraphPathSearch.ALL_PATHS); + Set<Path<TestVertex, TestEdge>> paths = result.paths(); + assertEquals("incorrect paths count", 1, paths.size()); + DisjointPathPair<TestVertex, TestEdge> dpp = (DisjointPathPair<TestVertex, TestEdge>) paths.iterator().next(); + assertEquals("incorrect disjoint paths count", 1, dpp.size()); + } + + @Test + public void noPath() { + setWeights(); + Graph<TestVertex, TestEdge> graph = new AdjacencyListsGraph<>(of(A, B, C, D), + of(new TestEdge(A, B, 1), + new TestEdge(B, C, 1), + new TestEdge(A, C, 4))); + GraphPathSearch.Result<TestVertex, TestEdge> result = + graphSearch().search(graph, A, D, weight, GraphPathSearch.ALL_PATHS); + Set<Path<TestVertex, TestEdge>> paths = result.paths(); + assertEquals("incorrect paths count", paths.size(), 0); + } + + @Test + public void disconnected() { + setWeights(); + Graph<TestVertex, TestEdge> graph = new AdjacencyListsGraph<>(of(A, B, C, D), + of()); + GraphPathSearch.Result<TestVertex, TestEdge> result = + graphSearch().search(graph, A, D, weight, GraphPathSearch.ALL_PATHS); + Set<Path<TestVertex, TestEdge>> paths = result.paths(); + assertEquals("incorrect paths count", 0, paths.size()); + } +} diff --git a/framework/src/onos/utils/misc/src/test/java/org/onlab/graph/TarjanGraphSearchTest.java b/framework/src/onos/utils/misc/src/test/java/org/onlab/graph/TarjanGraphSearchTest.java new file mode 100644 index 00000000..624c5781 --- /dev/null +++ b/framework/src/onos/utils/misc/src/test/java/org/onlab/graph/TarjanGraphSearchTest.java @@ -0,0 +1,125 @@ +/* + * Copyright 2014 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.graph; + +import org.junit.Test; + +import static com.google.common.collect.ImmutableSet.of; +import static org.junit.Assert.assertEquals; +import static org.onlab.graph.TarjanGraphSearch.SCCResult; + +/** + * Tarjan graph search tests. + */ +public class TarjanGraphSearchTest extends GraphTest { + + private void validate(SCCResult<TestVertex, TestEdge> result, int cc) { + System.out.println("Cluster count: " + result.clusterVertexes().size()); + System.out.println("Clusters: " + result.clusterVertexes()); + assertEquals("incorrect cluster count", cc, result.clusterCount()); + } + + private void validate(SCCResult<TestVertex, TestEdge> result, + int i, int vc, int ec) { + assertEquals("incorrect cluster count", vc, result.clusterVertexes().get(i).size()); + assertEquals("incorrect edge count", ec, result.clusterEdges().get(i).size()); + } + + @Test + public void basic() { + graph = new AdjacencyListsGraph<>(vertexes(), edges()); + TarjanGraphSearch<TestVertex, TestEdge> gs = new TarjanGraphSearch<>(); + SCCResult<TestVertex, TestEdge> result = gs.search(graph, null); + validate(result, 6); + } + + @Test + public void singleCluster() { + graph = new AdjacencyListsGraph<>(vertexes(), + of(new TestEdge(A, B, 1), + new TestEdge(B, C, 1), + new TestEdge(C, D, 1), + new TestEdge(D, E, 1), + new TestEdge(E, F, 1), + new TestEdge(F, G, 1), + new TestEdge(G, H, 1), + new TestEdge(H, A, 1))); + + TarjanGraphSearch<TestVertex, TestEdge> gs = new TarjanGraphSearch<>(); + SCCResult<TestVertex, TestEdge> result = gs.search(graph, null); + validate(result, 1); + validate(result, 0, 8, 8); + } + + @Test + public void twoUnconnectedCluster() { + graph = new AdjacencyListsGraph<>(vertexes(), + of(new TestEdge(A, B, 1), + new TestEdge(B, C, 1), + new TestEdge(C, D, 1), + new TestEdge(D, A, 1), + new TestEdge(E, F, 1), + new TestEdge(F, G, 1), + new TestEdge(G, H, 1), + new TestEdge(H, E, 1))); + TarjanGraphSearch<TestVertex, TestEdge> gs = new TarjanGraphSearch<>(); + SCCResult<TestVertex, TestEdge> result = gs.search(graph, null); + validate(result, 2); + validate(result, 0, 4, 4); + validate(result, 1, 4, 4); + } + + @Test + public void twoWeaklyConnectedClusters() { + graph = new AdjacencyListsGraph<>(vertexes(), + of(new TestEdge(A, B, 1), + new TestEdge(B, C, 1), + new TestEdge(C, D, 1), + new TestEdge(D, A, 1), + new TestEdge(E, F, 1), + new TestEdge(F, G, 1), + new TestEdge(G, H, 1), + new TestEdge(H, E, 1), + new TestEdge(B, E, 1))); + TarjanGraphSearch<TestVertex, TestEdge> gs = new TarjanGraphSearch<>(); + SCCResult<TestVertex, TestEdge> result = gs.search(graph, null); + validate(result, 2); + validate(result, 0, 4, 4); + validate(result, 1, 4, 4); + } + + @Test + public void twoClustersConnectedWithIgnoredEdges() { + graph = new AdjacencyListsGraph<>(vertexes(), + of(new TestEdge(A, B, 1), + new TestEdge(B, C, 1), + new TestEdge(C, D, 1), + new TestEdge(D, A, 1), + new TestEdge(E, F, 1), + new TestEdge(F, G, 1), + new TestEdge(G, H, 1), + new TestEdge(H, E, 1), + new TestEdge(B, E, -1), + new TestEdge(E, B, -1))); + + TarjanGraphSearch<TestVertex, TestEdge> gs = new TarjanGraphSearch<>(); + SCCResult<TestVertex, TestEdge> result = gs.search(graph, weight); + validate(result, 2); + validate(result, 0, 4, 4); + validate(result, 1, 4, 4); + } + +} diff --git a/framework/src/onos/utils/misc/src/test/java/org/onlab/graph/TestEdge.java b/framework/src/onos/utils/misc/src/test/java/org/onlab/graph/TestEdge.java new file mode 100644 index 00000000..951f655b --- /dev/null +++ b/framework/src/onos/utils/misc/src/test/java/org/onlab/graph/TestEdge.java @@ -0,0 +1,73 @@ +/* + * Copyright 2014 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.graph; + +import java.util.Objects; + +import static com.google.common.base.MoreObjects.toStringHelper; + +/** + * Test edge. + */ +public class TestEdge extends AbstractEdge<TestVertex> { + + private final double weight; + + /** + * Creates a new edge between the specified source and destination vertexes. + * + * @param src source vertex + * @param dst destination vertex + * @param weight edge weight + */ + public TestEdge(TestVertex src, TestVertex dst, double weight) { + super(src, dst); + this.weight = weight; + } + + /** + * Returns the edge weight. + * + * @return edge weight + */ + public double weight() { + return weight; + } + + @Override + public int hashCode() { + return 31 * super.hashCode() + Objects.hash(weight); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj instanceof TestEdge) { + final TestEdge other = (TestEdge) obj; + return super.equals(obj) && Objects.equals(this.weight, other.weight); + } + return false; + } + + @Override + public String toString() { + return toStringHelper(this).add("src", src()).add("dst", dst()). + add("weight", weight).toString(); + } + +} diff --git a/framework/src/onos/utils/misc/src/test/java/org/onlab/graph/TestVertex.java b/framework/src/onos/utils/misc/src/test/java/org/onlab/graph/TestVertex.java new file mode 100644 index 00000000..be92bd27 --- /dev/null +++ b/framework/src/onos/utils/misc/src/test/java/org/onlab/graph/TestVertex.java @@ -0,0 +1,53 @@ +/* + * Copyright 2014 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.graph; + +import java.util.Objects; + +/** + * Test vertex. + */ +public class TestVertex implements Vertex { + + private final String name; + + public TestVertex(String name) { + this.name = name; + } + + @Override + public int hashCode() { + return Objects.hash(name); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj instanceof TestVertex) { + final TestVertex other = (TestVertex) obj; + return Objects.equals(this.name, other.name); + } + return false; + } + + @Override + public String toString() { + return name; + } + +} diff --git a/framework/src/onos/utils/misc/src/test/java/org/onlab/packet/ArpTest.java b/framework/src/onos/utils/misc/src/test/java/org/onlab/packet/ArpTest.java new file mode 100644 index 00000000..62246245 --- /dev/null +++ b/framework/src/onos/utils/misc/src/test/java/org/onlab/packet/ArpTest.java @@ -0,0 +1,88 @@ +/* + * 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 org.junit.Before; +import org.junit.Test; + +import java.nio.ByteBuffer; +import java.util.Arrays; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +/** + * Unit tests for the ARP class. + */ +public class ArpTest { + + private Deserializer<ARP> deserializer = ARP.deserializer(); + + private final byte hwAddressLength = 6; + private final byte protoAddressLength = 4; + + private MacAddress srcMac = MacAddress.valueOf(1); + private MacAddress targetMac = MacAddress.valueOf(2); + private Ip4Address srcIp = Ip4Address.valueOf(1); + private Ip4Address targetIp = Ip4Address.valueOf(2); + + private byte[] byteHeader; + + @Before + public void setUp() { + ByteBuffer bb = ByteBuffer.allocate(ARP.INITIAL_HEADER_LENGTH + + 2 * hwAddressLength + 2 * protoAddressLength); + bb.putShort(ARP.HW_TYPE_ETHERNET); + bb.putShort(ARP.PROTO_TYPE_IP); + bb.put(hwAddressLength); + bb.put(protoAddressLength); + bb.putShort(ARP.OP_REPLY); + + bb.put(srcMac.toBytes()); + bb.put(srcIp.toOctets()); + bb.put(targetMac.toBytes()); + bb.put(targetIp.toOctets()); + + byteHeader = bb.array(); + } + + @Test + public void testDeserializeBadInput() throws Exception { + PacketTestUtils.testDeserializeBadInput(deserializer); + } + + @Test + public void testDeserializeTruncated() throws Exception { + PacketTestUtils.testDeserializeTruncated(deserializer, byteHeader); + } + + @Test + public void testDeserialize() throws Exception { + ARP arp = deserializer.deserialize(byteHeader, 0, byteHeader.length); + + assertEquals(ARP.HW_TYPE_ETHERNET, arp.getHardwareType()); + assertEquals(ARP.PROTO_TYPE_IP, arp.getProtocolType()); + assertEquals(hwAddressLength, arp.getHardwareAddressLength()); + assertEquals(protoAddressLength, arp.getProtocolAddressLength()); + assertEquals(ARP.OP_REPLY, arp.getOpCode()); + + assertTrue(Arrays.equals(srcMac.toBytes(), arp.getSenderHardwareAddress())); + assertTrue(Arrays.equals(srcIp.toOctets(), arp.getSenderProtocolAddress())); + assertTrue(Arrays.equals(targetMac.toBytes(), arp.getTargetHardwareAddress())); + assertTrue(Arrays.equals(targetIp.toOctets(), arp.getTargetProtocolAddress())); + } +} diff --git a/framework/src/onos/utils/misc/src/test/java/org/onlab/packet/DhcpTest.java b/framework/src/onos/utils/misc/src/test/java/org/onlab/packet/DhcpTest.java new file mode 100644 index 00000000..aac81412 --- /dev/null +++ b/framework/src/onos/utils/misc/src/test/java/org/onlab/packet/DhcpTest.java @@ -0,0 +1,137 @@ +/* + * 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 com.google.common.base.Charsets; +import org.junit.Before; +import org.junit.Test; + +import java.nio.ByteBuffer; +import java.util.Arrays; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +/** + * Unit tests for DHCP class. + */ +public class DhcpTest { + + private Deserializer<DHCP> deserializer = DHCP.deserializer(); + + private byte opCode = 1; + private byte hardwareType = 1; + private byte hardwareAddressLength = Ethernet.DATALAYER_ADDRESS_LENGTH; + private byte hops = 0; + private int transactionId = 0x2ed4eb50; + private short seconds = 0; + private short flags = 0; + private int clientIpAddress = 1; + private int yourIpAddress = 2; + private int serverIpAddress = 3; + private int gatewayIpAddress = 4; + private byte[] clientHardwareAddress = MacAddress.valueOf(500).toBytes(); + private String serverName = "test-server"; + private String bootFileName = "test-file"; + + private String hostName = "test-host"; + private DHCPOption hostNameOption = new DHCPOption(); + + private byte[] byteHeader; + + @Before + public void setUp() { + hostNameOption.setCode((byte) 55); + hostNameOption.setLength((byte) hostName.length()); + hostNameOption.setData(hostName.getBytes(Charsets.US_ASCII)); + + // Packet length is the fixed DHCP header plus option length plus an + // extra byte to indicate 'end of options'. + ByteBuffer bb = ByteBuffer.allocate(DHCP.MIN_HEADER_LENGTH + + 2 + hostNameOption.getLength() + 1); + + bb.put(opCode); + bb.put(hardwareType); + bb.put(hardwareAddressLength); + bb.put(hops); + bb.putInt(transactionId); + bb.putShort(seconds); + bb.putShort(flags); + bb.putInt(clientIpAddress); + bb.putInt(yourIpAddress); + bb.putInt(serverIpAddress); + bb.putInt(gatewayIpAddress); + bb.put(clientHardwareAddress); + + // need 16 bytes of zeros to pad out the client hardware address field + bb.put(new byte[16 - hardwareAddressLength]); + + // Put server name and pad out to 64 bytes + bb.put(serverName.getBytes(Charsets.US_ASCII)); + bb.put(new byte[64 - serverName.length()]); + + // Put boot file name and pad out to 128 bytes + bb.put(bootFileName.getBytes(Charsets.US_ASCII)); + bb.put(new byte[128 - bootFileName.length()]); + + // Magic cookie + bb.put("DHCP".getBytes(Charsets.US_ASCII)); + + bb.put(hostNameOption.getCode()); + bb.put(hostNameOption.getLength()); + bb.put(hostNameOption.getData()); + + // End of options marker + bb.put((byte) (0xff & 255)); + + byteHeader = bb.array(); + } + + @Test + public void testDeserializeBadInput() throws Exception { + PacketTestUtils.testDeserializeBadInput(deserializer); + } + + @Test + public void testDeserializeTruncated() throws Exception { + PacketTestUtils.testDeserializeTruncated(deserializer, byteHeader); + } + + @Test + public void testDeserialize() throws Exception { + DHCP dhcp = deserializer.deserialize(byteHeader, 0, byteHeader.length); + + assertEquals(opCode, dhcp.opCode); + assertEquals(hardwareType, dhcp.hardwareType); + assertEquals(hardwareAddressLength, dhcp.hardwareAddressLength); + assertEquals(hops, dhcp.hops); + assertEquals(transactionId, dhcp.transactionId); + assertEquals(seconds, dhcp.seconds); + assertEquals(flags, dhcp.flags); + assertEquals(clientIpAddress, dhcp.clientIPAddress); + assertEquals(yourIpAddress, dhcp.yourIPAddress); + assertEquals(serverIpAddress, dhcp.serverIPAddress); + assertEquals(gatewayIpAddress, dhcp.gatewayIPAddress); + assertTrue(Arrays.equals(clientHardwareAddress, dhcp.clientHardwareAddress)); + + assertEquals(serverName, dhcp.serverName); + assertEquals(bootFileName, dhcp.bootFileName); + assertEquals(2, dhcp.options.size()); + assertEquals(hostNameOption, dhcp.options.get(0)); + } + +} diff --git a/framework/src/onos/utils/misc/src/test/java/org/onlab/packet/EthernetTest.java b/framework/src/onos/utils/misc/src/test/java/org/onlab/packet/EthernetTest.java new file mode 100644 index 00000000..15a01fc3 --- /dev/null +++ b/framework/src/onos/utils/misc/src/test/java/org/onlab/packet/EthernetTest.java @@ -0,0 +1,103 @@ +/* + * 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 org.junit.Before; +import org.junit.Test; + +import java.nio.ByteBuffer; + +import static org.junit.Assert.assertEquals; + +/** + * Unit tests for the Ethernet class. + */ +public class EthernetTest { + + private MacAddress dstMac; + private MacAddress srcMac; + private short ethertype = 6; + private short vlan = 5; + + private Deserializer<Ethernet> deserializer; + + private byte[] byteHeader; + private byte[] vlanByteHeader; + + @Before + public void setUp() { + deserializer = Ethernet.deserializer(); + + byte[] dstMacBytes = { + (byte) 0x88, (byte) 0x88, (byte) 0x88, (byte) 0x88, (byte) 0x88, + (byte) 0x88 }; + dstMac = MacAddress.valueOf(dstMacBytes); + byte[] srcMacBytes = { + (byte) 0xaa, (byte) 0xaa, (byte) 0xaa, (byte) 0xaa, (byte) 0xaa, + (byte) 0xaa }; + srcMac = MacAddress.valueOf(srcMacBytes); + + // Create Ethernet byte array with no VLAN header + ByteBuffer bb = ByteBuffer.allocate(Ethernet.ETHERNET_HEADER_LENGTH); + bb.put(dstMacBytes); + bb.put(srcMacBytes); + bb.putShort(ethertype); + + byteHeader = bb.array(); + + // Create Ethernet byte array with a VLAN header + bb = ByteBuffer.allocate(Ethernet.ETHERNET_HEADER_LENGTH + Ethernet.VLAN_HEADER_LENGTH); + bb.put(dstMacBytes); + bb.put(srcMacBytes); + bb.putShort(Ethernet.TYPE_VLAN); + bb.putShort(vlan); + bb.putShort(ethertype); + + vlanByteHeader = bb.array(); + } + + @Test + public void testDeserializeBadInput() throws Exception { + PacketTestUtils.testDeserializeBadInput(deserializer); + } + + @Test + public void testDeserializeTruncated() throws DeserializationException { + PacketTestUtils.testDeserializeTruncated(deserializer, vlanByteHeader); + } + + @Test + public void testDeserializeNoVlan() throws Exception { + Ethernet eth = deserializer.deserialize(byteHeader, 0, byteHeader.length); + + assertEquals(dstMac, eth.getDestinationMAC()); + assertEquals(srcMac, eth.getSourceMAC()); + assertEquals(Ethernet.VLAN_UNTAGGED, eth.getVlanID()); + assertEquals(ethertype, eth.getEtherType()); + } + + @Test + public void testDeserializeWithVlan() throws Exception { + Ethernet eth = deserializer.deserialize(vlanByteHeader, 0, vlanByteHeader.length); + + assertEquals(dstMac, eth.getDestinationMAC()); + assertEquals(srcMac, eth.getSourceMAC()); + assertEquals(vlan, eth.getVlanID()); + assertEquals(ethertype, eth.getEtherType()); + } + +} diff --git a/framework/src/onos/utils/misc/src/test/java/org/onlab/packet/ICMP6Test.java b/framework/src/onos/utils/misc/src/test/java/org/onlab/packet/ICMP6Test.java new file mode 100644 index 00000000..39ddc24c --- /dev/null +++ b/framework/src/onos/utils/misc/src/test/java/org/onlab/packet/ICMP6Test.java @@ -0,0 +1,110 @@ +/* + * Copyright 2014-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 org.junit.BeforeClass; +import org.junit.Test; + +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + +/** + * Tests for class {@link ICMP6}. + */ +public class ICMP6Test { + private static final byte[] IPV6_SOURCE_ADDRESS = { + (byte) 0xfe, (byte) 0x80, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x01 + }; + private static final byte[] IPV6_DESTINATION_ADDRESS = { + (byte) 0xfe, (byte) 0x80, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x02 + }; + + private static IPv6 ipv6 = new IPv6(); + private static byte[] bytePacket = { + ICMP6.ECHO_REQUEST, // type + (byte) 0x00, // code + (byte) 0x82, (byte) 0xbc, // checksum + }; + + @BeforeClass + public static void setUpBeforeClass() throws Exception { + ipv6.setSourceAddress(IPV6_SOURCE_ADDRESS); + ipv6.setDestinationAddress(IPV6_DESTINATION_ADDRESS); + ipv6.setNextHeader(IPv6.PROTOCOL_ICMP6); + } + + /** + * Tests serialize and setters. + */ + @Test + public void testSerialize() { + ICMP6 icmp6 = new ICMP6(); + icmp6.setIcmpType(ICMP6.ECHO_REQUEST); + icmp6.setIcmpCode((byte) 0); + icmp6.setParent(ipv6); + + assertArrayEquals(bytePacket, icmp6.serialize()); + } + + @Test + public void testDeserializeBadInput() throws Exception { + PacketTestUtils.testDeserializeBadInput(ICMP6.deserializer()); + } + + @Test + public void testDeserializeTruncated() throws Exception { + PacketTestUtils.testDeserializeTruncated(ICMP6.deserializer(), bytePacket); + } + + /** + * Tests deserialize and getters. + */ + @Test + public void testDeserialize() throws Exception { + ICMP6 icmp6 = ICMP6.deserializer().deserialize(bytePacket, 0, bytePacket.length); + + assertThat(icmp6.getIcmpType(), is(ICMP6.ECHO_REQUEST)); + assertThat(icmp6.getIcmpCode(), is((byte) 0x00)); + assertThat(icmp6.getChecksum(), is((short) 0x82bc)); + } + + /** + * Tests comparator. + */ + @Test + public void testEqual() { + ICMP6 icmp61 = new ICMP6(); + icmp61.setIcmpType(ICMP6.ECHO_REQUEST); + icmp61.setIcmpCode((byte) 0); + icmp61.setChecksum((short) 0); + + ICMP6 icmp62 = new ICMP6(); + icmp62.setIcmpType(ICMP6.ECHO_REPLY); + icmp62.setIcmpCode((byte) 0); + icmp62.setChecksum((short) 0); + + assertTrue(icmp61.equals(icmp61)); + assertFalse(icmp61.equals(icmp62)); + } +} diff --git a/framework/src/onos/utils/misc/src/test/java/org/onlab/packet/ICMPTest.java b/framework/src/onos/utils/misc/src/test/java/org/onlab/packet/ICMPTest.java new file mode 100644 index 00000000..8514aa59 --- /dev/null +++ b/framework/src/onos/utils/misc/src/test/java/org/onlab/packet/ICMPTest.java @@ -0,0 +1,70 @@ +/* + * 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 org.junit.Before; +import org.junit.Test; + +import java.nio.ByteBuffer; + +import static org.junit.Assert.assertEquals; + +/** + * Unit tests for the ICMP class. + */ +public class ICMPTest { + + private Deserializer<ICMP> deserializer; + + private byte icmpType = ICMP.TYPE_ECHO_REQUEST; + private byte icmpCode = 4; + private short checksum = 870; + + private byte[] headerBytes; + + @Before + public void setUp() throws Exception { + deserializer = ICMP.deserializer(); + + ByteBuffer bb = ByteBuffer.allocate(ICMP.ICMP_HEADER_LENGTH); + + bb.put(icmpType); + bb.put(icmpCode); + bb.putShort(checksum); + + headerBytes = bb.array(); + } + + @Test + public void testDeserializeBadInput() throws Exception { + PacketTestUtils.testDeserializeBadInput(deserializer); + } + + @Test + public void testDeserializeTruncated() throws Exception { + PacketTestUtils.testDeserializeTruncated(deserializer, headerBytes); + } + + @Test + public void testDeserialize() throws Exception { + ICMP icmp = deserializer.deserialize(headerBytes, 0, headerBytes.length); + + assertEquals(icmpType, icmp.getIcmpType()); + assertEquals(icmpCode, icmp.getIcmpCode()); + assertEquals(checksum, icmp.getChecksum()); + } +} diff --git a/framework/src/onos/utils/misc/src/test/java/org/onlab/packet/IGMPTest.java b/framework/src/onos/utils/misc/src/test/java/org/onlab/packet/IGMPTest.java new file mode 100644 index 00000000..a25f7217 --- /dev/null +++ b/framework/src/onos/utils/misc/src/test/java/org/onlab/packet/IGMPTest.java @@ -0,0 +1,96 @@ +/* + * 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 org.junit.Before; +import org.junit.Test; + +import static junit.framework.Assert.assertTrue; + +/** + * Unit tests for IGMP class. + */ +public class IGMPTest { + private Deserializer<IGMP> deserializer; + + private IGMP igmpQuery; + private IGMP igmpMembership; + + private Ip4Address gaddr1; + private Ip4Address gaddr2; + private Ip4Address saddr1; + private Ip4Address saddr2; + + @Before + public void setUp() throws Exception { + gaddr1 = Ip4Address.valueOf(0xe1010101); + gaddr2 = Ip4Address.valueOf(0xe2020202); + saddr1 = Ip4Address.valueOf(0x0a010101); + saddr2 = Ip4Address.valueOf(0x0b020202); + + deserializer = IGMP.deserializer(); + + // Create an IGMP Query object + igmpQuery = new IGMP(); + igmpQuery.setIgmpType(IGMP.TYPE_IGMPV3_MEMBERSHIP_QUERY); + igmpQuery.setMaxRespCode((byte) 0x7f); + IGMPQuery q = new IGMPQuery(gaddr1, (byte) 0x7f); + q.addSource(saddr1); + q.addSource(saddr2); + q.setSbit(false); + igmpQuery.groups.add(q); + + // Create an IGMP Membership Object + igmpMembership = new IGMP(); + igmpMembership.setIgmpType(IGMP.TYPE_IGMPV3_MEMBERSHIP_REPORT); + IGMPMembership g1 = new IGMPMembership(gaddr1); + g1.addSource(saddr1); + g1.addSource(saddr2); + igmpMembership.groups.add(g1); + IGMPMembership g2 = new IGMPMembership(gaddr2); + g2.addSource(saddr1); + g2.addSource(saddr2); + igmpMembership.groups.add(g2); + } + + @Test + public void testDeserializeBadInput() throws Exception { + PacketTestUtils.testDeserializeBadInput(deserializer); + } + + @Test + public void testDeserializeTruncated() throws Exception { + byte [] bits = igmpQuery.serialize(); + PacketTestUtils.testDeserializeTruncated(deserializer, bits); + + bits = igmpMembership.serialize(); + PacketTestUtils.testDeserializeTruncated(deserializer, bits); + } + + @Test + public void testDeserializeQuery() throws Exception { + byte [] data = igmpQuery.serialize(); + IGMP igmp = deserializer.deserialize(data, 0, data.length); + assertTrue(igmp.equals(igmpQuery)); + } + + @Test + public void testDeserializeMembership() throws Exception { + byte [] data = igmpMembership.serialize(); + IGMP igmp = deserializer.deserialize(data, 0, data.length); + assertTrue(igmp.equals(igmpMembership)); + } +} diff --git a/framework/src/onos/utils/misc/src/test/java/org/onlab/packet/IPv4Test.java b/framework/src/onos/utils/misc/src/test/java/org/onlab/packet/IPv4Test.java new file mode 100644 index 00000000..1bacf2a2 --- /dev/null +++ b/framework/src/onos/utils/misc/src/test/java/org/onlab/packet/IPv4Test.java @@ -0,0 +1,99 @@ +/* + * 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 org.junit.Before; +import org.junit.Test; + +import java.nio.ByteBuffer; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +/** + * Unit tests for IPv4 class. + */ +public class IPv4Test { + + private Deserializer<IPv4> deserializer; + + private byte version = 4; + private byte headerLength = 6; + private byte diffServ = 2; + private short totalLength = 20; + private short identification = 1; + private byte flags = 1; + private short fragmentOffset = 1; + private byte ttl = 60; + private byte protocol = 4; + private short checksum = 4; + private int sourceAddress = 1; + private int destinationAddress = 2; + private byte[] options = new byte[] {0x1, 0x2, 0x3, 0x4}; + + private byte[] headerBytes; + + @Before + public void setUp() throws Exception { + deserializer = IPv4.deserializer(); + + ByteBuffer bb = ByteBuffer.allocate(headerLength * 4); + + bb.put((byte) ((version & 0xf) << 4 | headerLength & 0xf)); + bb.put(diffServ); + bb.putShort(totalLength); + bb.putShort(identification); + bb.putShort((short) ((flags & 0x7) << 13 | fragmentOffset & 0x1fff)); + bb.put(ttl); + bb.put(protocol); + bb.putShort(checksum); + bb.putInt(sourceAddress); + bb.putInt(destinationAddress); + bb.put(options); + + headerBytes = bb.array(); + } + + @Test + public void testDeserializeBadInput() throws Exception { + PacketTestUtils.testDeserializeBadInput(deserializer); + } + + @Test + public void testDeserializeTruncated() throws Exception { + PacketTestUtils.testDeserializeTruncated(deserializer, headerBytes); + } + + @Test + public void testDeserialize() throws Exception { + IPv4 ipv4 = deserializer.deserialize(headerBytes, 0, headerBytes.length); + + assertEquals(version, ipv4.getVersion()); + assertEquals(headerLength, ipv4.getHeaderLength()); + assertEquals(diffServ, ipv4.getDiffServ()); + assertEquals(totalLength, ipv4.getTotalLength()); + assertEquals(identification, ipv4.getIdentification()); + assertEquals(flags, ipv4.getFlags()); + assertEquals(fragmentOffset, ipv4.getFragmentOffset()); + assertEquals(ttl, ipv4.getTtl()); + assertEquals(protocol, ipv4.getProtocol()); + assertEquals(checksum, ipv4.getChecksum()); + assertEquals(sourceAddress, ipv4.getSourceAddress()); + assertEquals(destinationAddress, ipv4.getDestinationAddress()); + assertTrue(ipv4.isTruncated()); + } +} diff --git a/framework/src/onos/utils/misc/src/test/java/org/onlab/packet/IPv6Test.java b/framework/src/onos/utils/misc/src/test/java/org/onlab/packet/IPv6Test.java new file mode 100644 index 00000000..720a4d20 --- /dev/null +++ b/framework/src/onos/utils/misc/src/test/java/org/onlab/packet/IPv6Test.java @@ -0,0 +1,154 @@ +/* + * Copyright 2014-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 org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +import java.nio.ByteBuffer; + +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + +/** + * Tests for class {@link IPv6}. + */ +public class IPv6Test { + private static final byte[] SOURCE_ADDRESS = { + (byte) 0x20, (byte) 0x01, (byte) 0x0f, (byte) 0x18, (byte) 0x01, (byte) 0x13, (byte) 0x02, (byte) 0x15, + (byte) 0xca, (byte) 0x2a, (byte) 0x14, (byte) 0xff, (byte) 0xfe, (byte) 0x35, (byte) 0x26, (byte) 0xce + }; + private static final byte[] DESTINATION_ADDRESS = { + (byte) 0x20, (byte) 0x01, (byte) 0x0f, (byte) 0x18, (byte) 0x01, (byte) 0x13, (byte) 0x02, (byte) 0x15, + (byte) 0xe6, (byte) 0xce, (byte) 0x8f, (byte) 0xff, (byte) 0xfe, (byte) 0x54, (byte) 0x37, (byte) 0xc8 + }; + private static Data data; + private static UDP udp; + private static byte[] bytePacket; + + private Deserializer<IPv6> deserializer; + + @BeforeClass + public static void setUpBeforeClass() throws Exception { + data = new Data(); + data.setData("testSerialize".getBytes()); + udp = new UDP(); + udp.setPayload(data); + + byte[] bytePayload = udp.serialize(); + byte[] byteHeader = { + (byte) 0x69, (byte) 0x31, (byte) 0x35, (byte) 0x79, + (byte) (bytePayload.length >> 8 & 0xff), (byte) (bytePayload.length & 0xff), + (byte) 0x11, (byte) 0x20, + (byte) 0x20, (byte) 0x01, (byte) 0x0f, (byte) 0x18, (byte) 0x01, (byte) 0x13, (byte) 0x02, (byte) 0x15, + (byte) 0xca, (byte) 0x2a, (byte) 0x14, (byte) 0xff, (byte) 0xfe, (byte) 0x35, (byte) 0x26, (byte) 0xce, + (byte) 0x20, (byte) 0x01, (byte) 0x0f, (byte) 0x18, (byte) 0x01, (byte) 0x13, (byte) 0x02, (byte) 0x15, + (byte) 0xe6, (byte) 0xce, (byte) 0x8f, (byte) 0xff, (byte) 0xfe, (byte) 0x54, (byte) 0x37, (byte) 0xc8, + }; + bytePacket = new byte[byteHeader.length + bytePayload.length]; + System.arraycopy(byteHeader, 0, bytePacket, 0, byteHeader.length); + System.arraycopy(bytePayload, 0, bytePacket, byteHeader.length, bytePayload.length); + } + + @Before + public void setUp() { + deserializer = IPv6.deserializer(); + } + + /** + * Tests serialize and setters. + */ + @Test + public void testSerialize() { + IPv6 ipv6 = new IPv6(); + ipv6.setPayload(udp); + ipv6.setVersion((byte) 6); + ipv6.setTrafficClass((byte) 0x93); + ipv6.setFlowLabel(0x13579); + ipv6.setNextHeader(IPv6.PROTOCOL_UDP); + ipv6.setHopLimit((byte) 32); + ipv6.setSourceAddress(SOURCE_ADDRESS); + ipv6.setDestinationAddress(DESTINATION_ADDRESS); + + assertArrayEquals(ipv6.serialize(), bytePacket); + } + + @Test + public void testDeserializeBadInput() throws Exception { + PacketTestUtils.testDeserializeBadInput(deserializer); + } + + @Test + public void testDeserializeTruncated() throws Exception { + // Run the truncation test only on the IPv6 header + byte[] ipv6Header = new byte[IPv6.FIXED_HEADER_LENGTH]; + ByteBuffer.wrap(bytePacket).get(ipv6Header); + + PacketTestUtils.testDeserializeTruncated(deserializer, ipv6Header); + } + + /** + * Tests deserialize and getters. + */ + @Test + public void testDeserialize() throws DeserializationException { + IPv6 ipv6 = deserializer.deserialize(bytePacket, 0, bytePacket.length); + + assertThat(ipv6.getVersion(), is((byte) 6)); + assertThat(ipv6.getTrafficClass(), is((byte) 0x93)); + assertThat(ipv6.getFlowLabel(), is(0x13579)); + assertThat(ipv6.getNextHeader(), is(IPv6.PROTOCOL_UDP)); + assertThat(ipv6.getHopLimit(), is((byte) 32)); + assertArrayEquals(ipv6.getSourceAddress(), SOURCE_ADDRESS); + assertArrayEquals(ipv6.getDestinationAddress(), DESTINATION_ADDRESS); + } + + /** + * Tests comparator. + */ + @Test + public void testEqual() { + IPv6 packet1 = new IPv6(); + packet1.setPayload(udp); + packet1.setVersion((byte) 6); + packet1.setTrafficClass((byte) 0x93); + packet1.setFlowLabel(0x13579); + packet1.setNextHeader(IPv6.PROTOCOL_UDP); + packet1.setHopLimit((byte) 32); + packet1.setSourceAddress(SOURCE_ADDRESS); + packet1.setDestinationAddress(DESTINATION_ADDRESS); + + IPv6 packet2 = new IPv6(); + packet2.setPayload(udp); + packet2.setVersion((byte) 6); + packet2.setTrafficClass((byte) 0x93); + packet2.setFlowLabel(0x13579); + packet2.setNextHeader(IPv6.PROTOCOL_UDP); + packet2.setHopLimit((byte) 32); + packet2.setSourceAddress(DESTINATION_ADDRESS); + packet2.setDestinationAddress(SOURCE_ADDRESS); + + assertTrue(packet1.equals(packet1)); + assertFalse(packet1.equals(packet2)); + } +} diff --git a/framework/src/onos/utils/misc/src/test/java/org/onlab/packet/Ip4AddressTest.java b/framework/src/onos/utils/misc/src/test/java/org/onlab/packet/Ip4AddressTest.java new file mode 100644 index 00000000..3bbf0009 --- /dev/null +++ b/framework/src/onos/utils/misc/src/test/java/org/onlab/packet/Ip4AddressTest.java @@ -0,0 +1,432 @@ +/* + * Copyright 2014 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 com.google.common.net.InetAddresses; +import com.google.common.testing.EqualsTester; +import org.junit.Test; + +import java.net.InetAddress; + +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; +import static org.onlab.junit.ImmutableClassChecker.assertThatClassIsImmutable; + +/** + * Tests for class {@link Ip4Address}. + */ +public class Ip4AddressTest { + /** + * Tests the immutability of {@link Ip4Address}. + */ + @Test + public void testImmutable() { + assertThatClassIsImmutable(Ip4Address.class); + } + + /** + * Tests the IPv4 address version constant. + */ + @Test + public void testAddressVersion() { + assertThat(Ip4Address.VERSION, is(IpAddress.Version.INET)); + } + + /** + * Tests the length of the address in bytes (octets). + */ + @Test + public void testAddrByteLength() { + assertThat(Ip4Address.BYTE_LENGTH, is(4)); + } + + /** + * Tests the length of the address in bits. + */ + @Test + public void testAddrBitLength() { + assertThat(Ip4Address.BIT_LENGTH, is(32)); + } + + /** + * Tests returning the IP address version. + */ + @Test + public void testVersion() { + Ip4Address ipAddress; + + // IPv4 + ipAddress = Ip4Address.valueOf("0.0.0.0"); + assertThat(ipAddress.version(), is(IpAddress.Version.INET)); + } + + /** + * Tests returning an IPv4 address as a byte array. + */ + @Test + public void testAddressToOctetsIPv4() { + Ip4Address ipAddress; + byte[] value; + + value = new byte[] {1, 2, 3, 4}; + ipAddress = Ip4Address.valueOf("1.2.3.4"); + assertThat(ipAddress.toOctets(), is(value)); + + value = new byte[] {0, 0, 0, 0}; + ipAddress = Ip4Address.valueOf("0.0.0.0"); + assertThat(ipAddress.toOctets(), is(value)); + + value = new byte[] {(byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff}; + ipAddress = Ip4Address.valueOf("255.255.255.255"); + assertThat(ipAddress.toOctets(), is(value)); + } + + /** + * Tests returning an IPv4 address as an integer. + */ + @Test + public void testToInt() { + Ip4Address ipAddress; + + ipAddress = Ip4Address.valueOf("1.2.3.4"); + assertThat(ipAddress.toInt(), is(0x01020304)); + + ipAddress = Ip4Address.valueOf("0.0.0.0"); + assertThat(ipAddress.toInt(), is(0)); + + ipAddress = Ip4Address.valueOf("255.255.255.255"); + assertThat(ipAddress.toInt(), is(-1)); + } + + /** + * Tests valueOf() converter for IPv4 integer value. + */ + @Test + public void testValueOfForIntegerIPv4() { + Ip4Address ipAddress; + + ipAddress = Ip4Address.valueOf(0x01020304); + assertThat(ipAddress.toString(), is("1.2.3.4")); + + ipAddress = Ip4Address.valueOf(0); + assertThat(ipAddress.toString(), is("0.0.0.0")); + + ipAddress = Ip4Address.valueOf(0xffffffff); + assertThat(ipAddress.toString(), is("255.255.255.255")); + } + + /** + * Tests valueOf() converter for IPv4 byte array. + */ + @Test + public void testValueOfByteArrayIPv4() { + Ip4Address ipAddress; + byte[] value; + + value = new byte[] {1, 2, 3, 4}; + ipAddress = Ip4Address.valueOf(value); + assertThat(ipAddress.toString(), is("1.2.3.4")); + + value = new byte[] {0, 0, 0, 0}; + ipAddress = Ip4Address.valueOf(value); + assertThat(ipAddress.toString(), is("0.0.0.0")); + + value = new byte[] {(byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff}; + ipAddress = Ip4Address.valueOf(value); + assertThat(ipAddress.toString(), is("255.255.255.255")); + } + + /** + * Tests invalid valueOf() converter for a null array for IPv4. + */ + @Test(expected = NullPointerException.class) + public void testInvalidValueOfNullArrayIPv4() { + Ip4Address ipAddress; + byte[] value; + + value = null; + ipAddress = Ip4Address.valueOf(value); + } + + /** + * Tests invalid valueOf() converger for an array that is too short for + * IPv4. + */ + @Test(expected = IllegalArgumentException.class) + public void testInvalidValueOfShortArrayIPv4() { + Ip4Address ipAddress; + byte[] value; + + value = new byte[] {1, 2, 3}; + ipAddress = Ip4Address.valueOf(value); + } + + /** + * Tests valueOf() converter for IPv4 byte array and an offset. + */ + @Test + public void testValueOfByteArrayOffsetIPv4() { + Ip4Address ipAddress; + byte[] value; + + value = new byte[] {11, 22, 33, // Preamble + 1, 2, 3, 4, + 44, 55}; // Extra bytes + ipAddress = Ip4Address.valueOf(value, 3); + assertThat(ipAddress.toString(), is("1.2.3.4")); + + value = new byte[] {11, 22, // Preamble + 0, 0, 0, 0, + 33}; // Extra bytes + ipAddress = Ip4Address.valueOf(value, 2); + assertThat(ipAddress.toString(), is("0.0.0.0")); + + value = new byte[] {11, 22, // Preamble + (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, + 33}; // Extra bytes + ipAddress = Ip4Address.valueOf(value, 2); + assertThat(ipAddress.toString(), is("255.255.255.255")); + } + + /** + * Tests invalid valueOf() converger for an array and an invalid offset + * for IPv4. + */ + @Test(expected = IllegalArgumentException.class) + public void testInvalidValueOfArrayInvalidOffsetIPv4() { + Ip4Address ipAddress; + byte[] value; + + value = new byte[] {11, 22, 33, // Preamble + 1, 2, 3, 4, + 44, 55}; // Extra bytes + ipAddress = Ip4Address.valueOf(value, 6); + } + + /** + * Tests valueOf() converter for IPv4 InetAddress. + */ + @Test + public void testValueOfInetAddressIPv4() { + Ip4Address ipAddress; + InetAddress inetAddress; + + inetAddress = InetAddresses.forString("1.2.3.4"); + ipAddress = Ip4Address.valueOf(inetAddress); + assertThat(ipAddress.toString(), is("1.2.3.4")); + + inetAddress = InetAddresses.forString("0.0.0.0"); + ipAddress = Ip4Address.valueOf(inetAddress); + assertThat(ipAddress.toString(), is("0.0.0.0")); + + inetAddress = InetAddresses.forString("255.255.255.255"); + ipAddress = Ip4Address.valueOf(inetAddress); + assertThat(ipAddress.toString(), is("255.255.255.255")); + } + + /** + * Tests valueOf() converter for IPv4 string. + */ + @Test + public void testValueOfStringIPv4() { + Ip4Address ipAddress; + + ipAddress = Ip4Address.valueOf("1.2.3.4"); + assertThat(ipAddress.toString(), is("1.2.3.4")); + + ipAddress = Ip4Address.valueOf("0.0.0.0"); + assertThat(ipAddress.toString(), is("0.0.0.0")); + + ipAddress = Ip4Address.valueOf("255.255.255.255"); + assertThat(ipAddress.toString(), is("255.255.255.255")); + } + + /** + * Tests invalid valueOf() converter for a null string. + */ + @Test(expected = NullPointerException.class) + public void testInvalidValueOfNullString() { + Ip4Address ipAddress; + + String fromString = null; + ipAddress = Ip4Address.valueOf(fromString); + } + + /** + * Tests invalid valueOf() converter for an empty string. + */ + @Test(expected = IllegalArgumentException.class) + public void testInvalidValueOfEmptyString() { + Ip4Address ipAddress; + + String fromString = ""; + ipAddress = Ip4Address.valueOf(fromString); + } + + /** + * Tests invalid valueOf() converter for an incorrect string. + */ + @Test(expected = IllegalArgumentException.class) + public void testInvalidValueOfIncorrectString() { + Ip4Address ipAddress; + + String fromString = "NoSuchIpAddress"; + ipAddress = Ip4Address.valueOf(fromString); + } + + /** + * Tests making a mask prefix for a given prefix length for IPv4. + */ + @Test + public void testMakeMaskPrefixIPv4() { + Ip4Address ipAddress; + + ipAddress = Ip4Address.makeMaskPrefix(25); + assertThat(ipAddress.toString(), is("255.255.255.128")); + + ipAddress = Ip4Address.makeMaskPrefix(0); + assertThat(ipAddress.toString(), is("0.0.0.0")); + + ipAddress = Ip4Address.makeMaskPrefix(32); + assertThat(ipAddress.toString(), is("255.255.255.255")); + } + + /** + * Tests making a mask prefix for an invalid prefix length for IPv4: + * negative prefix length. + */ + @Test(expected = IllegalArgumentException.class) + public void testInvalidMakeNegativeMaskPrefixIPv4() { + Ip4Address ipAddress; + + ipAddress = Ip4Address.makeMaskPrefix(-1); + } + + /** + * Tests making a mask prefix for an invalid prefix length for IPv4: + * too long prefix length. + */ + @Test(expected = IllegalArgumentException.class) + public void testInvalidMakeTooLongMaskPrefixIPv4() { + Ip4Address ipAddress; + + ipAddress = Ip4Address.makeMaskPrefix(33); + } + + /** + * Tests making of a masked address for IPv4. + */ + @Test + public void testMakeMaskedAddressIPv4() { + Ip4Address ipAddress = Ip4Address.valueOf("1.2.3.5"); + Ip4Address ipAddressMasked; + + ipAddressMasked = Ip4Address.makeMaskedAddress(ipAddress, 24); + assertThat(ipAddressMasked.toString(), is("1.2.3.0")); + + ipAddressMasked = Ip4Address.makeMaskedAddress(ipAddress, 0); + assertThat(ipAddressMasked.toString(), is("0.0.0.0")); + + ipAddressMasked = Ip4Address.makeMaskedAddress(ipAddress, 32); + assertThat(ipAddressMasked.toString(), is("1.2.3.5")); + } + + /** + * Tests making of a masked address for invalid prefix length for IPv4: + * negative prefix length. + */ + @Test(expected = IllegalArgumentException.class) + public void testInvalidMakeNegativeMaskedAddressIPv4() { + Ip4Address ipAddress = Ip4Address.valueOf("1.2.3.5"); + Ip4Address ipAddressMasked; + + ipAddressMasked = Ip4Address.makeMaskedAddress(ipAddress, -1); + } + + /** + * Tests making of a masked address for an invalid prefix length for IPv4: + * too long prefix length. + */ + @Test(expected = IllegalArgumentException.class) + public void testInvalidMakeTooLongMaskedAddressIPv4() { + Ip4Address ipAddress = Ip4Address.valueOf("1.2.3.5"); + Ip4Address ipAddressMasked; + + ipAddressMasked = Ip4Address.makeMaskedAddress(ipAddress, 33); + } + + /** + * Tests comparison of {@link Ip4Address} for IPv4. + */ + @Test + public void testComparisonIPv4() { + Ip4Address addr1, addr2, addr3, addr4; + + addr1 = Ip4Address.valueOf("1.2.3.4"); + addr2 = Ip4Address.valueOf("1.2.3.4"); + addr3 = Ip4Address.valueOf("1.2.3.3"); + addr4 = Ip4Address.valueOf("1.2.3.5"); + assertTrue(addr1.compareTo(addr2) == 0); + assertTrue(addr1.compareTo(addr3) > 0); + assertTrue(addr1.compareTo(addr4) < 0); + + addr1 = Ip4Address.valueOf("255.2.3.4"); + addr2 = Ip4Address.valueOf("255.2.3.4"); + addr3 = Ip4Address.valueOf("255.2.3.3"); + addr4 = Ip4Address.valueOf("255.2.3.5"); + assertTrue(addr1.compareTo(addr2) == 0); + assertTrue(addr1.compareTo(addr3) > 0); + assertTrue(addr1.compareTo(addr4) < 0); + } + + /** + * Tests equality of {@link Ip4Address} for IPv4. + */ + @Test + public void testEqualityIPv4() { + new EqualsTester() + .addEqualityGroup(Ip4Address.valueOf("1.2.3.4"), + Ip4Address.valueOf("1.2.3.4")) + .addEqualityGroup(Ip4Address.valueOf("1.2.3.5"), + Ip4Address.valueOf("1.2.3.5")) + .addEqualityGroup(Ip4Address.valueOf("0.0.0.0"), + Ip4Address.valueOf("0.0.0.0")) + .addEqualityGroup(Ip4Address.valueOf("255.255.255.255"), + Ip4Address.valueOf("255.255.255.255")) + .testEquals(); + } + + /** + * Tests object string representation for IPv4. + */ + @Test + public void testToStringIPv4() { + Ip4Address ipAddress; + + ipAddress = Ip4Address.valueOf("1.2.3.4"); + assertThat(ipAddress.toString(), is("1.2.3.4")); + + ipAddress = Ip4Address.valueOf("0.0.0.0"); + assertThat(ipAddress.toString(), is("0.0.0.0")); + + ipAddress = Ip4Address.valueOf("255.255.255.255"); + assertThat(ipAddress.toString(), is("255.255.255.255")); + } +} diff --git a/framework/src/onos/utils/misc/src/test/java/org/onlab/packet/Ip4PrefixTest.java b/framework/src/onos/utils/misc/src/test/java/org/onlab/packet/Ip4PrefixTest.java new file mode 100644 index 00000000..c731ed66 --- /dev/null +++ b/framework/src/onos/utils/misc/src/test/java/org/onlab/packet/Ip4PrefixTest.java @@ -0,0 +1,534 @@ +/* + * Copyright 2014 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 com.google.common.testing.EqualsTester; +import org.junit.Test; + +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.onlab.junit.ImmutableClassChecker.assertThatClassIsImmutable; + +/** + * Tests for class {@link Ip4Prefix}. + */ +public class Ip4PrefixTest { + /** + * Tests the immutability of {@link Ip4Prefix}. + */ + @Test + public void testImmutable() { + assertThatClassIsImmutable(Ip4Prefix.class); + } + + /** + * Tests the IPv4 prefix address version constant. + */ + @Test + public void testAddressVersion() { + assertThat(Ip4Prefix.VERSION, is(IpAddress.Version.INET)); + } + + /** + * Tests the maximum mask length. + */ + @Test + public void testMaxMaskLength() { + assertThat(Ip4Prefix.MAX_MASK_LENGTH, is(32)); + } + + /** + * Tests returning the IP version of the prefix. + */ + @Test + public void testVersion() { + Ip4Prefix ipPrefix; + + // IPv4 + ipPrefix = Ip4Prefix.valueOf("0.0.0.0/0"); + assertThat(ipPrefix.version(), is(IpAddress.Version.INET)); + } + + /** + * Tests returning the IP address value and IP address prefix length of + * an IPv4 prefix. + */ + @Test + public void testAddressAndPrefixLengthIPv4() { + Ip4Prefix ipPrefix; + + ipPrefix = Ip4Prefix.valueOf("1.2.3.0/24"); + assertThat(ipPrefix.address(), equalTo(Ip4Address.valueOf("1.2.3.0"))); + assertThat(ipPrefix.prefixLength(), is(24)); + + ipPrefix = Ip4Prefix.valueOf("1.2.3.4/24"); + assertThat(ipPrefix.address(), equalTo(Ip4Address.valueOf("1.2.3.0"))); + assertThat(ipPrefix.prefixLength(), is(24)); + + ipPrefix = Ip4Prefix.valueOf("1.2.3.4/32"); + assertThat(ipPrefix.address(), equalTo(Ip4Address.valueOf("1.2.3.4"))); + assertThat(ipPrefix.prefixLength(), is(32)); + + ipPrefix = Ip4Prefix.valueOf("1.2.3.5/32"); + assertThat(ipPrefix.address(), equalTo(Ip4Address.valueOf("1.2.3.5"))); + assertThat(ipPrefix.prefixLength(), is(32)); + + ipPrefix = Ip4Prefix.valueOf("0.0.0.0/0"); + assertThat(ipPrefix.address(), equalTo(Ip4Address.valueOf("0.0.0.0"))); + assertThat(ipPrefix.prefixLength(), is(0)); + + ipPrefix = Ip4Prefix.valueOf("255.255.255.255/32"); + assertThat(ipPrefix.address(), + equalTo(Ip4Address.valueOf("255.255.255.255"))); + assertThat(ipPrefix.prefixLength(), is(32)); + } + + /** + * Tests valueOf() converter for IPv4 integer value. + */ + @Test + public void testValueOfForIntegerIPv4() { + Ip4Prefix ipPrefix; + + ipPrefix = Ip4Prefix.valueOf(0x01020304, 24); + assertThat(ipPrefix.toString(), is("1.2.3.0/24")); + + ipPrefix = Ip4Prefix.valueOf(0x01020304, 32); + assertThat(ipPrefix.toString(), is("1.2.3.4/32")); + + ipPrefix = Ip4Prefix.valueOf(0x01020305, 32); + assertThat(ipPrefix.toString(), is("1.2.3.5/32")); + + ipPrefix = Ip4Prefix.valueOf(0, 0); + assertThat(ipPrefix.toString(), is("0.0.0.0/0")); + + ipPrefix = Ip4Prefix.valueOf(0, 32); + assertThat(ipPrefix.toString(), is("0.0.0.0/32")); + + ipPrefix = Ip4Prefix.valueOf(0xffffffff, 0); + assertThat(ipPrefix.toString(), is("0.0.0.0/0")); + + ipPrefix = Ip4Prefix.valueOf(0xffffffff, 16); + assertThat(ipPrefix.toString(), is("255.255.0.0/16")); + + ipPrefix = Ip4Prefix.valueOf(0xffffffff, 32); + assertThat(ipPrefix.toString(), is("255.255.255.255/32")); + } + + /** + * Tests invalid valueOf() converter for IPv4 integer value and + * negative prefix length. + */ + @Test(expected = IllegalArgumentException.class) + public void testInvalidValueOfIntegerNegativePrefixLengthIPv4() { + Ip4Prefix ipPrefix; + + ipPrefix = Ip4Prefix.valueOf(0x01020304, -1); + } + + /** + * Tests invalid valueOf() converter for IPv4 integer value and + * too long prefix length. + */ + @Test(expected = IllegalArgumentException.class) + public void testInvalidValueOfIntegerTooLongPrefixLengthIPv4() { + Ip4Prefix ipPrefix; + + ipPrefix = Ip4Prefix.valueOf(0x01020304, 33); + } + + /** + * Tests valueOf() converter for IPv4 byte array. + */ + @Test + public void testValueOfByteArrayIPv4() { + Ip4Prefix ipPrefix; + byte[] value; + + value = new byte[] {1, 2, 3, 4}; + ipPrefix = Ip4Prefix.valueOf(value, 24); + assertThat(ipPrefix.toString(), is("1.2.3.0/24")); + + ipPrefix = Ip4Prefix.valueOf(value, 32); + assertThat(ipPrefix.toString(), is("1.2.3.4/32")); + + value = new byte[] {1, 2, 3, 5}; + ipPrefix = Ip4Prefix.valueOf(value, 32); + assertThat(ipPrefix.toString(), is("1.2.3.5/32")); + + value = new byte[] {0, 0, 0, 0}; + ipPrefix = Ip4Prefix.valueOf(value, 0); + assertThat(ipPrefix.toString(), is("0.0.0.0/0")); + + ipPrefix = Ip4Prefix.valueOf(value, 32); + assertThat(ipPrefix.toString(), is("0.0.0.0/32")); + + value = new byte[] {(byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff}; + ipPrefix = Ip4Prefix.valueOf(value, 0); + assertThat(ipPrefix.toString(), is("0.0.0.0/0")); + + ipPrefix = Ip4Prefix.valueOf(value, 16); + assertThat(ipPrefix.toString(), is("255.255.0.0/16")); + + ipPrefix = Ip4Prefix.valueOf(value, 32); + assertThat(ipPrefix.toString(), is("255.255.255.255/32")); + } + + /** + * Tests invalid valueOf() converter for a null array for IPv4. + */ + @Test(expected = NullPointerException.class) + public void testInvalidValueOfNullArrayIPv4() { + Ip4Prefix ipPrefix; + byte[] value; + + value = null; + ipPrefix = Ip4Prefix.valueOf(value, 24); + } + + /** + * Tests invalid valueOf() converter for a short array for IPv4. + */ + @Test(expected = IllegalArgumentException.class) + public void testInvalidValueOfShortArrayIPv4() { + Ip4Prefix ipPrefix; + byte[] value; + + value = new byte[] {1, 2, 3}; + ipPrefix = Ip4Prefix.valueOf(value, 24); + } + + /** + * Tests invalid valueOf() converter for IPv4 byte array and + * negative prefix length. + */ + @Test(expected = IllegalArgumentException.class) + public void testInvalidValueOfByteArrayNegativePrefixLengthIPv4() { + Ip4Prefix ipPrefix; + byte[] value; + + value = new byte[] {1, 2, 3, 4}; + ipPrefix = Ip4Prefix.valueOf(value, -1); + } + + /** + * Tests invalid valueOf() converter for IPv4 byte array and + * too long prefix length. + */ + @Test(expected = IllegalArgumentException.class) + public void testInvalidValueOfByteArrayTooLongPrefixLengthIPv4() { + Ip4Prefix ipPrefix; + byte[] value; + + value = new byte[] {1, 2, 3, 4}; + ipPrefix = Ip4Prefix.valueOf(value, 33); + } + + /** + * Tests valueOf() converter for IPv4 address. + */ + @Test + public void testValueOfAddressIPv4() { + Ip4Address ipAddress; + Ip4Prefix ipPrefix; + + ipAddress = Ip4Address.valueOf("1.2.3.4"); + ipPrefix = Ip4Prefix.valueOf(ipAddress, 24); + assertThat(ipPrefix.toString(), is("1.2.3.0/24")); + + ipPrefix = Ip4Prefix.valueOf(ipAddress, 32); + assertThat(ipPrefix.toString(), is("1.2.3.4/32")); + + ipAddress = Ip4Address.valueOf("1.2.3.5"); + ipPrefix = Ip4Prefix.valueOf(ipAddress, 32); + assertThat(ipPrefix.toString(), is("1.2.3.5/32")); + + ipAddress = Ip4Address.valueOf("0.0.0.0"); + ipPrefix = Ip4Prefix.valueOf(ipAddress, 0); + assertThat(ipPrefix.toString(), is("0.0.0.0/0")); + + ipPrefix = Ip4Prefix.valueOf(ipAddress, 32); + assertThat(ipPrefix.toString(), is("0.0.0.0/32")); + + ipAddress = Ip4Address.valueOf("255.255.255.255"); + ipPrefix = Ip4Prefix.valueOf(ipAddress, 0); + assertThat(ipPrefix.toString(), is("0.0.0.0/0")); + + ipPrefix = Ip4Prefix.valueOf(ipAddress, 16); + assertThat(ipPrefix.toString(), is("255.255.0.0/16")); + + ipPrefix = Ip4Prefix.valueOf(ipAddress, 32); + assertThat(ipPrefix.toString(), is("255.255.255.255/32")); + } + + /** + * Tests invalid valueOf() converter for a null IP address. + */ + @Test(expected = NullPointerException.class) + public void testInvalidValueOfNullAddress() { + Ip4Address ipAddress; + Ip4Prefix ipPrefix; + + ipAddress = null; + ipPrefix = Ip4Prefix.valueOf(ipAddress, 24); + } + + /** + * Tests invalid valueOf() converter for IPv4 address and + * negative prefix length. + */ + @Test(expected = IllegalArgumentException.class) + public void testInvalidValueOfAddressNegativePrefixLengthIPv4() { + Ip4Address ipAddress; + Ip4Prefix ipPrefix; + + ipAddress = Ip4Address.valueOf("1.2.3.4"); + ipPrefix = Ip4Prefix.valueOf(ipAddress, -1); + } + + /** + * Tests invalid valueOf() converter for IPv4 address and + * too long prefix length. + */ + @Test(expected = IllegalArgumentException.class) + public void testInvalidValueOfAddressTooLongPrefixLengthIPv4() { + Ip4Address ipAddress; + Ip4Prefix ipPrefix; + + ipAddress = Ip4Address.valueOf("1.2.3.4"); + ipPrefix = Ip4Prefix.valueOf(ipAddress, 33); + } + + /** + * Tests valueOf() converter for IPv4 string. + */ + @Test + public void testValueOfStringIPv4() { + Ip4Prefix ipPrefix; + + ipPrefix = Ip4Prefix.valueOf("1.2.3.4/24"); + assertThat(ipPrefix.toString(), is("1.2.3.0/24")); + + ipPrefix = Ip4Prefix.valueOf("1.2.3.4/32"); + assertThat(ipPrefix.toString(), is("1.2.3.4/32")); + + ipPrefix = Ip4Prefix.valueOf("1.2.3.5/32"); + assertThat(ipPrefix.toString(), is("1.2.3.5/32")); + + ipPrefix = Ip4Prefix.valueOf("0.0.0.0/0"); + assertThat(ipPrefix.toString(), is("0.0.0.0/0")); + + ipPrefix = Ip4Prefix.valueOf("0.0.0.0/32"); + assertThat(ipPrefix.toString(), is("0.0.0.0/32")); + + ipPrefix = Ip4Prefix.valueOf("255.255.255.255/0"); + assertThat(ipPrefix.toString(), is("0.0.0.0/0")); + + ipPrefix = Ip4Prefix.valueOf("255.255.255.255/16"); + assertThat(ipPrefix.toString(), is("255.255.0.0/16")); + + ipPrefix = Ip4Prefix.valueOf("255.255.255.255/32"); + assertThat(ipPrefix.toString(), is("255.255.255.255/32")); + } + + /** + * Tests invalid valueOf() converter for a null string. + */ + @Test(expected = NullPointerException.class) + public void testInvalidValueOfNullString() { + Ip4Prefix ipPrefix; + String fromString; + + fromString = null; + ipPrefix = Ip4Prefix.valueOf(fromString); + } + + /** + * Tests invalid valueOf() converter for an empty string. + */ + @Test(expected = IllegalArgumentException.class) + public void testInvalidValueOfEmptyString() { + Ip4Prefix ipPrefix; + String fromString; + + fromString = ""; + ipPrefix = Ip4Prefix.valueOf(fromString); + } + + /** + * Tests invalid valueOf() converter for an incorrect string. + */ + @Test(expected = IllegalArgumentException.class) + public void testInvalidValueOfIncorrectString() { + Ip4Prefix ipPrefix; + String fromString; + + fromString = "NoSuchIpPrefix"; + ipPrefix = Ip4Prefix.valueOf(fromString); + } + + /** + * Tests invalid valueOf() converter for IPv4 string and + * negative prefix length. + */ + @Test(expected = IllegalArgumentException.class) + public void testInvalidValueOfStringNegativePrefixLengthIPv4() { + Ip4Prefix ipPrefix; + + ipPrefix = Ip4Prefix.valueOf("1.2.3.4/-1"); + } + + /** + * Tests invalid valueOf() converter for IPv4 string and + * too long prefix length. + */ + @Test(expected = IllegalArgumentException.class) + public void testInvalidValueOfStringTooLongPrefixLengthIPv4() { + Ip4Prefix ipPrefix; + + ipPrefix = Ip4Prefix.valueOf("1.2.3.4/33"); + } + + /** + * Tests IP prefix contains another IP prefix for IPv4. + */ + @Test + public void testContainsIpPrefixIPv4() { + Ip4Prefix ipPrefix; + + ipPrefix = Ip4Prefix.valueOf("1.2.0.0/24"); + assertTrue(ipPrefix.contains(Ip4Prefix.valueOf("1.2.0.0/24"))); + assertTrue(ipPrefix.contains(Ip4Prefix.valueOf("1.2.0.0/32"))); + assertTrue(ipPrefix.contains(Ip4Prefix.valueOf("1.2.0.4/32"))); + assertFalse(ipPrefix.contains(Ip4Prefix.valueOf("1.2.0.0/16"))); + assertFalse(ipPrefix.contains(Ip4Prefix.valueOf("1.3.0.0/24"))); + assertFalse(ipPrefix.contains(Ip4Prefix.valueOf("0.0.0.0/16"))); + assertFalse(ipPrefix.contains(Ip4Prefix.valueOf("0.0.0.0/0"))); + assertFalse(ipPrefix.contains(Ip4Prefix.valueOf("255.255.255.255/32"))); + + ipPrefix = Ip4Prefix.valueOf("1.2.0.0/32"); + assertFalse(ipPrefix.contains(Ip4Prefix.valueOf("1.2.0.0/24"))); + assertTrue(ipPrefix.contains(Ip4Prefix.valueOf("1.2.0.0/32"))); + assertFalse(ipPrefix.contains(Ip4Prefix.valueOf("1.2.0.4/32"))); + assertFalse(ipPrefix.contains(Ip4Prefix.valueOf("1.2.0.0/16"))); + assertFalse(ipPrefix.contains(Ip4Prefix.valueOf("1.3.0.0/24"))); + assertFalse(ipPrefix.contains(Ip4Prefix.valueOf("0.0.0.0/16"))); + assertFalse(ipPrefix.contains(Ip4Prefix.valueOf("0.0.0.0/0"))); + assertFalse(ipPrefix.contains(Ip4Prefix.valueOf("255.255.255.255/32"))); + + ipPrefix = Ip4Prefix.valueOf("0.0.0.0/0"); + assertTrue(ipPrefix.contains(Ip4Prefix.valueOf("1.2.0.0/24"))); + assertTrue(ipPrefix.contains(Ip4Prefix.valueOf("1.2.0.0/32"))); + assertTrue(ipPrefix.contains(Ip4Prefix.valueOf("1.2.0.4/32"))); + assertTrue(ipPrefix.contains(Ip4Prefix.valueOf("1.2.0.0/16"))); + assertTrue(ipPrefix.contains(Ip4Prefix.valueOf("1.3.0.0/24"))); + assertTrue(ipPrefix.contains(Ip4Prefix.valueOf("0.0.0.0/16"))); + assertTrue(ipPrefix.contains(Ip4Prefix.valueOf("0.0.0.0/0"))); + assertTrue(ipPrefix.contains(Ip4Prefix.valueOf("255.255.255.255/32"))); + + ipPrefix = Ip4Prefix.valueOf("255.255.255.255/32"); + assertFalse(ipPrefix.contains(Ip4Prefix.valueOf("1.2.0.0/24"))); + assertFalse(ipPrefix.contains(Ip4Prefix.valueOf("1.2.0.0/32"))); + assertFalse(ipPrefix.contains(Ip4Prefix.valueOf("1.2.0.4/32"))); + assertFalse(ipPrefix.contains(Ip4Prefix.valueOf("1.2.0.0/16"))); + assertFalse(ipPrefix.contains(Ip4Prefix.valueOf("1.3.0.0/24"))); + assertFalse(ipPrefix.contains(Ip4Prefix.valueOf("0.0.0.0/16"))); + assertFalse(ipPrefix.contains(Ip4Prefix.valueOf("0.0.0.0/0"))); + assertTrue(ipPrefix.contains(Ip4Prefix.valueOf("255.255.255.255/32"))); + } + + /** + * Tests IP prefix contains IP address for IPv4. + */ + @Test + public void testContainsIpAddressIPv4() { + Ip4Prefix ipPrefix; + + ipPrefix = Ip4Prefix.valueOf("1.2.0.0/24"); + assertTrue(ipPrefix.contains(Ip4Address.valueOf("1.2.0.0"))); + assertTrue(ipPrefix.contains(Ip4Address.valueOf("1.2.0.4"))); + assertFalse(ipPrefix.contains(Ip4Address.valueOf("1.3.0.0"))); + assertFalse(ipPrefix.contains(Ip4Address.valueOf("0.0.0.0"))); + assertFalse(ipPrefix.contains(Ip4Address.valueOf("255.255.255.255"))); + + ipPrefix = Ip4Prefix.valueOf("1.2.0.0/32"); + assertTrue(ipPrefix.contains(Ip4Address.valueOf("1.2.0.0"))); + assertFalse(ipPrefix.contains(Ip4Address.valueOf("1.2.0.4"))); + assertFalse(ipPrefix.contains(Ip4Address.valueOf("1.3.0.0"))); + assertFalse(ipPrefix.contains(Ip4Address.valueOf("0.0.0.0"))); + assertFalse(ipPrefix.contains(Ip4Address.valueOf("255.255.255.255"))); + + ipPrefix = Ip4Prefix.valueOf("0.0.0.0/0"); + assertTrue(ipPrefix.contains(Ip4Address.valueOf("1.2.0.0"))); + assertTrue(ipPrefix.contains(Ip4Address.valueOf("1.2.0.4"))); + assertTrue(ipPrefix.contains(Ip4Address.valueOf("1.3.0.0"))); + assertTrue(ipPrefix.contains(Ip4Address.valueOf("0.0.0.0"))); + assertTrue(ipPrefix.contains(Ip4Address.valueOf("255.255.255.255"))); + + ipPrefix = Ip4Prefix.valueOf("255.255.255.255/32"); + assertFalse(ipPrefix.contains(Ip4Address.valueOf("1.2.0.0"))); + assertFalse(ipPrefix.contains(Ip4Address.valueOf("1.2.0.4"))); + assertFalse(ipPrefix.contains(Ip4Address.valueOf("1.3.0.0"))); + assertFalse(ipPrefix.contains(Ip4Address.valueOf("0.0.0.0"))); + assertTrue(ipPrefix.contains(Ip4Address.valueOf("255.255.255.255"))); + } + + /** + * Tests equality of {@link Ip4Prefix} for IPv4. + */ + @Test + public void testEqualityIPv4() { + new EqualsTester() + .addEqualityGroup(Ip4Prefix.valueOf("1.2.0.0/24"), + Ip4Prefix.valueOf("1.2.0.0/24"), + Ip4Prefix.valueOf("1.2.0.4/24")) + .addEqualityGroup(Ip4Prefix.valueOf("1.2.0.0/16"), + Ip4Prefix.valueOf("1.2.0.0/16")) + .addEqualityGroup(Ip4Prefix.valueOf("1.2.0.0/32"), + Ip4Prefix.valueOf("1.2.0.0/32")) + .addEqualityGroup(Ip4Prefix.valueOf("1.3.0.0/24"), + Ip4Prefix.valueOf("1.3.0.0/24")) + .addEqualityGroup(Ip4Prefix.valueOf("0.0.0.0/0"), + Ip4Prefix.valueOf("0.0.0.0/0")) + .addEqualityGroup(Ip4Prefix.valueOf("255.255.255.255/32"), + Ip4Prefix.valueOf("255.255.255.255/32")) + .testEquals(); + } + + /** + * Tests object string representation for IPv4. + */ + @Test + public void testToStringIPv4() { + Ip4Prefix ipPrefix; + + ipPrefix = Ip4Prefix.valueOf("1.2.3.0/24"); + assertThat(ipPrefix.toString(), is("1.2.3.0/24")); + + ipPrefix = Ip4Prefix.valueOf("1.2.3.4/24"); + assertThat(ipPrefix.toString(), is("1.2.3.0/24")); + + ipPrefix = Ip4Prefix.valueOf("0.0.0.0/0"); + assertThat(ipPrefix.toString(), is("0.0.0.0/0")); + + ipPrefix = Ip4Prefix.valueOf("255.255.255.255/32"); + assertThat(ipPrefix.toString(), is("255.255.255.255/32")); + } +} diff --git a/framework/src/onos/utils/misc/src/test/java/org/onlab/packet/Ip6AddressTest.java b/framework/src/onos/utils/misc/src/test/java/org/onlab/packet/Ip6AddressTest.java new file mode 100644 index 00000000..e7d017d1 --- /dev/null +++ b/framework/src/onos/utils/misc/src/test/java/org/onlab/packet/Ip6AddressTest.java @@ -0,0 +1,499 @@ +/* + * Copyright 2014 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 com.google.common.net.InetAddresses; +import com.google.common.testing.EqualsTester; +import org.junit.Test; + +import java.net.InetAddress; + +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; +import static org.onlab.junit.ImmutableClassChecker.assertThatClassIsImmutable; + +/** + * Tests for class {@link Ip6Address}. + */ +public class Ip6AddressTest { + /** + * Tests the immutability of {@link Ip6Address}. + */ + @Test + public void testImmutable() { + assertThatClassIsImmutable(Ip6Address.class); + } + + /** + * Tests the IPv4 address version constant. + */ + @Test + public void testAddressVersion() { + assertThat(Ip6Address.VERSION, is(IpAddress.Version.INET6)); + } + + /** + * Tests the length of the address in bytes (octets). + */ + @Test + public void testAddrByteLength() { + assertThat(Ip6Address.BYTE_LENGTH, is(16)); + } + + /** + * Tests the length of the address in bits. + */ + @Test + public void testAddrBitLength() { + assertThat(Ip6Address.BIT_LENGTH, is(128)); + } + + /** + * Tests returning the IP address version. + */ + @Test + public void testVersion() { + IpAddress ipAddress; + + // IPv6 + ipAddress = IpAddress.valueOf("::"); + assertThat(ipAddress.version(), is(IpAddress.Version.INET6)); + } + + /** + * Tests returning an IPv6 address as a byte array. + */ + @Test + public void testAddressToOctetsIPv6() { + Ip6Address ipAddress; + byte[] value; + + value = new byte[] {0x11, 0x11, 0x22, 0x22, + 0x33, 0x33, 0x44, 0x44, + 0x55, 0x55, 0x66, 0x66, + 0x77, 0x77, + (byte) 0x88, (byte) 0x88}; + ipAddress = + Ip6Address.valueOf("1111:2222:3333:4444:5555:6666:7777:8888"); + assertThat(ipAddress.toOctets(), is(value)); + + value = new byte[] {0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00}; + ipAddress = Ip6Address.valueOf("::"); + assertThat(ipAddress.toOctets(), is(value)); + + value = new byte[] {(byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff}; + ipAddress = + Ip6Address.valueOf("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"); + assertThat(ipAddress.toOctets(), is(value)); + } + + /** + * Tests valueOf() converter for IPv6 byte array. + */ + @Test + public void testValueOfByteArrayIPv6() { + Ip6Address ipAddress; + byte[] value; + + value = new byte[] {0x11, 0x11, 0x22, 0x22, + 0x33, 0x33, 0x44, 0x44, + 0x55, 0x55, 0x66, 0x66, + 0x77, 0x77, + (byte) 0x88, (byte) 0x88}; + ipAddress = Ip6Address.valueOf(value); + assertThat(ipAddress.toString(), + is("1111:2222:3333:4444:5555:6666:7777:8888")); + + value = new byte[] {0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00}; + ipAddress = Ip6Address.valueOf(value); + assertThat(ipAddress.toString(), is("::")); + + value = new byte[] {(byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff}; + ipAddress = Ip6Address.valueOf(value); + assertThat(ipAddress.toString(), + is("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff")); + } + + /** + * Tests invalid valueOf() converter for a null array for IPv6. + */ + @Test(expected = NullPointerException.class) + public void testInvalidValueOfNullArrayIPv6() { + Ip6Address ipAddress; + byte[] value; + + value = null; + ipAddress = Ip6Address.valueOf(value); + } + + /** + * Tests invalid valueOf() converger for an array that is too short for + * IPv6. + */ + @Test(expected = IllegalArgumentException.class) + public void testInvalidValueOfShortArrayIPv6() { + Ip6Address ipAddress; + byte[] value; + + value = new byte[] {1, 2, 3, 4, 5, 6, 7, 8, 9}; + ipAddress = Ip6Address.valueOf(value); + } + + /** + * Tests valueOf() converter for IPv6 byte array and an offset. + */ + @Test + public void testValueOfByteArrayOffsetIPv6() { + Ip6Address ipAddress; + byte[] value; + + value = new byte[] {11, 22, 33, // Preamble + 0x11, 0x11, 0x22, 0x22, + 0x33, 0x33, 0x44, 0x44, + 0x55, 0x55, 0x66, 0x66, + 0x77, 0x77, + (byte) 0x88, (byte) 0x88, + 44, 55}; // Extra bytes + ipAddress = Ip6Address.valueOf(value, 3); + assertThat(ipAddress.toString(), + is("1111:2222:3333:4444:5555:6666:7777:8888")); + + value = new byte[] {11, 22, // Preamble + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 33}; // Extra bytes + ipAddress = Ip6Address.valueOf(value, 2); + assertThat(ipAddress.toString(), is("::")); + + value = new byte[] {11, 22, // Preamble + (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, + 33}; // Extra bytes + ipAddress = Ip6Address.valueOf(value, 2); + assertThat(ipAddress.toString(), + is("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff")); + } + + /** + * Tests invalid valueOf() converger for an array and an invalid offset + * for IPv6. + */ + @Test(expected = IllegalArgumentException.class) + public void testInvalidValueOfArrayInvalidOffsetIPv6() { + Ip6Address ipAddress; + byte[] value; + + value = new byte[] {11, 22, 33, // Preamble + 0x11, 0x11, 0x22, 0x22, + 0x33, 0x33, 0x44, 0x44, + 0x55, 0x55, 0x66, 0x66, + 0x77, 0x77, + (byte) 0x88, (byte) 0x88, + 44, 55}; // Extra bytes + ipAddress = Ip6Address.valueOf(value, 6); + } + + /** + * Tests valueOf() converter for IPv6 InetAddress. + */ + @Test + public void testValueOfInetAddressIPv6() { + Ip6Address ipAddress; + InetAddress inetAddress; + + inetAddress = + InetAddresses.forString("1111:2222:3333:4444:5555:6666:7777:8888"); + ipAddress = Ip6Address.valueOf(inetAddress); + assertThat(ipAddress.toString(), + is("1111:2222:3333:4444:5555:6666:7777:8888")); + + inetAddress = InetAddresses.forString("::"); + ipAddress = Ip6Address.valueOf(inetAddress); + assertThat(ipAddress.toString(), is("::")); + + inetAddress = + InetAddresses.forString("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"); + ipAddress = Ip6Address.valueOf(inetAddress); + assertThat(ipAddress.toString(), + is("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff")); + } + + /** + * Tests valueOf() converter for IPv6 string. + */ + @Test + public void testValueOfStringIPv6() { + Ip6Address ipAddress; + + ipAddress = + Ip6Address.valueOf("1111:2222:3333:4444:5555:6666:7777:8888"); + assertThat(ipAddress.toString(), + is("1111:2222:3333:4444:5555:6666:7777:8888")); + + ipAddress = Ip6Address.valueOf("::"); + assertThat(ipAddress.toString(), is("::")); + + ipAddress = + Ip6Address.valueOf("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"); + assertThat(ipAddress.toString(), + is("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff")); + } + + /** + * Tests invalid valueOf() converter for a null string. + */ + @Test(expected = NullPointerException.class) + public void testInvalidValueOfNullString() { + Ip6Address ipAddress; + + String fromString = null; + ipAddress = Ip6Address.valueOf(fromString); + } + + /** + * Tests invalid valueOf() converter for an empty string. + */ + @Test(expected = IllegalArgumentException.class) + public void testInvalidValueOfEmptyString() { + Ip6Address ipAddress; + + String fromString = ""; + ipAddress = Ip6Address.valueOf(fromString); + } + + /** + * Tests invalid valueOf() converter for an incorrect string. + */ + @Test(expected = IllegalArgumentException.class) + public void testInvalidValueOfIncorrectString() { + Ip6Address ipAddress; + + String fromString = "NoSuchIpAddress"; + ipAddress = Ip6Address.valueOf(fromString); + } + + /** + * Tests making a mask prefix for a given prefix length for IPv6. + */ + @Test + public void testMakeMaskPrefixIPv6() { + Ip6Address ipAddress; + + ipAddress = Ip6Address.makeMaskPrefix(8); + assertThat(ipAddress.toString(), is("ff00::")); + + ipAddress = Ip6Address.makeMaskPrefix(120); + assertThat(ipAddress.toString(), + is("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ff00")); + + ipAddress = Ip6Address.makeMaskPrefix(0); + assertThat(ipAddress.toString(), is("::")); + + ipAddress = Ip6Address.makeMaskPrefix(128); + assertThat(ipAddress.toString(), + is("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff")); + + ipAddress = Ip6Address.makeMaskPrefix(64); + assertThat(ipAddress.toString(), is("ffff:ffff:ffff:ffff::")); + } + + /** + * Tests making a mask prefix for an invalid prefix length for IPv6: + * negative prefix length. + */ + @Test(expected = IllegalArgumentException.class) + public void testInvalidMakeNegativeMaskPrefixIPv6() { + Ip6Address ipAddress; + + ipAddress = Ip6Address.makeMaskPrefix(-1); + } + + /** + * Tests making a mask prefix for an invalid prefix length for IPv6: + * too long prefix length. + */ + @Test(expected = IllegalArgumentException.class) + public void testInvalidMakeTooLongMaskPrefixIPv6() { + Ip6Address ipAddress; + + ipAddress = Ip6Address.makeMaskPrefix(129); + } + + /** + * Tests making of a masked address for IPv6. + */ + @Test + public void testMakeMaskedAddressIPv6() { + Ip6Address ipAddress = + Ip6Address.valueOf("1111:2222:3333:4444:5555:6666:7777:8885"); + Ip6Address ipAddressMasked; + + ipAddressMasked = Ip6Address.makeMaskedAddress(ipAddress, 8); + assertThat(ipAddressMasked.toString(), is("1100::")); + + ipAddressMasked = Ip6Address.makeMaskedAddress(ipAddress, 120); + assertThat(ipAddressMasked.toString(), + is("1111:2222:3333:4444:5555:6666:7777:8800")); + + ipAddressMasked = Ip6Address.makeMaskedAddress(ipAddress, 0); + assertThat(ipAddressMasked.toString(), is("::")); + + ipAddressMasked = Ip6Address.makeMaskedAddress(ipAddress, 128); + assertThat(ipAddressMasked.toString(), + is("1111:2222:3333:4444:5555:6666:7777:8885")); + + ipAddressMasked = Ip6Address.makeMaskedAddress(ipAddress, 64); + assertThat(ipAddressMasked.toString(), is("1111:2222:3333:4444::")); + } + + /** + * Tests making of a masked address for invalid prefix length for IPv6: + * negative prefix length. + */ + @Test(expected = IllegalArgumentException.class) + public void testInvalidMakeNegativeMaskedAddressIPv6() { + Ip6Address ipAddress = + Ip6Address.valueOf("1111:2222:3333:4444:5555:6666:7777:8885"); + Ip6Address ipAddressMasked; + + ipAddressMasked = Ip6Address.makeMaskedAddress(ipAddress, -1); + } + + /** + * Tests making of a masked address for an invalid prefix length for IPv6: + * too long prefix length. + */ + @Test(expected = IllegalArgumentException.class) + public void testInvalidMakeTooLongMaskedAddressIPv6() { + Ip6Address ipAddress = + Ip6Address.valueOf("1111:2222:3333:4444:5555:6666:7777:8885"); + Ip6Address ipAddressMasked; + + ipAddressMasked = Ip6Address.makeMaskedAddress(ipAddress, 129); + } + + /** + * Tests comparison of {@link Ip6Address} for IPv6. + */ + @Test + public void testComparisonIPv6() { + Ip6Address addr1, addr2, addr3, addr4; + + addr1 = Ip6Address.valueOf("1111:2222:3333:4444:5555:6666:7777:8888"); + addr2 = Ip6Address.valueOf("1111:2222:3333:4444:5555:6666:7777:8888"); + addr3 = Ip6Address.valueOf("1111:2222:3333:4444:5555:6666:7777:8887"); + addr4 = Ip6Address.valueOf("1111:2222:3333:4444:5555:6666:7777:8889"); + assertTrue(addr1.compareTo(addr2) == 0); + assertTrue(addr1.compareTo(addr3) > 0); + assertTrue(addr1.compareTo(addr4) < 0); + + addr1 = Ip6Address.valueOf("ffff:2222:3333:4444:5555:6666:7777:8888"); + addr2 = Ip6Address.valueOf("ffff:2222:3333:4444:5555:6666:7777:8888"); + addr3 = Ip6Address.valueOf("ffff:2222:3333:4444:5555:6666:7777:8887"); + addr4 = Ip6Address.valueOf("ffff:2222:3333:4444:5555:6666:7777:8889"); + assertTrue(addr1.compareTo(addr2) == 0); + assertTrue(addr1.compareTo(addr3) > 0); + assertTrue(addr1.compareTo(addr4) < 0); + + addr1 = Ip6Address.valueOf("ffff:2222:3333:4444:5555:6666:7777:8888"); + addr2 = Ip6Address.valueOf("ffff:2222:3333:4444:5555:6666:7777:8888"); + addr3 = Ip6Address.valueOf("ffff:2222:3333:4443:5555:6666:7777:8888"); + addr4 = Ip6Address.valueOf("ffff:2222:3333:4445:5555:6666:7777:8888"); + assertTrue(addr1.compareTo(addr2) == 0); + assertTrue(addr1.compareTo(addr3) > 0); + assertTrue(addr1.compareTo(addr4) < 0); + } + + /** + * Tests equality of {@link Ip6Address} for IPv6. + */ + @Test + public void testEqualityIPv6() { + new EqualsTester() + .addEqualityGroup( + Ip6Address.valueOf("1111:2222:3333:4444:5555:6666:7777:8888"), + Ip6Address.valueOf("1111:2222:3333:4444:5555:6666:7777:8888")) + .addEqualityGroup( + Ip6Address.valueOf("1111:2222:3333:4444:5555:6666:7777:888a"), + Ip6Address.valueOf("1111:2222:3333:4444:5555:6666:7777:888a")) + .addEqualityGroup( + Ip6Address.valueOf("::"), + Ip6Address.valueOf("::")) + .addEqualityGroup( + Ip6Address.valueOf("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"), + Ip6Address.valueOf("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff")) + .testEquals(); + } + + /** + * Tests object string representation for IPv6. + */ + @Test + public void testToStringIPv6() { + Ip6Address ipAddress; + + ipAddress = + Ip6Address.valueOf("1111:2222:3333:4444:5555:6666:7777:8888"); + assertThat(ipAddress.toString(), + is("1111:2222:3333:4444:5555:6666:7777:8888")); + + ipAddress = Ip6Address.valueOf("1111::8888"); + assertThat(ipAddress.toString(), is("1111::8888")); + + ipAddress = Ip6Address.valueOf("1111::"); + assertThat(ipAddress.toString(), is("1111::")); + + ipAddress = Ip6Address.valueOf("::8888"); + assertThat(ipAddress.toString(), is("::8888")); + + ipAddress = Ip6Address.valueOf("::"); + assertThat(ipAddress.toString(), is("::")); + + ipAddress = + Ip6Address.valueOf("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"); + assertThat(ipAddress.toString(), + is("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff")); + } +} diff --git a/framework/src/onos/utils/misc/src/test/java/org/onlab/packet/Ip6PrefixTest.java b/framework/src/onos/utils/misc/src/test/java/org/onlab/packet/Ip6PrefixTest.java new file mode 100644 index 00000000..dceeb840 --- /dev/null +++ b/framework/src/onos/utils/misc/src/test/java/org/onlab/packet/Ip6PrefixTest.java @@ -0,0 +1,568 @@ +/* + * Copyright 2014 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 com.google.common.testing.EqualsTester; +import org.junit.Test; + +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.onlab.junit.ImmutableClassChecker.assertThatClassIsImmutable; + +/** + * Tests for class {@link Ip6Prefix}. + */ +public class Ip6PrefixTest { + /** + * Tests the immutability of {@link Ip6Prefix}. + */ + @Test + public void testImmutable() { + assertThatClassIsImmutable(Ip6Prefix.class); + } + + /** + * Tests the IPv4 prefix address version constant. + */ + @Test + public void testAddressVersion() { + assertThat(Ip6Prefix.VERSION, is(IpAddress.Version.INET6)); + } + + /** + * Tests the maximum mask length. + */ + @Test + public void testMaxMaskLength() { + assertThat(Ip6Prefix.MAX_MASK_LENGTH, is(128)); + } + + /** + * Tests returning the IP version of the prefix. + */ + @Test + public void testVersion() { + Ip6Prefix ipPrefix; + + // IPv6 + ipPrefix = Ip6Prefix.valueOf("::/0"); + assertThat(ipPrefix.version(), is(IpAddress.Version.INET6)); + } + + /** + * Tests returning the IP address value and IP address prefix length of + * an IPv6 prefix. + */ + @Test + public void testAddressAndPrefixLengthIPv6() { + Ip6Prefix ipPrefix; + + ipPrefix = Ip6Prefix.valueOf("1100::/8"); + assertThat(ipPrefix.address(), equalTo(Ip6Address.valueOf("1100::"))); + assertThat(ipPrefix.prefixLength(), is(8)); + + ipPrefix = + Ip6Prefix.valueOf("1111:2222:3333:4444:5555:6666:7777:8885/8"); + assertThat(ipPrefix.address(), equalTo(Ip6Address.valueOf("1100::"))); + assertThat(ipPrefix.prefixLength(), is(8)); + + ipPrefix = + Ip6Prefix.valueOf("1111:2222:3333:4444:5555:6666:7777:8800/120"); + assertThat(ipPrefix.address(), + equalTo(Ip6Address.valueOf("1111:2222:3333:4444:5555:6666:7777:8800"))); + assertThat(ipPrefix.prefixLength(), is(120)); + + ipPrefix = + Ip6Prefix.valueOf("1111:2222:3333:4444:5555:6666:7777:8885/128"); + assertThat(ipPrefix.address(), + equalTo(Ip6Address.valueOf("1111:2222:3333:4444:5555:6666:7777:8885"))); + assertThat(ipPrefix.prefixLength(), is(128)); + + ipPrefix = Ip6Prefix.valueOf("::/0"); + assertThat(ipPrefix.address(), equalTo(Ip6Address.valueOf("::"))); + assertThat(ipPrefix.prefixLength(), is(0)); + + ipPrefix = + Ip6Prefix.valueOf("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/128"); + assertThat(ipPrefix.address(), + equalTo(Ip6Address.valueOf("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"))); + assertThat(ipPrefix.prefixLength(), is(128)); + + ipPrefix = + Ip6Prefix.valueOf("1111:2222:3333:4444:5555:6666:7777:8885/64"); + assertThat(ipPrefix.address(), + equalTo(Ip6Address.valueOf("1111:2222:3333:4444::"))); + assertThat(ipPrefix.prefixLength(), is(64)); + } + + /** + * Tests valueOf() converter for IPv6 byte array. + */ + @Test + public void testValueOfByteArrayIPv6() { + Ip6Prefix ipPrefix; + byte[] value; + + value = new byte[] {0x11, 0x11, 0x22, 0x22, + 0x33, 0x33, 0x44, 0x44, + 0x55, 0x55, 0x66, 0x66, + 0x77, 0x77, (byte) 0x88, (byte) 0x88}; + ipPrefix = Ip6Prefix.valueOf(value, 120); + assertThat(ipPrefix.toString(), + is("1111:2222:3333:4444:5555:6666:7777:8800/120")); + + ipPrefix = Ip6Prefix.valueOf(value, 128); + assertThat(ipPrefix.toString(), + is("1111:2222:3333:4444:5555:6666:7777:8888/128")); + + value = new byte[] {0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00}; + ipPrefix = Ip6Prefix.valueOf(value, 0); + assertThat(ipPrefix.toString(), is("::/0")); + + ipPrefix = Ip6Prefix.valueOf(value, 128); + assertThat(ipPrefix.toString(), is("::/128")); + + value = new byte[] {(byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff}; + ipPrefix = Ip6Prefix.valueOf(value, 0); + assertThat(ipPrefix.toString(), is("::/0")); + + ipPrefix = Ip6Prefix.valueOf(value, 64); + assertThat(ipPrefix.toString(), is("ffff:ffff:ffff:ffff::/64")); + + ipPrefix = Ip6Prefix.valueOf(value, 128); + assertThat(ipPrefix.toString(), + is("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/128")); + } + + /** + * Tests invalid valueOf() converter for a null array for IPv6. + */ + @Test(expected = NullPointerException.class) + public void testInvalidValueOfNullArrayIPv6() { + Ip6Prefix ipPrefix; + byte[] value; + + value = null; + ipPrefix = Ip6Prefix.valueOf(value, 120); + } + + /** + * Tests invalid valueOf() converter for a short array for IPv6. + */ + @Test(expected = IllegalArgumentException.class) + public void testInvalidValueOfShortArrayIPv6() { + Ip6Prefix ipPrefix; + byte[] value; + + value = new byte[] {1, 2, 3, 4, 5, 6, 7, 8, 9}; + ipPrefix = Ip6Prefix.valueOf(value, 120); + } + + /** + * Tests invalid valueOf() converter for IPv6 byte array and + * negative prefix length. + */ + @Test(expected = IllegalArgumentException.class) + public void testInvalidValueOfByteArrayNegativePrefixLengthIPv6() { + Ip6Prefix ipPrefix; + byte[] value; + + value = new byte[] {0x11, 0x11, 0x22, 0x22, + 0x33, 0x33, 0x44, 0x44, + 0x55, 0x55, 0x66, 0x66, + 0x77, 0x77, (byte) 0x88, (byte) 0x88}; + ipPrefix = Ip6Prefix.valueOf(value, -1); + } + + /** + * Tests invalid valueOf() converter for IPv6 byte array and + * too long prefix length. + */ + @Test(expected = IllegalArgumentException.class) + public void testInvalidValueOfByteArrayTooLongPrefixLengthIPv6() { + Ip6Prefix ipPrefix; + byte[] value; + + value = new byte[] {0x11, 0x11, 0x22, 0x22, + 0x33, 0x33, 0x44, 0x44, + 0x55, 0x55, 0x66, 0x66, + 0x77, 0x77, (byte) 0x88, (byte) 0x88}; + ipPrefix = Ip6Prefix.valueOf(value, 129); + } + + /** + * Tests valueOf() converter for IPv6 address. + */ + @Test + public void testValueOfAddressIPv6() { + Ip6Address ipAddress; + Ip6Prefix ipPrefix; + + ipAddress = + Ip6Address.valueOf("1111:2222:3333:4444:5555:6666:7777:8888"); + ipPrefix = Ip6Prefix.valueOf(ipAddress, 120); + assertThat(ipPrefix.toString(), + is("1111:2222:3333:4444:5555:6666:7777:8800/120")); + + ipPrefix = Ip6Prefix.valueOf(ipAddress, 128); + assertThat(ipPrefix.toString(), + is("1111:2222:3333:4444:5555:6666:7777:8888/128")); + + ipAddress = Ip6Address.valueOf("::"); + ipPrefix = Ip6Prefix.valueOf(ipAddress, 0); + assertThat(ipPrefix.toString(), is("::/0")); + + ipPrefix = Ip6Prefix.valueOf(ipAddress, 128); + assertThat(ipPrefix.toString(), is("::/128")); + + ipAddress = + Ip6Address.valueOf("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"); + ipPrefix = Ip6Prefix.valueOf(ipAddress, 0); + assertThat(ipPrefix.toString(), is("::/0")); + + ipPrefix = Ip6Prefix.valueOf(ipAddress, 64); + assertThat(ipPrefix.toString(), is("ffff:ffff:ffff:ffff::/64")); + + ipPrefix = Ip6Prefix.valueOf(ipAddress, 128); + assertThat(ipPrefix.toString(), + is("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/128")); + } + + /** + * Tests invalid valueOf() converter for a null IP address. + */ + @Test(expected = NullPointerException.class) + public void testInvalidValueOfNullAddress() { + Ip6Address ipAddress; + Ip6Prefix ipPrefix; + + ipAddress = null; + ipPrefix = Ip6Prefix.valueOf(ipAddress, 24); + } + + /** + * Tests invalid valueOf() converter for IPv6 address and + * negative prefix length. + */ + @Test(expected = IllegalArgumentException.class) + public void testInvalidValueOfAddressNegativePrefixLengthIPv6() { + Ip6Address ipAddress; + Ip6Prefix ipPrefix; + + ipAddress = + Ip6Address.valueOf("1111:2222:3333:4444:5555:6666:7777:8888"); + ipPrefix = Ip6Prefix.valueOf(ipAddress, -1); + } + + /** + * Tests invalid valueOf() converter for IPv6 address and + * too long prefix length. + */ + @Test(expected = IllegalArgumentException.class) + public void testInvalidValueOfAddressTooLongPrefixLengthIPv6() { + Ip6Address ipAddress; + Ip6Prefix ipPrefix; + + ipAddress = + Ip6Address.valueOf("1111:2222:3333:4444:5555:6666:7777:8888"); + ipPrefix = Ip6Prefix.valueOf(ipAddress, 129); + } + + /** + * Tests valueOf() converter for IPv6 string. + */ + @Test + public void testValueOfStringIPv6() { + Ip6Prefix ipPrefix; + + ipPrefix = + Ip6Prefix.valueOf("1111:2222:3333:4444:5555:6666:7777:8888/120"); + assertThat(ipPrefix.toString(), + is("1111:2222:3333:4444:5555:6666:7777:8800/120")); + + ipPrefix = + Ip6Prefix.valueOf("1111:2222:3333:4444:5555:6666:7777:8888/128"); + assertThat(ipPrefix.toString(), + is("1111:2222:3333:4444:5555:6666:7777:8888/128")); + + ipPrefix = Ip6Prefix.valueOf("::/0"); + assertThat(ipPrefix.toString(), is("::/0")); + + ipPrefix = Ip6Prefix.valueOf("::/128"); + assertThat(ipPrefix.toString(), is("::/128")); + + ipPrefix = + Ip6Prefix.valueOf("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/0"); + assertThat(ipPrefix.toString(), is("::/0")); + + ipPrefix = + Ip6Prefix.valueOf("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/64"); + assertThat(ipPrefix.toString(), is("ffff:ffff:ffff:ffff::/64")); + + ipPrefix = + Ip6Prefix.valueOf("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/128"); + assertThat(ipPrefix.toString(), + is("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/128")); + } + + /** + * Tests invalid valueOf() converter for a null string. + */ + @Test(expected = NullPointerException.class) + public void testInvalidValueOfNullString() { + Ip6Prefix ipPrefix; + String fromString; + + fromString = null; + ipPrefix = Ip6Prefix.valueOf(fromString); + } + + /** + * Tests invalid valueOf() converter for an empty string. + */ + @Test(expected = IllegalArgumentException.class) + public void testInvalidValueOfEmptyString() { + Ip6Prefix ipPrefix; + String fromString; + + fromString = ""; + ipPrefix = Ip6Prefix.valueOf(fromString); + } + + /** + * Tests invalid valueOf() converter for an incorrect string. + */ + @Test(expected = IllegalArgumentException.class) + public void testInvalidValueOfIncorrectString() { + Ip6Prefix ipPrefix; + String fromString; + + fromString = "NoSuchIpPrefix"; + ipPrefix = Ip6Prefix.valueOf(fromString); + } + + /** + * Tests invalid valueOf() converter for IPv6 string and + * negative prefix length. + */ + @Test(expected = IllegalArgumentException.class) + public void testInvalidValueOfStringNegativePrefixLengthIPv6() { + Ip6Prefix ipPrefix; + + ipPrefix = + Ip6Prefix.valueOf("1111:2222:3333:4444:5555:6666:7777:8888/-1"); + } + + /** + * Tests invalid valueOf() converter for IPv6 string and + * too long prefix length. + */ + @Test(expected = IllegalArgumentException.class) + public void testInvalidValueOfStringTooLongPrefixLengthIPv6() { + Ip6Prefix ipPrefix; + + ipPrefix = + Ip6Prefix.valueOf("1111:2222:3333:4444:5555:6666:7777:8888/129"); + } + + /** + * Tests IP prefix contains another IP prefix for IPv6. + */ + @Test + public void testContainsIpPrefixIPv6() { + Ip6Prefix ipPrefix; + + ipPrefix = Ip6Prefix.valueOf("1111:2222:3333:4444::/120"); + assertTrue(ipPrefix.contains( + Ip6Prefix.valueOf("1111:2222:3333:4444::/120"))); + assertTrue(ipPrefix.contains( + Ip6Prefix.valueOf("1111:2222:3333:4444::/128"))); + assertTrue(ipPrefix.contains( + Ip6Prefix.valueOf("1111:2222:3333:4444::1/128"))); + assertFalse(ipPrefix.contains( + Ip6Prefix.valueOf("1111:2222:3333:4444::/64"))); + assertFalse(ipPrefix.contains( + Ip6Prefix.valueOf("1111:2222:3333:4445::/120"))); + assertFalse(ipPrefix.contains(Ip6Prefix.valueOf("::/64"))); + assertFalse(ipPrefix.contains(Ip6Prefix.valueOf("::/0"))); + assertFalse(ipPrefix.contains( + Ip6Prefix.valueOf("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/128"))); + + ipPrefix = Ip6Prefix.valueOf("1111:2222:3333:4444::/128"); + assertFalse(ipPrefix.contains( + Ip6Prefix.valueOf("1111:2222:3333:4444::/120"))); + assertTrue(ipPrefix.contains( + Ip6Prefix.valueOf("1111:2222:3333:4444::/128"))); + assertFalse(ipPrefix.contains( + Ip6Prefix.valueOf("1111:2222:3333:4444::1/128"))); + assertFalse(ipPrefix.contains( + Ip6Prefix.valueOf("1111:2222:3333:4444::/64"))); + assertFalse(ipPrefix.contains( + Ip6Prefix.valueOf("1111:2222:3333:4445::/120"))); + assertFalse(ipPrefix.contains(Ip6Prefix.valueOf("::/64"))); + assertFalse(ipPrefix.contains(Ip6Prefix.valueOf("::/0"))); + assertFalse(ipPrefix.contains( + Ip6Prefix.valueOf("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/128"))); + + ipPrefix = Ip6Prefix.valueOf("::/0"); + assertTrue(ipPrefix.contains( + Ip6Prefix.valueOf("1111:2222:3333:4444::/120"))); + assertTrue(ipPrefix.contains( + Ip6Prefix.valueOf("1111:2222:3333:4444::/128"))); + assertTrue(ipPrefix.contains( + Ip6Prefix.valueOf("1111:2222:3333:4444::1/128"))); + assertTrue(ipPrefix.contains( + Ip6Prefix.valueOf("1111:2222:3333:4444::/64"))); + assertTrue(ipPrefix.contains( + Ip6Prefix.valueOf("1111:2222:3333:4445::/120"))); + assertTrue(ipPrefix.contains(Ip6Prefix.valueOf("::/64"))); + assertTrue(ipPrefix.contains(Ip6Prefix.valueOf("::/0"))); + assertTrue(ipPrefix.contains( + Ip6Prefix.valueOf("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/128"))); + + ipPrefix = + Ip6Prefix.valueOf("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/128"); + assertFalse(ipPrefix.contains( + Ip6Prefix.valueOf("1111:2222:3333:4444::/120"))); + assertFalse(ipPrefix.contains( + Ip6Prefix.valueOf("1111:2222:3333:4444::/128"))); + assertFalse(ipPrefix.contains( + Ip6Prefix.valueOf("1111:2222:3333:4444::1/128"))); + assertFalse(ipPrefix.contains( + Ip6Prefix.valueOf("1111:2222:3333:4444::/64"))); + assertFalse(ipPrefix.contains( + Ip6Prefix.valueOf("1111:2222:3333:4445::/120"))); + assertFalse(ipPrefix.contains(Ip6Prefix.valueOf("::/64"))); + assertFalse(ipPrefix.contains(Ip6Prefix.valueOf("::/0"))); + assertTrue(ipPrefix.contains( + Ip6Prefix.valueOf("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/128"))); + } + + /** + * Tests IP prefix contains IP address for IPv6. + */ + @Test + public void testContainsIpAddressIPv6() { + Ip6Prefix ipPrefix; + + ipPrefix = Ip6Prefix.valueOf("1111:2222:3333:4444::/120"); + assertTrue(ipPrefix.contains( + Ip6Address.valueOf("1111:2222:3333:4444::"))); + assertTrue(ipPrefix.contains( + Ip6Address.valueOf("1111:2222:3333:4444::1"))); + assertFalse(ipPrefix.contains( + Ip6Address.valueOf("1111:2222:3333:4445::"))); + assertFalse(ipPrefix.contains(Ip6Address.valueOf("::"))); + assertFalse(ipPrefix.contains( + Ip6Address.valueOf("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"))); + + ipPrefix = Ip6Prefix.valueOf("1111:2222:3333:4444::/128"); + assertTrue(ipPrefix.contains( + Ip6Address.valueOf("1111:2222:3333:4444::"))); + assertFalse(ipPrefix.contains( + Ip6Address.valueOf("1111:2222:3333:4444::1"))); + assertFalse(ipPrefix.contains( + Ip6Address.valueOf("1111:2222:3333:4445::"))); + assertFalse(ipPrefix.contains(Ip6Address.valueOf("::"))); + assertFalse(ipPrefix.contains( + Ip6Address.valueOf("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"))); + + ipPrefix = Ip6Prefix.valueOf("::/0"); + assertTrue(ipPrefix.contains( + Ip6Address.valueOf("1111:2222:3333:4444::"))); + assertTrue(ipPrefix.contains( + Ip6Address.valueOf("1111:2222:3333:4444::1"))); + assertTrue(ipPrefix.contains( + Ip6Address.valueOf("1111:2222:3333:4445::"))); + assertTrue(ipPrefix.contains(Ip6Address.valueOf("::"))); + assertTrue(ipPrefix.contains( + Ip6Address.valueOf("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"))); + + ipPrefix = + Ip6Prefix.valueOf("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/128"); + assertFalse(ipPrefix.contains( + Ip6Address.valueOf("1111:2222:3333:4444::"))); + assertFalse(ipPrefix.contains( + Ip6Address.valueOf("1111:2222:3333:4444::1"))); + assertFalse(ipPrefix.contains( + Ip6Address.valueOf("1111:2222:3333:4445::"))); + assertFalse(ipPrefix.contains(Ip6Address.valueOf("::"))); + assertTrue(ipPrefix.contains( + Ip6Address.valueOf("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"))); + } + + /** + * Tests equality of {@link Ip6Prefix} for IPv6. + */ + @Test + public void testEqualityIPv6() { + new EqualsTester() + .addEqualityGroup( + Ip6Prefix.valueOf("1111:2222:3333:4444::/120"), + Ip6Prefix.valueOf("1111:2222:3333:4444::1/120"), + Ip6Prefix.valueOf("1111:2222:3333:4444::/120")) + .addEqualityGroup( + Ip6Prefix.valueOf("1111:2222:3333:4444::/64"), + Ip6Prefix.valueOf("1111:2222:3333:4444::/64")) + .addEqualityGroup( + Ip6Prefix.valueOf("1111:2222:3333:4444::/128"), + Ip6Prefix.valueOf("1111:2222:3333:4444::/128")) + .addEqualityGroup( + Ip6Prefix.valueOf("1111:2222:3333:4445::/64"), + Ip6Prefix.valueOf("1111:2222:3333:4445::/64")) + .addEqualityGroup( + Ip6Prefix.valueOf("::/0"), + Ip6Prefix.valueOf("::/0")) + .addEqualityGroup( + Ip6Prefix.valueOf("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/128"), + Ip6Prefix.valueOf("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/128")) + .testEquals(); + } + + /** + * Tests object string representation for IPv6. + */ + @Test + public void testToStringIPv6() { + Ip6Prefix ipPrefix; + + ipPrefix = Ip6Prefix.valueOf("1100::/8"); + assertThat(ipPrefix.toString(), is("1100::/8")); + + ipPrefix = Ip6Prefix.valueOf("1111:2222:3333:4444:5555:6666:7777:8885/8"); + assertThat(ipPrefix.toString(), is("1100::/8")); + + ipPrefix = Ip6Prefix.valueOf("::/0"); + assertThat(ipPrefix.toString(), is("::/0")); + + ipPrefix = Ip6Prefix.valueOf("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/128"); + assertThat(ipPrefix.toString(), + is("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/128")); + } +} diff --git a/framework/src/onos/utils/misc/src/test/java/org/onlab/packet/IpAddressTest.java b/framework/src/onos/utils/misc/src/test/java/org/onlab/packet/IpAddressTest.java new file mode 100644 index 00000000..8618c466 --- /dev/null +++ b/framework/src/onos/utils/misc/src/test/java/org/onlab/packet/IpAddressTest.java @@ -0,0 +1,946 @@ +/* + * Copyright 2014-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.net.InetAddress; + +import org.junit.Test; + +import com.google.common.net.InetAddresses; +import com.google.common.testing.EqualsTester; + +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.assertFalse; +import static org.onlab.junit.ImmutableClassChecker.assertThatClassIsImmutableBaseClass; + +/** + * Tests for class {@link IpAddress}. + */ +public class IpAddressTest { + /** + * Tests the immutability of {@link IpAddress}. + */ + @Test + public void testImmutable() { + assertThatClassIsImmutableBaseClass(IpAddress.class); + } + + /** + * Tests the length of the address in bytes (octets). + */ + @Test + public void testAddrByteLength() { + assertThat(IpAddress.INET_BYTE_LENGTH, is(4)); + assertThat(IpAddress.INET6_BYTE_LENGTH, is(16)); + assertThat(IpAddress.byteLength(IpAddress.Version.INET), is(4)); + assertThat(IpAddress.byteLength(IpAddress.Version.INET6), is(16)); + } + + /** + * Tests the length of the address in bits. + */ + @Test + public void testAddrBitLength() { + assertThat(IpAddress.INET_BIT_LENGTH, is(32)); + assertThat(IpAddress.INET6_BIT_LENGTH, is(128)); + } + + /** + * Tests returning the IP address version. + */ + @Test + public void testVersion() { + IpAddress ipAddress; + + // IPv4 + ipAddress = IpAddress.valueOf("0.0.0.0"); + assertThat(ipAddress.version(), is(IpAddress.Version.INET)); + + // IPv6 + ipAddress = IpAddress.valueOf("::"); + assertThat(ipAddress.version(), is(IpAddress.Version.INET6)); + } + + /** + * Tests whether the IP version of an address is IPv4. + */ + @Test + public void testIsIp4() { + IpAddress ipAddress; + + // IPv4 + ipAddress = IpAddress.valueOf("0.0.0.0"); + assertTrue(ipAddress.isIp4()); + + // IPv6 + ipAddress = IpAddress.valueOf("::"); + assertFalse(ipAddress.isIp4()); + } + + /** + * Tests whether the IP version of an address is IPv6. + */ + @Test + public void testIsIp6() { + IpAddress ipAddress; + + // IPv4 + ipAddress = IpAddress.valueOf("0.0.0.0"); + assertFalse(ipAddress.isIp6()); + + // IPv6 + ipAddress = IpAddress.valueOf("::"); + assertTrue(ipAddress.isIp6()); + } + + /** + * Tests getting the Ip4Address and Ip6Address view of the IP address. + */ + @Test + public void testGetIp4AndIp6AddressView() { + IpAddress ipAddress; + Ip4Address ip4Address; + Ip6Address ip6Address; + + // Pure IPv4 IpAddress + ipAddress = IpAddress.valueOf("1.2.3.4"); + ip4Address = ipAddress.getIp4Address(); + ip6Address = ipAddress.getIp6Address(); + assertThat(ip4Address.toString(), is("1.2.3.4")); + assertNull(ip6Address); + + // IPv4 IpAddress that is Ip4Address + ipAddress = Ip4Address.valueOf("1.2.3.4"); + ip4Address = ipAddress.getIp4Address(); + ip6Address = ipAddress.getIp6Address(); + assertThat(ip4Address.toString(), is("1.2.3.4")); + assertNull(ip6Address); + + // Pure IPv6 IpAddress + ipAddress = IpAddress.valueOf("1111:2222::"); + ip4Address = ipAddress.getIp4Address(); + ip6Address = ipAddress.getIp6Address(); + assertNull(ip4Address); + assertThat(ip6Address.toString(), is("1111:2222::")); + + // IPv6 IpAddress that is Ip6Address + ipAddress = Ip6Address.valueOf("1111:2222::"); + ip4Address = ipAddress.getIp4Address(); + ip6Address = ipAddress.getIp6Address(); + assertNull(ip4Address); + assertThat(ip6Address.toString(), is("1111:2222::")); + } + + /** + * Tests returning an IPv4 address as a byte array. + */ + @Test + public void testAddressToOctetsIPv4() { + IpAddress ipAddress; + byte[] value; + + value = new byte[] {1, 2, 3, 4}; + ipAddress = IpAddress.valueOf("1.2.3.4"); + assertThat(ipAddress.toOctets(), is(value)); + + value = new byte[] {0, 0, 0, 0}; + ipAddress = IpAddress.valueOf("0.0.0.0"); + assertThat(ipAddress.toOctets(), is(value)); + + value = new byte[] {(byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff}; + ipAddress = IpAddress.valueOf("255.255.255.255"); + assertThat(ipAddress.toOctets(), is(value)); + } + + /** + * Tests returning an IPv6 address as a byte array. + */ + @Test + public void testAddressToOctetsIPv6() { + IpAddress ipAddress; + byte[] value; + + value = new byte[] {0x11, 0x11, 0x22, 0x22, + 0x33, 0x33, 0x44, 0x44, + 0x55, 0x55, 0x66, 0x66, + 0x77, 0x77, + (byte) 0x88, (byte) 0x88}; + ipAddress = + IpAddress.valueOf("1111:2222:3333:4444:5555:6666:7777:8888"); + assertThat(ipAddress.toOctets(), is(value)); + + value = new byte[] {0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00}; + ipAddress = IpAddress.valueOf("::"); + assertThat(ipAddress.toOctets(), is(value)); + + value = new byte[] {(byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff}; + ipAddress = + IpAddress.valueOf("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"); + assertThat(ipAddress.toOctets(), is(value)); + } + + /** + * Tests valueOf() converter for IPv4 integer value. + */ + @Test + public void testValueOfForIntegerIPv4() { + IpAddress ipAddress; + + ipAddress = IpAddress.valueOf(0x01020304); + assertThat(ipAddress.toString(), is("1.2.3.4")); + + ipAddress = IpAddress.valueOf(0); + assertThat(ipAddress.toString(), is("0.0.0.0")); + + ipAddress = IpAddress.valueOf(0xffffffff); + assertThat(ipAddress.toString(), is("255.255.255.255")); + } + + /** + * Tests valueOf() converter for IPv4 byte array. + */ + @Test + public void testValueOfByteArrayIPv4() { + IpAddress ipAddress; + byte[] value; + + value = new byte[] {1, 2, 3, 4}; + ipAddress = IpAddress.valueOf(IpAddress.Version.INET, value); + assertThat(ipAddress.toString(), is("1.2.3.4")); + + value = new byte[] {0, 0, 0, 0}; + ipAddress = IpAddress.valueOf(IpAddress.Version.INET, value); + assertThat(ipAddress.toString(), is("0.0.0.0")); + + value = new byte[] {(byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff}; + ipAddress = IpAddress.valueOf(IpAddress.Version.INET, value); + assertThat(ipAddress.toString(), is("255.255.255.255")); + } + + /** + * Tests valueOf() converter for IPv6 byte array. + */ + @Test + public void testValueOfByteArrayIPv6() { + IpAddress ipAddress; + byte[] value; + + value = new byte[] {0x11, 0x11, 0x22, 0x22, + 0x33, 0x33, 0x44, 0x44, + 0x55, 0x55, 0x66, 0x66, + 0x77, 0x77, + (byte) 0x88, (byte) 0x88}; + ipAddress = IpAddress.valueOf(IpAddress.Version.INET6, value); + assertThat(ipAddress.toString(), + is("1111:2222:3333:4444:5555:6666:7777:8888")); + + value = new byte[] {0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00}; + ipAddress = IpAddress.valueOf(IpAddress.Version.INET6, value); + assertThat(ipAddress.toString(), is("::")); + + value = new byte[] {(byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff}; + ipAddress = IpAddress.valueOf(IpAddress.Version.INET6, value); + assertThat(ipAddress.toString(), + is("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff")); + } + + /** + * Tests invalid valueOf() converter for a null array for IPv4. + */ + @Test(expected = NullPointerException.class) + public void testInvalidValueOfNullArrayIPv4() { + IpAddress ipAddress; + byte[] value; + + value = null; + ipAddress = IpAddress.valueOf(IpAddress.Version.INET, value); + } + + /** + * Tests invalid valueOf() converter for a null array for IPv6. + */ + @Test(expected = NullPointerException.class) + public void testInvalidValueOfNullArrayIPv6() { + IpAddress ipAddress; + byte[] value; + + value = null; + ipAddress = IpAddress.valueOf(IpAddress.Version.INET6, value); + } + + /** + * Tests invalid valueOf() converger for an array that is too short for + * IPv4. + */ + @Test(expected = IllegalArgumentException.class) + public void testInvalidValueOfShortArrayIPv4() { + IpAddress ipAddress; + byte[] value; + + value = new byte[] {1, 2, 3}; + ipAddress = IpAddress.valueOf(IpAddress.Version.INET, value); + } + + /** + * Tests invalid valueOf() converger for an array that is too short for + * IPv6. + */ + @Test(expected = IllegalArgumentException.class) + public void testInvalidValueOfShortArrayIPv6() { + IpAddress ipAddress; + byte[] value; + + value = new byte[] {1, 2, 3, 4, 5, 6, 7, 8, 9}; + ipAddress = IpAddress.valueOf(IpAddress.Version.INET6, value); + } + + /** + * Tests valueOf() converter for IPv4 byte array and an offset. + */ + @Test + public void testValueOfByteArrayOffsetIPv4() { + IpAddress ipAddress; + byte[] value; + + value = new byte[] {11, 22, 33, // Preamble + 1, 2, 3, 4, + 44, 55}; // Extra bytes + ipAddress = IpAddress.valueOf(IpAddress.Version.INET, value, 3); + assertThat(ipAddress.toString(), is("1.2.3.4")); + + value = new byte[] {11, 22, // Preamble + 0, 0, 0, 0, + 33}; // Extra bytes + ipAddress = IpAddress.valueOf(IpAddress.Version.INET, value, 2); + assertThat(ipAddress.toString(), is("0.0.0.0")); + + value = new byte[] {11, 22, // Preamble + (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, + 33}; // Extra bytes + ipAddress = IpAddress.valueOf(IpAddress.Version.INET, value, 2); + assertThat(ipAddress.toString(), is("255.255.255.255")); + } + + /** + * Tests valueOf() converter for IPv6 byte array and an offset. + */ + @Test + public void testValueOfByteArrayOffsetIPv6() { + IpAddress ipAddress; + byte[] value; + + value = new byte[] {11, 22, 33, // Preamble + 0x11, 0x11, 0x22, 0x22, + 0x33, 0x33, 0x44, 0x44, + 0x55, 0x55, 0x66, 0x66, + 0x77, 0x77, + (byte) 0x88, (byte) 0x88, + 44, 55}; // Extra bytes + ipAddress = IpAddress.valueOf(IpAddress.Version.INET6, value, 3); + assertThat(ipAddress.toString(), + is("1111:2222:3333:4444:5555:6666:7777:8888")); + + value = new byte[] {11, 22, // Preamble + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 33}; // Extra bytes + ipAddress = IpAddress.valueOf(IpAddress.Version.INET6, value, 2); + assertThat(ipAddress.toString(), is("::")); + + value = new byte[] {11, 22, // Preamble + (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, + 33}; // Extra bytes + ipAddress = IpAddress.valueOf(IpAddress.Version.INET6, value, 2); + assertThat(ipAddress.toString(), + is("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff")); + } + + /** + * Tests invalid valueOf() converger for an array and an invalid offset + * for IPv4. + */ + @Test(expected = IllegalArgumentException.class) + public void testInvalidValueOfArrayInvalidOffsetIPv4() { + IpAddress ipAddress; + byte[] value; + + value = new byte[] {11, 22, 33, // Preamble + 1, 2, 3, 4, + 44, 55}; // Extra bytes + ipAddress = IpAddress.valueOf(IpAddress.Version.INET, value, 6); + } + + /** + * Tests invalid valueOf() converger for an array and an invalid offset + * for IPv6. + */ + @Test(expected = IllegalArgumentException.class) + public void testInvalidValueOfArrayInvalidOffsetIPv6() { + IpAddress ipAddress; + byte[] value; + + value = new byte[] {11, 22, 33, // Preamble + 0x11, 0x11, 0x22, 0x22, + 0x33, 0x33, 0x44, 0x44, + 0x55, 0x55, 0x66, 0x66, + 0x77, 0x77, + (byte) 0x88, (byte) 0x88, + 44, 55}; // Extra bytes + ipAddress = IpAddress.valueOf(IpAddress.Version.INET6, value, 6); + } + + /** + * Tests valueOf() converter for IPv4 InetAddress. + */ + @Test + public void testValueOfInetAddressIPv4() { + IpAddress ipAddress; + InetAddress inetAddress; + + inetAddress = InetAddresses.forString("1.2.3.4"); + ipAddress = IpAddress.valueOf(inetAddress); + assertThat(ipAddress.toString(), is("1.2.3.4")); + + inetAddress = InetAddresses.forString("0.0.0.0"); + ipAddress = IpAddress.valueOf(inetAddress); + assertThat(ipAddress.toString(), is("0.0.0.0")); + + inetAddress = InetAddresses.forString("255.255.255.255"); + ipAddress = IpAddress.valueOf(inetAddress); + assertThat(ipAddress.toString(), is("255.255.255.255")); + } + + /** + * Tests valueOf() converter for IPv6 InetAddress. + */ + @Test + public void testValueOfInetAddressIPv6() { + IpAddress ipAddress; + InetAddress inetAddress; + + inetAddress = + InetAddresses.forString("1111:2222:3333:4444:5555:6666:7777:8888"); + ipAddress = IpAddress.valueOf(inetAddress); + assertThat(ipAddress.toString(), + is("1111:2222:3333:4444:5555:6666:7777:8888")); + + inetAddress = InetAddresses.forString("::"); + ipAddress = IpAddress.valueOf(inetAddress); + assertThat(ipAddress.toString(), is("::")); + + inetAddress = + InetAddresses.forString("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"); + ipAddress = IpAddress.valueOf(inetAddress); + assertThat(ipAddress.toString(), + is("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff")); + } + + /** + * Tests valueOf() converter for IPv4 string. + */ + @Test + public void testValueOfStringIPv4() { + IpAddress ipAddress; + + ipAddress = IpAddress.valueOf("1.2.3.4"); + assertThat(ipAddress.toString(), is("1.2.3.4")); + + ipAddress = IpAddress.valueOf("0.0.0.0"); + assertThat(ipAddress.toString(), is("0.0.0.0")); + + ipAddress = IpAddress.valueOf("255.255.255.255"); + assertThat(ipAddress.toString(), is("255.255.255.255")); + } + + /** + * Tests valueOf() converter for IPv6 string. + */ + @Test + public void testValueOfStringIPv6() { + IpAddress ipAddress; + + ipAddress = + IpAddress.valueOf("1111:2222:3333:4444:5555:6666:7777:8888"); + assertThat(ipAddress.toString(), + is("1111:2222:3333:4444:5555:6666:7777:8888")); + + ipAddress = IpAddress.valueOf("::"); + assertThat(ipAddress.toString(), is("::")); + + ipAddress = + IpAddress.valueOf("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"); + assertThat(ipAddress.toString(), + is("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff")); + } + + /** + * Tests invalid valueOf() converter for a null string. + */ + @Test(expected = NullPointerException.class) + public void testInvalidValueOfNullString() { + IpAddress ipAddress; + + String fromString = null; + ipAddress = IpAddress.valueOf(fromString); + } + + /** + * Tests invalid valueOf() converter for an empty string. + */ + @Test(expected = IllegalArgumentException.class) + public void testInvalidValueOfEmptyString() { + IpAddress ipAddress; + + String fromString = ""; + ipAddress = IpAddress.valueOf(fromString); + } + + /** + * Tests invalid valueOf() converter for an incorrect string. + */ + @Test(expected = IllegalArgumentException.class) + public void testInvalidValueOfIncorrectString() { + IpAddress ipAddress; + + String fromString = "NoSuchIpAddress"; + ipAddress = IpAddress.valueOf(fromString); + } + + /** + * Tests making a mask prefix for a given prefix length for IPv4. + */ + @Test + public void testMakeMaskPrefixIPv4() { + IpAddress ipAddress; + + ipAddress = IpAddress.makeMaskPrefix(IpAddress.Version.INET, 25); + assertThat(ipAddress.toString(), is("255.255.255.128")); + + ipAddress = IpAddress.makeMaskPrefix(IpAddress.Version.INET, 0); + assertThat(ipAddress.toString(), is("0.0.0.0")); + + ipAddress = IpAddress.makeMaskPrefix(IpAddress.Version.INET, 32); + assertThat(ipAddress.toString(), is("255.255.255.255")); + } + + /** + * Tests making a mask prefix for a given prefix length for IPv6. + */ + @Test + public void testMakeMaskPrefixIPv6() { + IpAddress ipAddress; + + ipAddress = IpAddress.makeMaskPrefix(IpAddress.Version.INET6, 8); + assertThat(ipAddress.toString(), is("ff00::")); + + ipAddress = IpAddress.makeMaskPrefix(IpAddress.Version.INET6, 120); + assertThat(ipAddress.toString(), + is("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ff00")); + + ipAddress = IpAddress.makeMaskPrefix(IpAddress.Version.INET6, 0); + assertThat(ipAddress.toString(), is("::")); + + ipAddress = IpAddress.makeMaskPrefix(IpAddress.Version.INET6, 128); + assertThat(ipAddress.toString(), + is("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff")); + + ipAddress = IpAddress.makeMaskPrefix(IpAddress.Version.INET6, 64); + assertThat(ipAddress.toString(), is("ffff:ffff:ffff:ffff::")); + } + + /** + * Tests making a mask prefix for an invalid prefix length for IPv4: + * negative prefix length. + */ + @Test(expected = IllegalArgumentException.class) + public void testInvalidMakeNegativeMaskPrefixIPv4() { + IpAddress ipAddress; + + ipAddress = IpAddress.makeMaskPrefix(IpAddress.Version.INET, -1); + } + + /** + * Tests making a mask prefix for an invalid prefix length for IPv6: + * negative prefix length. + */ + @Test(expected = IllegalArgumentException.class) + public void testInvalidMakeNegativeMaskPrefixIPv6() { + IpAddress ipAddress; + + ipAddress = IpAddress.makeMaskPrefix(IpAddress.Version.INET6, -1); + } + + /** + * Tests making a mask prefix for an invalid prefix length for IPv4: + * too long prefix length. + */ + @Test(expected = IllegalArgumentException.class) + public void testInvalidMakeTooLongMaskPrefixIPv4() { + IpAddress ipAddress; + + ipAddress = IpAddress.makeMaskPrefix(IpAddress.Version.INET, 33); + } + + /** + * Tests making a mask prefix for an invalid prefix length for IPv6: + * too long prefix length. + */ + @Test(expected = IllegalArgumentException.class) + public void testInvalidMakeTooLongMaskPrefixIPv6() { + IpAddress ipAddress; + + ipAddress = IpAddress.makeMaskPrefix(IpAddress.Version.INET6, 129); + } + + /** + * Tests making of a masked address for IPv4. + */ + @Test + public void testMakeMaskedAddressIPv4() { + IpAddress ipAddress = IpAddress.valueOf("1.2.3.5"); + IpAddress ipAddressMasked; + + ipAddressMasked = IpAddress.makeMaskedAddress(ipAddress, 24); + assertThat(ipAddressMasked.toString(), is("1.2.3.0")); + + ipAddressMasked = IpAddress.makeMaskedAddress(ipAddress, 0); + assertThat(ipAddressMasked.toString(), is("0.0.0.0")); + + ipAddressMasked = IpAddress.makeMaskedAddress(ipAddress, 32); + assertThat(ipAddressMasked.toString(), is("1.2.3.5")); + } + + /** + * Tests making of a masked address for IPv6. + */ + @Test + public void testMakeMaskedAddressIPv6() { + IpAddress ipAddress = + IpAddress.valueOf("1111:2222:3333:4444:5555:6666:7777:8885"); + IpAddress ipAddressMasked; + + ipAddressMasked = IpAddress.makeMaskedAddress(ipAddress, 8); + assertThat(ipAddressMasked.toString(), is("1100::")); + + ipAddressMasked = IpAddress.makeMaskedAddress(ipAddress, 120); + assertThat(ipAddressMasked.toString(), + is("1111:2222:3333:4444:5555:6666:7777:8800")); + + ipAddressMasked = IpAddress.makeMaskedAddress(ipAddress, 0); + assertThat(ipAddressMasked.toString(), is("::")); + + ipAddressMasked = IpAddress.makeMaskedAddress(ipAddress, 128); + assertThat(ipAddressMasked.toString(), + is("1111:2222:3333:4444:5555:6666:7777:8885")); + + ipAddressMasked = IpAddress.makeMaskedAddress(ipAddress, 64); + assertThat(ipAddressMasked.toString(), is("1111:2222:3333:4444::")); + } + + /** + * Tests making of a masked address for invalid prefix length for IPv4: + * negative prefix length. + */ + @Test(expected = IllegalArgumentException.class) + public void testInvalidMakeNegativeMaskedAddressIPv4() { + IpAddress ipAddress = IpAddress.valueOf("1.2.3.5"); + IpAddress ipAddressMasked; + + ipAddressMasked = IpAddress.makeMaskedAddress(ipAddress, -1); + } + + /** + * Tests making of a masked address for invalid prefix length for IPv6: + * negative prefix length. + */ + @Test(expected = IllegalArgumentException.class) + public void testInvalidMakeNegativeMaskedAddressIPv6() { + IpAddress ipAddress = + IpAddress.valueOf("1111:2222:3333:4444:5555:6666:7777:8885"); + IpAddress ipAddressMasked; + + ipAddressMasked = IpAddress.makeMaskedAddress(ipAddress, -1); + } + + /** + * Tests making of a masked address for an invalid prefix length for IPv4: + * too long prefix length. + */ + @Test(expected = IllegalArgumentException.class) + public void testInvalidMakeTooLongMaskedAddressIPv4() { + IpAddress ipAddress = IpAddress.valueOf("1.2.3.5"); + IpAddress ipAddressMasked; + + ipAddressMasked = IpAddress.makeMaskedAddress(ipAddress, 33); + } + + /** + * Tests making of a masked address for an invalid prefix length for IPv6: + * too long prefix length. + */ + @Test(expected = IllegalArgumentException.class) + public void testInvalidMakeTooLongMaskedAddressIPv6() { + IpAddress ipAddress = + IpAddress.valueOf("1111:2222:3333:4444:5555:6666:7777:8885"); + IpAddress ipAddressMasked; + + ipAddressMasked = IpAddress.makeMaskedAddress(ipAddress, 129); + } + + /** + * Tests if address is zero for IPv4. + */ + @Test + public void testIsZeroIPv4() { + IpAddress normalIP = IpAddress.valueOf("10.0.0.1"); + IpAddress zeroIP = IpAddress.valueOf("0.0.0.0"); + assertFalse(normalIP.isZero()); + assertTrue(zeroIP.isZero()); + } + + /** + * Tests if address is zero for IPv6. + */ + @Test + public void testIsZeroIPv6() { + IpAddress normalIP = IpAddress.valueOf("fe80::1"); + IpAddress zeroIP = IpAddress.valueOf("::"); + assertFalse(normalIP.isZero()); + assertTrue(zeroIP.isZero()); + } + + /** + * Tests if address is self-assigned for IPv4. + */ + @Test + public void testIsSelfAssignedIpv4() { + IpAddress normalIP = IpAddress.valueOf("10.0.0.1"); + IpAddress selfAssignedIP = IpAddress.valueOf("169.1.2.3"); + assertFalse(normalIP.isSelfAssigned()); + assertTrue(selfAssignedIP.isSelfAssigned()); + } + + /** + * Tests comparison of {@link IpAddress} for IPv4. + */ + @Test + public void testComparisonIPv4() { + IpAddress addr1, addr2, addr3, addr4; + + addr1 = IpAddress.valueOf("1.2.3.4"); + addr2 = IpAddress.valueOf("1.2.3.4"); + addr3 = IpAddress.valueOf("1.2.3.3"); + addr4 = IpAddress.valueOf("1.2.3.5"); + assertTrue(addr1.compareTo(addr2) == 0); + assertTrue(addr1.compareTo(addr3) > 0); + assertTrue(addr1.compareTo(addr4) < 0); + + addr1 = IpAddress.valueOf("255.2.3.4"); + addr2 = IpAddress.valueOf("255.2.3.4"); + addr3 = IpAddress.valueOf("255.2.3.3"); + addr4 = IpAddress.valueOf("255.2.3.5"); + assertTrue(addr1.compareTo(addr2) == 0); + assertTrue(addr1.compareTo(addr3) > 0); + assertTrue(addr1.compareTo(addr4) < 0); + } + + /** + * Tests comparison of {@link IpAddress} for IPv6. + */ + @Test + public void testComparisonIPv6() { + IpAddress addr1, addr2, addr3, addr4; + + addr1 = IpAddress.valueOf("1111:2222:3333:4444:5555:6666:7777:8888"); + addr2 = IpAddress.valueOf("1111:2222:3333:4444:5555:6666:7777:8888"); + addr3 = IpAddress.valueOf("1111:2222:3333:4444:5555:6666:7777:8887"); + addr4 = IpAddress.valueOf("1111:2222:3333:4444:5555:6666:7777:8889"); + assertTrue(addr1.compareTo(addr2) == 0); + assertTrue(addr1.compareTo(addr3) > 0); + assertTrue(addr1.compareTo(addr4) < 0); + + addr1 = IpAddress.valueOf("ffff:2222:3333:4444:5555:6666:7777:8888"); + addr2 = IpAddress.valueOf("ffff:2222:3333:4444:5555:6666:7777:8888"); + addr3 = IpAddress.valueOf("ffff:2222:3333:4444:5555:6666:7777:8887"); + addr4 = IpAddress.valueOf("ffff:2222:3333:4444:5555:6666:7777:8889"); + assertTrue(addr1.compareTo(addr2) == 0); + assertTrue(addr1.compareTo(addr3) > 0); + assertTrue(addr1.compareTo(addr4) < 0); + + addr1 = IpAddress.valueOf("ffff:2222:3333:4444:5555:6666:7777:8888"); + addr2 = IpAddress.valueOf("ffff:2222:3333:4444:5555:6666:7777:8888"); + addr3 = IpAddress.valueOf("ffff:2222:3333:4443:5555:6666:7777:8888"); + addr4 = IpAddress.valueOf("ffff:2222:3333:4445:5555:6666:7777:8888"); + assertTrue(addr1.compareTo(addr2) == 0); + assertTrue(addr1.compareTo(addr3) > 0); + assertTrue(addr1.compareTo(addr4) < 0); + } + + /** + * Tests equality of {@link IpAddress} for IPv4. + */ + @Test + public void testEqualityIPv4() { + new EqualsTester() + .addEqualityGroup(IpAddress.valueOf("1.2.3.4"), + IpAddress.valueOf("1.2.3.4")) + .addEqualityGroup(IpAddress.valueOf("1.2.3.5"), + IpAddress.valueOf("1.2.3.5")) + .addEqualityGroup(IpAddress.valueOf("0.0.0.0"), + IpAddress.valueOf("0.0.0.0")) + .addEqualityGroup(IpAddress.valueOf("255.255.255.255"), + IpAddress.valueOf("255.255.255.255")) + .testEquals(); + } + + /** + * Tests equality of {@link IpAddress} for IPv6. + */ + @Test + public void testEqualityIPv6() { + new EqualsTester() + .addEqualityGroup( + IpAddress.valueOf("1111:2222:3333:4444:5555:6666:7777:8888"), + IpAddress.valueOf("1111:2222:3333:4444:5555:6666:7777:8888")) + .addEqualityGroup( + IpAddress.valueOf("1111:2222:3333:4444:5555:6666:7777:888a"), + IpAddress.valueOf("1111:2222:3333:4444:5555:6666:7777:888a")) + .addEqualityGroup( + IpAddress.valueOf("::"), + IpAddress.valueOf("::")) + .addEqualityGroup( + IpAddress.valueOf("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"), + IpAddress.valueOf("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff")) + .testEquals(); + } + + /** + * Tests object string representation for IPv4. + */ + @Test + public void testToStringIPv4() { + IpAddress ipAddress; + + ipAddress = IpAddress.valueOf("1.2.3.4"); + assertThat(ipAddress.toString(), is("1.2.3.4")); + + ipAddress = IpAddress.valueOf("0.0.0.0"); + assertThat(ipAddress.toString(), is("0.0.0.0")); + + ipAddress = IpAddress.valueOf("255.255.255.255"); + assertThat(ipAddress.toString(), is("255.255.255.255")); + } + + /** + * Tests object string representation for IPv6. + */ + @Test + public void testToStringIPv6() { + IpAddress ipAddress; + + ipAddress = + IpAddress.valueOf("1111:2222:3333:4444:5555:6666:7777:8888"); + assertThat(ipAddress.toString(), + is("1111:2222:3333:4444:5555:6666:7777:8888")); + + ipAddress = IpAddress.valueOf("1111::8888"); + assertThat(ipAddress.toString(), is("1111::8888")); + + ipAddress = IpAddress.valueOf("1111::"); + assertThat(ipAddress.toString(), is("1111::")); + + ipAddress = IpAddress.valueOf("::8888"); + assertThat(ipAddress.toString(), is("::8888")); + + ipAddress = IpAddress.valueOf("::"); + assertThat(ipAddress.toString(), is("::")); + + ipAddress = + IpAddress.valueOf("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"); + assertThat(ipAddress.toString(), + is("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff")); + + ipAddress = + IpAddress.valueOf("::1111:2222"); + assertThat(ipAddress.toString(), + is("::1111:2222")); + + ipAddress = + IpAddress.valueOf("1:0:0:1:0:0:2:3"); + assertThat(ipAddress.toString(), + is("1::1:0:0:2:3")); + + ipAddress = + IpAddress.valueOf("::0123:0004"); + assertThat(ipAddress.toString(), + is("::123:4")); + + ipAddress = + IpAddress.valueOf("0:0:1:1:0:0:1:1"); + assertThat(ipAddress.toString(), + is("::1:1:0:0:1:1")); + + ipAddress = + IpAddress.valueOf("1:1a2b::"); + assertThat(ipAddress.toString(), + is("1:1a2b::")); + + ipAddress = + IpAddress.valueOf("0:0:00:00:0000:00:00:000"); + assertThat(ipAddress.toString(), + is("::")); + + ipAddress = + IpAddress.valueOf("0:0:0:1:0:0:0:0"); + assertThat(ipAddress.toString(), + is("0:0:0:1::")); + } +} diff --git a/framework/src/onos/utils/misc/src/test/java/org/onlab/packet/IpPrefixTest.java b/framework/src/onos/utils/misc/src/test/java/org/onlab/packet/IpPrefixTest.java new file mode 100644 index 00000000..052a4cbb --- /dev/null +++ b/framework/src/onos/utils/misc/src/test/java/org/onlab/packet/IpPrefixTest.java @@ -0,0 +1,1080 @@ +/* + * Copyright 2014-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 org.junit.Test; + +import com.google.common.testing.EqualsTester; + +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; +import static org.onlab.junit.ImmutableClassChecker.assertThatClassIsImmutableBaseClass; + +/** + * Tests for class {@link IpPrefix}. + */ +public class IpPrefixTest { + /** + * Tests the immutability of {@link IpPrefix}. + */ + @Test + public void testImmutable() { + assertThatClassIsImmutableBaseClass(IpPrefix.class); + } + + /** + * Tests the maximum mask length. + */ + @Test + public void testMaxMaskLength() { + assertThat(IpPrefix.MAX_INET_MASK_LENGTH, is(32)); + assertThat(IpPrefix.MAX_INET6_MASK_LENGTH, is(128)); + } + + /** + * Tests returning the IP version of the prefix. + */ + @Test + public void testVersion() { + IpPrefix ipPrefix; + + // IPv4 + ipPrefix = IpPrefix.valueOf("0.0.0.0/0"); + assertThat(ipPrefix.version(), is(IpAddress.Version.INET)); + + // IPv6 + ipPrefix = IpPrefix.valueOf("::/0"); + assertThat(ipPrefix.version(), is(IpAddress.Version.INET6)); + } + + /** + * Tests whether the IP version of a prefix is IPv4. + */ + @Test + public void testIsIp4() { + IpPrefix ipPrefix; + + // IPv4 + ipPrefix = IpPrefix.valueOf("0.0.0.0/0"); + assertTrue(ipPrefix.isIp4()); + + // IPv6 + ipPrefix = IpPrefix.valueOf("::/0"); + assertFalse(ipPrefix.isIp4()); + } + + /** + * Tests whether the IP version of a prefix is IPv6. + */ + @Test + public void testIsIp6() { + IpPrefix ipPrefix; + + // IPv4 + ipPrefix = IpPrefix.valueOf("0.0.0.0/0"); + assertFalse(ipPrefix.isIp6()); + + // IPv6 + ipPrefix = IpPrefix.valueOf("::/0"); + assertTrue(ipPrefix.isIp6()); + } + + /** + * Tests returning the IP address value and IP address prefix length of + * an IPv4 prefix. + */ + @Test + public void testAddressAndPrefixLengthIPv4() { + IpPrefix ipPrefix; + + ipPrefix = IpPrefix.valueOf("1.2.3.0/24"); + assertThat(ipPrefix.address(), equalTo(IpAddress.valueOf("1.2.3.0"))); + assertThat(ipPrefix.prefixLength(), is(24)); + + ipPrefix = IpPrefix.valueOf("1.2.3.4/24"); + assertThat(ipPrefix.address(), equalTo(IpAddress.valueOf("1.2.3.0"))); + assertThat(ipPrefix.prefixLength(), is(24)); + + ipPrefix = IpPrefix.valueOf("1.2.3.4/32"); + assertThat(ipPrefix.address(), equalTo(IpAddress.valueOf("1.2.3.4"))); + assertThat(ipPrefix.prefixLength(), is(32)); + + ipPrefix = IpPrefix.valueOf("1.2.3.5/32"); + assertThat(ipPrefix.address(), equalTo(IpAddress.valueOf("1.2.3.5"))); + assertThat(ipPrefix.prefixLength(), is(32)); + + ipPrefix = IpPrefix.valueOf("0.0.0.0/0"); + assertThat(ipPrefix.address(), equalTo(IpAddress.valueOf("0.0.0.0"))); + assertThat(ipPrefix.prefixLength(), is(0)); + + ipPrefix = IpPrefix.valueOf("255.255.255.255/32"); + assertThat(ipPrefix.address(), + equalTo(IpAddress.valueOf("255.255.255.255"))); + assertThat(ipPrefix.prefixLength(), is(32)); + } + + /** + * Tests returning the IP address value and IP address prefix length of + * an IPv6 prefix. + */ + @Test + public void testAddressAndPrefixLengthIPv6() { + IpPrefix ipPrefix; + + ipPrefix = IpPrefix.valueOf("1100::/8"); + assertThat(ipPrefix.address(), equalTo(IpAddress.valueOf("1100::"))); + assertThat(ipPrefix.prefixLength(), is(8)); + + ipPrefix = + IpPrefix.valueOf("1111:2222:3333:4444:5555:6666:7777:8885/8"); + assertThat(ipPrefix.address(), equalTo(IpAddress.valueOf("1100::"))); + assertThat(ipPrefix.prefixLength(), is(8)); + + ipPrefix = + IpPrefix.valueOf("1111:2222:3333:4444:5555:6666:7777:8800/120"); + assertThat(ipPrefix.address(), + equalTo(IpAddress.valueOf("1111:2222:3333:4444:5555:6666:7777:8800"))); + assertThat(ipPrefix.prefixLength(), is(120)); + + ipPrefix = + IpPrefix.valueOf("1111:2222:3333:4444:5555:6666:7777:8885/128"); + assertThat(ipPrefix.address(), + equalTo(IpAddress.valueOf("1111:2222:3333:4444:5555:6666:7777:8885"))); + assertThat(ipPrefix.prefixLength(), is(128)); + + ipPrefix = IpPrefix.valueOf("::/0"); + assertThat(ipPrefix.address(), equalTo(IpAddress.valueOf("::"))); + assertThat(ipPrefix.prefixLength(), is(0)); + + ipPrefix = + IpPrefix.valueOf("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/128"); + assertThat(ipPrefix.address(), + equalTo(IpAddress.valueOf("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"))); + assertThat(ipPrefix.prefixLength(), is(128)); + + ipPrefix = + IpPrefix.valueOf("1111:2222:3333:4444:5555:6666:7777:8885/64"); + assertThat(ipPrefix.address(), + equalTo(IpAddress.valueOf("1111:2222:3333:4444::"))); + assertThat(ipPrefix.prefixLength(), is(64)); + } + + /** + * Tests getting the Ip4Prefix and Ip6Prefix view of the IP prefix. + */ + @Test + public void testGetIp4AndIp6PrefixView() { + IpPrefix ipPrefix; + Ip4Prefix ip4Prefix; + Ip6Prefix ip6Prefix; + + // Pure IPv4 IpPrefix + ipPrefix = IpPrefix.valueOf("1.2.3.0/24"); + ip4Prefix = ipPrefix.getIp4Prefix(); + ip6Prefix = ipPrefix.getIp6Prefix(); + assertThat(ip4Prefix.toString(), is("1.2.3.0/24")); + assertNull(ip6Prefix); + + // IPv4 IpPrefix that is Ip4Prefix + ipPrefix = Ip4Prefix.valueOf("1.2.3.0/24"); + ip4Prefix = ipPrefix.getIp4Prefix(); + ip6Prefix = ipPrefix.getIp6Prefix(); + assertThat(ip4Prefix.toString(), is("1.2.3.0/24")); + assertNull(ip6Prefix); + + // Pure IPv6 IpPrefix + ipPrefix = IpPrefix.valueOf("1111:2222::/64"); + ip4Prefix = ipPrefix.getIp4Prefix(); + ip6Prefix = ipPrefix.getIp6Prefix(); + assertNull(ip4Prefix); + assertThat(ip6Prefix.toString(), is("1111:2222::/64")); + + // IPv6 IpPrefix that is Ip6Prefix + ipPrefix = Ip6Prefix.valueOf("1111:2222::/64"); + ip4Prefix = ipPrefix.getIp4Prefix(); + ip6Prefix = ipPrefix.getIp6Prefix(); + assertNull(ip4Prefix); + assertThat(ip6Prefix.toString(), is("1111:2222::/64")); + } + + /** + * Tests valueOf() converter for IPv4 integer value. + */ + @Test + public void testValueOfForIntegerIPv4() { + IpPrefix ipPrefix; + + ipPrefix = IpPrefix.valueOf(0x01020304, 24); + assertThat(ipPrefix.toString(), is("1.2.3.0/24")); + + ipPrefix = IpPrefix.valueOf(0x01020304, 32); + assertThat(ipPrefix.toString(), is("1.2.3.4/32")); + + ipPrefix = IpPrefix.valueOf(0x01020305, 32); + assertThat(ipPrefix.toString(), is("1.2.3.5/32")); + + ipPrefix = IpPrefix.valueOf(0, 0); + assertThat(ipPrefix.toString(), is("0.0.0.0/0")); + + ipPrefix = IpPrefix.valueOf(0, 32); + assertThat(ipPrefix.toString(), is("0.0.0.0/32")); + + ipPrefix = IpPrefix.valueOf(0xffffffff, 0); + assertThat(ipPrefix.toString(), is("0.0.0.0/0")); + + ipPrefix = IpPrefix.valueOf(0xffffffff, 16); + assertThat(ipPrefix.toString(), is("255.255.0.0/16")); + + ipPrefix = IpPrefix.valueOf(0xffffffff, 32); + assertThat(ipPrefix.toString(), is("255.255.255.255/32")); + } + + /** + * Tests invalid valueOf() converter for IPv4 integer value and + * negative prefix length. + */ + @Test(expected = IllegalArgumentException.class) + public void testInvalidValueOfIntegerNegativePrefixLengthIPv4() { + IpPrefix ipPrefix; + + ipPrefix = IpPrefix.valueOf(0x01020304, -1); + } + + /** + * Tests invalid valueOf() converter for IPv4 integer value and + * too long prefix length. + */ + @Test(expected = IllegalArgumentException.class) + public void testInvalidValueOfIntegerTooLongPrefixLengthIPv4() { + IpPrefix ipPrefix; + + ipPrefix = IpPrefix.valueOf(0x01020304, 33); + } + + /** + * Tests valueOf() converter for IPv4 byte array. + */ + @Test + public void testValueOfByteArrayIPv4() { + IpPrefix ipPrefix; + byte[] value; + + value = new byte[] {1, 2, 3, 4}; + ipPrefix = IpPrefix.valueOf(IpAddress.Version.INET, value, 24); + assertThat(ipPrefix.toString(), is("1.2.3.0/24")); + + ipPrefix = IpPrefix.valueOf(IpAddress.Version.INET, value, 32); + assertThat(ipPrefix.toString(), is("1.2.3.4/32")); + + value = new byte[] {1, 2, 3, 5}; + ipPrefix = IpPrefix.valueOf(IpAddress.Version.INET, value, 32); + assertThat(ipPrefix.toString(), is("1.2.3.5/32")); + + value = new byte[] {0, 0, 0, 0}; + ipPrefix = IpPrefix.valueOf(IpAddress.Version.INET, value, 0); + assertThat(ipPrefix.toString(), is("0.0.0.0/0")); + + ipPrefix = IpPrefix.valueOf(IpAddress.Version.INET, value, 32); + assertThat(ipPrefix.toString(), is("0.0.0.0/32")); + + value = new byte[] {(byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff}; + ipPrefix = IpPrefix.valueOf(IpAddress.Version.INET, value, 0); + assertThat(ipPrefix.toString(), is("0.0.0.0/0")); + + ipPrefix = IpPrefix.valueOf(IpAddress.Version.INET, value, 16); + assertThat(ipPrefix.toString(), is("255.255.0.0/16")); + + ipPrefix = IpPrefix.valueOf(IpAddress.Version.INET, value, 32); + assertThat(ipPrefix.toString(), is("255.255.255.255/32")); + } + + /** + * Tests valueOf() converter for IPv6 byte array. + */ + @Test + public void testValueOfByteArrayIPv6() { + IpPrefix ipPrefix; + byte[] value; + + value = new byte[] {0x11, 0x11, 0x22, 0x22, + 0x33, 0x33, 0x44, 0x44, + 0x55, 0x55, 0x66, 0x66, + 0x77, 0x77, (byte) 0x88, (byte) 0x88}; + ipPrefix = IpPrefix.valueOf(IpAddress.Version.INET6, value, 120); + assertThat(ipPrefix.toString(), + is("1111:2222:3333:4444:5555:6666:7777:8800/120")); + + ipPrefix = IpPrefix.valueOf(IpAddress.Version.INET6, value, 128); + assertThat(ipPrefix.toString(), + is("1111:2222:3333:4444:5555:6666:7777:8888/128")); + + value = new byte[] {0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00}; + ipPrefix = IpPrefix.valueOf(IpAddress.Version.INET6, value, 0); + assertThat(ipPrefix.toString(), is("::/0")); + + ipPrefix = IpPrefix.valueOf(IpAddress.Version.INET6, value, 128); + assertThat(ipPrefix.toString(), is("::/128")); + + value = new byte[] {(byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff}; + ipPrefix = IpPrefix.valueOf(IpAddress.Version.INET6, value, 0); + assertThat(ipPrefix.toString(), is("::/0")); + + ipPrefix = IpPrefix.valueOf(IpAddress.Version.INET6, value, 64); + assertThat(ipPrefix.toString(), is("ffff:ffff:ffff:ffff::/64")); + + ipPrefix = IpPrefix.valueOf(IpAddress.Version.INET6, value, 128); + assertThat(ipPrefix.toString(), + is("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/128")); + } + + /** + * Tests invalid valueOf() converter for a null array for IPv4. + */ + @Test(expected = NullPointerException.class) + public void testInvalidValueOfNullArrayIPv4() { + IpPrefix ipPrefix; + byte[] value; + + value = null; + ipPrefix = IpPrefix.valueOf(IpAddress.Version.INET, value, 24); + } + + /** + * Tests invalid valueOf() converter for a null array for IPv6. + */ + @Test(expected = NullPointerException.class) + public void testInvalidValueOfNullArrayIPv6() { + IpPrefix ipPrefix; + byte[] value; + + value = null; + ipPrefix = IpPrefix.valueOf(IpAddress.Version.INET6, value, 120); + } + + /** + * Tests invalid valueOf() converter for a short array for IPv4. + */ + @Test(expected = IllegalArgumentException.class) + public void testInvalidValueOfShortArrayIPv4() { + IpPrefix ipPrefix; + byte[] value; + + value = new byte[] {1, 2, 3}; + ipPrefix = IpPrefix.valueOf(IpAddress.Version.INET, value, 24); + } + + /** + * Tests invalid valueOf() converter for a short array for IPv6. + */ + @Test(expected = IllegalArgumentException.class) + public void testInvalidValueOfShortArrayIPv6() { + IpPrefix ipPrefix; + byte[] value; + + value = new byte[] {1, 2, 3, 4, 5, 6, 7, 8, 9}; + ipPrefix = IpPrefix.valueOf(IpAddress.Version.INET6, value, 120); + } + + /** + * Tests invalid valueOf() converter for IPv4 byte array and + * negative prefix length. + */ + @Test(expected = IllegalArgumentException.class) + public void testInvalidValueOfByteArrayNegativePrefixLengthIPv4() { + IpPrefix ipPrefix; + byte[] value; + + value = new byte[] {1, 2, 3, 4}; + ipPrefix = IpPrefix.valueOf(IpAddress.Version.INET, value, -1); + } + + /** + * Tests invalid valueOf() converter for IPv6 byte array and + * negative prefix length. + */ + @Test(expected = IllegalArgumentException.class) + public void testInvalidValueOfByteArrayNegativePrefixLengthIPv6() { + IpPrefix ipPrefix; + byte[] value; + + value = new byte[] {0x11, 0x11, 0x22, 0x22, + 0x33, 0x33, 0x44, 0x44, + 0x55, 0x55, 0x66, 0x66, + 0x77, 0x77, (byte) 0x88, (byte) 0x88}; + ipPrefix = IpPrefix.valueOf(IpAddress.Version.INET6, value, -1); + } + + /** + * Tests invalid valueOf() converter for IPv4 byte array and + * too long prefix length. + */ + @Test(expected = IllegalArgumentException.class) + public void testInvalidValueOfByteArrayTooLongPrefixLengthIPv4() { + IpPrefix ipPrefix; + byte[] value; + + value = new byte[] {1, 2, 3, 4}; + ipPrefix = IpPrefix.valueOf(IpAddress.Version.INET, value, 33); + } + + /** + * Tests invalid valueOf() converter for IPv6 byte array and + * too long prefix length. + */ + @Test(expected = IllegalArgumentException.class) + public void testInvalidValueOfByteArrayTooLongPrefixLengthIPv6() { + IpPrefix ipPrefix; + byte[] value; + + value = new byte[] {0x11, 0x11, 0x22, 0x22, + 0x33, 0x33, 0x44, 0x44, + 0x55, 0x55, 0x66, 0x66, + 0x77, 0x77, (byte) 0x88, (byte) 0x88}; + ipPrefix = IpPrefix.valueOf(IpAddress.Version.INET6, value, 129); + } + + /** + * Tests valueOf() converter for IPv4 address. + */ + @Test + public void testValueOfAddressIPv4() { + IpAddress ipAddress; + IpPrefix ipPrefix; + + ipAddress = IpAddress.valueOf("1.2.3.4"); + ipPrefix = IpPrefix.valueOf(ipAddress, 24); + assertThat(ipPrefix.toString(), is("1.2.3.0/24")); + + ipPrefix = IpPrefix.valueOf(ipAddress, 32); + assertThat(ipPrefix.toString(), is("1.2.3.4/32")); + + ipAddress = IpAddress.valueOf("1.2.3.5"); + ipPrefix = IpPrefix.valueOf(ipAddress, 32); + assertThat(ipPrefix.toString(), is("1.2.3.5/32")); + + ipAddress = IpAddress.valueOf("0.0.0.0"); + ipPrefix = IpPrefix.valueOf(ipAddress, 0); + assertThat(ipPrefix.toString(), is("0.0.0.0/0")); + + ipPrefix = IpPrefix.valueOf(ipAddress, 32); + assertThat(ipPrefix.toString(), is("0.0.0.0/32")); + + ipAddress = IpAddress.valueOf("255.255.255.255"); + ipPrefix = IpPrefix.valueOf(ipAddress, 0); + assertThat(ipPrefix.toString(), is("0.0.0.0/0")); + + ipPrefix = IpPrefix.valueOf(ipAddress, 16); + assertThat(ipPrefix.toString(), is("255.255.0.0/16")); + + ipPrefix = IpPrefix.valueOf(ipAddress, 32); + assertThat(ipPrefix.toString(), is("255.255.255.255/32")); + } + + /** + * Tests valueOf() converter for IPv6 address. + */ + @Test + public void testValueOfAddressIPv6() { + IpAddress ipAddress; + IpPrefix ipPrefix; + + ipAddress = + IpAddress.valueOf("1111:2222:3333:4444:5555:6666:7777:8888"); + ipPrefix = IpPrefix.valueOf(ipAddress, 120); + assertThat(ipPrefix.toString(), + is("1111:2222:3333:4444:5555:6666:7777:8800/120")); + + ipPrefix = IpPrefix.valueOf(ipAddress, 128); + assertThat(ipPrefix.toString(), + is("1111:2222:3333:4444:5555:6666:7777:8888/128")); + + ipAddress = IpAddress.valueOf("::"); + ipPrefix = IpPrefix.valueOf(ipAddress, 0); + assertThat(ipPrefix.toString(), is("::/0")); + + ipPrefix = IpPrefix.valueOf(ipAddress, 128); + assertThat(ipPrefix.toString(), is("::/128")); + + ipAddress = + IpAddress.valueOf("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"); + ipPrefix = IpPrefix.valueOf(ipAddress, 0); + assertThat(ipPrefix.toString(), is("::/0")); + + ipPrefix = IpPrefix.valueOf(ipAddress, 64); + assertThat(ipPrefix.toString(), is("ffff:ffff:ffff:ffff::/64")); + + ipPrefix = IpPrefix.valueOf(ipAddress, 128); + assertThat(ipPrefix.toString(), + is("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/128")); + } + + /** + * Tests invalid valueOf() converter for a null IP address. + */ + @Test(expected = NullPointerException.class) + public void testInvalidValueOfNullAddress() { + IpAddress ipAddress; + IpPrefix ipPrefix; + + ipAddress = null; + ipPrefix = IpPrefix.valueOf(ipAddress, 24); + } + + /** + * Tests invalid valueOf() converter for IPv4 address and + * negative prefix length. + */ + @Test(expected = IllegalArgumentException.class) + public void testInvalidValueOfAddressNegativePrefixLengthIPv4() { + IpAddress ipAddress; + IpPrefix ipPrefix; + + ipAddress = IpAddress.valueOf("1.2.3.4"); + ipPrefix = IpPrefix.valueOf(ipAddress, -1); + } + + /** + * Tests invalid valueOf() converter for IPv6 address and + * negative prefix length. + */ + @Test(expected = IllegalArgumentException.class) + public void testInvalidValueOfAddressNegativePrefixLengthIPv6() { + IpAddress ipAddress; + IpPrefix ipPrefix; + + ipAddress = + IpAddress.valueOf("1111:2222:3333:4444:5555:6666:7777:8888"); + ipPrefix = IpPrefix.valueOf(ipAddress, -1); + } + + /** + * Tests invalid valueOf() converter for IPv4 address and + * too long prefix length. + */ + @Test(expected = IllegalArgumentException.class) + public void testInvalidValueOfAddressTooLongPrefixLengthIPv4() { + IpAddress ipAddress; + IpPrefix ipPrefix; + + ipAddress = IpAddress.valueOf("1.2.3.4"); + ipPrefix = IpPrefix.valueOf(ipAddress, 33); + } + + /** + * Tests invalid valueOf() converter for IPv6 address and + * too long prefix length. + */ + @Test(expected = IllegalArgumentException.class) + public void testInvalidValueOfAddressTooLongPrefixLengthIPv6() { + IpAddress ipAddress; + IpPrefix ipPrefix; + + ipAddress = + IpAddress.valueOf("1111:2222:3333:4444:5555:6666:7777:8888"); + ipPrefix = IpPrefix.valueOf(ipAddress, 129); + } + + /** + * Tests valueOf() converter for IPv4 string. + */ + @Test + public void testValueOfStringIPv4() { + IpPrefix ipPrefix; + + ipPrefix = IpPrefix.valueOf("1.2.3.4/24"); + assertThat(ipPrefix.toString(), is("1.2.3.0/24")); + + ipPrefix = IpPrefix.valueOf("1.2.3.4/32"); + assertThat(ipPrefix.toString(), is("1.2.3.4/32")); + + ipPrefix = IpPrefix.valueOf("1.2.3.5/32"); + assertThat(ipPrefix.toString(), is("1.2.3.5/32")); + + ipPrefix = IpPrefix.valueOf("0.0.0.0/0"); + assertThat(ipPrefix.toString(), is("0.0.0.0/0")); + + ipPrefix = IpPrefix.valueOf("0.0.0.0/32"); + assertThat(ipPrefix.toString(), is("0.0.0.0/32")); + + ipPrefix = IpPrefix.valueOf("255.255.255.255/0"); + assertThat(ipPrefix.toString(), is("0.0.0.0/0")); + + ipPrefix = IpPrefix.valueOf("255.255.255.255/16"); + assertThat(ipPrefix.toString(), is("255.255.0.0/16")); + + ipPrefix = IpPrefix.valueOf("255.255.255.255/32"); + assertThat(ipPrefix.toString(), is("255.255.255.255/32")); + } + + /** + * Tests valueOf() converter for IPv6 string. + */ + @Test + public void testValueOfStringIPv6() { + IpPrefix ipPrefix; + + ipPrefix = + IpPrefix.valueOf("1111:2222:3333:4444:5555:6666:7777:8888/120"); + assertThat(ipPrefix.toString(), + is("1111:2222:3333:4444:5555:6666:7777:8800/120")); + + ipPrefix = + IpPrefix.valueOf("1111:2222:3333:4444:5555:6666:7777:8888/128"); + assertThat(ipPrefix.toString(), + is("1111:2222:3333:4444:5555:6666:7777:8888/128")); + + ipPrefix = IpPrefix.valueOf("::/0"); + assertThat(ipPrefix.toString(), is("::/0")); + + ipPrefix = IpPrefix.valueOf("::/128"); + assertThat(ipPrefix.toString(), is("::/128")); + + ipPrefix = + IpPrefix.valueOf("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/0"); + assertThat(ipPrefix.toString(), is("::/0")); + + ipPrefix = + IpPrefix.valueOf("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/64"); + assertThat(ipPrefix.toString(), is("ffff:ffff:ffff:ffff::/64")); + + ipPrefix = + IpPrefix.valueOf("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/128"); + assertThat(ipPrefix.toString(), + is("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/128")); + } + + /** + * Tests invalid valueOf() converter for a null string. + */ + @Test(expected = NullPointerException.class) + public void testInvalidValueOfNullString() { + IpPrefix ipPrefix; + String fromString; + + fromString = null; + ipPrefix = IpPrefix.valueOf(fromString); + } + + /** + * Tests invalid valueOf() converter for an empty string. + */ + @Test(expected = IllegalArgumentException.class) + public void testInvalidValueOfEmptyString() { + IpPrefix ipPrefix; + String fromString; + + fromString = ""; + ipPrefix = IpPrefix.valueOf(fromString); + } + + /** + * Tests invalid valueOf() converter for an incorrect string. + */ + @Test(expected = IllegalArgumentException.class) + public void testInvalidValueOfIncorrectString() { + IpPrefix ipPrefix; + String fromString; + + fromString = "NoSuchIpPrefix"; + ipPrefix = IpPrefix.valueOf(fromString); + } + + /** + * Tests invalid valueOf() converter for IPv4 string and + * negative prefix length. + */ + @Test(expected = IllegalArgumentException.class) + public void testInvalidValueOfStringNegativePrefixLengthIPv4() { + IpPrefix ipPrefix; + + ipPrefix = IpPrefix.valueOf("1.2.3.4/-1"); + } + + /** + * Tests invalid valueOf() converter for IPv6 string and + * negative prefix length. + */ + @Test(expected = IllegalArgumentException.class) + public void testInvalidValueOfStringNegativePrefixLengthIPv6() { + IpPrefix ipPrefix; + + ipPrefix = + IpPrefix.valueOf("1111:2222:3333:4444:5555:6666:7777:8888/-1"); + } + + /** + * Tests invalid valueOf() converter for IPv4 string and + * too long prefix length. + */ + @Test(expected = IllegalArgumentException.class) + public void testInvalidValueOfStringTooLongPrefixLengthIPv4() { + IpPrefix ipPrefix; + + ipPrefix = IpPrefix.valueOf("1.2.3.4/33"); + } + + /** + * Tests invalid valueOf() converter for IPv6 string and + * too long prefix length. + */ + @Test(expected = IllegalArgumentException.class) + public void testInvalidValueOfStringTooLongPrefixLengthIPv6() { + IpPrefix ipPrefix; + + ipPrefix = + IpPrefix.valueOf("1111:2222:3333:4444:5555:6666:7777:8888/129"); + } + + /** + * Tests IP prefix contains another IP prefix for IPv4. + */ + @Test + public void testContainsIpPrefixIPv4() { + IpPrefix ipPrefix; + + ipPrefix = IpPrefix.valueOf("1.2.0.0/24"); + assertTrue(ipPrefix.contains(IpPrefix.valueOf("1.2.0.0/24"))); + assertTrue(ipPrefix.contains(IpPrefix.valueOf("1.2.0.0/32"))); + assertTrue(ipPrefix.contains(IpPrefix.valueOf("1.2.0.4/32"))); + assertFalse(ipPrefix.contains(IpPrefix.valueOf("1.2.0.0/16"))); + assertFalse(ipPrefix.contains(IpPrefix.valueOf("1.3.0.0/24"))); + assertFalse(ipPrefix.contains(IpPrefix.valueOf("0.0.0.0/16"))); + assertFalse(ipPrefix.contains(IpPrefix.valueOf("0.0.0.0/0"))); + assertFalse(ipPrefix.contains(IpPrefix.valueOf("255.255.255.255/32"))); + + ipPrefix = IpPrefix.valueOf("1.2.0.0/32"); + assertFalse(ipPrefix.contains(IpPrefix.valueOf("1.2.0.0/24"))); + assertTrue(ipPrefix.contains(IpPrefix.valueOf("1.2.0.0/32"))); + assertFalse(ipPrefix.contains(IpPrefix.valueOf("1.2.0.4/32"))); + assertFalse(ipPrefix.contains(IpPrefix.valueOf("1.2.0.0/16"))); + assertFalse(ipPrefix.contains(IpPrefix.valueOf("1.3.0.0/24"))); + assertFalse(ipPrefix.contains(IpPrefix.valueOf("0.0.0.0/16"))); + assertFalse(ipPrefix.contains(IpPrefix.valueOf("0.0.0.0/0"))); + assertFalse(ipPrefix.contains(IpPrefix.valueOf("255.255.255.255/32"))); + + ipPrefix = IpPrefix.valueOf("0.0.0.0/0"); + assertTrue(ipPrefix.contains(IpPrefix.valueOf("1.2.0.0/24"))); + assertTrue(ipPrefix.contains(IpPrefix.valueOf("1.2.0.0/32"))); + assertTrue(ipPrefix.contains(IpPrefix.valueOf("1.2.0.4/32"))); + assertTrue(ipPrefix.contains(IpPrefix.valueOf("1.2.0.0/16"))); + assertTrue(ipPrefix.contains(IpPrefix.valueOf("1.3.0.0/24"))); + assertTrue(ipPrefix.contains(IpPrefix.valueOf("0.0.0.0/16"))); + assertTrue(ipPrefix.contains(IpPrefix.valueOf("0.0.0.0/0"))); + assertTrue(ipPrefix.contains(IpPrefix.valueOf("255.255.255.255/32"))); + + ipPrefix = IpPrefix.valueOf("255.255.255.255/32"); + assertFalse(ipPrefix.contains(IpPrefix.valueOf("1.2.0.0/24"))); + assertFalse(ipPrefix.contains(IpPrefix.valueOf("1.2.0.0/32"))); + assertFalse(ipPrefix.contains(IpPrefix.valueOf("1.2.0.4/32"))); + assertFalse(ipPrefix.contains(IpPrefix.valueOf("1.2.0.0/16"))); + assertFalse(ipPrefix.contains(IpPrefix.valueOf("1.3.0.0/24"))); + assertFalse(ipPrefix.contains(IpPrefix.valueOf("0.0.0.0/16"))); + assertFalse(ipPrefix.contains(IpPrefix.valueOf("0.0.0.0/0"))); + assertTrue(ipPrefix.contains(IpPrefix.valueOf("255.255.255.255/32"))); + + // Test when there is a mistmatch in the compared IP address families + ipPrefix = IpPrefix.valueOf("0.0.0.0/0"); + assertFalse(ipPrefix.contains(IpPrefix.valueOf("1111:2222:3333:4444::/120"))); + ipPrefix = IpPrefix.valueOf("255.255.255.255/32"); + assertFalse(ipPrefix.contains(IpPrefix.valueOf("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/128"))); + } + + /** + * Tests IP prefix contains another IP prefix for IPv6. + */ + @Test + public void testContainsIpPrefixIPv6() { + IpPrefix ipPrefix; + + ipPrefix = IpPrefix.valueOf("1111:2222:3333:4444::/120"); + assertTrue(ipPrefix.contains( + IpPrefix.valueOf("1111:2222:3333:4444::/120"))); + assertTrue(ipPrefix.contains( + IpPrefix.valueOf("1111:2222:3333:4444::/128"))); + assertTrue(ipPrefix.contains( + IpPrefix.valueOf("1111:2222:3333:4444::1/128"))); + assertFalse(ipPrefix.contains( + IpPrefix.valueOf("1111:2222:3333:4444::/64"))); + assertFalse(ipPrefix.contains( + IpPrefix.valueOf("1111:2222:3333:4445::/120"))); + assertFalse(ipPrefix.contains(IpPrefix.valueOf("::/64"))); + assertFalse(ipPrefix.contains(IpPrefix.valueOf("::/0"))); + assertFalse(ipPrefix.contains( + IpPrefix.valueOf("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/128"))); + + ipPrefix = IpPrefix.valueOf("1111:2222:3333:4444::/128"); + assertFalse(ipPrefix.contains( + IpPrefix.valueOf("1111:2222:3333:4444::/120"))); + assertTrue(ipPrefix.contains( + IpPrefix.valueOf("1111:2222:3333:4444::/128"))); + assertFalse(ipPrefix.contains( + IpPrefix.valueOf("1111:2222:3333:4444::1/128"))); + assertFalse(ipPrefix.contains( + IpPrefix.valueOf("1111:2222:3333:4444::/64"))); + assertFalse(ipPrefix.contains( + IpPrefix.valueOf("1111:2222:3333:4445::/120"))); + assertFalse(ipPrefix.contains(IpPrefix.valueOf("::/64"))); + assertFalse(ipPrefix.contains(IpPrefix.valueOf("::/0"))); + assertFalse(ipPrefix.contains( + IpPrefix.valueOf("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/128"))); + + ipPrefix = IpPrefix.valueOf("::/0"); + assertTrue(ipPrefix.contains( + IpPrefix.valueOf("1111:2222:3333:4444::/120"))); + assertTrue(ipPrefix.contains( + IpPrefix.valueOf("1111:2222:3333:4444::/128"))); + assertTrue(ipPrefix.contains( + IpPrefix.valueOf("1111:2222:3333:4444::1/128"))); + assertTrue(ipPrefix.contains( + IpPrefix.valueOf("1111:2222:3333:4444::/64"))); + assertTrue(ipPrefix.contains( + IpPrefix.valueOf("1111:2222:3333:4445::/120"))); + assertTrue(ipPrefix.contains(IpPrefix.valueOf("::/64"))); + assertTrue(ipPrefix.contains(IpPrefix.valueOf("::/0"))); + assertTrue(ipPrefix.contains( + IpPrefix.valueOf("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/128"))); + + ipPrefix = + IpPrefix.valueOf("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/128"); + assertFalse(ipPrefix.contains( + IpPrefix.valueOf("1111:2222:3333:4444::/120"))); + assertFalse(ipPrefix.contains( + IpPrefix.valueOf("1111:2222:3333:4444::/128"))); + assertFalse(ipPrefix.contains( + IpPrefix.valueOf("1111:2222:3333:4444::1/128"))); + assertFalse(ipPrefix.contains( + IpPrefix.valueOf("1111:2222:3333:4444::/64"))); + assertFalse(ipPrefix.contains( + IpPrefix.valueOf("1111:2222:3333:4445::/120"))); + assertFalse(ipPrefix.contains(IpPrefix.valueOf("::/64"))); + assertFalse(ipPrefix.contains(IpPrefix.valueOf("::/0"))); + assertTrue(ipPrefix.contains( + IpPrefix.valueOf("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/128"))); + + // Test when there is a mistmatch in the compared IP address families + ipPrefix = IpPrefix.valueOf("::/0"); + assertFalse(ipPrefix.contains(IpPrefix.valueOf("1.2.0.0/24"))); + ipPrefix = IpPrefix.valueOf("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/128"); + assertFalse(ipPrefix.contains(IpPrefix.valueOf("255.255.255.255/32"))); + } + + /** + * Tests IP prefix contains IP address for IPv4. + */ + @Test + public void testContainsIpAddressIPv4() { + IpPrefix ipPrefix; + + ipPrefix = IpPrefix.valueOf("1.2.0.0/24"); + assertTrue(ipPrefix.contains(IpAddress.valueOf("1.2.0.0"))); + assertTrue(ipPrefix.contains(IpAddress.valueOf("1.2.0.4"))); + assertFalse(ipPrefix.contains(IpAddress.valueOf("1.3.0.0"))); + assertFalse(ipPrefix.contains(IpAddress.valueOf("0.0.0.0"))); + assertFalse(ipPrefix.contains(IpAddress.valueOf("255.255.255.255"))); + + ipPrefix = IpPrefix.valueOf("1.2.0.0/32"); + assertTrue(ipPrefix.contains(IpAddress.valueOf("1.2.0.0"))); + assertFalse(ipPrefix.contains(IpAddress.valueOf("1.2.0.4"))); + assertFalse(ipPrefix.contains(IpAddress.valueOf("1.3.0.0"))); + assertFalse(ipPrefix.contains(IpAddress.valueOf("0.0.0.0"))); + assertFalse(ipPrefix.contains(IpAddress.valueOf("255.255.255.255"))); + + ipPrefix = IpPrefix.valueOf("0.0.0.0/0"); + assertTrue(ipPrefix.contains(IpAddress.valueOf("1.2.0.0"))); + assertTrue(ipPrefix.contains(IpAddress.valueOf("1.2.0.4"))); + assertTrue(ipPrefix.contains(IpAddress.valueOf("1.3.0.0"))); + assertTrue(ipPrefix.contains(IpAddress.valueOf("0.0.0.0"))); + assertTrue(ipPrefix.contains(IpAddress.valueOf("255.255.255.255"))); + + ipPrefix = IpPrefix.valueOf("255.255.255.255/32"); + assertFalse(ipPrefix.contains(IpAddress.valueOf("1.2.0.0"))); + assertFalse(ipPrefix.contains(IpAddress.valueOf("1.2.0.4"))); + assertFalse(ipPrefix.contains(IpAddress.valueOf("1.3.0.0"))); + assertFalse(ipPrefix.contains(IpAddress.valueOf("0.0.0.0"))); + assertTrue(ipPrefix.contains(IpAddress.valueOf("255.255.255.255"))); + + // Test when there is a mistmatch in the compared IP address families + ipPrefix = IpPrefix.valueOf("0.0.0.0/0"); + assertFalse(ipPrefix.contains(IpAddress.valueOf("1111:2222:3333:4444::"))); + ipPrefix = IpPrefix.valueOf("255.255.255.255/32"); + assertFalse(ipPrefix.contains(IpAddress.valueOf("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"))); + } + + /** + * Tests IP prefix contains IP address for IPv6. + */ + @Test + public void testContainsIpAddressIPv6() { + IpPrefix ipPrefix; + + ipPrefix = IpPrefix.valueOf("1111:2222:3333:4444::/120"); + assertTrue(ipPrefix.contains( + IpAddress.valueOf("1111:2222:3333:4444::"))); + assertTrue(ipPrefix.contains( + IpAddress.valueOf("1111:2222:3333:4444::1"))); + assertFalse(ipPrefix.contains( + IpAddress.valueOf("1111:2222:3333:4445::"))); + assertFalse(ipPrefix.contains(IpAddress.valueOf("::"))); + assertFalse(ipPrefix.contains( + IpAddress.valueOf("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"))); + + ipPrefix = IpPrefix.valueOf("1111:2222:3333:4444::/128"); + assertTrue(ipPrefix.contains( + IpAddress.valueOf("1111:2222:3333:4444::"))); + assertFalse(ipPrefix.contains( + IpAddress.valueOf("1111:2222:3333:4444::1"))); + assertFalse(ipPrefix.contains( + IpAddress.valueOf("1111:2222:3333:4445::"))); + assertFalse(ipPrefix.contains(IpAddress.valueOf("::"))); + assertFalse(ipPrefix.contains( + IpAddress.valueOf("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"))); + + ipPrefix = IpPrefix.valueOf("::/0"); + assertTrue(ipPrefix.contains( + IpAddress.valueOf("1111:2222:3333:4444::"))); + assertTrue(ipPrefix.contains( + IpAddress.valueOf("1111:2222:3333:4444::1"))); + assertTrue(ipPrefix.contains( + IpAddress.valueOf("1111:2222:3333:4445::"))); + assertTrue(ipPrefix.contains(IpAddress.valueOf("::"))); + assertTrue(ipPrefix.contains( + IpAddress.valueOf("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"))); + + ipPrefix = + IpPrefix.valueOf("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/128"); + assertFalse(ipPrefix.contains( + IpAddress.valueOf("1111:2222:3333:4444::"))); + assertFalse(ipPrefix.contains( + IpAddress.valueOf("1111:2222:3333:4444::1"))); + assertFalse(ipPrefix.contains( + IpAddress.valueOf("1111:2222:3333:4445::"))); + assertFalse(ipPrefix.contains(IpAddress.valueOf("::"))); + assertTrue(ipPrefix.contains( + IpAddress.valueOf("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"))); + + // Test when there is a mistmatch in the compared IP address families + ipPrefix = IpPrefix.valueOf("::/0"); + assertFalse(ipPrefix.contains(IpAddress.valueOf("1.2.0.0"))); + ipPrefix = IpPrefix.valueOf("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/128"); + assertFalse(ipPrefix.contains(IpAddress.valueOf("255.255.255.255"))); + } + + /** + * Tests equality of {@link IpPrefix} for IPv4. + */ + @Test + public void testEqualityIPv4() { + new EqualsTester() + .addEqualityGroup(IpPrefix.valueOf("1.2.0.0/24"), + IpPrefix.valueOf("1.2.0.0/24"), + IpPrefix.valueOf("1.2.0.4/24")) + .addEqualityGroup(IpPrefix.valueOf("1.2.0.0/16"), + IpPrefix.valueOf("1.2.0.0/16")) + .addEqualityGroup(IpPrefix.valueOf("1.2.0.0/32"), + IpPrefix.valueOf("1.2.0.0/32")) + .addEqualityGroup(IpPrefix.valueOf("1.3.0.0/24"), + IpPrefix.valueOf("1.3.0.0/24")) + .addEqualityGroup(IpPrefix.valueOf("0.0.0.0/0"), + IpPrefix.valueOf("0.0.0.0/0")) + .addEqualityGroup(IpPrefix.valueOf("255.255.255.255/32"), + IpPrefix.valueOf("255.255.255.255/32")) + .testEquals(); + } + + /** + * Tests equality of {@link IpPrefix} for IPv6. + */ + @Test + public void testEqualityIPv6() { + new EqualsTester() + .addEqualityGroup( + IpPrefix.valueOf("1111:2222:3333:4444::/120"), + IpPrefix.valueOf("1111:2222:3333:4444::1/120"), + IpPrefix.valueOf("1111:2222:3333:4444::/120")) + .addEqualityGroup( + IpPrefix.valueOf("1111:2222:3333:4444::/64"), + IpPrefix.valueOf("1111:2222:3333:4444::/64")) + .addEqualityGroup( + IpPrefix.valueOf("1111:2222:3333:4444::/128"), + IpPrefix.valueOf("1111:2222:3333:4444::/128")) + .addEqualityGroup( + IpPrefix.valueOf("1111:2222:3333:4445::/64"), + IpPrefix.valueOf("1111:2222:3333:4445::/64")) + .addEqualityGroup( + IpPrefix.valueOf("::/0"), + IpPrefix.valueOf("::/0")) + .addEqualityGroup( + IpPrefix.valueOf("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/128"), + IpPrefix.valueOf("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/128")) + .testEquals(); + } + + /** + * Tests object string representation for IPv4. + */ + @Test + public void testToStringIPv4() { + IpPrefix ipPrefix; + + ipPrefix = IpPrefix.valueOf("1.2.3.0/24"); + assertThat(ipPrefix.toString(), is("1.2.3.0/24")); + + ipPrefix = IpPrefix.valueOf("1.2.3.4/24"); + assertThat(ipPrefix.toString(), is("1.2.3.0/24")); + + ipPrefix = IpPrefix.valueOf("0.0.0.0/0"); + assertThat(ipPrefix.toString(), is("0.0.0.0/0")); + + ipPrefix = IpPrefix.valueOf("255.255.255.255/32"); + assertThat(ipPrefix.toString(), is("255.255.255.255/32")); + } + + /** + * Tests object string representation for IPv6. + */ + @Test + public void testToStringIPv6() { + IpPrefix ipPrefix; + + ipPrefix = IpPrefix.valueOf("1100::/8"); + assertThat(ipPrefix.toString(), is("1100::/8")); + + ipPrefix = IpPrefix.valueOf("1111:2222:3333:4444:5555:6666:7777:8885/8"); + assertThat(ipPrefix.toString(), is("1100::/8")); + + ipPrefix = IpPrefix.valueOf("::/0"); + assertThat(ipPrefix.toString(), is("::/0")); + + ipPrefix = IpPrefix.valueOf("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/128"); + assertThat(ipPrefix.toString(), + is("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/128")); + } +} diff --git a/framework/src/onos/utils/misc/src/test/java/org/onlab/packet/LLCTest.java b/framework/src/onos/utils/misc/src/test/java/org/onlab/packet/LLCTest.java new file mode 100644 index 00000000..39bb72f4 --- /dev/null +++ b/framework/src/onos/utils/misc/src/test/java/org/onlab/packet/LLCTest.java @@ -0,0 +1,70 @@ +/* + * 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 org.junit.Before; +import org.junit.Test; + +import java.nio.ByteBuffer; + +import static org.junit.Assert.assertEquals; + +/** + * Unit tests for LLC class. + */ +public class LLCTest { + + private Deserializer<LLC> deserializer; + + private byte dsap = 10; + private byte ssap = 20; + private byte ctrl = 30; + + private byte[] bytes; + + @Before + public void setUp() throws Exception { + deserializer = LLC.deserializer(); + + ByteBuffer bb = ByteBuffer.allocate(LLC.LLC_HEADER_LENGTH); + + bb.put(dsap); + bb.put(ssap); + bb.put(ctrl); + + bytes = bb.array(); + } + + @Test + public void testDeserializeBadInput() throws Exception { + PacketTestUtils.testDeserializeBadInput(deserializer); + } + + @Test + public void testDeserializeTruncated() throws Exception { + PacketTestUtils.testDeserializeTruncated(deserializer, bytes); + } + + @Test + public void testDeserialize() throws Exception { + LLC llc = deserializer.deserialize(bytes, 0, bytes.length); + + assertEquals(dsap, llc.getDsap()); + assertEquals(ssap, llc.getSsap()); + assertEquals(ctrl, llc.getCtrl()); + } +} diff --git a/framework/src/onos/utils/misc/src/test/java/org/onlab/packet/LLDPTest.java b/framework/src/onos/utils/misc/src/test/java/org/onlab/packet/LLDPTest.java new file mode 100644 index 00000000..95b0b04a --- /dev/null +++ b/framework/src/onos/utils/misc/src/test/java/org/onlab/packet/LLDPTest.java @@ -0,0 +1,115 @@ +/* + * 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 org.junit.Before; +import org.junit.Test; + +import java.nio.ByteBuffer; +import java.util.Arrays; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +/** + * Unit tests for the LLDP class. + */ +public class LLDPTest { + + private Deserializer<LLDP> deserializer; + + private byte[] chassisValue = new byte[] {0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7}; + private byte[] portValue = new byte[] {0x1, 0x2, 0x3, 0x4, 0x5}; + private byte[] ttlValue = new byte[] {0x0, 0x20}; + + private short optionalTlvSize = 6; + private byte[] optionalTlvValue = new byte[] {0x6, 0x5, 0x4, 0x3, 0x2, 0x1}; + + private byte[] bytes; + + @Before + public void setUp() throws Exception { + deserializer = LLDP.deserializer(); + + // Each TLV is 2 bytes for the type+length, plus the size of the value + // There are 2 zero-bytes at the end + ByteBuffer bb = ByteBuffer.allocate(2 + LLDP.CHASSIS_TLV_SIZE + + 2 + LLDP.PORT_TLV_SIZE + + 2 + LLDP.TTL_TLV_SIZE + + 2 + optionalTlvSize + + 2); + + // Chassis TLV + bb.putShort(getTypeLength(LLDP.CHASSIS_TLV_TYPE, LLDP.CHASSIS_TLV_SIZE)); + bb.put(chassisValue); + + // Port TLV + bb.putShort(getTypeLength(LLDP.PORT_TLV_TYPE, LLDP.PORT_TLV_SIZE)); + bb.put(portValue); + + // TTL TLV + bb.putShort(getTypeLength(LLDP.TTL_TLV_TYPE, LLDP.TTL_TLV_SIZE)); + bb.put(ttlValue); + + // Optional TLV + bb.putShort(getTypeLength(LLDPOrganizationalTLV.ORGANIZATIONAL_TLV_TYPE, optionalTlvSize)); + bb.put(optionalTlvValue); + + bb.putShort((short) 0); + + bytes = bb.array(); + + } + + private short getTypeLength(byte type, short length) { + return (short) ((0x7f & type) << 9 | 0x1ff & length); + } + + @Test + public void testDeserializeBadInput() throws Exception { + PacketTestUtils.testDeserializeBadInput(deserializer); + } + + @Test + public void testDeserializeTruncated() throws Exception { + PacketTestUtils.testDeserializeTruncated(deserializer, bytes); + } + + @Test + public void testDeserialize() throws Exception { + LLDP lldp = deserializer.deserialize(bytes, 0, bytes.length); + + assertEquals(LLDP.CHASSIS_TLV_TYPE, lldp.getChassisId().getType()); + assertEquals(LLDP.CHASSIS_TLV_SIZE, lldp.getChassisId().getLength()); + assertTrue(Arrays.equals(chassisValue, lldp.getChassisId().getValue())); + + assertEquals(LLDP.PORT_TLV_TYPE, lldp.getPortId().getType()); + assertEquals(LLDP.PORT_TLV_SIZE, lldp.getPortId().getLength()); + assertTrue(Arrays.equals(portValue, lldp.getPortId().getValue())); + + assertEquals(LLDP.TTL_TLV_TYPE, lldp.getTtl().getType()); + assertEquals(LLDP.TTL_TLV_SIZE, lldp.getTtl().getLength()); + assertTrue(Arrays.equals(ttlValue, lldp.getTtl().getValue())); + + assertEquals(1, lldp.getOptionalTLVList().size()); + LLDPTLV optionalTlv = lldp.getOptionalTLVList().get(0); + + assertEquals(LLDPOrganizationalTLV.ORGANIZATIONAL_TLV_TYPE, optionalTlv.getType()); + assertEquals(optionalTlvSize, optionalTlv.getLength()); + assertTrue(Arrays.equals(optionalTlvValue, optionalTlv.getValue())); + } +} diff --git a/framework/src/onos/utils/misc/src/test/java/org/onlab/packet/MplsTest.java b/framework/src/onos/utils/misc/src/test/java/org/onlab/packet/MplsTest.java new file mode 100644 index 00000000..2ab8ff98 --- /dev/null +++ b/framework/src/onos/utils/misc/src/test/java/org/onlab/packet/MplsTest.java @@ -0,0 +1,74 @@ +/* + * 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 org.junit.Before; +import org.junit.Test; + +import java.nio.ByteBuffer; +import java.util.HashMap; + +import static org.junit.Assert.assertEquals; + +/** + * Unit tests for MPLS class. + */ +public class MplsTest { + + private Deserializer<MPLS> deserializer; + + private int label = 1048575; + private byte bos = 1; + private byte ttl = 20; + private byte protocol = MPLS.PROTOCOL_IPV4; + + private byte[] bytes; + + @Before + public void setUp() throws Exception { + // Replace normal deserializer map with an empty map. This will cause + // the DataDeserializer to be used which will silently handle 0-byte input. + MPLS.protocolDeserializerMap = new HashMap<>(); + + deserializer = MPLS.deserializer(); + + ByteBuffer bb = ByteBuffer.allocate(MPLS.HEADER_LENGTH); + bb.putInt(((label & 0x000fffff) << 12) | ((bos & 0x1) << 8 | (ttl & 0xff))); + + bytes = bb.array(); + } + + @Test + public void testDeserializeBadInput() throws Exception { + PacketTestUtils.testDeserializeBadInput(deserializer); + } + + @Test + public void testDeserializeTruncated() throws Exception { + PacketTestUtils.testDeserializeTruncated(deserializer, bytes); + } + + @Test + public void testDeserialize() throws Exception { + MPLS mpls = deserializer.deserialize(bytes, 0, bytes.length); + + assertEquals(label, mpls.label); + assertEquals(bos, mpls.bos); + assertEquals(ttl, mpls.ttl); + assertEquals(protocol, mpls.protocol); + } +} diff --git a/framework/src/onos/utils/misc/src/test/java/org/onlab/packet/PacketTestUtils.java b/framework/src/onos/utils/misc/src/test/java/org/onlab/packet/PacketTestUtils.java new file mode 100644 index 00000000..209b1d27 --- /dev/null +++ b/framework/src/onos/utils/misc/src/test/java/org/onlab/packet/PacketTestUtils.java @@ -0,0 +1,98 @@ +/* + * 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 junit.framework.TestCase.fail; +import static org.junit.Assert.assertTrue; + +/** + * Utilities for testing packet methods. + */ +public final class PacketTestUtils { + + private PacketTestUtils() { + } + + /** + * Tests that the Deserializer function is resilient to bad input parameters + * such as null input, negative offset and length, etc. + * + * @param deserializer deserializer function to test + */ + public static void testDeserializeBadInput(Deserializer deserializer) { + byte[] bytes = ByteBuffer.allocate(4).array(); + + try { + deserializer.deserialize(null, 0, 4); + fail("NullPointerException was not thrown"); + } catch (NullPointerException e) { + assertTrue(true); + } catch (DeserializationException e) { + fail("NullPointerException was not thrown"); + } + + // input byte array length, offset and length don't make sense + expectDeserializationException(deserializer, bytes, -1, 0); + expectDeserializationException(deserializer, bytes, 0, -1); + expectDeserializationException(deserializer, bytes, 0, 5); + expectDeserializationException(deserializer, bytes, 2, 3); + expectDeserializationException(deserializer, bytes, 5, 0); + } + + /** + * Tests that the Deserializer function is resilient to truncated input, or + * cases where the input byte array does not contain enough bytes to + * deserialize the packet. + * + * @param deserializer deserializer function to test + * @param header byte array of a full-size packet + */ + public static void testDeserializeTruncated(Deserializer deserializer, + byte[] header) { + byte[] truncated; + + for (int i = 0; i < header.length; i++) { + truncated = new byte[i]; + + ByteBuffer.wrap(header).get(truncated); + + expectDeserializationException(deserializer, truncated, 0, truncated.length); + } + } + + /** + * Run the given deserializer function against the given inputs and verify + * that a DeserializationException is thrown. The the test will fail if a + * DeserializationException is not thrown by the deserializer function. + * + * @param deserializer deserializer function to test + * @param bytes input byte array + * @param offset input offset + * @param length input length + */ + public static void expectDeserializationException(Deserializer deserializer, + byte[] bytes, int offset, int length) { + try { + deserializer.deserialize(bytes, offset, length); + fail("DeserializationException was not thrown"); + } catch (DeserializationException e) { + assertTrue(true); + } + } +} diff --git a/framework/src/onos/utils/misc/src/test/java/org/onlab/packet/TCPTest.java b/framework/src/onos/utils/misc/src/test/java/org/onlab/packet/TCPTest.java new file mode 100644 index 00000000..faab5c2c --- /dev/null +++ b/framework/src/onos/utils/misc/src/test/java/org/onlab/packet/TCPTest.java @@ -0,0 +1,163 @@ +/* + * Copyright 2014-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 org.junit.BeforeClass; +import org.junit.Test; + +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + +/** + * Tests for class {@link TCP}. + */ +public class TCPTest { + private static final byte[] IPV4_SOURCE_ADDRESS = { + (byte) 192, (byte) 168, (byte) 1, (byte) 1 + }; + private static final byte[] IPV4_DESTINATION_ADDRESS = { + (byte) 192, (byte) 168, (byte) 1, (byte) 2 + }; + private static final byte[] IPV6_SOURCE_ADDRESS = { + (byte) 0xfe, (byte) 0x80, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x01 + }; + private static final byte[] IPV6_DESTINATION_ADDRESS = { + (byte) 0xfe, (byte) 0x80, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x02 + }; + + private static IPv4 ipv4 = new IPv4(); + private static IPv6 ipv6 = new IPv6(); + private static byte[] bytePacketTCP4 = { + (byte) 0x00, (byte) 0x50, (byte) 0x00, (byte) 0x60, // src,dst port + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x10, // seq + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x20, // ack + (byte) 0x50, (byte) 0x02, // offset,flag + (byte) 0x10, (byte) 0x00, // window + (byte) 0x1b, (byte) 0xae, // checksum + (byte) 0x00, (byte) 0x01 // urgent + }; + private static byte[] bytePacketTCP6 = { + (byte) 0x00, (byte) 0x50, (byte) 0x00, (byte) 0x60, // src,dst port + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x10, // seq + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x20, // ack + (byte) 0x50, (byte) 0x02, // offset,flag + (byte) 0x10, (byte) 0x00, // window + (byte) 0xa1, (byte) 0xfd, // checksum + (byte) 0x00, (byte) 0x01 // urgent + }; + + private static Deserializer<TCP> deserializer; + + @BeforeClass + public static void setUpBeforeClass() throws Exception { + deserializer = TCP.deserializer(); + + ipv4.setSourceAddress(IPv4.toIPv4Address(IPV4_SOURCE_ADDRESS)); + ipv4.setDestinationAddress(IPv4.toIPv4Address(IPV4_DESTINATION_ADDRESS)); + ipv4.setProtocol(IPv4.PROTOCOL_TCP); + + ipv6.setSourceAddress(IPV6_SOURCE_ADDRESS); + ipv6.setDestinationAddress(IPV6_DESTINATION_ADDRESS); + ipv6.setNextHeader(IPv6.PROTOCOL_TCP); + } + + /** + * Tests serialize and setters. + */ + @Test + public void testSerialize() { + TCP tcp = new TCP(); + tcp.setSourcePort(0x50); + tcp.setDestinationPort(0x60); + tcp.setSequence(0x10); + tcp.setAcknowledge(0x20); + tcp.setDataOffset((byte) 0x5); + tcp.setFlags((short) 0x2); + tcp.setWindowSize((short) 0x1000); + tcp.setUrgentPointer((short) 0x1); + + tcp.setParent(ipv4); + assertArrayEquals(bytePacketTCP4, tcp.serialize()); + tcp.resetChecksum(); + tcp.setParent(ipv6); + assertArrayEquals(bytePacketTCP6, tcp.serialize()); + } + + @Test + public void testDeserializeBadInput() throws Exception { + PacketTestUtils.testDeserializeBadInput(deserializer); + } + + @Test + public void testDeserializeTruncated() throws Exception { + PacketTestUtils.testDeserializeTruncated(deserializer, bytePacketTCP4); + } + + /** + * Tests deserialize and getters. + */ + @Test + public void testDeserialize() throws Exception { + TCP tcp = deserializer.deserialize(bytePacketTCP4, 0, bytePacketTCP4.length); + + assertThat(tcp.getSourcePort(), is(0x50)); + assertThat(tcp.getDestinationPort(), is(0x60)); + assertThat(tcp.getSequence(), is(0x10)); + assertThat(tcp.getAcknowledge(), is(0x20)); + assertThat(tcp.getDataOffset(), is((byte) 0x5)); + assertThat(tcp.getFlags(), is((short) 0x2)); + assertThat(tcp.getWindowSize(), is((short) 0x1000)); + assertThat(tcp.getUrgentPointer(), is((short) 0x1)); + assertThat(tcp.getChecksum(), is((short) 0x1bae)); + } + + /** + * Tests comparator. + */ + @Test + public void testEqual() { + TCP tcp1 = new TCP(); + tcp1.setSourcePort(0x50); + tcp1.setDestinationPort(0x60); + tcp1.setSequence(0x10); + tcp1.setAcknowledge(0x20); + tcp1.setDataOffset((byte) 0x5); + tcp1.setFlags((short) 0x2); + tcp1.setWindowSize((short) 0x1000); + tcp1.setUrgentPointer((short) 0x1); + + TCP tcp2 = new TCP(); + tcp2.setSourcePort(0x70); + tcp2.setDestinationPort(0x60); + tcp2.setSequence(0x10); + tcp2.setAcknowledge(0x20); + tcp2.setDataOffset((byte) 0x5); + tcp2.setFlags((short) 0x2); + tcp2.setWindowSize((short) 0x1000); + tcp2.setUrgentPointer((short) 0x1); + + assertTrue(tcp1.equals(tcp1)); + assertFalse(tcp1.equals(tcp2)); + } +} diff --git a/framework/src/onos/utils/misc/src/test/java/org/onlab/packet/UDPTest.java b/framework/src/onos/utils/misc/src/test/java/org/onlab/packet/UDPTest.java new file mode 100644 index 00000000..ba453f61 --- /dev/null +++ b/framework/src/onos/utils/misc/src/test/java/org/onlab/packet/UDPTest.java @@ -0,0 +1,134 @@ +/* + * Copyright 2014-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 org.junit.BeforeClass; +import org.junit.Test; + +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + +/** + * Tests for class {@link UDP}. + */ +public class UDPTest { + private static final byte[] IPV4_SOURCE_ADDRESS = { + (byte) 192, (byte) 168, (byte) 1, (byte) 1 + }; + private static final byte[] IPV4_DESTINATION_ADDRESS = { + (byte) 192, (byte) 168, (byte) 1, (byte) 2 + }; + private static final byte[] IPV6_SOURCE_ADDRESS = { + (byte) 0xfe, (byte) 0x80, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x01 + }; + private static final byte[] IPV6_DESTINATION_ADDRESS = { + (byte) 0xfe, (byte) 0x80, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x02 + }; + + private static IPv4 ipv4 = new IPv4(); + private static IPv6 ipv6 = new IPv6(); + private static byte[] bytePacketUDP4 = { + (byte) 0x00, (byte) 0x50, // src port + (byte) 0x00, (byte) 0x60, // dst port + (byte) 0x00, (byte) 0x08, // length + (byte) 0x7b, (byte) 0xda, // checksum + }; + private static byte[] bytePacketUDP6 = { + (byte) 0x00, (byte) 0x50, // src port + (byte) 0x00, (byte) 0x60, // dst port + (byte) 0x00, (byte) 0x08, // length + (byte) 0x02, (byte) 0x2a, // checksum + }; + + private static Deserializer<UDP> deserializer; + + @BeforeClass + public static void setUpBeforeClass() throws Exception { + deserializer = UDP.deserializer(); + + ipv4.setSourceAddress(IPv4.toIPv4Address(IPV4_SOURCE_ADDRESS)); + ipv4.setDestinationAddress(IPv4.toIPv4Address(IPV4_DESTINATION_ADDRESS)); + ipv4.setProtocol(IPv4.PROTOCOL_UDP); + + ipv6.setSourceAddress(IPV6_SOURCE_ADDRESS); + ipv6.setDestinationAddress(IPV6_DESTINATION_ADDRESS); + ipv6.setNextHeader(IPv6.PROTOCOL_UDP); + } + + /** + * Tests serialize and setters. + */ + @Test + public void testSerialize() { + UDP udp = new UDP(); + udp.setSourcePort(0x50); + udp.setDestinationPort(0x60); + + udp.setParent(ipv4); + assertArrayEquals(bytePacketUDP4, udp.serialize()); + udp.resetChecksum(); + udp.setParent(ipv6); + assertArrayEquals(bytePacketUDP6, udp.serialize()); + } + + @Test + public void testDeserializeBadInput() throws Exception { + PacketTestUtils.testDeserializeBadInput(deserializer); + } + + @Test + public void testDeserializeTruncated() throws Exception { + PacketTestUtils.testDeserializeTruncated(deserializer, bytePacketUDP4); + } + + /** + * Tests deserialize and getters. + */ + @Test + public void testDeserialize() throws Exception { + UDP udp = deserializer.deserialize(bytePacketUDP4, 0, bytePacketUDP4.length); + + assertThat(udp.getSourcePort(), is(0x50)); + assertThat(udp.getDestinationPort(), is(0x60)); + assertThat(udp.getLength(), is((short) 8)); + assertThat(udp.getChecksum(), is((short) 0x7bda)); + } + + /** + * Tests comparator. + */ + @Test + public void testEqual() { + UDP udp1 = new UDP(); + udp1.setSourcePort(0x50); + udp1.setDestinationPort(0x60); + + UDP udp2 = new UDP(); + udp2.setSourcePort(0x70); + udp2.setDestinationPort(0x60); + + assertTrue(udp1.equals(udp1)); + assertFalse(udp1.equals(udp2)); + } +} diff --git a/framework/src/onos/utils/misc/src/test/java/org/onlab/packet/VlanIdTest.java b/framework/src/onos/utils/misc/src/test/java/org/onlab/packet/VlanIdTest.java new file mode 100644 index 00000000..9ec8ddda --- /dev/null +++ b/framework/src/onos/utils/misc/src/test/java/org/onlab/packet/VlanIdTest.java @@ -0,0 +1,53 @@ +/* + * Copyright 2014 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 static org.junit.Assert.assertEquals; + +import org.junit.Test; + +import com.google.common.testing.EqualsTester; + +public class VlanIdTest { + + @Test + public void testEquality() { + + VlanId vlan1 = VlanId.vlanId((short) -1); + VlanId vlan2 = VlanId.vlanId((short) 100); + VlanId vlan3 = VlanId.vlanId((short) 100); + + new EqualsTester().addEqualityGroup(VlanId.vlanId(), vlan1) + .addEqualityGroup(vlan2, vlan3) + .addEqualityGroup(VlanId.vlanId((short) 10)); + + } + + @Test + public void basics() { + // purposefully create UNTAGGED VLAN + VlanId vlan1 = VlanId.vlanId((short) 10); + VlanId vlan2 = VlanId.vlanId((short) -1); + + assertEquals("incorrect VLAN value", 10, vlan1.toShort()); + assertEquals("invalid untagged value", VlanId.UNTAGGED, vlan2.toShort()); + } + + @Test(expected = IllegalArgumentException.class) + public void testIllicitVLAN() { + VlanId.vlanId((short) 5000); + } +} diff --git a/framework/src/onos/utils/misc/src/test/java/org/onlab/packet/ipv6/AuthenticationTest.java b/framework/src/onos/utils/misc/src/test/java/org/onlab/packet/ipv6/AuthenticationTest.java new file mode 100644 index 00000000..39ab9104 --- /dev/null +++ b/framework/src/onos/utils/misc/src/test/java/org/onlab/packet/ipv6/AuthenticationTest.java @@ -0,0 +1,121 @@ +/* + * Copyright 2014-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.ipv6; + +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.onlab.packet.Data; +import org.onlab.packet.Deserializer; +import org.onlab.packet.UDP; + +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + +/** + * Tests for class {@link Authentication}. + */ +public class AuthenticationTest { + private static Data data; + private static UDP udp; + private static byte[] icv = { + (byte) 0x11, (byte) 0x22, (byte) 0x33, (byte) 0x44 + }; + private static byte[] bytePacket; + + private Deserializer<Authentication> deserializer; + + @BeforeClass + public static void setUpBeforeClass() throws Exception { + data = new Data(); + data.setData("testSerialize".getBytes()); + udp = new UDP(); + udp.setPayload(data); + + byte[] bytePayload = udp.serialize(); + byte[] byteHeader = { + (byte) 0x11, (byte) 0x02, (byte) 0x00, (byte) 0x00, + (byte) 0x13, (byte) 0x57, (byte) 0x24, (byte) 0x68, + (byte) 0x00, (byte) 0xff, (byte) 0xff, (byte) 0x00, + (byte) 0x11, (byte) 0x22, (byte) 0x33, (byte) 0x44 + }; + bytePacket = new byte[byteHeader.length + bytePayload.length]; + System.arraycopy(byteHeader, 0, bytePacket, 0, byteHeader.length); + System.arraycopy(bytePayload, 0, bytePacket, byteHeader.length, bytePayload.length); + } + + @Before + public void setUp() { + deserializer = Authentication.deserializer(); + } + + /** + * Tests serialize and setters. + */ + @Test + public void testSerialize() { + Authentication auth = new Authentication(); + auth.setNextHeader((byte) 0x11); + auth.setPayloadLength((byte) 0x02); + auth.setSecurityParamIndex(0x13572468); + auth.setSequence(0xffff00); + auth.setIngegrityCheck(icv); + auth.setPayload(udp); + + assertArrayEquals(auth.serialize(), bytePacket); + } + + /** + * Tests deserialize and getters. + */ + @Test + public void testDeserialize() throws Exception { + Authentication auth = deserializer.deserialize(bytePacket, 0, bytePacket.length); + + assertThat(auth.getNextHeader(), is((byte) 0x11)); + assertThat(auth.getPayloadLength(), is((byte) 0x02)); + assertThat(auth.getSecurityParamIndex(), is(0x13572468)); + assertThat(auth.getSequence(), is(0xffff00)); + assertArrayEquals(auth.getIntegrityCheck(), icv); + } + + /** + * Tests comparator. + */ + @Test + public void testEqual() { + Authentication auth1 = new Authentication(); + auth1.setNextHeader((byte) 0x11); + auth1.setPayloadLength((byte) 0x02); + auth1.setSecurityParamIndex(0x13572468); + auth1.setSequence(0xffff00); + auth1.setIngegrityCheck(icv); + + Authentication auth2 = new Authentication(); + auth2.setNextHeader((byte) 0x11); + auth2.setPayloadLength((byte) 0x02); + auth2.setSecurityParamIndex(0x13572467); + auth2.setSequence(0xffff00); + auth2.setIngegrityCheck(icv); + + assertTrue(auth1.equals(auth1)); + assertFalse(auth1.equals(auth2)); + } +} diff --git a/framework/src/onos/utils/misc/src/test/java/org/onlab/packet/ipv6/BaseOptionsTest.java b/framework/src/onos/utils/misc/src/test/java/org/onlab/packet/ipv6/BaseOptionsTest.java new file mode 100644 index 00000000..bb91e9e7 --- /dev/null +++ b/framework/src/onos/utils/misc/src/test/java/org/onlab/packet/ipv6/BaseOptionsTest.java @@ -0,0 +1,115 @@ +/* + * Copyright 2014-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.ipv6; + +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.onlab.packet.Data; +import org.onlab.packet.Deserializer; +import org.onlab.packet.IPv6; +import org.onlab.packet.UDP; + +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + +/** + * Tests for class {@link BaseOptions}. + */ +public class BaseOptionsTest { + private static Data data; + private static UDP udp; + private static byte[] options = { + (byte) 0x00, (byte) 0x03, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x00 + }; + private static byte[] bytePacket; + + private Deserializer<BaseOptions> deserializer; + + @BeforeClass + public static void setUpBeforeClass() throws Exception { + data = new Data(); + data.setData("testSerialize".getBytes()); + udp = new UDP(); + udp.setPayload(data); + + byte[] bytePayload = udp.serialize(); + byte[] byteHeader = { + (byte) 0x11, (byte) 0x00, (byte) 0x00, (byte) 0x03, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x00 + }; + bytePacket = new byte[byteHeader.length + bytePayload.length]; + System.arraycopy(byteHeader, 0, bytePacket, 0, byteHeader.length); + System.arraycopy(bytePayload, 0, bytePacket, byteHeader.length, bytePayload.length); + } + + @Before + public void setUp() { + deserializer = BaseOptions.deserializer(); + } + + /** + * Tests serialize and setters. + */ + @Test + public void testSerialize() { + BaseOptions baseopt = new BaseOptions(); + baseopt.setNextHeader((byte) 0x11); + baseopt.setHeaderExtLength((byte) 0x00); + baseopt.setOptions(options); + baseopt.setPayload(udp); + + assertArrayEquals(baseopt.serialize(), bytePacket); + } + + /** + * Tests deserialize and getters. + */ + @Test + public void testDeserialize() throws Exception { + BaseOptions baseopt = deserializer.deserialize(bytePacket, 0, bytePacket.length); + + assertThat(baseopt.getNextHeader(), is((byte) 0x11)); + assertThat(baseopt.getHeaderExtLength(), is((byte) 0x00)); + assertArrayEquals(baseopt.getOptions(), options); + } + + /** + * Tests comparator. + */ + @Test + public void testEqual() { + BaseOptions baseopt1 = new BaseOptions(); + baseopt1.setNextHeader((byte) 0x11); + baseopt1.setHeaderExtLength((byte) 0x00); + baseopt1.setOptions(options); + baseopt1.setType(IPv6.PROTOCOL_HOPOPT); + + BaseOptions baseopt2 = new BaseOptions(); + baseopt2.setNextHeader((byte) 0x11); + baseopt2.setHeaderExtLength((byte) 0x00); + baseopt2.setOptions(options); + baseopt1.setType(IPv6.PROTOCOL_DSTOPT); + + assertTrue(baseopt1.equals(baseopt1)); + assertFalse(baseopt1.equals(baseopt2)); + } +} diff --git a/framework/src/onos/utils/misc/src/test/java/org/onlab/packet/ipv6/DestinationOptionsTest.java b/framework/src/onos/utils/misc/src/test/java/org/onlab/packet/ipv6/DestinationOptionsTest.java new file mode 100644 index 00000000..29dd1266 --- /dev/null +++ b/framework/src/onos/utils/misc/src/test/java/org/onlab/packet/ipv6/DestinationOptionsTest.java @@ -0,0 +1,37 @@ +/* + * Copyright 2014-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.ipv6; + +import org.junit.Test; +import org.onlab.packet.IPv6; + +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; + +/** + * Tests for class {@link DestinationOptions}. + */ +public class DestinationOptionsTest { + /** + * Tests constructor. + */ + @Test + public void testConstructor() { + DestinationOptions dstopt = new DestinationOptions(); + assertThat(dstopt.getType(), is(IPv6.PROTOCOL_DSTOPT)); + } +} diff --git a/framework/src/onos/utils/misc/src/test/java/org/onlab/packet/ipv6/EncapSecurityPayloadTest.java b/framework/src/onos/utils/misc/src/test/java/org/onlab/packet/ipv6/EncapSecurityPayloadTest.java new file mode 100644 index 00000000..e0e99191 --- /dev/null +++ b/framework/src/onos/utils/misc/src/test/java/org/onlab/packet/ipv6/EncapSecurityPayloadTest.java @@ -0,0 +1,104 @@ +/* + * Copyright 2014-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.ipv6; + +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.onlab.packet.Data; +import org.onlab.packet.DeserializationException; +import org.onlab.packet.Deserializer; + +import java.util.Arrays; + +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + +/** + * Tests for class {@link EncapSecurityPayload}. + */ +public class EncapSecurityPayloadTest { + private static Data data; + private static byte[] dataByte = new byte[32]; + private static byte[] bytePacket; + + private Deserializer<EncapSecurityPayload> deserializer; + + @BeforeClass + public static void setUpBeforeClass() throws Exception { + Arrays.fill(dataByte, (byte) 0xff); + data = new Data().setData(dataByte); + + byte[] bytePayload = data.serialize(); + byte[] byteHeader = { + (byte) 0x13, (byte) 0x57, (byte) 0x24, (byte) 0x68, + (byte) 0x00, (byte) 0xff, (byte) 0xff, (byte) 0x00 + }; + bytePacket = new byte[byteHeader.length + bytePayload.length]; + System.arraycopy(byteHeader, 0, bytePacket, 0, byteHeader.length); + System.arraycopy(bytePayload, 0, bytePacket, byteHeader.length, bytePayload.length); + } + + @Before + public void setUp() { + deserializer = EncapSecurityPayload.deserializer(); + } + + /** + * Tests serialize and setters. + */ + @Test + public void testSerialize() { + EncapSecurityPayload esp = new EncapSecurityPayload(); + esp.setSecurityParamIndex(0x13572468); + esp.setSequence(0xffff00); + esp.setPayload(data); + + assertArrayEquals(esp.serialize(), bytePacket); + } + + /** + * Tests deserialize and getters. + */ + @Test + public void testDeserialize() throws DeserializationException { + EncapSecurityPayload esp = deserializer.deserialize(bytePacket, 0, bytePacket.length); + + assertThat(esp.getSecurityParamIndex(), is(0x13572468)); + assertThat(esp.getSequence(), is(0xffff00)); + } + + /** + * Tests comparator. + */ + @Test + public void testEqual() { + EncapSecurityPayload esp1 = new EncapSecurityPayload(); + esp1.setSecurityParamIndex(0x13572468); + esp1.setSequence(0xffff00); + + EncapSecurityPayload esp2 = new EncapSecurityPayload(); + esp2.setSecurityParamIndex(0x13572468); + esp2.setSequence(0xfffff0); + + assertTrue(esp1.equals(esp1)); + assertFalse(esp1.equals(esp2)); + } +} diff --git a/framework/src/onos/utils/misc/src/test/java/org/onlab/packet/ipv6/FragmentTest.java b/framework/src/onos/utils/misc/src/test/java/org/onlab/packet/ipv6/FragmentTest.java new file mode 100644 index 00000000..f2d5e48e --- /dev/null +++ b/framework/src/onos/utils/misc/src/test/java/org/onlab/packet/ipv6/FragmentTest.java @@ -0,0 +1,113 @@ +/* + * Copyright 2014-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.ipv6; + +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.onlab.packet.Data; +import org.onlab.packet.DeserializationException; +import org.onlab.packet.Deserializer; +import org.onlab.packet.UDP; + +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + +/** + * Tests for class {@link Fragment}. + */ +public class FragmentTest { + private static Data data; + private static UDP udp; + private static byte[] bytePacket; + + private Deserializer<Fragment> deserializer; + + @BeforeClass + public static void setUpBeforeClass() throws Exception { + data = new Data(); + data.setData("testSerialize".getBytes()); + udp = new UDP(); + udp.setPayload(data); + + byte[] bytePayload = udp.serialize(); + byte[] byteHeader = { + (byte) 0x11, (byte) 0x00, (byte) 0x00, (byte) 0xf9, + (byte) 0x00, (byte) 0x00, (byte) 0x13, (byte) 0x57 + }; + bytePacket = new byte[byteHeader.length + bytePayload.length]; + System.arraycopy(byteHeader, 0, bytePacket, 0, byteHeader.length); + System.arraycopy(bytePayload, 0, bytePacket, byteHeader.length, bytePayload.length); + } + + @Before + public void setUp() { + deserializer = Fragment.deserializer(); + } + + /** + * Tests serialize and setters. + */ + @Test + public void testSerialize() { + Fragment frag = new Fragment(); + frag.setNextHeader((byte) 0x11); + frag.setFragmentOffset((short) 0x1f); + frag.setMoreFragment((byte) 1); + frag.setIdentification(0x1357); + frag.setPayload(udp); + + assertArrayEquals(frag.serialize(), bytePacket); + } + + /** + * Tests deserialize and getters. + */ + @Test + public void testDeserialize() throws DeserializationException { + Fragment frag = deserializer.deserialize(bytePacket, 0, bytePacket.length); + + assertThat(frag.getNextHeader(), is((byte) 0x11)); + assertThat(frag.getFragmentOffset(), is((short) 0x1f)); + assertThat(frag.getMoreFragment(), is((byte) 1)); + assertThat(frag.getIdentification(), is(0x1357)); + } + + /** + * Tests comparator. + */ + @Test + public void testEqual() { + Fragment frag1 = new Fragment(); + frag1.setNextHeader((byte) 0x11); + frag1.setFragmentOffset((short) 0x1f); + frag1.setMoreFragment((byte) 1); + frag1.setIdentification(0x1357); + + Fragment frag2 = new Fragment(); + frag2.setNextHeader((byte) 0x11); + frag2.setFragmentOffset((short) 0x1f); + frag2.setMoreFragment((byte) 1); + frag2.setIdentification(0x1358); + + assertTrue(frag1.equals(frag1)); + assertFalse(frag1.equals(frag2)); + } +} diff --git a/framework/src/onos/utils/misc/src/test/java/org/onlab/packet/ipv6/HopByHopOptionsTest.java b/framework/src/onos/utils/misc/src/test/java/org/onlab/packet/ipv6/HopByHopOptionsTest.java new file mode 100644 index 00000000..1e9be2f6 --- /dev/null +++ b/framework/src/onos/utils/misc/src/test/java/org/onlab/packet/ipv6/HopByHopOptionsTest.java @@ -0,0 +1,37 @@ +/* + * Copyright 2014-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.ipv6; + +import org.junit.Test; +import org.onlab.packet.IPv6; + +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; + +/** + * Tests for class {@link HopByHopOptions}. + */ +public class HopByHopOptionsTest { + /** + * Tests constructor. + */ + @Test + public void testConstructor() { + HopByHopOptions hopopt = new HopByHopOptions(); + assertThat(hopopt.getType(), is(IPv6.PROTOCOL_HOPOPT)); + } +} diff --git a/framework/src/onos/utils/misc/src/test/java/org/onlab/packet/ipv6/RoutingTest.java b/framework/src/onos/utils/misc/src/test/java/org/onlab/packet/ipv6/RoutingTest.java new file mode 100644 index 00000000..a03bacc9 --- /dev/null +++ b/framework/src/onos/utils/misc/src/test/java/org/onlab/packet/ipv6/RoutingTest.java @@ -0,0 +1,128 @@ +/* + * Copyright 2014-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.ipv6; + +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.onlab.packet.Data; +import org.onlab.packet.DeserializationException; +import org.onlab.packet.Deserializer; +import org.onlab.packet.UDP; + +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + +/** + * Tests for class {@link Routing}. + */ +public class RoutingTest { + private static Data data; + private static UDP udp; + private static byte[] routingData = { + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x20, (byte) 0x01, (byte) 0x0f, (byte) 0x18, + (byte) 0x01, (byte) 0x13, (byte) 0x02, (byte) 0x15, + (byte) 0xca, (byte) 0x2a, (byte) 0x14, (byte) 0xff, + (byte) 0xfe, (byte) 0x35, (byte) 0x26, (byte) 0xce + }; + private static byte[] bytePacket; + + private Deserializer<Routing> deserializer; + + @BeforeClass + public static void setUpBeforeClass() throws Exception { + data = new Data(); + data.setData("testSerialize".getBytes()); + udp = new UDP(); + udp.setPayload(data); + + byte[] bytePayload = udp.serialize(); + byte[] byteHeader = { + (byte) 0x11, (byte) 0x02, (byte) 0x00, (byte) 0x03, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x20, (byte) 0x01, (byte) 0x0f, (byte) 0x18, + (byte) 0x01, (byte) 0x13, (byte) 0x02, (byte) 0x15, + (byte) 0xca, (byte) 0x2a, (byte) 0x14, (byte) 0xff, + (byte) 0xfe, (byte) 0x35, (byte) 0x26, (byte) 0xce + }; + bytePacket = new byte[byteHeader.length + bytePayload.length]; + System.arraycopy(byteHeader, 0, bytePacket, 0, byteHeader.length); + System.arraycopy(bytePayload, 0, bytePacket, byteHeader.length, bytePayload.length); + } + + @Before + public void setUp() { + deserializer = Routing.deserializer(); + } + + /** + * Tests serialize and setters. + */ + @Test + public void testSerialize() { + Routing routing = new Routing(); + routing.setNextHeader((byte) 0x11); + routing.setHeaderExtLength((byte) 0x02); + routing.setRoutingType((byte) 0x00); + routing.setSegmntsLeft((byte) 0x03); + routing.setRoutingData(routingData); + routing.setPayload(udp); + + assertArrayEquals(routing.serialize(), bytePacket); + } + + /** + * Tests deserialize and getters. + */ + @Test + public void testDeserialize() throws DeserializationException { + Routing routing = deserializer.deserialize(bytePacket, 0, bytePacket.length); + + assertThat(routing.getNextHeader(), is((byte) 0x11)); + assertThat(routing.getHeaderExtLength(), is((byte) 0x02)); + assertThat(routing.getRoutingType(), is((byte) 0x00)); + assertThat(routing.getSegmentsLeft(), is((byte) 0x03)); + assertArrayEquals(routing.getRoutingData(), routingData); + } + + /** + * Tests comparator. + */ + @Test + public void testEqual() { + Routing routing1 = new Routing(); + routing1.setNextHeader((byte) 0x11); + routing1.setHeaderExtLength((byte) 0x02); + routing1.setRoutingType((byte) 0x00); + routing1.setSegmntsLeft((byte) 0x03); + routing1.setRoutingData(routingData); + + Routing routing2 = new Routing(); + routing2.setNextHeader((byte) 0x11); + routing2.setHeaderExtLength((byte) 0x02); + routing2.setRoutingType((byte) 0x00); + routing2.setSegmntsLeft((byte) 0x02); + routing2.setRoutingData(routingData); + + assertTrue(routing1.equals(routing1)); + assertFalse(routing1.equals(routing2)); + } +} diff --git a/framework/src/onos/utils/misc/src/test/java/org/onlab/packet/ndp/NeighborAdvertisementTest.java b/framework/src/onos/utils/misc/src/test/java/org/onlab/packet/ndp/NeighborAdvertisementTest.java new file mode 100644 index 00000000..d537251c --- /dev/null +++ b/framework/src/onos/utils/misc/src/test/java/org/onlab/packet/ndp/NeighborAdvertisementTest.java @@ -0,0 +1,140 @@ +/* + * Copyright 2014-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.ndp; + +import org.junit.BeforeClass; +import org.junit.Test; +import org.onlab.packet.DeserializationException; +import org.onlab.packet.Deserializer; +import org.onlab.packet.MacAddress; +import org.onlab.packet.PacketTestUtils; + +import java.nio.ByteBuffer; + +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + +/** + * Tests for class {@link NeighborAdvertisement}. + */ +public class NeighborAdvertisementTest { + private static final byte[] TARGET_ADDRESS = { + (byte) 0x20, (byte) 0x01, (byte) 0x0f, (byte) 0x18, + (byte) 0x01, (byte) 0x13, (byte) 0x02, (byte) 0x15, + (byte) 0xca, (byte) 0x2a, (byte) 0x14, (byte) 0xff, + (byte) 0xfe, (byte) 0x35, (byte) 0x26, (byte) 0xce + }; + private static final MacAddress MAC_ADDRESS = + MacAddress.valueOf("11:22:33:44:55:66"); + + private static byte[] bytePacket; + + private Deserializer<NeighborAdvertisement> deserializer + = NeighborAdvertisement.deserializer(); + + @BeforeClass + public static void setUpBeforeClass() throws Exception { + byte[] byteHeader = { + (byte) 0xe0, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x20, (byte) 0x01, (byte) 0x0f, (byte) 0x18, + (byte) 0x01, (byte) 0x13, (byte) 0x02, (byte) 0x15, + (byte) 0xca, (byte) 0x2a, (byte) 0x14, (byte) 0xff, + (byte) 0xfe, (byte) 0x35, (byte) 0x26, (byte) 0xce, + (byte) 0x02, (byte) 0x01, (byte) 0x11, (byte) 0x22, + (byte) 0x33, (byte) 0x44, (byte) 0x55, (byte) 0x66 + }; + bytePacket = new byte[byteHeader.length]; + System.arraycopy(byteHeader, 0, bytePacket, 0, byteHeader.length); + } + + /** + * Tests serialize and setters. + */ + @Test + public void testSerialize() { + NeighborAdvertisement na = new NeighborAdvertisement(); + na.setRouterFlag((byte) 1); + na.setSolicitedFlag((byte) 1); + na.setOverrideFlag((byte) 1); + na.setTargetAddress(TARGET_ADDRESS); + na.addOption(NeighborDiscoveryOptions.TYPE_TARGET_LL_ADDRESS, + MAC_ADDRESS.toBytes()); + + assertArrayEquals(na.serialize(), bytePacket); + } + + @Test + public void testDeserializeBadInput() throws Exception { + PacketTestUtils.testDeserializeBadInput(NeighborAdvertisement.deserializer()); + } + + @Test + public void testDeserializeTruncated() throws Exception { + // Run the truncation test only on the NeighborAdvertisement header + byte[] naHeader = new byte[NeighborAdvertisement.HEADER_LENGTH]; + ByteBuffer.wrap(bytePacket).get(naHeader); + + PacketTestUtils.testDeserializeTruncated(NeighborAdvertisement.deserializer(), naHeader); + } + + /** + * Tests deserialize and getters. + */ + @Test + public void testDeserialize() throws DeserializationException { + NeighborAdvertisement na = deserializer.deserialize(bytePacket, 0, bytePacket.length); + + assertThat(na.getRouterFlag(), is((byte) 1)); + assertThat(na.getSolicitedFlag(), is((byte) 1)); + assertThat(na.getOverrideFlag(), is((byte) 1)); + assertArrayEquals(na.getTargetAddress(), TARGET_ADDRESS); + + // Check the option(s) + assertThat(na.getOptions().size(), is(1)); + NeighborDiscoveryOptions.Option option = na.getOptions().get(0); + assertThat(option.type(), + is(NeighborDiscoveryOptions.TYPE_TARGET_LL_ADDRESS)); + assertArrayEquals(option.data(), MAC_ADDRESS.toBytes()); + } + + /** + * Tests comparator. + */ + @Test + public void testEqual() { + NeighborAdvertisement na1 = new NeighborAdvertisement(); + na1.setRouterFlag((byte) 1); + na1.setSolicitedFlag((byte) 1); + na1.setOverrideFlag((byte) 1); + na1.setTargetAddress(TARGET_ADDRESS); + na1.addOption(NeighborDiscoveryOptions.TYPE_TARGET_LL_ADDRESS, + MAC_ADDRESS.toBytes()); + + NeighborAdvertisement na2 = new NeighborAdvertisement(); + na2.setRouterFlag((byte) 1); + na2.setSolicitedFlag((byte) 1); + na2.setOverrideFlag((byte) 0); + na2.setTargetAddress(TARGET_ADDRESS); + na2.addOption(NeighborDiscoveryOptions.TYPE_TARGET_LL_ADDRESS, + MAC_ADDRESS.toBytes()); + + assertTrue(na1.equals(na1)); + assertFalse(na1.equals(na2)); + } +} diff --git a/framework/src/onos/utils/misc/src/test/java/org/onlab/packet/ndp/NeighborSolicitationTest.java b/framework/src/onos/utils/misc/src/test/java/org/onlab/packet/ndp/NeighborSolicitationTest.java new file mode 100644 index 00000000..03e57f5e --- /dev/null +++ b/framework/src/onos/utils/misc/src/test/java/org/onlab/packet/ndp/NeighborSolicitationTest.java @@ -0,0 +1,134 @@ +/* + * Copyright 2014-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.ndp; + +import org.junit.BeforeClass; +import org.junit.Test; +import org.onlab.packet.DeserializationException; +import org.onlab.packet.Deserializer; +import org.onlab.packet.MacAddress; +import org.onlab.packet.PacketTestUtils; + +import java.nio.ByteBuffer; + +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + +/** + * Tests for class {@link NeighborSolicitation}. + */ +public class NeighborSolicitationTest { + private static final byte[] TARGET_ADDRESS = { + (byte) 0x20, (byte) 0x01, (byte) 0x0f, (byte) 0x18, + (byte) 0x01, (byte) 0x13, (byte) 0x02, (byte) 0x15, + (byte) 0xca, (byte) 0x2a, (byte) 0x14, (byte) 0xff, + (byte) 0xfe, (byte) 0x35, (byte) 0x26, (byte) 0xce + }; + private static final byte[] TARGET_ADDRESS2 = { + (byte) 0x20, (byte) 0x01, (byte) 0x0f, (byte) 0x18, + (byte) 0x01, (byte) 0x13, (byte) 0x02, (byte) 0x15, + (byte) 0xe6, (byte) 0xce, (byte) 0x8f, (byte) 0xff, + (byte) 0xfe, (byte) 0x54, (byte) 0x37, (byte) 0xc8 + }; + private static final MacAddress MAC_ADDRESS = + MacAddress.valueOf("11:22:33:44:55:66"); + + private static byte[] bytePacket; + + private Deserializer<NeighborSolicitation> deserializer + = NeighborSolicitation.deserializer(); + + @BeforeClass + public static void setUpBeforeClass() throws Exception { + byte[] byteHeader = { + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x20, (byte) 0x01, (byte) 0x0f, (byte) 0x18, + (byte) 0x01, (byte) 0x13, (byte) 0x02, (byte) 0x15, + (byte) 0xca, (byte) 0x2a, (byte) 0x14, (byte) 0xff, + (byte) 0xfe, (byte) 0x35, (byte) 0x26, (byte) 0xce, + (byte) 0x02, (byte) 0x01, (byte) 0x11, (byte) 0x22, + (byte) 0x33, (byte) 0x44, (byte) 0x55, (byte) 0x66 + }; + bytePacket = new byte[byteHeader.length]; + System.arraycopy(byteHeader, 0, bytePacket, 0, byteHeader.length); + } + + /** + * Tests serialize and setters. + */ + @Test + public void testSerialize() { + NeighborSolicitation ns = new NeighborSolicitation(); + ns.setTargetAddress(TARGET_ADDRESS); + ns.addOption(NeighborDiscoveryOptions.TYPE_TARGET_LL_ADDRESS, + MAC_ADDRESS.toBytes()); + + assertArrayEquals(ns.serialize(), bytePacket); + } + + @Test + public void testDeserializeBadInput() throws Exception { + PacketTestUtils.testDeserializeBadInput(NeighborSolicitation.deserializer()); + } + + @Test + public void testDeserializeTruncated() throws Exception { + // Run the truncation test only on the NeighborSolicitation header + byte[] nsHeader = new byte[NeighborSolicitation.HEADER_LENGTH]; + ByteBuffer.wrap(bytePacket).get(nsHeader); + + PacketTestUtils.testDeserializeTruncated(NeighborSolicitation.deserializer(), nsHeader); + } + + /** + * Tests deserialize and getters. + */ + @Test + public void testDeserialize() throws DeserializationException { + NeighborSolicitation ns = deserializer.deserialize(bytePacket, 0, bytePacket.length); + + assertArrayEquals(ns.getTargetAddress(), TARGET_ADDRESS); + + // Check the option(s) + assertThat(ns.getOptions().size(), is(1)); + NeighborDiscoveryOptions.Option option = ns.getOptions().get(0); + assertThat(option.type(), + is(NeighborDiscoveryOptions.TYPE_TARGET_LL_ADDRESS)); + assertArrayEquals(option.data(), MAC_ADDRESS.toBytes()); + } + + /** + * Tests comparator. + */ + @Test + public void testEqual() { + NeighborSolicitation ns1 = new NeighborSolicitation(); + ns1.setTargetAddress(TARGET_ADDRESS); + ns1.addOption(NeighborDiscoveryOptions.TYPE_TARGET_LL_ADDRESS, + MAC_ADDRESS.toBytes()); + + NeighborSolicitation ns2 = new NeighborSolicitation(); + ns2.setTargetAddress(TARGET_ADDRESS2); + ns2.addOption(NeighborDiscoveryOptions.TYPE_TARGET_LL_ADDRESS, + MAC_ADDRESS.toBytes()); + + assertTrue(ns1.equals(ns1)); + assertFalse(ns1.equals(ns2)); + } +} diff --git a/framework/src/onos/utils/misc/src/test/java/org/onlab/packet/ndp/RedirectTest.java b/framework/src/onos/utils/misc/src/test/java/org/onlab/packet/ndp/RedirectTest.java new file mode 100644 index 00000000..31157145 --- /dev/null +++ b/framework/src/onos/utils/misc/src/test/java/org/onlab/packet/ndp/RedirectTest.java @@ -0,0 +1,147 @@ +/* + * Copyright 2014-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.ndp; + +import org.junit.BeforeClass; +import org.junit.Test; +import org.onlab.packet.DeserializationException; +import org.onlab.packet.Deserializer; +import org.onlab.packet.MacAddress; +import org.onlab.packet.PacketTestUtils; + +import java.nio.ByteBuffer; + +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + +/** + * Tests for class {@link Redirect}. + */ +public class RedirectTest { + private static final byte[] TARGET_ADDRESS = { + (byte) 0x20, (byte) 0x01, (byte) 0x0f, (byte) 0x18, + (byte) 0x01, (byte) 0x13, (byte) 0x02, (byte) 0x15, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00 + }; + private static final byte[] DESTINATION_ADDRESS = { + (byte) 0x20, (byte) 0x01, (byte) 0x0f, (byte) 0x18, + (byte) 0x01, (byte) 0x13, (byte) 0x02, (byte) 0x15, + (byte) 0xca, (byte) 0x2a, (byte) 0x14, (byte) 0xff, + (byte) 0xfe, (byte) 0x35, (byte) 0x26, (byte) 0xce + }; + private static final byte[] DESTINATION_ADDRESS2 = { + (byte) 0x20, (byte) 0x01, (byte) 0x0f, (byte) 0x18, + (byte) 0x01, (byte) 0x13, (byte) 0x02, (byte) 0x15, + (byte) 0xe6, (byte) 0xce, (byte) 0x8f, (byte) 0xff, + (byte) 0xfe, (byte) 0x54, (byte) 0x37, (byte) 0xc8 + }; + private static final MacAddress MAC_ADDRESS = + MacAddress.valueOf("11:22:33:44:55:66"); + + private static byte[] bytePacket; + + private Deserializer<Redirect> deserializer = Redirect.deserializer(); + + @BeforeClass + public static void setUpBeforeClass() throws Exception { + byte[] byteHeader = { + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x20, (byte) 0x01, (byte) 0x0f, (byte) 0x18, + (byte) 0x01, (byte) 0x13, (byte) 0x02, (byte) 0x15, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x20, (byte) 0x01, (byte) 0x0f, (byte) 0x18, + (byte) 0x01, (byte) 0x13, (byte) 0x02, (byte) 0x15, + (byte) 0xca, (byte) 0x2a, (byte) 0x14, (byte) 0xff, + (byte) 0xfe, (byte) 0x35, (byte) 0x26, (byte) 0xce, + (byte) 0x02, (byte) 0x01, (byte) 0x11, (byte) 0x22, + (byte) 0x33, (byte) 0x44, (byte) 0x55, (byte) 0x66 + }; + bytePacket = new byte[byteHeader.length]; + System.arraycopy(byteHeader, 0, bytePacket, 0, byteHeader.length); + } + + /** + * Tests serialize and setters. + */ + @Test + public void testSerialize() { + Redirect rd = new Redirect(); + rd.setTargetAddress(TARGET_ADDRESS); + rd.setDestinationAddress(DESTINATION_ADDRESS); + rd.addOption(NeighborDiscoveryOptions.TYPE_TARGET_LL_ADDRESS, + MAC_ADDRESS.toBytes()); + + assertArrayEquals(rd.serialize(), bytePacket); + } + + @Test + public void testDeserializeBadInput() throws Exception { + PacketTestUtils.testDeserializeBadInput(Redirect.deserializer()); + } + + @Test + public void testDeserializeTruncated() throws Exception { + // Run the truncation test only on the Redirect header + byte[] rdHeader = new byte[Redirect.HEADER_LENGTH]; + ByteBuffer.wrap(bytePacket).get(rdHeader); + + PacketTestUtils.testDeserializeTruncated(Redirect.deserializer(), rdHeader); + } + + /** + * Tests deserialize and getters. + */ + @Test + public void testDeserialize() throws DeserializationException { + Redirect rd = deserializer.deserialize(bytePacket, 0, bytePacket.length); + + assertArrayEquals(rd.getTargetAddress(), TARGET_ADDRESS); + assertArrayEquals(rd.getDestinationAddress(), DESTINATION_ADDRESS); + + // Check the option(s) + assertThat(rd.getOptions().size(), is(1)); + NeighborDiscoveryOptions.Option option = rd.getOptions().get(0); + assertThat(option.type(), + is(NeighborDiscoveryOptions.TYPE_TARGET_LL_ADDRESS)); + assertArrayEquals(option.data(), MAC_ADDRESS.toBytes()); + } + + /** + * Tests comparator. + */ + @Test + public void testEqual() { + Redirect rd1 = new Redirect(); + rd1.setTargetAddress(TARGET_ADDRESS); + rd1.setDestinationAddress(DESTINATION_ADDRESS); + rd1.addOption(NeighborDiscoveryOptions.TYPE_TARGET_LL_ADDRESS, + MAC_ADDRESS.toBytes()); + + Redirect rd2 = new Redirect(); + rd2.setTargetAddress(TARGET_ADDRESS); + rd2.setDestinationAddress(DESTINATION_ADDRESS2); + rd2.addOption(NeighborDiscoveryOptions.TYPE_TARGET_LL_ADDRESS, + MAC_ADDRESS.toBytes()); + + assertTrue(rd1.equals(rd1)); + assertFalse(rd1.equals(rd2)); + } +} diff --git a/framework/src/onos/utils/misc/src/test/java/org/onlab/packet/ndp/RouterAdvertisementTest.java b/framework/src/onos/utils/misc/src/test/java/org/onlab/packet/ndp/RouterAdvertisementTest.java new file mode 100644 index 00000000..ede15747 --- /dev/null +++ b/framework/src/onos/utils/misc/src/test/java/org/onlab/packet/ndp/RouterAdvertisementTest.java @@ -0,0 +1,140 @@ +/* + * Copyright 2014-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.ndp; + +import org.junit.BeforeClass; +import org.junit.Test; +import org.onlab.packet.DeserializationException; +import org.onlab.packet.Deserializer; +import org.onlab.packet.MacAddress; +import org.onlab.packet.PacketTestUtils; + +import java.nio.ByteBuffer; + +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + +/** + * Tests for class {@link RouterAdvertisement}. + */ +public class RouterAdvertisementTest { + private static final MacAddress MAC_ADDRESS = + MacAddress.valueOf("11:22:33:44:55:66"); + + private static byte[] bytePacket; + + private Deserializer<RouterAdvertisement> deserializer + = RouterAdvertisement.deserializer(); + + @BeforeClass + public static void setUpBeforeClass() throws Exception { + byte[] byteHeader = { + (byte) 0x03, (byte) 0xc0, (byte) 0x02, (byte) 0x58, + (byte) 0x00, (byte) 0x00, (byte) 0x03, (byte) 0xe8, + (byte) 0x00, (byte) 0x00, (byte) 0x01, (byte) 0xf4, + (byte) 0x02, (byte) 0x01, (byte) 0x11, (byte) 0x22, + (byte) 0x33, (byte) 0x44, (byte) 0x55, (byte) 0x66 + }; + bytePacket = new byte[byteHeader.length]; + System.arraycopy(byteHeader, 0, bytePacket, 0, byteHeader.length); + } + + /** + * Tests serialize and setters. + */ + @Test + public void testSerialize() { + RouterAdvertisement ra = new RouterAdvertisement(); + ra.setCurrentHopLimit((byte) 3); + ra.setMFlag((byte) 1); + ra.setOFlag((byte) 1); + ra.setRouterLifetime((short) 0x258); + ra.setReachableTime(0x3e8); + ra.setRetransmitTimer(0x1f4); + ra.addOption(NeighborDiscoveryOptions.TYPE_TARGET_LL_ADDRESS, + MAC_ADDRESS.toBytes()); + + assertArrayEquals(ra.serialize(), bytePacket); + } + + @Test + public void testDeserializeBadInput() throws Exception { + PacketTestUtils.testDeserializeBadInput(RouterAdvertisement.deserializer()); + } + + @Test + public void testDeserializeTruncated() throws Exception { + // Run the truncation test only on the RouterAdvertisement header + byte[] raHeader = new byte[RouterAdvertisement.HEADER_LENGTH]; + ByteBuffer.wrap(bytePacket).get(raHeader); + + PacketTestUtils.testDeserializeTruncated(RouterAdvertisement.deserializer(), raHeader); + } + + /** + * Tests deserialize and getters. + */ + @Test + public void testDeserialize() throws DeserializationException { + RouterAdvertisement ra = deserializer.deserialize(bytePacket, 0, bytePacket.length); + + assertThat(ra.getCurrentHopLimit(), is((byte) 3)); + assertThat(ra.getMFlag(), is((byte) 1)); + assertThat(ra.getOFlag(), is((byte) 1)); + assertThat(ra.getRouterLifetime(), is((short) 0x258)); + assertThat(ra.getReachableTime(), is(0x3e8)); + assertThat(ra.getRetransmitTimer(), is(0x1f4)); + + // Check the option(s) + assertThat(ra.getOptions().size(), is(1)); + NeighborDiscoveryOptions.Option option = ra.getOptions().get(0); + assertThat(option.type(), + is(NeighborDiscoveryOptions.TYPE_TARGET_LL_ADDRESS)); + assertArrayEquals(option.data(), MAC_ADDRESS.toBytes()); + } + + /** + * Tests comparator. + */ + @Test + public void testEqual() { + RouterAdvertisement ra1 = new RouterAdvertisement(); + ra1.setCurrentHopLimit((byte) 3); + ra1.setMFlag((byte) 1); + ra1.setOFlag((byte) 1); + ra1.setRouterLifetime((short) 0x258); + ra1.setReachableTime(0x3e8); + ra1.setRetransmitTimer(0x1f4); + ra1.addOption(NeighborDiscoveryOptions.TYPE_TARGET_LL_ADDRESS, + MAC_ADDRESS.toBytes()); + + RouterAdvertisement ra2 = new RouterAdvertisement(); + ra2.setCurrentHopLimit((byte) 3); + ra2.setMFlag((byte) 0); + ra2.setOFlag((byte) 0); + ra2.setRouterLifetime((short) 0x1f4); + ra2.setReachableTime(0x3e8); + ra2.setRetransmitTimer(0x1f4); + ra2.addOption(NeighborDiscoveryOptions.TYPE_TARGET_LL_ADDRESS, + MAC_ADDRESS.toBytes()); + + assertTrue(ra1.equals(ra1)); + assertFalse(ra1.equals(ra2)); + } +} diff --git a/framework/src/onos/utils/misc/src/test/java/org/onlab/packet/ndp/RouterSolicitationTest.java b/framework/src/onos/utils/misc/src/test/java/org/onlab/packet/ndp/RouterSolicitationTest.java new file mode 100644 index 00000000..e3b5686c --- /dev/null +++ b/framework/src/onos/utils/misc/src/test/java/org/onlab/packet/ndp/RouterSolicitationTest.java @@ -0,0 +1,114 @@ +/* + * Copyright 2014-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.ndp; + +import org.junit.BeforeClass; +import org.junit.Test; +import org.onlab.packet.Deserializer; +import org.onlab.packet.MacAddress; +import org.onlab.packet.PacketTestUtils; + +import java.nio.ByteBuffer; + +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + +/** + * Tests for class {@link RouterSolicitation}. + */ +public class RouterSolicitationTest { + private static final MacAddress MAC_ADDRESS1 = + MacAddress.valueOf("11:22:33:44:55:66"); + private static final MacAddress MAC_ADDRESS2 = + MacAddress.valueOf("11:22:33:44:55:00"); + + private static byte[] bytePacket; + + private Deserializer<RouterSolicitation> deserializer + = RouterSolicitation.deserializer(); + + @BeforeClass + public static void setUpBeforeClass() throws Exception { + byte[] byteHeader = { + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x02, (byte) 0x01, (byte) 0x11, (byte) 0x22, + (byte) 0x33, (byte) 0x44, (byte) 0x55, (byte) 0x66 + }; + bytePacket = new byte[byteHeader.length]; + System.arraycopy(byteHeader, 0, bytePacket, 0, byteHeader.length); + } + + /** + * Tests serialize and setters. + */ + @Test + public void testSerialize() { + RouterSolicitation rs = new RouterSolicitation(); + rs.addOption(NeighborDiscoveryOptions.TYPE_TARGET_LL_ADDRESS, + MAC_ADDRESS1.toBytes()); + + assertArrayEquals(rs.serialize(), bytePacket); + } + + @Test + public void testDeserializeBadInput() throws Exception { + PacketTestUtils.testDeserializeBadInput(RouterSolicitation.deserializer()); + } + + @Test + public void testDeserializeTruncated() throws Exception { + // Run the truncation test only on the RouterSolicitation header + byte[] rsHeader = new byte[RouterSolicitation.HEADER_LENGTH]; + ByteBuffer.wrap(bytePacket).get(rsHeader); + + PacketTestUtils.testDeserializeTruncated(RouterSolicitation.deserializer(), rsHeader); + } + + /** + * Tests deserialize and getters. + */ + @Test + public void testDeserialize() throws Exception { + RouterSolicitation rs = deserializer.deserialize(bytePacket, 0, bytePacket.length); + + // Check the option(s) + assertThat(rs.getOptions().size(), is(1)); + NeighborDiscoveryOptions.Option option = rs.getOptions().get(0); + assertThat(option.type(), + is(NeighborDiscoveryOptions.TYPE_TARGET_LL_ADDRESS)); + assertArrayEquals(option.data(), MAC_ADDRESS1.toBytes()); + } + + /** + * Tests comparator. + */ + @Test + public void testEqual() { + RouterSolicitation rs1 = new RouterSolicitation(); + rs1.addOption(NeighborDiscoveryOptions.TYPE_TARGET_LL_ADDRESS, + MAC_ADDRESS1.toBytes()); + + RouterSolicitation rs2 = new RouterSolicitation(); + rs2.addOption(NeighborDiscoveryOptions.TYPE_TARGET_LL_ADDRESS, + MAC_ADDRESS2.toBytes()); + + assertTrue(rs1.equals(rs1)); + assertFalse(rs1.equals(rs2)); + } +} diff --git a/framework/src/onos/utils/misc/src/test/java/org/onlab/util/AbstractAccumulatorTest.java b/framework/src/onos/utils/misc/src/test/java/org/onlab/util/AbstractAccumulatorTest.java new file mode 100644 index 00000000..02f0deb1 --- /dev/null +++ b/framework/src/onos/utils/misc/src/test/java/org/onlab/util/AbstractAccumulatorTest.java @@ -0,0 +1,184 @@ +/* + * 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.util; + +import org.junit.Ignore; +import org.junit.Test; + +import java.util.List; +import java.util.Timer; +import java.util.stream.IntStream; + +import static org.junit.Assert.*; +import static org.onlab.junit.TestTools.assertAfter; +import static org.onlab.junit.TestTools.delay; + +/** + * Tests the operation of the accumulator. + */ +public class AbstractAccumulatorTest { + + private final Timer timer = new Timer(); + + @Test + public void basics() throws Exception { + TestAccumulator accumulator = new TestAccumulator(); + assertEquals("incorrect timer", timer, accumulator.timer()); + assertEquals("incorrect max events", 5, accumulator.maxItems()); + assertEquals("incorrect max ms", 100, accumulator.maxBatchMillis()); + assertEquals("incorrect idle ms", 70, accumulator.maxIdleMillis()); + } + + @Ignore("FIXME: timing sensitive test failing randomly.") + @Test + public void eventTrigger() { + TestAccumulator accumulator = new TestAccumulator(); + accumulator.add(new TestItem("a")); + accumulator.add(new TestItem("b")); + accumulator.add(new TestItem("c")); + accumulator.add(new TestItem("d")); + assertTrue("should not have fired yet", accumulator.batch.isEmpty()); + accumulator.add(new TestItem("e")); + delay(20); + assertFalse("should have fired", accumulator.batch.isEmpty()); + assertEquals("incorrect batch", "abcde", accumulator.batch); + } + + @Ignore("FIXME: timing sensitive test failing randomly.") + @Test + public void timeTrigger() { + TestAccumulator accumulator = new TestAccumulator(); + accumulator.add(new TestItem("a")); + delay(30); + assertTrue("should not have fired yet", accumulator.batch.isEmpty()); + accumulator.add(new TestItem("b")); + delay(30); + assertTrue("should not have fired yet", accumulator.batch.isEmpty()); + accumulator.add(new TestItem("c")); + delay(30); + assertTrue("should not have fired yet", accumulator.batch.isEmpty()); + accumulator.add(new TestItem("d")); + delay(60); + assertFalse("should have fired", accumulator.batch.isEmpty()); + assertEquals("incorrect batch", "abcd", accumulator.batch); + } + + @Ignore("FIXME: timing sensitive test failing randomly.") + @Test + public void idleTrigger() { + TestAccumulator accumulator = new TestAccumulator(); + accumulator.add(new TestItem("a")); + assertTrue("should not have fired yet", accumulator.batch.isEmpty()); + accumulator.add(new TestItem("b")); + delay(80); + assertFalse("should have fired", accumulator.batch.isEmpty()); + assertEquals("incorrect batch", "ab", accumulator.batch); + } + + @Ignore("FIXME: timing sensitive test failing randomly.") + @Test + public void readyIdleTrigger() { + TestAccumulator accumulator = new TestAccumulator(); + accumulator.ready = false; + accumulator.add(new TestItem("a")); + assertTrue("should not have fired yet", accumulator.batch.isEmpty()); + accumulator.add(new TestItem("b")); + delay(80); + assertTrue("should not have fired yet", accumulator.batch.isEmpty()); + accumulator.ready = true; + delay(80); + assertFalse("should have fired", accumulator.batch.isEmpty()); + assertEquals("incorrect batch", "ab", accumulator.batch); + } + + @Ignore("FIXME: timing sensitive test failing randomly.") + @Test + public void readyLongTrigger() { + TestAccumulator accumulator = new TestAccumulator(); + accumulator.ready = false; + delay(120); + assertTrue("should not have fired yet", accumulator.batch.isEmpty()); + accumulator.add(new TestItem("a")); + assertTrue("should not have fired yet", accumulator.batch.isEmpty()); + accumulator.ready = true; + delay(80); + assertFalse("should have fired", accumulator.batch.isEmpty()); + assertEquals("incorrect batch", "a", accumulator.batch); + } + + @Ignore("FIXME: timing sensitive test failing randomly.") + @Test + public void readyMaxTrigger() { + TestAccumulator accumulator = new TestAccumulator(); + accumulator.ready = false; + accumulator.add(new TestItem("a")); + accumulator.add(new TestItem("b")); + accumulator.add(new TestItem("c")); + accumulator.add(new TestItem("d")); + accumulator.add(new TestItem("e")); + accumulator.add(new TestItem("f")); + assertTrue("should not have fired yet", accumulator.batch.isEmpty()); + accumulator.ready = true; + accumulator.add(new TestItem("g")); + delay(5); + assertFalse("should have fired", accumulator.batch.isEmpty()); + assertEquals("incorrect batch", "abcdefg", accumulator.batch); + } + + @Ignore("FIXME: timing sensitive test failing randomly.") + @Test + public void stormTest() { + TestAccumulator accumulator = new TestAccumulator(); + IntStream.range(0, 1000).forEach(i -> accumulator.add(new TestItem("#" + i))); + assertAfter(100, () -> assertEquals("wrong item count", 1000, accumulator.itemCount)); + assertEquals("wrong batch count", 200, accumulator.batchCount); + } + + private class TestItem { + private final String s; + + public TestItem(String s) { + this.s = s; + } + } + + private class TestAccumulator extends AbstractAccumulator<TestItem> { + + String batch = ""; + boolean ready = true; + int batchCount = 0; + int itemCount = 0; + + protected TestAccumulator() { + super(timer, 5, 100, 70); + } + + @Override + public void processItems(List<TestItem> items) { + batchCount++; + itemCount += items.size(); + for (TestItem item : items) { + batch += item.s; + } + } + + @Override + public boolean isReady() { + return ready; + } + } + +} diff --git a/framework/src/onos/utils/misc/src/test/java/org/onlab/util/BandwidthTest.java b/framework/src/onos/utils/misc/src/test/java/org/onlab/util/BandwidthTest.java new file mode 100644 index 00000000..a3baf06c --- /dev/null +++ b/framework/src/onos/utils/misc/src/test/java/org/onlab/util/BandwidthTest.java @@ -0,0 +1,82 @@ +/* + * 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.util; + +import com.google.common.testing.EqualsTester; +import org.junit.Test; + +import static org.hamcrest.Matchers.greaterThan; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.lessThan; +import static org.junit.Assert.assertThat; + +/** + * Unit tests for Bandwidth. + */ +public class BandwidthTest { + + private final Bandwidth small = Bandwidth.kbps(100.0); + private final Bandwidth large = Bandwidth.mbps(1.0); + + /** + * Tests equality of Bandwidth instances. + */ + @Test + public void testEquality() { + new EqualsTester() + .addEqualityGroup(Bandwidth.kbps(1000.0), Bandwidth.kbps(1000.0), Bandwidth.mbps(1.0)) + .addEqualityGroup(Bandwidth.gbps(1.0)) + .testEquals(); + } + + /** + * Tests add operation of two Bandwidths. + */ + @Test + public void testAdd() { + Bandwidth expected = Bandwidth.kbps(1100.0); + + assertThat(small.add(large), is(expected)); + } + + /** + * Tests subtract operation of two Bandwidths. + */ + @Test + public void testSubtract() { + Bandwidth expected = Bandwidth.kbps(900.0); + + assertThat(large.subtract(small), is(expected)); + } + + /** + * Tests if the first object is less than the second object. + */ + @Test + public void testLessThan() { + assertThat(small, is(lessThan(large))); + assertThat(small.isLessThan(large), is(true)); + } + + /** + * Tests if the first object is greater than the second object. + */ + @Test + public void testGreaterThan() { + assertThat(large, is(greaterThan(small))); + assertThat(large.isGreaterThan(small), is(true)); + } +} diff --git a/framework/src/onos/utils/misc/src/test/java/org/onlab/util/BlockingBooleanTest.java b/framework/src/onos/utils/misc/src/test/java/org/onlab/util/BlockingBooleanTest.java new file mode 100644 index 00000000..2d8b688e --- /dev/null +++ b/framework/src/onos/utils/misc/src/test/java/org/onlab/util/BlockingBooleanTest.java @@ -0,0 +1,210 @@ +/* + * 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.util; + +import org.apache.commons.lang.mutable.MutableBoolean; +import org.junit.Ignore; +import org.junit.Test; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +import static org.junit.Assert.*; + +/** + * Tests of the BlockingBoolean utility. + */ +public class BlockingBooleanTest { + + private static final int TIMEOUT = 100; //ms + + @Test + public void basics() { + BlockingBoolean b = new BlockingBoolean(false); + assertEquals(false, b.get()); + b.set(true); + assertEquals(true, b.get()); + b.set(true); + assertEquals(true, b.get()); + b.set(false); + assertEquals(false, b.get()); + } + + private void waitChange(boolean value, int numThreads) { + BlockingBoolean b = new BlockingBoolean(!value); + + CountDownLatch latch = new CountDownLatch(numThreads); + ExecutorService exec = Executors.newFixedThreadPool(numThreads); + for (int i = 0; i < numThreads; i++) { + exec.submit(() -> { + try { + b.await(value); + latch.countDown(); + } catch (InterruptedException e) { + fail(); + } + }); + } + b.set(value); + try { + assertTrue(latch.await(TIMEOUT, TimeUnit.MILLISECONDS)); + } catch (InterruptedException e) { + fail(); + } + exec.shutdown(); + } + + @Test + public void waitTrueChange() { + waitChange(true, 4); + } + + @Test + public void waitFalseChange() { + waitChange(false, 4); + } + + @Test + public void waitSame() { + BlockingBoolean b = new BlockingBoolean(true); + + CountDownLatch latch = new CountDownLatch(1); + ExecutorService exec = Executors.newSingleThreadExecutor(); + exec.submit(() -> { + try { + b.await(true); + latch.countDown(); + } catch (InterruptedException e) { + fail(); + } + }); + try { + assertTrue(latch.await(TIMEOUT, TimeUnit.MILLISECONDS)); + } catch (InterruptedException e) { + fail(); + } + exec.shutdown(); + } + + @Test + public void someWait() { + BlockingBoolean b = new BlockingBoolean(false); + + int numThreads = 4; + CountDownLatch sameLatch = new CountDownLatch(numThreads / 2); + CountDownLatch waitLatch = new CountDownLatch(numThreads / 2); + + ExecutorService exec = Executors.newFixedThreadPool(numThreads); + for (int i = 0; i < numThreads; i++) { + final boolean value = (i % 2 == 1); + exec.submit(() -> { + try { + b.await(value); + if (value) { + waitLatch.countDown(); + } else { + sameLatch.countDown(); + } + } catch (InterruptedException e) { + fail(); + } + }); + } + try { + assertTrue(sameLatch.await(TIMEOUT, TimeUnit.MILLISECONDS)); + assertEquals(waitLatch.getCount(), numThreads / 2); + } catch (InterruptedException e) { + fail(); + } + b.set(true); + try { + assertTrue(waitLatch.await(TIMEOUT, TimeUnit.MILLISECONDS)); + } catch (InterruptedException e) { + fail(); + } + exec.shutdown(); + } + + @Test + public void waitTimeout() { + BlockingBoolean b = new BlockingBoolean(true); + + CountDownLatch latch = new CountDownLatch(1); + ExecutorService exec = Executors.newSingleThreadExecutor(); + exec.submit(() -> { + try { + if (!b.await(false, 1, TimeUnit.NANOSECONDS)) { + latch.countDown(); + } else { + fail(); + } + } catch (InterruptedException e) { + fail(); + } + }); + try { + assertTrue(latch.await(TIMEOUT, TimeUnit.MILLISECONDS)); + } catch (InterruptedException e) { + fail(); + } + exec.shutdown(); + + } + + @Test + @Ignore + public void samePerf() { + int iters = 10_000; + + BlockingBoolean b1 = new BlockingBoolean(false); + long t1 = System.nanoTime(); + for (int i = 0; i < iters; i++) { + b1.set(false); + } + long t2 = System.nanoTime(); + MutableBoolean b2 = new MutableBoolean(false); + for (int i = 0; i < iters; i++) { + b2.setValue(false); + } + long t3 = System.nanoTime(); + System.out.println((t2 - t1) + " " + (t3 - t2) + " " + ((t2 - t1) <= (t3 - t2))); + } + + @Test + @Ignore + public void changePerf() { + int iters = 10_000; + + BlockingBoolean b1 = new BlockingBoolean(false); + boolean v = true; + long t1 = System.nanoTime(); + for (int i = 0; i < iters; i++) { + b1.set(v); + v = !v; + } + long t2 = System.nanoTime(); + MutableBoolean b2 = new MutableBoolean(false); + for (int i = 0; i < iters; i++) { + b2.setValue(v); + v = !v; + } + long t3 = System.nanoTime(); + System.out.println((t2 - t1) + " " + (t3 - t2) + " " + ((t2 - t1) <= (t3 - t2))); + } + +} diff --git a/framework/src/onos/utils/misc/src/test/java/org/onlab/util/BoundedThreadPoolTest.java b/framework/src/onos/utils/misc/src/test/java/org/onlab/util/BoundedThreadPoolTest.java new file mode 100644 index 00000000..c6132de1 --- /dev/null +++ b/framework/src/onos/utils/misc/src/test/java/org/onlab/util/BoundedThreadPoolTest.java @@ -0,0 +1,227 @@ +/* + * 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.util; + +import com.google.common.collect.Lists; +import org.junit.Test; + +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + +import static org.junit.Assert.*; +import static org.onlab.util.BoundedThreadPool.*; +import static org.onlab.util.Tools.namedThreads; + +/** + * Test of BoundedThreadPool. + */ +public final class BoundedThreadPoolTest { + + @Test + public void simpleJob() { + final Thread myThread = Thread.currentThread(); + final AtomicBoolean sameThread = new AtomicBoolean(true); + final CountDownLatch latch = new CountDownLatch(1); + + BoundedThreadPool exec = newSingleThreadExecutor(namedThreads("test")); + exec.submit(() -> { + sameThread.set(myThread.equals(Thread.currentThread())); + latch.countDown(); + }); + + try { + assertTrue("Job not run", latch.await(100, TimeUnit.MILLISECONDS)); + assertFalse("Runnable used caller thread", sameThread.get()); + } catch (InterruptedException e) { + fail(); + } finally { + exec.shutdown(); + } + + // TODO perhaps move to tearDown + try { + assertTrue(exec.awaitTermination(1, TimeUnit.SECONDS)); + } catch (InterruptedException e) { + fail(); + } + } + + private List<CountDownLatch> fillExecutor(BoundedThreadPool exec) { + int numThreads = exec.getMaximumPoolSize(); + List<CountDownLatch> latches = Lists.newArrayList(); + final CountDownLatch started = new CountDownLatch(numThreads); + List<CountDownLatch> finished = Lists.newArrayList(); + + // seed the executor's threads + for (int i = 0; i < numThreads; i++) { + final CountDownLatch latch = new CountDownLatch(1); + final CountDownLatch fin = new CountDownLatch(1); + latches.add(latch); + finished.add(fin); + exec.submit(() -> { + try { + started.countDown(); + latch.await(); + fin.countDown(); + } catch (InterruptedException e) { + fail(); + } + }); + } + try { + assertTrue(started.await(100, TimeUnit.MILLISECONDS)); + } catch (InterruptedException e) { + fail(); + } + // fill the queue + CountDownLatch startedBlocked = new CountDownLatch(1); + while (exec.getQueue().remainingCapacity() > 0) { + final CountDownLatch latch = new CountDownLatch(1); + latches.add(latch); + exec.submit(() -> { + try { + startedBlocked.countDown(); + latch.await(); + } catch (InterruptedException e) { + fail(); + } + }); + } + + latches.remove(0).countDown(); // release one of the executors + // ... we need to do this because load is recomputed when jobs are taken + // Note: For this to work, 1 / numThreads must be less than the load threshold (0.2) + + // verify that the old job has terminated + try { + assertTrue("Job didn't finish", + finished.remove(0).await(100, TimeUnit.MILLISECONDS)); + } catch (InterruptedException e) { + fail(); + } + + // verify that a previously blocked thread has started + try { + assertTrue(startedBlocked.await(10, TimeUnit.MILLISECONDS)); + } catch (InterruptedException e) { + fail(); + } + + + // add another job to fill the queue + final CountDownLatch latch = new CountDownLatch(1); + latches.add(latch); + exec.submit(() -> { + try { + latch.await(); + } catch (InterruptedException e) { + fail(); + } + }); + assertEquals(exec.getQueue().size(), maxQueueSize); + + return latches; + } + + @Test + public void releaseOneThread() { + maxQueueSize = 10; + BoundedThreadPool exec = newFixedThreadPool(4, namedThreads("test")); + List<CountDownLatch> latches = fillExecutor(exec); + + CountDownLatch myLatch = new CountDownLatch(1); + ExecutorService myExec = Executors.newSingleThreadExecutor(); + Future<Thread> expected = myExec.submit(Thread::currentThread); + + assertEquals(exec.getQueue().size(), maxQueueSize); + long start = System.nanoTime(); + Future<Thread> actual = myExec.submit(() -> { + return exec.submit(() -> { + myLatch.countDown(); + return Thread.currentThread(); + }).get(); + }); + + try { + assertFalse("Thread should still be blocked", + myLatch.await(10, TimeUnit.MILLISECONDS)); + + latches.remove(0).countDown(); // release the first thread + assertFalse("Thread should still be blocked", + myLatch.await(10, TimeUnit.MILLISECONDS)); + latches.remove(0).countDown(); // release the second thread + + assertTrue("Thread should be unblocked", + myLatch.await(10, TimeUnit.MILLISECONDS)); + long delta = System.nanoTime() - start; + double load = exec.getQueue().size() / (double) maxQueueSize; + assertTrue("Load is greater than threshold", load <= 0.8); + assertTrue("Load is less than threshold", load >= 0.6); + assertEquals("Work done on wrong thread", expected.get(), actual.get()); + assertTrue("Took more than one second", delta < Math.pow(10, 9)); + } catch (InterruptedException | ExecutionException e) { + fail(); + } finally { + latches.forEach(CountDownLatch::countDown); + exec.shutdown(); + } + + // TODO perhaps move to tearDown + try { + assertTrue(exec.awaitTermination(1, TimeUnit.SECONDS)); + } catch (InterruptedException e) { + fail(); + } + + } + + @Test + public void highLoadTimeout() { + maxQueueSize = 10; + BoundedThreadPool exec = newFixedThreadPool(2, namedThreads("test")); + List<CountDownLatch> latches = fillExecutor(exec); + + // true if the job is executed and it is done on the test thread + final AtomicBoolean sameThread = new AtomicBoolean(false); + final Thread myThread = Thread.currentThread(); + long start = System.nanoTime(); + exec.submit(() -> { + sameThread.set(myThread.equals(Thread.currentThread())); + }); + + long delta = System.nanoTime() - start; + assertEquals(maxQueueSize, exec.getQueue().size()); + assertTrue("Work done on wrong thread (or didn't happen)", sameThread.get()); + assertTrue("Took less than one second. Actual: " + delta / 1_000_000.0 + "ms", + delta > Math.pow(10, 9)); + assertTrue("Took more than two seconds", delta < 2 * Math.pow(10, 9)); + latches.forEach(CountDownLatch::countDown); + exec.shutdown(); + + // TODO perhaps move to tearDown + try { + assertTrue(exec.awaitTermination(1, TimeUnit.SECONDS)); + } catch (InterruptedException e) { + fail(); + } + } +}
\ No newline at end of file diff --git a/framework/src/onos/utils/misc/src/test/java/org/onlab/util/ByteArraySizeHashPrinterTest.java b/framework/src/onos/utils/misc/src/test/java/org/onlab/util/ByteArraySizeHashPrinterTest.java new file mode 100644 index 00000000..e3a5e945 --- /dev/null +++ b/framework/src/onos/utils/misc/src/test/java/org/onlab/util/ByteArraySizeHashPrinterTest.java @@ -0,0 +1,53 @@ +/* + * Copyright 2014 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.util; + +import static org.junit.Assert.*; + +import java.util.Arrays; + +import org.junit.Test; + +/** + * Test cases for byte[] pretty printer. + */ +public class ByteArraySizeHashPrinterTest { + + /** + * Test method for {@link org.onlab.util.ByteArraySizeHashPrinter#toString()}. + */ + @Test + public void testToStringNull() { + final byte[] none = null; + + assertEquals("byte[]{null}", String.valueOf(ByteArraySizeHashPrinter.of(none))); + assertNull(ByteArraySizeHashPrinter.orNull(none)); + } + + /** + * Test method for {@link org.onlab.util.ByteArraySizeHashPrinter#toString()}. + */ + @Test + public void testToString() { + final byte[] some = new byte[] {2, 5, 0, 1 }; + final String expected = "byte[]{length=" + some.length + ", hash=" + Arrays.hashCode(some) + "}"; + + assertEquals(expected, String.valueOf(ByteArraySizeHashPrinter.of(some))); + assertNotNull(ByteArraySizeHashPrinter.orNull(some)); + } + +} diff --git a/framework/src/onos/utils/misc/src/test/java/org/onlab/util/CounterTest.java b/framework/src/onos/utils/misc/src/test/java/org/onlab/util/CounterTest.java new file mode 100644 index 00000000..d30e1b59 --- /dev/null +++ b/framework/src/onos/utils/misc/src/test/java/org/onlab/util/CounterTest.java @@ -0,0 +1,86 @@ +/* + * Copyright 2014 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.util; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.onlab.junit.TestTools.delay; + +/** + * Tests of the Counter utility. + */ +public class CounterTest { + + @Test + public void basics() { + Counter tt = new Counter(); + assertEquals("incorrect number of bytes", 0L, tt.total()); + assertEquals("incorrect throughput", 0.0, tt.throughput(), 0.0001); + tt.add(1234567890L); + assertEquals("incorrect number of bytes", 1234567890L, tt.total()); + assertTrue("incorrect throughput", 1234567890.0 < tt.throughput()); + delay(1500); + tt.add(1L); + assertEquals("incorrect number of bytes", 1234567891L, tt.total()); + assertTrue("incorrect throughput", 1234567891.0 > tt.throughput()); + tt.reset(); + assertEquals("incorrect number of bytes", 0L, tt.total()); + assertEquals("incorrect throughput", 0.0, tt.throughput(), 0.0001); + } + + @Test + public void freeze() { + Counter tt = new Counter(); + tt.add(123L); + assertEquals("incorrect number of bytes", 123L, tt.total()); + delay(1000); + tt.freeze(); + tt.add(123L); + assertEquals("incorrect number of bytes", 123L, tt.total()); + + double d = tt.duration(); + double t = tt.throughput(); + assertEquals("incorrect duration", d, tt.duration(), 0.0001); + assertEquals("incorrect throughput", t, tt.throughput(), 0.0001); + assertEquals("incorrect number of bytes", 123L, tt.total()); + } + + @Test + public void reset() { + Counter tt = new Counter(); + tt.add(123L); + assertEquals("incorrect number of bytes", 123L, tt.total()); + + double d = tt.duration(); + double t = tt.throughput(); + assertEquals("incorrect duration", d, tt.duration(), 0.0001); + assertEquals("incorrect throughput", t, tt.throughput(), 0.0001); + assertEquals("incorrect number of bytes", 123L, tt.total()); + + tt.reset(); + assertEquals("incorrect throughput", 0.0, tt.throughput(), 0.0001); + assertEquals("incorrect number of bytes", 0, tt.total()); + } + + @Test + public void syntheticTracker() { + Counter tt = new Counter(5000, 1000, 6000); + assertEquals("incorrect duration", 1, tt.duration(), 0.1); + assertEquals("incorrect throughput", 1000, tt.throughput(), 1.0); + } +} diff --git a/framework/src/onos/utils/misc/src/test/java/org/onlab/util/FrequencyTest.java b/framework/src/onos/utils/misc/src/test/java/org/onlab/util/FrequencyTest.java new file mode 100644 index 00000000..727c0f73 --- /dev/null +++ b/framework/src/onos/utils/misc/src/test/java/org/onlab/util/FrequencyTest.java @@ -0,0 +1,107 @@ +/* + * 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.util; + +import com.google.common.testing.EqualsTester; +import org.junit.Test; +import org.onlab.junit.ImmutableClassChecker; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.greaterThan; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.lessThan; + +public class FrequencyTest { + + private final Frequency frequency1 = Frequency.ofMHz(1000); + private final Frequency sameFrequency1 = Frequency.ofMHz(1000); + private final Frequency frequency2 = Frequency.ofGHz(1000); + private final Frequency sameFrequency2 = Frequency.ofGHz(1000); + private final Frequency moreSameFrequency2 = Frequency.ofTHz(1); + private final Frequency frequency3 = Frequency.ofTHz(193.1); + private final Frequency sameFrequency3 = Frequency.ofGHz(193100); + + /** + * Tests immutability of Frequency. + */ + @Test + public void testImmutability() { + ImmutableClassChecker.assertThatClassIsImmutable(Frequency.class); + } + + /** + * Tests equality of Frequency instances. + */ + @Test + public void testEquality() { + new EqualsTester() + .addEqualityGroup(frequency1, sameFrequency1) + .addEqualityGroup(frequency2, sameFrequency2, moreSameFrequency2) + .addEqualityGroup(frequency3, sameFrequency3) + .testEquals(); + } + + /** + * Tests the first object is less than the second object. + */ + @Test + public void testLessThan() { + assertThat(frequency1, is(lessThan(frequency2))); + assertThat(frequency1.isLessThan(frequency2), is(true)); + } + + @Test + public void testGreaterThan() { + assertThat(frequency2, is(greaterThan(frequency1))); + assertThat(frequency2.isGreaterThan(frequency1), is(true)); + } + + /** + * Tests add operation of two Frequencies. + */ + @Test + public void testAdd() { + Frequency low = Frequency.ofMHz(100); + Frequency high = Frequency.ofGHz(1); + Frequency expected = Frequency.ofMHz(1100); + + assertThat(low.add(high), is(expected)); + } + + /** + * Tests subtract operation of two Frequencies. + */ + @Test + public void testSubtract() { + Frequency high = Frequency.ofGHz(1); + Frequency low = Frequency.ofMHz(100); + Frequency expected = Frequency.ofMHz(900); + + assertThat(high.subtract(low), is(expected)); + } + + /** + * Tests multiply operation of Frequency. + */ + @Test + public void testMultiply() { + Frequency frequency = Frequency.ofMHz(1000); + long factor = 5; + Frequency expected = Frequency.ofGHz(5); + + assertThat(frequency.multiply(5), is(expected)); + } +} diff --git a/framework/src/onos/utils/misc/src/test/java/org/onlab/util/GroupedThreadFactoryTest.java b/framework/src/onos/utils/misc/src/test/java/org/onlab/util/GroupedThreadFactoryTest.java new file mode 100644 index 00000000..5be1cda4 --- /dev/null +++ b/framework/src/onos/utils/misc/src/test/java/org/onlab/util/GroupedThreadFactoryTest.java @@ -0,0 +1,53 @@ +/* + * 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.util; + +import org.junit.Test; +import org.onlab.junit.TestTools; + +import static org.junit.Assert.*; + +/** + * Tests of the group thread factory. + */ +public class GroupedThreadFactoryTest { + + @Test + public void basics() { + GroupedThreadFactory a = GroupedThreadFactory.groupedThreadFactory("foo"); + GroupedThreadFactory b = GroupedThreadFactory.groupedThreadFactory("foo"); + assertSame("factories should be same", a, b); + + assertTrue("wrong toString", a.toString().contains("foo")); + Thread t = a.newThread(() -> TestTools.print("yo")); + assertSame("wrong group", a.threadGroup(), t.getThreadGroup()); + } + + @Test + public void hierarchical() { + GroupedThreadFactory a = GroupedThreadFactory.groupedThreadFactory("foo/bar"); + GroupedThreadFactory b = GroupedThreadFactory.groupedThreadFactory("foo/goo"); + GroupedThreadFactory p = GroupedThreadFactory.groupedThreadFactory("foo"); + + assertSame("groups should be same", p.threadGroup(), a.threadGroup().getParent()); + assertSame("groups should be same", p.threadGroup(), b.threadGroup().getParent()); + + assertEquals("wrong name", "foo/bar", a.threadGroup().getName()); + assertEquals("wrong name", "foo/goo", b.threadGroup().getName()); + assertEquals("wrong name", "foo", p.threadGroup().getName()); + } + +}
\ No newline at end of file diff --git a/framework/src/onos/utils/misc/src/test/java/org/onlab/util/HexStringTest.java b/framework/src/onos/utils/misc/src/test/java/org/onlab/util/HexStringTest.java new file mode 100644 index 00000000..27652123 --- /dev/null +++ b/framework/src/onos/utils/misc/src/test/java/org/onlab/util/HexStringTest.java @@ -0,0 +1,85 @@ +/* + * Copyright 2014 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.util; + +import org.junit.Test; + +import com.esotericsoftware.minlog.Log; + +import junit.framework.TestCase; + +/** + * Test of the Hexstring. + * + */ + +public class HexStringTest extends TestCase { + + @Test + public void testMarshalling() throws Exception { + String dpidStr = "00:00:00:23:20:2d:16:71"; + long dpid = HexString.toLong(dpidStr); + String testStr = HexString.toHexString(dpid); + TestCase.assertEquals(dpidStr, testStr); + } + + @Test + public void testToLong() { + String dpidStr = "3e:1f:01:fc:72:8c:63:31"; + long valid = 0x3e1f01fc728c6331L; + long testLong = HexString.toLong(dpidStr); + TestCase.assertEquals(valid, testLong); + } + + @Test + public void testToLongMSB() { + String dpidStr = "ca:7c:5e:d1:64:7a:95:9b"; + long valid = -3856102927509056101L; + long testLong = HexString.toLong(dpidStr); + TestCase.assertEquals(valid, testLong); + } + + @Test + public void testToLongError() { + String dpidStr = "09:08:07:06:05:04:03:02:01"; + try { + HexString.toLong(dpidStr); + fail("HexString.toLong() should have thrown a NumberFormatException"); + } catch (NumberFormatException expected) { + Log.info("HexString.toLong() have thrown a NumberFormatException"); + } + } + + @Test + public void testToStringBytes() { + byte[] dpid = {0, 0, 0, 0, 0, 0, 0, -1 }; + String valid = "00:00:00:00:00:00:00:ff"; + String testString = HexString.toHexString(dpid); + TestCase.assertEquals(valid, testString); + } + + @Test + public void testFromHexStringError() { + String invalidStr = "00:00:00:00:00:00:ffff"; + try { + HexString.fromHexString(invalidStr); + fail("HexString.fromHexString() should have thrown a NumberFormatException"); + } catch (NumberFormatException expected) { + Log.info("HexString.toLong() have thrown a NumberFormatException"); + } + } +} + diff --git a/framework/src/onos/utils/misc/src/test/java/org/onlab/util/PositionalParameterStringFormatterTest.java b/framework/src/onos/utils/misc/src/test/java/org/onlab/util/PositionalParameterStringFormatterTest.java new file mode 100644 index 00000000..9758511f --- /dev/null +++ b/framework/src/onos/utils/misc/src/test/java/org/onlab/util/PositionalParameterStringFormatterTest.java @@ -0,0 +1,61 @@ +/* + * 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.util; + +import static org.junit.Assert.*; +import static org.onlab.util.PositionalParameterStringFormatter.format; + +import org.junit.Test; + +public class PositionalParameterStringFormatterTest { + + @Test + public void testFormat0() { + String fmt = "Some string 1 2 3"; + assertEquals("Some string 1 2 3", format(fmt)); + } + + @Test + public void testFormat1() { + String fmt = "Some string {} 2 3"; + assertEquals("Some string 1 2 3", format(fmt, 1)); + } + + @Test + public void testFormat2() { + String fmt = "Some string {} 2 {}"; + assertEquals("Some string 1 2 3", format(fmt, 1, "3")); + } + + @Test + public void testFormatNull() { + String fmt = "Some string {} 2 {}"; + assertEquals("Some string 1 2 null", format(fmt, 1, null)); + } + + @Test + public void testFormatExtraBracket() { + String fmt = "Some string {} 2 {}"; + assertEquals("Some string 1 2 {}", format(fmt, 1)); + } + + @Test + public void testFormatMissingBracket() { + String fmt = "Some string 1 2 3"; + assertEquals("Some string 1 2 3", format(fmt, 7)); + } +} diff --git a/framework/src/onos/utils/misc/src/test/java/org/onlab/util/RetryingFunctionTest.java b/framework/src/onos/utils/misc/src/test/java/org/onlab/util/RetryingFunctionTest.java new file mode 100644 index 00000000..4b08d2fc --- /dev/null +++ b/framework/src/onos/utils/misc/src/test/java/org/onlab/util/RetryingFunctionTest.java @@ -0,0 +1,94 @@ +/* + * 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.util; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +/** + * Unit tests for RetryingFunction. + * + */ +public class RetryingFunctionTest { + + private int round; + + @Before + public void setUp() { + round = 1; + } + + @After + public void tearDown() { + round = 0; + } + + @Test(expected = RetryableException.class) + public void testNoRetries() { + new RetryingFunction<>(this::succeedAfterOneFailure, RetryableException.class, 0, 10).apply(null); + } + + @Test + public void testSuccessAfterOneRetry() { + new RetryingFunction<>(this::succeedAfterOneFailure, RetryableException.class, 1, 10).apply(null); + } + + @Test(expected = RetryableException.class) + public void testFailureAfterOneRetry() { + new RetryingFunction<>(this::succeedAfterTwoFailures, RetryableException.class, 1, 10).apply(null); + } + + @Test + public void testFailureAfterTwoRetries() { + new RetryingFunction<>(this::succeedAfterTwoFailures, RetryableException.class, 2, 10).apply(null); + } + + @Test(expected = NonRetryableException.class) + public void testFailureWithNonRetryableFailure() { + new RetryingFunction<>(this::failCompletely, RetryableException.class, 2, 10).apply(null); + } + + private String succeedAfterOneFailure(String input) { + if (round++ <= 1) { + throw new RetryableException(); + } else { + return "pass"; + } + } + + private String succeedAfterTwoFailures(String input) { + if (round++ <= 2) { + throw new RetryableException(); + } else { + return "pass"; + } + } + + private String failCompletely(String input) { + if (round++ <= 1) { + throw new NonRetryableException(); + } else { + return "pass"; + } + } + + private class RetryableException extends RuntimeException { + } + + private class NonRetryableException extends RuntimeException { + } +} diff --git a/framework/src/onos/utils/misc/src/test/java/org/onlab/util/SharedExecutorsTest.java b/framework/src/onos/utils/misc/src/test/java/org/onlab/util/SharedExecutorsTest.java new file mode 100644 index 00000000..1730ca1f --- /dev/null +++ b/framework/src/onos/utils/misc/src/test/java/org/onlab/util/SharedExecutorsTest.java @@ -0,0 +1,54 @@ +/* + * 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.util; + +import org.junit.Test; + +import java.util.concurrent.ExecutorService; + +import static org.junit.Assert.*; + +/** + * Tests of the SharedExecutors Test. + */ +public class SharedExecutorsTest { + + @Test + public void singleThread() { + ExecutorService a = SharedExecutors.getSingleThreadExecutor(); + assertNotNull("ExecutorService must not be null", a); + ExecutorService b = SharedExecutors.getSingleThreadExecutor(); + assertSame("factories should be same", a, b); + + } + + @Test + public void poolThread() { + ExecutorService a = SharedExecutors.getPoolThreadExecutor(); + assertNotNull("ExecutorService must not be null", a); + ExecutorService b = SharedExecutors.getPoolThreadExecutor(); + assertSame("factories should be same", a, b); + + } + + @Test + public void timer() { + java.util.Timer a = SharedExecutors.getTimer(); + assertNotNull("Timer must not be null", a); + java.util.Timer b = SharedExecutors.getTimer(); + assertSame("factories should be same", a, b); + } +} diff --git a/framework/src/onos/utils/misc/src/test/java/org/onlab/util/SlidingWindowCounterTest.java b/framework/src/onos/utils/misc/src/test/java/org/onlab/util/SlidingWindowCounterTest.java new file mode 100644 index 00000000..c15cc8a6 --- /dev/null +++ b/framework/src/onos/utils/misc/src/test/java/org/onlab/util/SlidingWindowCounterTest.java @@ -0,0 +1,105 @@ +/* + * 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.util; + +import org.junit.After; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; + +import static junit.framework.TestCase.fail; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +/** + * Unit tests for the sliding window counter. + */ + +@Ignore("Disable these for now because of intermittent load related failures on Jenkins runs.") +public class SlidingWindowCounterTest { + + private SlidingWindowCounter counter; + + @Before + public void setUp() { + counter = new SlidingWindowCounter(2); + } + + @After + public void tearDown() { + counter.destroy(); + } + + @Test + public void testIncrementCount() { + assertEquals(0, counter.get(1)); + assertEquals(0, counter.get(2)); + counter.incrementCount(); + assertEquals(1, counter.get(1)); + assertEquals(1, counter.get(2)); + counter.incrementCount(2); + assertEquals(3, counter.get(2)); + } + + @Test + public void testSlide() { + counter.incrementCount(); + counter.advanceHead(); + assertEquals(0, counter.get(1)); + assertEquals(1, counter.get(2)); + counter.incrementCount(2); + assertEquals(2, counter.get(1)); + assertEquals(3, counter.get(2)); + } + + @Test + public void testWrap() { + counter.incrementCount(); + counter.advanceHead(); + counter.incrementCount(2); + counter.advanceHead(); + assertEquals(0, counter.get(1)); + assertEquals(2, counter.get(2)); + counter.advanceHead(); + assertEquals(0, counter.get(1)); + assertEquals(0, counter.get(2)); + + } + + @Test + public void testCornerCases() { + try { + counter.get(3); + fail("Exception should have been thrown"); + } catch (IllegalArgumentException e) { + assertTrue(true); + } + + try { + new SlidingWindowCounter(0); + fail("Exception should have been thrown"); + } catch (IllegalArgumentException e) { + assertTrue(true); + } + + try { + new SlidingWindowCounter(-1); + fail("Exception should have been thrown"); + } catch (IllegalArgumentException e) { + assertTrue(true); + } + } +} diff --git a/framework/src/onos/utils/misc/src/test/java/org/onlab/util/ToolsTest.java b/framework/src/onos/utils/misc/src/test/java/org/onlab/util/ToolsTest.java new file mode 100644 index 00000000..56f0f957 --- /dev/null +++ b/framework/src/onos/utils/misc/src/test/java/org/onlab/util/ToolsTest.java @@ -0,0 +1,76 @@ +/* + * Copyright 2014-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.util; + +import org.junit.Test; +import org.onlab.junit.TestTools; + +import java.util.concurrent.ThreadFactory; + +import static org.junit.Assert.*; +import static org.onlab.junit.TestTools.assertAfter; + +/** + * Test of the miscellaneous tools. + */ +public class ToolsTest { + + @Test + public void fromHex() throws Exception { + assertEquals(15, Tools.fromHex("0f")); + assertEquals(16, Tools.fromHex("10")); + assertEquals(65535, Tools.fromHex("ffff")); + assertEquals(4096, Tools.fromHex("1000")); + assertEquals(0xffffffffffffffffL, Tools.fromHex("ffffffffffffffff")); + } + + @Test + public void toHex() throws Exception { + assertEquals("0f", Tools.toHex(15, 2)); + assertEquals("ffff", Tools.toHex(65535, 4)); + assertEquals("1000", Tools.toHex(4096, 4)); + assertEquals("000000000000000f", Tools.toHex(15)); + assertEquals("ffffffffffffffff", Tools.toHex(0xffffffffffffffffL)); + + } + + @Test + public void namedThreads() { + ThreadFactory f = Tools.namedThreads("foo-%d"); + Thread t = f.newThread(() -> TestTools.print("yo")); + assertTrue("wrong pattern", t.getName().startsWith("foo-")); + } + + @Test + public void groupedThreads() { + ThreadFactory f = Tools.groupedThreads("foo/bar-me", "foo-%d"); + Thread t = f.newThread(() -> TestTools.print("yo")); + assertTrue("wrong pattern", t.getName().startsWith("foo-bar-me-foo-")); + assertTrue("wrong group", t.getThreadGroup().getName().equals("foo/bar-me")); + } + + @Test + public void exceptionHandler() throws InterruptedException { + ThreadFactory f = Tools.namedThreads("foo"); + Thread t = f.newThread(() -> { + throw new IllegalStateException("BOOM!"); + }); + assertNotNull("thread should have exception handler", t.getUncaughtExceptionHandler()); + t.start(); + assertAfter(100, () -> assertEquals("incorrect thread state", Thread.State.TERMINATED, t.getState())); + } + +} |