From: mart & charlie Date: Fri, 22 Jan 2016 18:55:28 +0000 (-0500) Subject: working setup for a4 X-Git-Url: https://git.martlubbers.net/?a=commitdiff_plain;h=ed3a2ed6d6da6e6ca85921b9988340da5af7365a;p=tt2015.git working setup for a4 --- diff --git a/a4/tcp/README.txt b/a4/tcp/README.txt new file mode 100644 index 0000000..5e899a3 --- /dev/null +++ b/a4/tcp/README.txt @@ -0,0 +1,13 @@ +Start een sessie +$ sudo bash setup.sh +$ bash bouw.sh +$ bash run.sh + +Start een nieuwe sessie +$ cd ~/tt2015/a4/tcp/server +$ make +$ sudo java Main + +Start een nieuwe sessie +$ cd ~/tt2015/a4/tcp/adapter +$ sudo python listener.py diff --git a/a4/tcp/adapter/.listener.py.swp b/a4/tcp/adapter/.listener.py.swp new file mode 100644 index 0000000..2e6fddc Binary files /dev/null and b/a4/tcp/adapter/.listener.py.swp differ diff --git a/a4/tcp/adapter/adapter.py b/a4/tcp/adapter/adapter.py new file mode 100644 index 0000000..e75f6f4 --- /dev/null +++ b/a4/tcp/adapter/adapter.py @@ -0,0 +1,76 @@ +#!/usr/bin/python +from sender import Sender +import sys +import socket + +PORT = 8888 + +if __name__ == "__main__": + serverPort = 10000 + if len(sys.argv) > 1: + serverPort = int(sys.argv[1]) + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + s.bind(('', PORT)) + s.listen(1) + print 'Listening on port {}'.format(PORT) + conn, addr = s.accept() + print 'Connected by {}'.format(addr) + sender = None + seqnr = 100 + sender = Sender(serverIP="127.0.0.1", networkInterface="lo", isLocal=True, serverPort=serverPort, waitTime=1, isVerbose=0) + response = '' + while True: + data = conn.recv(1024) + if not data: + break + data = data.strip() + print 'input: ' + data + if data == 'Listen?': + sender = Sender(serverIP="127.0.0.1", networkInterface="lo", isLocal=True, serverPort=serverPort, waitTime=1, isVerbose=0) + response = '' + elif data == 'SYN?': + seqnr = 100 + print 'S: {}'.format(seqnr) + response = sender.sendInput('S', seqnr, seqnr) + elif data == 'ACK?': + seqnr += 1 + print 'A: {}'.format(seqnr) + response = sender.sendInput('A', seqnr, sender.lastSeqReceived + 1) + elif data == 'FINACK?': + seqnr += 1 + print 'FA: {}'.format(seqnr) + response = sender.sendInput("FA", seqnr, sender.lastSeqReceived + 1) + elif data == 'FIN?': + seqnr += 1 + print 'F: {}'.format(seqnr) + response = sender.sendInput("F", seqnr, sender.lastSeqReceived + 1) + elif data == 'RST?': + seqnr += 1 + print 'RP: {}'.format(seqnr) + response = sender.sendInput("RP", seqnr, 0) + sender.sendReset() + elif data == 'DAT?': + print 'DAT: {} sending: a'.format(seqnr) + response = sender.sendInput('A', seqnr, sender.lastSeqReceived + 1) + response = sender.sendInput('PA', seqnr, sender.lastSeqReceived + 1, data='a\n') + seqnr += 2 + if response: + print 'response: {}'.format(response) + if response == 'Timeout': + print 'data: {} - TIMEOUT!'.format(data) + conn.send('TIMEOUT!\n') + elif response[0] == 'A' or response[0] == 'PA': + print 'data: {} - ACK!'.format(data) + conn.send('ACK!\n') + elif response[0] == 'R': + print 'data: {} - RST!'.format(data) + conn.send('RST!\n') + elif response[0] == 'SA': + print 'data: {} - SYNACK!'.format(data) + conn.send('SYNACK!\n') + else: + print 'data: {}'.format(data) + + print 'closed' + conn.close() + exit() diff --git a/a4/tcp/adapter/listener.py b/a4/tcp/adapter/listener.py new file mode 100644 index 0000000..94896c9 --- /dev/null +++ b/a4/tcp/adapter/listener.py @@ -0,0 +1,92 @@ +#!/usr/bin/python +from sender import Sender +import sys +import socket + +PORT = 8888 + +if __name__ == "__main__": + serverPort = 10000 + if len(sys.argv) > 1: + serverPort = int(sys.argv[1]) + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + s.bind(('', PORT)) + s.listen(1) + print 'Listening on port {}'.format(PORT) + conn, addr = s.accept() + print 'Connected by {}'.format(addr) + print 'Initiating connection to EchoServer at port {}...'.format(serverPort) + sender = Sender(serverIP="127.0.0.1", networkInterface="lo", isLocal=True, serverPort=serverPort, waitTime=1, isVerbose=0) + seqnr = 0 + response = '' + data = '' + while True: + print 'waiting for data' + done = False + while not done: + d = conn.recv(1) + if not d: + break + if d != '\n': + data += d + if d == '\n': + done = True + print 'received: {}'.format(data) + if data == 'RES': + print 'resetting the SUT...' + sender = Sender(serverIP="127.0.0.1", networkInterface="lo", isLocal=True, serverPort=serverPort, waitTime=1, isVerbose=0) + data = '' + continue + elif data == 'SYN': + seqnr = 100 + print 'S: {}'.format(seqnr) + response = sender.sendInput('S', seqnr, seqnr) + elif data == 'ACK': + response = 'Timeout' + if sender.lastSeqReceived != None: + seqnr += 1 + print 'A: {}'.format(seqnr) + response = sender.sendInput('A', seqnr, sender.lastSeqReceived + 1) + elif data == 'DAT': + response = 'Timeout' + if sender.lastSeqReceived != None: + seqnr += 1 + print 'DAT: {} sending: a'.format(seqnr) + response = sender.sendInput('PA', seqnr, sender.lastSeqReceived + 1, 'a') + elif data == 'RST': + response = 'Timeout' + if sender.lastSeqReceived != None: + seqnr += 1 + print 'RP: {}'.format(seqnr) + response = sender.sendInput("RP", seqnr, 0) + sender.sendReset() + elif data == 'FIN': + response = 'Timeout' + if sender.lastSeqReceived != None: + seqnr += 1 + print 'F: {}'.format(seqnr) + response = sender.sendInput("FA", seqnr, sender.lastSeqReceived + 1) + else: + print 'INVALID INPUT!: {}'.format(repr(data)) + break + + if response == 'Timeout': + data = 'TO' + elif response[0] == 'A': + data = 'ACK' + elif response[0] == 'PA': + data = 'DATA' + elif response[0] == 'R': + data = 'RST' + elif response[0] == 'SA': + data = 'SYN-ACK' + else: + data = 'ERR' + + print 'RESPONSE: '+data + conn.send('{}\n'.format(data)) + print 'sent...' + data = '' + print 'closed' + conn.close() + exit() diff --git a/a4/tcp/adapter/sender.py b/a4/tcp/adapter/sender.py new file mode 100644 index 0000000..f80bd4b --- /dev/null +++ b/a4/tcp/adapter/sender.py @@ -0,0 +1,150 @@ +from scapy.all import * + +verbose = 0 + +def vb_print(msg): + global verbose + if verbose == 1: + print msg + +# the sender sends packets with configurable parameters to a server and retrieves responses +class Sender: + # information on the SUT + def __init__(self, serverIP, serverPort=8000, + networkInterface="lo", isLocal=True, senderPortMinimum=20000, + senderPortMaximum=40000, portNumberFile="sn.txt", + isVerbose=0, waitTime=1): + + + # file where the last sender port used is stored + self.portNumberFile = portNumberFile; + + # when choosing a fresh port, a new port is chosen + # within boundaries defined by the parameters below + self.senderPortMinimum = senderPortMinimum + self.senderPortMaximum = senderPortMaximum + + # data on sender and server needed to send packets + self.serverIP = serverIP + self.serverPort = serverPort + self.networkInterface = networkInterface + self.senderPort = self.getNextPort() + self.isLocal = isLocal + + # time to wait for a response from the server before concluding a timeout + self.waitTime = waitTime + + # verbose or not + self.isVerbose = isVerbose + + # variables added so you can easily test the last system response + self.lastSeqReceived = None + self.lastAckReceived = None + self.isTimeout = None + self.lastFlagsReceived = None + self.lastDataReceived = None + + + # chooses a new port to send packets from + def refreshNetworkPort(self): + vb_print("previous local port: " + str(self.senderPort)) + self.senderPort = self.getNextPort() + vb_print("next local port: " + str(self.senderPort) + "\n") + return self.senderPort + + # gets a new port number, an increment of the old within the specified limits. Uses a file. + def getNextPort(self): + f = open(self.portNumberFile, "a+") + f.seek(0) + line = f.readline() + if line == '' or int(line) < self.senderPortMinimum: + networkPort = self.senderPortMinimum + else: + networkPort = (int(line) + 1) % self.senderPortMaximum + f.closed + f = open(self.portNumberFile, "w") + f.write(str(networkPort)) + f.closed + return networkPort + + # send a packet onto the network with the given parameters, and return the response packet + # in case of a timeout, returns None, otherwise, returns the tuple (flags, seqNo, ackNo) + def sendPacket(self, flagsSet, seqNr, ackNr, data = None): + packet = self.createPacket(flagsSet, seqNr, ackNr, data) + # consider adding the parameter: iface="ethx" if you don't receive a response. Also consider increasing the wait time + scapyResponse = sr1(packet, timeout=self.waitTime, verbose=self.isVerbose) + if scapyResponse is not None: + # scapyResponse.show() + # ^^ in case you want to show the packet content + # here is what you store from every packet response + if Raw not in scapyResponse: + response = (self.intToFlags(scapyResponse[TCP].flags), scapyResponse[TCP].seq, scapyResponse[TCP].ack, None) + else: + response = (self.intToFlags(scapyResponse[TCP].flags), scapyResponse[TCP].seq, scapyResponse[TCP].ack, scapyResponse[Raw].load) + else: + response = "Timeout" + return response + + # function that creates packet from data strings/integers + # data is used for attaching data to the packet + def createPacket(self, tcpFlagsSet, seqNr, ackNr, data=None): + vb_print("" + tcpFlagsSet + " " + str(seqNr) + " " + str(ackNr)) + pIP = IP(dst=self.serverIP, flags="DF") + pTCP = TCP(sport=self.senderPort, + dport=self.serverPort, + seq=seqNr, + ack=ackNr, + flags=tcpFlagsSet) + if data is None: + p = pIP / pTCP + else: + p = pIP / pTCP / Raw(load=data) + return p + + # check whether there is a 1 at the given bit-position of the integer + def checkForFlag(self, x, flagPosition): + if x & 2 ** flagPosition == 0: + return False + else: + return True + + # the flags-parameter of a network packets is returned as an int, this function converts + # it to a string (such as "FA" if the Fin-flag and Ack-flag have been set) + def intToFlags(self, x): + result = "" + if self.checkForFlag(x, 0): + result = result + "F" + if self.checkForFlag(x, 1): + result = result + "S" + if self.checkForFlag(x, 2): + result = result + "R" + if self.checkForFlag(x, 3): + result = result + "P" + if self.checkForFlag(x, 4): + result = result + "A" + return result + + # sends input over the network to the server + def sendInput(self, input1, seqNr, ackNr, data = None): + conf.sniff_promisc = False + conf.iface = self.networkInterface + if self.isLocal == True: + conf.L3socket = L3RawSocket # if the connection is local/localhost, use l3 raw sockets + vb_print("sending: "+ str((input1, seqNr, ackNr, data))) + response = self.sendPacket(input1, seqNr, ackNr, data) + + # updating sender state variables + if response != "Timeout": + (self.lastFlagsReceived, self.lastSeqReceived, self.lastAckReceived, self.lastDataReceived) = response + self.isTimeout = False + else: + self.isTimeout = True + + # printing response + vb_print("received: "+ str(response)) + return response + + # resets the connection by changing the port number. On some OSes (Win 8) upon hitting a certain number of + # connections opened on a port, packets are sent to close down connections. + def sendReset(self): + self.refreshNetworkPort() diff --git a/a4/tcp/bouw.sh b/a4/tcp/bouw.sh new file mode 100755 index 0000000..c80234a --- /dev/null +++ b/a4/tcp/bouw.sh @@ -0,0 +1,2 @@ +#!/bin/bash -x +javac -d . -sourcepath tester/ -cp ":lib/automata-parent.jar:lib/learnlib-parent.jar" tester/learner/*.java diff --git a/a4/tcp/lib/automata-parent.jar b/a4/tcp/lib/automata-parent.jar new file mode 100644 index 0000000..ed9f582 Binary files /dev/null and b/a4/tcp/lib/automata-parent.jar differ diff --git a/a4/tcp/lib/learnlib-parent.jar b/a4/tcp/lib/learnlib-parent.jar new file mode 100644 index 0000000..f4c9ea8 Binary files /dev/null and b/a4/tcp/lib/learnlib-parent.jar differ diff --git a/a4/tcp/run.sh b/a4/tcp/run.sh new file mode 100755 index 0000000..d3d70ef --- /dev/null +++ b/a4/tcp/run.sh @@ -0,0 +1,2 @@ +#!/bin/bash -x +java -cp ":lib/automata-parent.jar:lib/learnlib-parent.jar" learner.Main diff --git a/a4/tcp/schoonop.sh b/a4/tcp/schoonop.sh new file mode 100755 index 0000000..0b3a81d --- /dev/null +++ b/a4/tcp/schoonop.sh @@ -0,0 +1,2 @@ +#!/bin/bash -x +rm -r learner *.{pdf,dot} diff --git a/a4/tcp/server/DefaultHandler.java b/a4/tcp/server/DefaultHandler.java new file mode 100644 index 0000000..c90ff57 --- /dev/null +++ b/a4/tcp/server/DefaultHandler.java @@ -0,0 +1,35 @@ +import java.net.Socket; +import java.net.SocketException; + +/** + * Default connection handler. Very basic, does not read or send anything. + */ +public class DefaultHandler implements Runnable { + private Socket socket; + + public DefaultHandler(Socket socket) { + this.socket = socket; + try { + socket.setTcpNoDelay(false); + } catch (SocketException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + new Thread(this).start(); + } + + public void run() { + { + // here you can customize operations you want to test though it's not necessary + System.out.println("new socket opening on " + socket.getLocalPort()); + while (!socket.isOutputShutdown()) { + try { + Thread.sleep(100); + } catch (InterruptedException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + } + } +} \ No newline at end of file diff --git a/a4/tcp/server/EchoHandler.java b/a4/tcp/server/EchoHandler.java new file mode 100644 index 0000000..c60a9f0 --- /dev/null +++ b/a4/tcp/server/EchoHandler.java @@ -0,0 +1,46 @@ +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.net.Socket; +import java.net.SocketException; + +/** + * Connection echo handler. Everything it receives is sent back. + */ +public class EchoHandler implements Runnable { + private Socket socket; + + public EchoHandler(Socket socket) { + this.socket = socket; + try { + socket.setTcpNoDelay(false); + } catch (SocketException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + new Thread(this).start(); + } + + public void run() { + { + try { + System.out.println("new socket opening on " + + socket.getLocalPort()); + InputStreamReader in = new InputStreamReader( + socket.getInputStream()); + OutputStreamWriter out = new OutputStreamWriter(socket.getOutputStream()); + int s; + while(((s=in.read()) != -1)) { + out.append((char)s); + out.flush(); + System.out.print((char)s); + } + System.out.println(); + System.out.println("Closing handler"); + } catch (Exception e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + } + } +} diff --git a/a4/tcp/server/Main.java b/a4/tcp/server/Main.java new file mode 100644 index 0000000..ee9bc59 --- /dev/null +++ b/a4/tcp/server/Main.java @@ -0,0 +1,40 @@ +import java.net.InetAddress; + +// Run the TCPServer on the the port testPort +public class Main { + private static final int DEFAULT_PORT = 10000; + private static final String DEFAULT_ADDRESS = "127.0.0.1"; + + /** + * Run the program with arguments to set a custom port and address, see the comments in the code + * @param args + * @throws Exception + */ + public static void main(String[] args) throws Exception { + System.setProperty("java.net.preferIPv4Stack", "true"); // force ipv4 + int port; + String address; + if (args.length == 0) { + // no arguments: default port and address + port = DEFAULT_PORT; + address = DEFAULT_ADDRESS; + } else if (args.length == 1) { + // one argument for port, default address + port = Integer.valueOf(args[0]); + address = DEFAULT_ADDRESS; + } else if (args.length == 2) { + // two arguments for port and address + // for example, call it like 'java Main 10000 127.0.0.1' + port = Integer.valueOf(args[0]); + address = args[1]; + } else { + return; + } + TCPServer server = new TCPServer(port, InetAddress.getByName(address)); + + // comment this for the default handler, otherwise the echo server is used + server.setHandlerType("echo"); + + server.handleConnections(); + } +} diff --git a/a4/tcp/server/Makefile b/a4/tcp/server/Makefile new file mode 100644 index 0000000..379202a --- /dev/null +++ b/a4/tcp/server/Makefile @@ -0,0 +1,7 @@ +all: Main.class + +%.class: %.java + javac $< + +clean: + $(RM) *.class diff --git a/a4/tcp/server/NetHelper.java b/a4/tcp/server/NetHelper.java new file mode 100644 index 0000000..abbca1b --- /dev/null +++ b/a4/tcp/server/NetHelper.java @@ -0,0 +1,94 @@ +import static java.lang.System.out; + +import java.net.InetAddress; +import java.net.NetworkInterface; +import java.net.SocketException; +import java.util.Collections; +import java.util.Enumeration; + +// this utility displays information on addresses and ports, you don't need to use it +public class NetHelper +{ + public static void main(String args[]) throws SocketException { + Enumeration nets = NetworkInterface.getNetworkInterfaces(); + + for (NetworkInterface netIf : Collections.list(nets)) { + out.printf("Display name: %s\n", netIf.getDisplayName()); + out.printf("Name: %s\n", netIf.getName()); + displayInterfaceInformation(netIf); + displaySubInterfaces(netIf); + out.printf("\n"); + } + } + + public static NetworkInterface getNetworkInterface(String netName) { + NetworkInterface netIntf = null; + try { + for (Enumeration en = NetworkInterface + .getNetworkInterfaces(); en.hasMoreElements();) { + NetworkInterface intf = en.nextElement(); + if (intf.getName().compareToIgnoreCase(netName) == 0) { + netIntf = intf; + break; + } + } + } catch (SocketException e) { + System.out + .println("Socket Exception failed to find internet addresses!"); + e.printStackTrace(); + System.exit(0); + } + return netIntf; + } + + public static InetAddress getFirstNonLocalHost(NetworkInterface netIntf) { + Enumeration hosts = netIntf.getInetAddresses(); + InetAddress address = null; + while (hosts.hasMoreElements()) { + InetAddress host = hosts.nextElement(); + if (!host.isLinkLocalAddress() && !host.isLoopbackAddress()) { + address = host; + break; + } + } + return address; + } + + + + static String getMac(NetworkInterface netint) throws SocketException { + byte[] mac = netint.getHardwareAddress(); + String rez = "null"; + if(mac != null) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < mac.length; i++) { + sb.append(String.format("%02X%s", mac[i], (i < mac.length - 1) ? "-" : "")); + } + rez = sb.toString(); + } + return rez; + //System.out.println(sb.toString()); + } + + static void displayInterfaceInformation(NetworkInterface netint) throws SocketException { + out.printf("Display name: %s\n", netint.getDisplayName()); + out.printf("Name: %s\n", netint.getName()); + out.printf("MAC: %s\n", getMac(netint)); + Enumeration inetAddresses = netint.getInetAddresses(); + for (InetAddress inetAddress : Collections.list(inetAddresses)) { + out.printf("InetAddress: %s\n", inetAddress); + out.println("LOCAL:" + Boolean.toString(inetAddress.isLoopbackAddress() || inetAddress.isLinkLocalAddress())); + } + out.printf("\n"); + } + + static void displaySubInterfaces(NetworkInterface netIf) throws SocketException { + Enumeration subIfs = netIf.getSubInterfaces(); + + for (NetworkInterface subIf : Collections.list(subIfs)) { + out.printf("\tSub Interface Display name: %s\n", subIf.getDisplayName()); + out.printf("\tSub Interface Name: %s\n", subIf.getName()); + displayInterfaceInformation(subIf); + } + } +} \ No newline at end of file diff --git a/a4/tcp/server/TCPServer.java b/a4/tcp/server/TCPServer.java new file mode 100644 index 0000000..7ecd7f7 --- /dev/null +++ b/a4/tcp/server/TCPServer.java @@ -0,0 +1,67 @@ +import java.io.IOException; +import java.net.InetAddress; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.UnknownHostException; + +/** + * A simple TCP server. Listens for connections, and for each incoming connection, + * it creates a 'handler' (either echo handler or default handler) which controls + * that connection + */ +public class TCPServer implements Runnable { + public ServerSocket server; + public Socket socket; + private String handler; + + public TCPServer(int port, InetAddress address) { + try { + server = new ServerSocket(port, 0, address); + server.setReuseAddress(true); + System.out.println("Listening on:\n" + + server.getInetAddress().toString() + "\nport: " + + server.getLocalPort()); + } catch (IOException e) { + e.printStackTrace(); + } + } + + public TCPServer(int port) throws UnknownHostException { + this(port,InetAddress.getLocalHost()); + } + + public void setHandlerType(String handlerClass) { + this.handler = handlerClass; + } + + private void startHandler(Socket socket) { + System.out.println("Starting handler '"+ ((handler!=null) ? handler : "default") + "'"); + if("echo".equalsIgnoreCase(handler)) { + new EchoHandler(socket); + } else { + new DefaultHandler(socket); + } + } + + public void handleConnections() { + Thread thread = new Thread(this); + thread.start(); + } + + @Override + public void run() { + System.out.println("Waiting for client messages..."); + + // accept all requests for connections + while (true) { + try { + socket = server.accept(); + startHandler(socket); + } catch (IOException e) { + //e.printStackTrace(); + System.err.println("Closed socket"); + break; + } + } + } +} diff --git a/a4/tcp/setup.sh b/a4/tcp/setup.sh new file mode 100644 index 0000000..6006919 --- /dev/null +++ b/a4/tcp/setup.sh @@ -0,0 +1,3 @@ +#!/bin/sh +iptables -A OUTPUT -p tcp --tcp-flags PSH PSH -j ACCEPT +iptables -A OUTPUT -p tcp --tcp-flags RST RST -j DROP diff --git a/a4/tcp/tester/learner/CacheInconsistencyException.java b/a4/tcp/tester/learner/CacheInconsistencyException.java new file mode 100644 index 0000000..e8c1d35 --- /dev/null +++ b/a4/tcp/tester/learner/CacheInconsistencyException.java @@ -0,0 +1,60 @@ +package learner; + +import net.automatalib.words.Word; + +/** + * Contains the full input for which non-determinism was observed, as well as the full new output + * and the (possibly shorter) old output with which it disagrees + * + * @author Ramon Janssen + */ +public class CacheInconsistencyException extends RuntimeException { + private final Word oldOutput, newOutput, input; + + public CacheInconsistencyException(Word input, Word oldOutput, Word newOutput) { + this.input = input; + this.oldOutput = oldOutput; + this.newOutput = newOutput; + } + + public CacheInconsistencyException(String message, Word input, Word oldOutput, Word newOutput) { + super(message); + this.input = input; + this.oldOutput = oldOutput; + this.newOutput = newOutput; + } + + + /** + * The shortest cached output word which does not correspond with the new output + * @return + */ + public Word getOldOutput() { + return this.oldOutput; + } + + /** + * The full new output word + * @return + */ + public Word getNewOutput() { + return this.newOutput; + } + + /** + * The shortest sublist of the input word which still shows non-determinism + * @return + */ + public Word getShortestInconsistentInput() { + int indexOfInconsistency = 0; + while (oldOutput.getSymbol(indexOfInconsistency).equals(newOutput.getSymbol(indexOfInconsistency))) { + indexOfInconsistency ++; + } + return this.input.subWord(0, indexOfInconsistency); + } + + @Override + public String toString() { + return "Non-determinism detected\nfull input:\n" + this.input + "\nfull new output:\n" + this.newOutput + "\nold output:\n" + this.oldOutput; + } +} diff --git a/a4/tcp/tester/learner/ExampleSUL.java b/a4/tcp/tester/learner/ExampleSUL.java new file mode 100644 index 0000000..f06c0c4 --- /dev/null +++ b/a4/tcp/tester/learner/ExampleSUL.java @@ -0,0 +1,90 @@ +package learner; + +import de.learnlib.api.SUL; +import de.learnlib.api.SULException; + +/** + * Example of a three-state system, hard-coded. + * + * @author Ramon Janssen + */ +public class ExampleSUL implements SUL { + private enum State{s0,s1,s2}; + private State currentState; + private static boolean VERBOSE = false; + + @Override + public void pre() { + // add any code here that should be run at the beginning of every 'session', + // i.e. put the system in its initial state + if (VERBOSE) { + System.out.println("Starting SUL"); + } + currentState = State.s0; + } + + @Override + public void post() { + // add any code here that should be run at the end of every 'session' + if (VERBOSE) { + System.out.println("Shutting down SUL"); + } + } + + @Override + public String step(String input) throws SULException { + State previousState = this.currentState; + String output = makeTransition(input); + State nextState = this.currentState; + if (VERBOSE) { + System.out.println(previousState + " --" + input + "/" + output + "-> " + nextState); + } + return output; + } + + /** + * The behaviour of the SUL. It takes one input, and returns an output. It now + * contains a hardcoded state-machine (so the result is easy to check). To learn + * an external program/system, connect this method to the SUL (e.g. via sockets + * or stdin/stdout) and make it perform an actual input, and retrieve an actual + * output. + * @param input + * @return + */ + public String makeTransition(String input) { + switch (currentState) { + case s0: + switch(input) { + case "a": + currentState = State.s1; + return "x"; + case "b": + currentState = State.s2; + return "y"; + case "c": + return "z"; + } + case s1: + switch(input) { + case "a": + return "z"; + case "b": + currentState = State.s2; + return "y"; + case "c": + return "z"; + } + case s2: + switch(input) { + case "a": + return "z"; + case "b": + currentState = State.s0; + return "y"; + case "c": + return "z"; + } + } + throw new SULException(new IllegalArgumentException("Argument '" + input + "' was not handled")); + } +} diff --git a/a4/tcp/tester/learner/Main.java b/a4/tcp/tester/learner/Main.java new file mode 100644 index 0000000..0ac2018 --- /dev/null +++ b/a4/tcp/tester/learner/Main.java @@ -0,0 +1,271 @@ +package learner; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.PrintWriter; +import java.net.InetAddress; +import java.util.Arrays; +import java.util.Calendar; +import java.util.Random; + +import net.automatalib.automata.transout.MealyMachine; +import net.automatalib.commons.dotutil.DOT; +import net.automatalib.graphs.concepts.GraphViewable; +import net.automatalib.util.graphs.dot.GraphDOT; +import net.automatalib.words.Alphabet; +import net.automatalib.words.Word; +import net.automatalib.words.impl.SimpleAlphabet; + +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Lists; + +import de.learnlib.acex.analyzers.AcexAnalyzers; +import de.learnlib.algorithms.kv.mealy.KearnsVaziraniMealy; +import de.learnlib.algorithms.lstargeneric.ce.ObservationTableCEXHandlers; +import de.learnlib.algorithms.lstargeneric.closing.ClosingStrategies; +import de.learnlib.algorithms.lstargeneric.mealy.ExtensibleLStarMealy; +import de.learnlib.algorithms.ttt.mealy.TTTLearnerMealy; +import de.learnlib.api.EquivalenceOracle; +import de.learnlib.api.LearningAlgorithm; +import de.learnlib.api.MembershipOracle.MealyMembershipOracle; +import de.learnlib.api.SUL; +import de.learnlib.eqtests.basic.WMethodEQOracle; +import de.learnlib.eqtests.basic.WpMethodEQOracle; +import de.learnlib.eqtests.basic.mealy.RandomWalkEQOracle; +import de.learnlib.experiments.Experiment.MealyExperiment; +import de.learnlib.oracles.DefaultQuery; +import de.learnlib.oracles.ResetCounterSUL; +import de.learnlib.oracles.SULOracle; +import de.learnlib.oracles.SymbolCounterSUL; +import de.learnlib.statistics.Counter; + +/** + * General learning testing framework. The most important parameters are the input alphabet and the SUL (The + * first two static attributes). Other settings can also be configured. + * + * Based on the learner experiment setup of Joshua Moerman, https://gitlab.science.ru.nl/moerman/Learnlib-Experiments + * + * @author Ramon Janssen + */ +public class Main { + //*****************// + // SUL information // + //*****************// + // Defines the input alphabet, adapt for your socket (you can even use other types than string, if you + // change the generic-values, e.g. make your SUL of type SUL for int-input and float-output + private static final Alphabet inputAlphabet = + new SimpleAlphabet(ImmutableSet.of( + "SYN", "ACK", + "DAT", "RST", "FIN")); + // There are two SULs predefined, an example (see ExampleSul.java) and a socket SUL which connects to the SUL over socket + private static final SULType sulType = SULType.Socket; + public enum SULType { Example, Socket } + // For SULs over socket, the socket address/port can be set here + private static final InetAddress socketIp = InetAddress.getLoopbackAddress(); + private static final int socketPort = 8888; + private static final boolean printNewLineAfterEveryInput = true; // print newlines in the socket connection + private static final String resetCmd = "RES"; // the command to send over socket to reset sut + + //*******************// + // Learning settings // + //*******************// + // file for writing the resulting .dot-file and .pdf-file (extensions are added automatically) + private static final String OUTPUT_FILENAME = "learnedModel"; + // the learning and testing algorithms. LStar is the basic algorithm, TTT performs much faster + // but is a bit more inaccurate and produces more intermediate hypotheses, so test well) + private static final LearningMethod learningAlgorithm = LearningMethod.LStar; + public enum LearningMethod { LStar, RivestSchapire, TTT, KearnsVazirani } + // Random walk is the simplest, but performs badly on large models: the chance of hitting a + // erroneous long trace is very small + private static final TestingMethod testMethod = TestingMethod.RandomWalk; + public enum TestingMethod { RandomWalk, WMethod, WpMethod } + // for random walk, the chance to do a reset after an input and the number of + // inputs to test before accepting a hypothesis + private static final double chanceOfResetting = 0.1; + private static final int numberOfSymbols = 100; + // Simple experiments produce very little feedback, controlled produces feedback after + // every hypotheses and are better suited to adjust by programming + private static final boolean runControlledExperiment = true; + // For controlled experiments only: store every hypotheses as a file. Useful for 'debugging' + // if the learner does not terminate (hint: the TTT-algorithm produces many hypotheses). + private static final boolean saveAllHypotheses = false; + + public static void main(String [] args) throws IOException { + // Load the actual SUL-class, depending on which SUL-type is set at the top of this file + // You can also program an own SUL-class if you extend SUL (or SUL in + // general, with S and T the input and output types - you'll have to change some of the + // code below) + SUL sul; + switch (sulType) { + case Example: + sul = new ExampleSUL(); + break; + case Socket: + sul = new SocketSUL(socketIp, socketPort, printNewLineAfterEveryInput, resetCmd); + break; + default: + throw new RuntimeException("No SUL-type defined"); + } + + // Wrap the SUL in a detector for non-determinism + sul = new NonDeterminismCheckingSUL(sul); + // Wrap the SUL in counters for symbols/resets, so that we can record some statistics + SymbolCounterSUL symbolCounterSul = new SymbolCounterSUL<>("symbol counter", sul); + ResetCounterSUL resetCounterSul = new ResetCounterSUL<>("reset counter", symbolCounterSul); + Counter nrSymbols = symbolCounterSul.getStatisticalData(), nrResets = resetCounterSul.getStatisticalData(); + // we should use the sul only through those wrappers + sul = resetCounterSul; + // Most testing/learning-algorithms want a membership-oracle instead of a SUL directly + MealyMembershipOracle sulOracle = new SULOracle<>(sul); + + // Choosing an equivalence oracle + EquivalenceOracle, String, Word> eqOracle = null; + switch (testMethod){ + // simplest method, but doesn't perform well in practice, especially for large models + case RandomWalk: + eqOracle = new RandomWalkEQOracle<>(chanceOfResetting, numberOfSymbols, true, new Random(123456l), sul); + break; + // Other methods are somewhat smarter than random testing: state coverage, trying to distinguish states, etc. + case WMethod: + eqOracle = new WMethodEQOracle.MealyWMethodEQOracle<>(3, sulOracle); + break; + case WpMethod: + eqOracle = new WpMethodEQOracle.MealyWpMethodEQOracle<>(3, sulOracle); + break; + default: + throw new RuntimeException("No test oracle selected!"); + } + + // Choosing a learner + LearningAlgorithm, String, Word> learner = null; + switch (learningAlgorithm){ + case LStar: + learner = new ExtensibleLStarMealy<>(inputAlphabet, sulOracle, Lists.>newArrayList(), ObservationTableCEXHandlers.CLASSIC_LSTAR, ClosingStrategies.CLOSE_SHORTEST); + break; + case RivestSchapire: + learner = new ExtensibleLStarMealy<>(inputAlphabet, sulOracle, Lists.>newArrayList(), ObservationTableCEXHandlers.RIVEST_SCHAPIRE, ClosingStrategies.CLOSE_SHORTEST); + break; + case TTT: + learner = new TTTLearnerMealy<>(inputAlphabet, sulOracle, AcexAnalyzers.LINEAR_FWD); + break; + case KearnsVazirani: + learner = new KearnsVaziraniMealy<>(inputAlphabet, sulOracle, false, AcexAnalyzers.LINEAR_FWD); + break; + default: + throw new RuntimeException("No learner selected"); + } + + // Running the actual experiments! + if (runControlledExperiment) { + runControlledExperiment(learner, eqOracle, nrSymbols, nrResets, inputAlphabet); + } else { + runSimpleExperiment(learner, eqOracle, inputAlphabet); + } + } + + /** + * Simple example of running a learning experiment + * @param learner Learning algorithm, wrapping the SUL + * @param eqOracle Testing algorithm, wrapping the SUL + * @param alphabet Input alphabet + * @throws IOException if the result cannot be written + */ + public static void runSimpleExperiment( + LearningAlgorithm, String, Word> learner, + EquivalenceOracle, String, Word> eqOracle, + Alphabet alphabet) throws IOException { + MealyExperiment experiment = new MealyExperiment(learner, eqOracle, alphabet); + experiment.run(); + System.out.println("Ran " + experiment.getRounds().getCount() + " rounds"); + produceOutput(OUTPUT_FILENAME, experiment.getFinalHypothesis(), alphabet, true); + } + + /** + * More detailed example of running a learning experiment. Starts learning, and then loops testing, + * and if counterexamples are found, refining again. Also prints some statistics about the experiment + * @param learner learner Learning algorithm, wrapping the SUL + * @param eqOracle Testing algorithm, wrapping the SUL + * @param nrSymbols A counter for the number of symbols that have been sent to the SUL (for statistics) + * @param nrResets A counter for the number of resets that have been sent to the SUL (for statistics) + * @param alphabet Input alphabet + * @throws IOException + */ + public static void runControlledExperiment( + LearningAlgorithm, String, Word> learner, + EquivalenceOracle, String, Word> eqOracle, + Counter nrSymbols, Counter nrResets, + Alphabet alphabet) throws IOException { + + // prepare some counters for printing statistics + int stage = 0; + long lastNrResetsValue = 0, lastNrSymbolsValue = 0; + + // start the actual learning + learner.startLearning(); + + while(true) { + // store hypothesis as file + if(saveAllHypotheses) { + String outputFilename = "hyp." + stage + ".obf.dot"; + PrintWriter output = new PrintWriter(outputFilename); + produceOutput(outputFilename, learner.getHypothesisModel(), alphabet, false); + output.close(); + } + + // Print statistics + System.out.println(stage + ": " + Calendar.getInstance().getTime()); + // Log number of queries/symbols + System.out.println("Hypothesis size: " + learner.getHypothesisModel().size() + " states"); + long roundResets = nrResets.getCount() - lastNrResetsValue, roundSymbols = nrSymbols.getCount() - lastNrSymbolsValue; + System.out.println("learning queries/symbols: " + nrResets.getCount() + "/" + nrSymbols.getCount() + + "(" + roundResets + "/" + roundSymbols + " this learning round)"); + lastNrResetsValue = nrResets.getCount(); + lastNrSymbolsValue = nrSymbols.getCount(); + + // Search for CE + DefaultQuery> ce = eqOracle.findCounterExample(learner.getHypothesisModel(), alphabet); + + // Log number of queries/symbols + roundResets = nrResets.getCount() - lastNrResetsValue; + roundSymbols = nrSymbols.getCount() - lastNrSymbolsValue; + System.out.println("testing queries/symbols: " + nrResets.getCount() + "/" + nrSymbols.getCount() + + "(" + roundResets + "/" + roundSymbols + " this testing round)"); + lastNrResetsValue = nrResets.getCount(); + lastNrSymbolsValue = nrSymbols.getCount(); + + if(ce == null) { + // No counterexample found, stop learning + System.out.println("\nFinished learning!"); + produceOutput(OUTPUT_FILENAME, learner.getHypothesisModel(), alphabet, true); + break; + } else { + // Counterexample found, rinse and repeat + System.out.println(); + stage++; + learner.refineHypothesis(ce); + } + } + } + + /** + * Produces a dot-file and a PDF (if graphviz is installed) + * @param fileName filename without extension - will be used for the .dot and .pdf + * @param model + * @param alphabet + * @param verboseError whether to print an error explaing that you need graphviz + * @throws FileNotFoundException + * @throws IOException + */ + public static void produceOutput(String fileName, MealyMachine model, Alphabet alphabet, boolean verboseError) throws FileNotFoundException, IOException { + GraphDOT.write(model, alphabet, new PrintWriter(OUTPUT_FILENAME + ".dot")); + try { + DOT.runDOT(new File(OUTPUT_FILENAME + ".dot"), "pdf", new File(OUTPUT_FILENAME + ".pdf")); + } catch (Exception e) { + if (verboseError) { + System.err.println("Warning: Install graphviz to convert dot-files to PDF"); + System.err.println(e.getMessage()); + } + } + } +} diff --git a/a4/tcp/tester/learner/NonDeterminismCheckingSUL.java b/a4/tcp/tester/learner/NonDeterminismCheckingSUL.java new file mode 100644 index 0000000..02bbfa0 --- /dev/null +++ b/a4/tcp/tester/learner/NonDeterminismCheckingSUL.java @@ -0,0 +1,48 @@ +package learner; + +import java.util.ArrayList; +import java.util.List; + +import de.learnlib.api.SUL; +import de.learnlib.api.SULException; + +/** + * SUL-wrapper to check for non-determinism, by use of an observation tree. + * + * @author Ramon Janssen + * + * @param + * @param + */ +public class NonDeterminismCheckingSUL implements SUL { + private final SUL sul; + private final ObservationTree root = new ObservationTree(); + private final List inputs = new ArrayList<>(); + private final List outputs = new ArrayList<>(); + + public NonDeterminismCheckingSUL(SUL sul) { + this.sul = sul; + } + + @Override + public void post() { + sul.post(); + // check for non-determinism: crashes if outputs are inconsistent with previous ones + root.addObservation(inputs, outputs); + inputs.clear(); + outputs.clear(); + } + + @Override + public void pre() { + sul.pre(); + } + + @Override + public O step(I input) throws SULException { + O result = sul.step(input); + inputs.add(input); + outputs.add(result); + return result; + } +} diff --git a/a4/tcp/tester/learner/ObservationTree.java b/a4/tcp/tester/learner/ObservationTree.java new file mode 100644 index 0000000..9729157 --- /dev/null +++ b/a4/tcp/tester/learner/ObservationTree.java @@ -0,0 +1,118 @@ +package learner; + +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import net.automatalib.words.Word; + +/** + * @author Ramon Janssen + * + * @param the input type of the observations + * @param the output type of the observations + */ +public class ObservationTree { + private final ObservationTree parent; + private final I parentInput; + private final O parentOutput; + private final Map> children; + private final Map outputs; + + public ObservationTree() { + this(null, null, null); + } + + private ObservationTree(ObservationTree parent, I parentInput, O parentOutput) { + this.children = new HashMap<>(); + this.outputs = new HashMap<>(); + this.parent = parent; + this.parentInput = parentInput; + this.parentOutput = parentOutput; + } + + /** + * @return The outputs observed from the root of the tree until this node + */ + private List getOutputChain() { + if (this.parent == null) { + return new LinkedList(); + } else { + List parentChain = this.parent.getOutputChain(); + parentChain.add(parentOutput); + return parentChain; + } + } + + private List getInputChain() { + if (this.parent == null) { + return new LinkedList(); + } else { + List parentChain = this.parent.getInputChain(); + parentChain.add(this.parentInput); + return parentChain; + } + } + + /** + * Add one input and output symbol and traverse the tree to the next node + * @param input + * @param output + * @return the next node + * @throws InconsistencyException + */ + public ObservationTree addObservation(I input, O output) throws CacheInconsistencyException { + O previousOutput = this.outputs.get(input); + boolean createNewBranch = previousOutput == null; + if (createNewBranch) { + // input hasn't been queried before, make a new branch for it and traverse + this.outputs.put(input, output); + ObservationTree child = new ObservationTree(this, input, output); + this.children.put(input, child); + return child; + } else if (!previousOutput.equals(output)) { + // input is inconsistent with previous observations, throw exception + List oldOutputChain = this.children.get(input).getOutputChain(); + List newOutputChain = this.getOutputChain(); + List inputChain = this.getInputChain(); + newOutputChain.add(output); + throw new CacheInconsistencyException(toWord(inputChain), toWord(oldOutputChain), toWord(newOutputChain)); + } else { + // input is consistent with previous observations, just traverse + return this.children.get(input); + } + } + + /** + * Add Observation to the tree + * @param inputs + * @param outputs + * @throws CacheInconsistencyException Inconsistency between new and stored observations + */ + public void addObservation(Word inputs, Word outputs) throws CacheInconsistencyException { + addObservation(inputs.asList(), outputs.asList()); + } + + + public void addObservation(List inputs, List outputs) throws CacheInconsistencyException { + if (inputs.isEmpty() && outputs.isEmpty()) { + return; + } else if (inputs.isEmpty() || outputs.isEmpty()) { + throw new RuntimeException("Input and output words should have the same length:\n" + inputs + "\n" + outputs); + } else { + I firstInput = inputs.get(0); + O firstOutput = outputs.get(0); + try { + this.addObservation(firstInput, firstOutput) + .addObservation(inputs.subList(1, inputs.size()), outputs.subList(1, outputs.size())); + } catch (CacheInconsistencyException e) { + throw new CacheInconsistencyException(toWord(inputs), e.getOldOutput(), toWord(outputs)); + } + } + } + + public static Word toWord(List symbolList) { + return Word.fromList(symbolList); + } +} diff --git a/a4/tcp/tester/learner/SocketSUL.java b/a4/tcp/tester/learner/SocketSUL.java new file mode 100644 index 0000000..95dc004 --- /dev/null +++ b/a4/tcp/tester/learner/SocketSUL.java @@ -0,0 +1,80 @@ +package learner; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.PrintWriter; +import java.net.InetAddress; +import java.net.Socket; +import java.net.UnknownHostException; + +import de.learnlib.api.SUL; +import de.learnlib.api.SULException; + +/** + * Socket interface to connect to an SUT/test adapter over TCP. + * + * As an example, type into a unix terminal "nc -vl {ip} {port}" (where {ip} and + * {port} are the chosen values), and run this socketSUL. You can now control the + * SUL through the terminal. + * @author Ramon Janssen + */ +public class SocketSUL implements SUL, AutoCloseable { + private final BufferedReader SULoutput; + private final PrintWriter SULinput; + private final Socket socket; + private final boolean extraNewLine; + private final String resetCmd; + + /** + * Socket-interface for SUTs. Connects to a SUT (or test-adapter) + * @param ip the ip-address + * @param port the tcp-port + * @param extraNewLine whether to print a newline after every input to the SUT + * @param resetCmd the command to send for resetting the SUT + * @throws UnknownHostException + * @throws IOException + */ + public SocketSUL(InetAddress ip, int port, boolean extraNewLine, String resetCmd) throws UnknownHostException, IOException { + this.socket = new Socket(ip, port); + this.SULoutput = new BufferedReader(new InputStreamReader(socket.getInputStream())); + this.SULinput = new PrintWriter(socket.getOutputStream(), true); + this.extraNewLine = extraNewLine; + this.resetCmd = resetCmd; + } + + @Override + public void post() { + if (extraNewLine) { + this.SULinput.write(this.resetCmd + System.lineSeparator()); + } else { + this.SULinput.write(this.resetCmd); + } + this.SULinput.flush(); + } + + @Override + public void pre() { + + } + + @Override + public String step(String input) throws SULException { + if (extraNewLine) { + this.SULinput.write(input + System.lineSeparator()); + } else { + this.SULinput.write(input); + } + this.SULinput.flush(); + try { + return this.SULoutput.readLine(); + } catch (IOException e) { + throw new SULException(e); + } + } + + @Override + public void close() throws Exception { + this.socket.close(); + } +} diff --git a/a4/tcp/tester/learner/SutSocketWrapper.java b/a4/tcp/tester/learner/SutSocketWrapper.java new file mode 100644 index 0000000..26e504a --- /dev/null +++ b/a4/tcp/tester/learner/SutSocketWrapper.java @@ -0,0 +1,93 @@ +package learner; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; +import java.net.Socket; + + +public class SutSocketWrapper { + private Socket sock; + private PrintWriter sockout; + private BufferedReader sockin; + private int run; + + public SutSocketWrapper(int portNumber) { + + try { + sock = new Socket("localhost", portNumber); + + /* Call setTcpNoDelay to improve communication performance : */ + + sock.setTcpNoDelay(true); // remove unnecessary delay in socket communication! + + + // make char writer from byte writer which automatically encodes chars using UTF-8 and + // automatically flushes the buffer on each println call. + sockout = new PrintWriter(new OutputStreamWriter(sock.getOutputStream(), "UTF-8"),true); + // make char reader from byte reader which automatically decodes bytes to chars using UTF-8 + sockin = new BufferedReader(new InputStreamReader(sock.getInputStream(), "UTF-8")); + + + run=1; + } catch (IOException e) { + // e.printStackTrace(); + System.err.println(""); + System.err.println("\n\nPROBLEM: problem connecting with SUT:\n\n " + e.getMessage() +"\n\n"); + System.exit(1); + } + } + + + + public String sendInput(String inputStr) { + try { + + // Send input to SUT + sockout.println(inputStr); + sockout.flush(); + + // Receive output from SUT + String outputStr=sockin.readLine(); + if (outputStr==null) { + System.err.println(""); + System.err.println("\n\nPROBLEM: problem reading output from SUT: SUT closed connection\n\n " ); + System.exit(1); + } + + return outputStr; + } catch (IOException e) { + e.printStackTrace(); + return null; + } + } + + public void sendReset() { + + // send reset to SUT + sockout.println("reset"); + sockout.flush(); + + run=run+1; + } + + + + + public void close() { + /* + try { + sockin.close(); + sockout.close(); + sock.close(); + } catch (IOException ex) { + + } + */ + } + + + +}