Added to GitHub
This commit is contained in:
parent
a57aecc05b
commit
afec066586
|
|
@ -0,0 +1,36 @@
|
||||||
|
|
||||||
|
# Java
|
||||||
|
*.class
|
||||||
|
*.jar
|
||||||
|
*.war
|
||||||
|
*.ear
|
||||||
|
|
||||||
|
# Scala
|
||||||
|
*.class
|
||||||
|
*.log
|
||||||
|
# sbt specific
|
||||||
|
dist/*
|
||||||
|
target/
|
||||||
|
lib_managed/
|
||||||
|
src_managed/
|
||||||
|
project/boot/
|
||||||
|
project/plugins/project/
|
||||||
|
# Scala-IDE specific
|
||||||
|
.scala_dependencies
|
||||||
|
|
||||||
|
# Maven
|
||||||
|
target/
|
||||||
|
|
||||||
|
# Netbeans
|
||||||
|
build/
|
||||||
|
dist/
|
||||||
|
#nbproject/
|
||||||
|
|
||||||
|
# IntelliJ
|
||||||
|
.idea/
|
||||||
|
|
||||||
|
# Eclipse
|
||||||
|
.settings/
|
||||||
|
|
||||||
|
# Project
|
||||||
|
Mtik.java
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
<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/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<groupId>adept</groupId>
|
||||||
|
<artifactId>mikrotik</artifactId>
|
||||||
|
<version>1.0-SNAPSHOT</version>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
|
<name>mikrotik</name>
|
||||||
|
<url>http://maven.apache.org</url>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>junit</groupId>
|
||||||
|
<artifactId>junit</artifactId>
|
||||||
|
<version>3.8.1</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</project>
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
package me.legrange.mikrotik;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thrown when the Mikrotik returns an error when receiving our command.
|
||||||
|
* @author GideonLeGrange
|
||||||
|
*/
|
||||||
|
public class ApiCommandException extends MikrotikApiException {
|
||||||
|
|
||||||
|
|
||||||
|
/** return the tag associated with this exception, if there is one */
|
||||||
|
public String getTag() {
|
||||||
|
return tag;
|
||||||
|
}
|
||||||
|
|
||||||
|
ApiCommandException(String msg) {
|
||||||
|
super(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
ApiCommandException(String msg, Throwable err) {
|
||||||
|
super(msg, err);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
ApiCommandException(Error err) {
|
||||||
|
super(err.getMessage());
|
||||||
|
tag = err.getTag();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String tag = null;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,430 @@
|
||||||
|
package me.legrange.mikrotik;
|
||||||
|
|
||||||
|
import java.io.DataInputStream;
|
||||||
|
import java.io.DataOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.UnsupportedEncodingException;
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.net.UnknownHostException;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.LinkedBlockingQueue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Mikrotik API connection. This is the class used to connect to a remote
|
||||||
|
* Mikrotik and send commands to it.
|
||||||
|
*
|
||||||
|
* @author GideonLeGrange
|
||||||
|
*/
|
||||||
|
public class ApiConnection {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new API connection to the give device on the supplied port
|
||||||
|
*/
|
||||||
|
public static ApiConnection connect(String host, int port) throws ApiConnectionException {
|
||||||
|
ApiConnection con = new ApiConnection();
|
||||||
|
con.open(host, port);
|
||||||
|
return con;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new API connection to the give device on the default API port
|
||||||
|
*/
|
||||||
|
public static ApiConnection connect(String host) throws ApiConnectionException {
|
||||||
|
return connect(host, DEFAULT_PORT);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* State of connection
|
||||||
|
*
|
||||||
|
* @return - if connection is established to router it returns true.
|
||||||
|
*/
|
||||||
|
public boolean isConnected() {
|
||||||
|
return connected;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disconnect from the remote API
|
||||||
|
*/
|
||||||
|
public void disconnect() throws ApiConnectionException {
|
||||||
|
if (!connected) {
|
||||||
|
throw new ApiConnectionException(("Not/no longer connected to remote Mikrotik"));
|
||||||
|
}
|
||||||
|
connected = false;
|
||||||
|
reader.interrupt();
|
||||||
|
try {
|
||||||
|
sock.close();
|
||||||
|
} catch (IOException ex) {
|
||||||
|
throw new ApiConnectionException(String.format("Error closing socket: %s", ex.getMessage()), ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** cancel a command */
|
||||||
|
public void cancel(Command can) throws MikrotikApiException {
|
||||||
|
Command cmd = new Command("/cancel");
|
||||||
|
cmd.addParameter("tag", can.getTag());
|
||||||
|
execute(cmd);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* set up method that will log you in
|
||||||
|
*
|
||||||
|
* @param name - username of the user on the router
|
||||||
|
* @param password - password for the user
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public void login(String name, String pwd) throws MikrotikApiException, ApiCommandException, InterruptedException {
|
||||||
|
List<Result> list = execute("/login");
|
||||||
|
Result res = list.get(0);
|
||||||
|
String hash = res.get("ret");
|
||||||
|
String chal = Util.hexStrToStr("00") + new String(makePass(pwd)) + Util.hexStrToStr(hash);
|
||||||
|
chal = Util.hashMD5(chal);
|
||||||
|
execute("/login name=" + name + " response=00" + chal);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<Result> execute(Command cmd) throws MikrotikApiException {
|
||||||
|
SyncListener l = new SyncListener();
|
||||||
|
execute(cmd, l);
|
||||||
|
return l.getResults();
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Result> execute(String cmd) throws MikrotikApiException {
|
||||||
|
return execute(Parser.parse(cmd));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Command execute(String cmd, ResultListener lis) throws MikrotikApiException {
|
||||||
|
return execute(Parser.parse(cmd), lis);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Command execute(Command cmd, ResultListener lis) throws MikrotikApiException {
|
||||||
|
String tag = nextTag();
|
||||||
|
cmd.setTag(tag);
|
||||||
|
listeners.put(tag, lis);
|
||||||
|
try {
|
||||||
|
Util.write(cmd, out);
|
||||||
|
} catch (UnsupportedEncodingException ex) {
|
||||||
|
throw new ApiDataException(ex.getMessage(), ex);
|
||||||
|
} catch (IOException ex) {
|
||||||
|
throw new ApiConnectionException(ex.getMessage(), ex);
|
||||||
|
}
|
||||||
|
return cmd;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ApiConnection() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start the API. Connects to the Mikrotik
|
||||||
|
*/
|
||||||
|
private void open(String host, int port) throws ApiConnectionException {
|
||||||
|
try {
|
||||||
|
InetAddress ia = InetAddress.getByName(host);
|
||||||
|
if (ia.isReachable(1000)) {
|
||||||
|
sock = new Socket(ia, port);
|
||||||
|
in = new DataInputStream(sock.getInputStream());
|
||||||
|
out = new DataOutputStream(sock.getOutputStream());
|
||||||
|
connected = true;
|
||||||
|
reader = new Reader();
|
||||||
|
reader.setDaemon(true);
|
||||||
|
reader.start();
|
||||||
|
processor = new Processor();
|
||||||
|
processor.setDaemon(true);
|
||||||
|
processor.start();
|
||||||
|
} else {
|
||||||
|
throw new ApiConnectionException(String.format("Host '%s' port %d is uncreachable", host, port));
|
||||||
|
}
|
||||||
|
} catch (UnknownHostException ex) {
|
||||||
|
connected = false;
|
||||||
|
throw new ApiConnectionException(String.format("Unknown host '%s'", host), ex);
|
||||||
|
} catch (IOException ex) {
|
||||||
|
connected = false;
|
||||||
|
throw new ApiConnectionException(String.format("Error connecting to '%s': %s", host, ex.getMessage()), ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private char[] makePass(String pass) {
|
||||||
|
if (true) {
|
||||||
|
return pass.toCharArray();
|
||||||
|
}
|
||||||
|
char[] res = new char[pass.length() + 1];
|
||||||
|
System.arraycopy(pass.toCharArray(), 0, res, 0, pass.length());
|
||||||
|
res[pass.length()] = 0x0;
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
private synchronized String nextTag() {
|
||||||
|
_tag++;
|
||||||
|
return Integer.toHexString(_tag);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final int DEFAULT_PORT = 8728;
|
||||||
|
private Socket sock = null;
|
||||||
|
private DataOutputStream out = null;
|
||||||
|
private DataInputStream in = null;
|
||||||
|
private boolean connected = false;
|
||||||
|
private Reader reader;
|
||||||
|
private Processor processor;
|
||||||
|
private final Map<String, ResultListener> listeners = new HashMap<String, ResultListener>();
|
||||||
|
private Integer _tag = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* thread to read data from the socket and process it into Strings
|
||||||
|
*/
|
||||||
|
private class Reader extends Thread {
|
||||||
|
|
||||||
|
private String take() throws ApiConnectionException, ApiDataException {
|
||||||
|
Object val = null;
|
||||||
|
try {
|
||||||
|
val = queue.take();
|
||||||
|
} catch (InterruptedException ex) {
|
||||||
|
throw new ApiConnectionException("Interrupted while reading data from queue.", ex);
|
||||||
|
}
|
||||||
|
if (val instanceof ApiConnectionException) {
|
||||||
|
throw (ApiConnectionException) val;
|
||||||
|
} else if (val instanceof ApiDataException) {
|
||||||
|
throw (ApiDataException) val;
|
||||||
|
}
|
||||||
|
return (String) val;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isEmpty() {
|
||||||
|
return queue.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
while (connected) {
|
||||||
|
try {
|
||||||
|
String s = Util.decode(in);
|
||||||
|
if (s != null) {
|
||||||
|
queue.put(s);
|
||||||
|
}
|
||||||
|
} catch (ApiDataException ex) {
|
||||||
|
try {
|
||||||
|
queue.put(ex);
|
||||||
|
} catch (InterruptedException ex2) {
|
||||||
|
}
|
||||||
|
} catch (ApiConnectionException ex) {
|
||||||
|
} catch (InterruptedException ex1) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private LinkedBlockingQueue queue = new LinkedBlockingQueue(40);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thread to take the received strings and process it into Result objects
|
||||||
|
*/
|
||||||
|
private class Processor extends Thread {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
while (connected) {
|
||||||
|
Response res;
|
||||||
|
try {
|
||||||
|
res = unpack();
|
||||||
|
} catch (ApiCommandException ex) {
|
||||||
|
String tag = ex.getTag();
|
||||||
|
if (tag != null) {
|
||||||
|
res = new Error(tag, ex.getMessage());
|
||||||
|
} else {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
} catch (MikrotikApiException ex) {
|
||||||
|
ex.printStackTrace();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
ResultListener l = listeners.get(res.getTag());
|
||||||
|
if (l != null) {
|
||||||
|
if (res instanceof Result) {
|
||||||
|
l.receive((Result) res);
|
||||||
|
} else {
|
||||||
|
if (res instanceof Done) {
|
||||||
|
listeners.remove(res.getTag());
|
||||||
|
}
|
||||||
|
if (l instanceof ResponseListener) {
|
||||||
|
ResponseListener rl = (ResponseListener) l;
|
||||||
|
if (res instanceof Done) {
|
||||||
|
rl.completed((Done) res);
|
||||||
|
} else if (res instanceof Error) {
|
||||||
|
rl.error((Error) res);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void nextLine() throws ApiConnectionException, ApiDataException {
|
||||||
|
if (lines.isEmpty()) {
|
||||||
|
String block = reader.take();
|
||||||
|
String parts[] = block.split("\n");
|
||||||
|
lines.addAll(Arrays.asList(parts));
|
||||||
|
}
|
||||||
|
line = lines.remove(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean hasNextLine() {
|
||||||
|
return !lines.isEmpty() || !reader.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Response unpack() throws MikrotikApiException {
|
||||||
|
if (line == null) {
|
||||||
|
nextLine();
|
||||||
|
}
|
||||||
|
if (line.equals("!re")) {
|
||||||
|
return unpackRe();
|
||||||
|
} else if (line.equals("!done")) {
|
||||||
|
return unpackDone();
|
||||||
|
} else if (line.equals("!trap")) {
|
||||||
|
return unpackError();
|
||||||
|
} else if (line.equals("!halt")) {
|
||||||
|
return unpackError();
|
||||||
|
} else {
|
||||||
|
throw new ApiDataException(String.format("Unexpected line '%s'", line));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Result unpackRe() throws ApiDataException, ApiConnectionException {
|
||||||
|
nextLine();
|
||||||
|
int l = 0;
|
||||||
|
Result res = new Result();
|
||||||
|
while (!line.startsWith(("!"))) {
|
||||||
|
l++;
|
||||||
|
if (line.startsWith(("="))) {
|
||||||
|
String parts[] = line.split("=", 3);
|
||||||
|
if (parts.length == 3) {
|
||||||
|
res.put(parts[1], parts[2]);
|
||||||
|
} else {
|
||||||
|
throw new ApiDataException(String.format("Malformed line '%s'", line));
|
||||||
|
}
|
||||||
|
} else if (line.startsWith(".tag=")) {
|
||||||
|
String parts[] = line.split("=", 2);
|
||||||
|
if (parts.length == 2) {
|
||||||
|
res.setTag(parts[1]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new ApiDataException(String.format("Unexpected line '%s'", line));
|
||||||
|
}
|
||||||
|
if (hasNextLine()) {
|
||||||
|
nextLine();
|
||||||
|
} else {
|
||||||
|
line = null;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Done unpackDone() throws MikrotikApiException {
|
||||||
|
Done done = new Done(null);
|
||||||
|
if (hasNextLine()) {
|
||||||
|
nextLine();
|
||||||
|
|
||||||
|
while (!line.startsWith("!")) {
|
||||||
|
if (line.startsWith(".tag=")) {
|
||||||
|
String parts[] = line.split("=", 2);
|
||||||
|
if (parts.length == 2) {
|
||||||
|
done.setTag(parts[1]);
|
||||||
|
}
|
||||||
|
} else if (line.startsWith(("=ret"))) {
|
||||||
|
String parts[] = line.split("=", 3);
|
||||||
|
if (parts.length == 3) {
|
||||||
|
done.setHash(parts[2]);
|
||||||
|
} else {
|
||||||
|
throw new ApiDataException(String.format("Malformed line '%s'", line));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (hasNextLine()) {
|
||||||
|
nextLine();
|
||||||
|
} else {
|
||||||
|
line = null;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return done;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Error unpackError() throws MikrotikApiException {
|
||||||
|
nextLine();
|
||||||
|
Error err = new Error();
|
||||||
|
if (hasNextLine()) {
|
||||||
|
while (!line.startsWith("!")) {
|
||||||
|
if (line.startsWith(".tag=")) {
|
||||||
|
String parts[] = line.split("=", 2);
|
||||||
|
if (parts.length == 2) {
|
||||||
|
err.setTag(parts[1]);
|
||||||
|
}
|
||||||
|
} else if (line.startsWith("=message=")) {
|
||||||
|
err.setMessage(line.split("=", 3)[2]);
|
||||||
|
}
|
||||||
|
if (hasNextLine()) {
|
||||||
|
nextLine();
|
||||||
|
} else {
|
||||||
|
line = null;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void queue(Response res) {
|
||||||
|
String tag = res.getTag();
|
||||||
|
if (tag != null) {
|
||||||
|
ResultListener rl = listeners.get(tag);
|
||||||
|
if (rl != null) {
|
||||||
|
if (res instanceof Result) {
|
||||||
|
rl.receive((Result) res);
|
||||||
|
} else {
|
||||||
|
// rl.error((Error)res);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private List<String> lines = new LinkedList<String>();
|
||||||
|
private String line;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class SyncListener implements ResponseListener {
|
||||||
|
|
||||||
|
public synchronized void error(Error err) {
|
||||||
|
this.err = err;
|
||||||
|
notify();
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void completed(Done done) {
|
||||||
|
if (done.getHash() != null) {
|
||||||
|
Result res = new Result();
|
||||||
|
res.put("ret", done.getHash());
|
||||||
|
results.add(res);
|
||||||
|
}
|
||||||
|
notify();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void receive(Result result) {
|
||||||
|
results.add(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<Result> getResults() throws ApiCommandException, ApiConnectionException {
|
||||||
|
try {
|
||||||
|
synchronized (this) {
|
||||||
|
wait();
|
||||||
|
}
|
||||||
|
} catch (InterruptedException ex) {
|
||||||
|
throw new ApiConnectionException(ex.getMessage(), ex);
|
||||||
|
}
|
||||||
|
if (err != null) {
|
||||||
|
throw new ApiCommandException(err);
|
||||||
|
}
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
private List<Result> results = new LinkedList<Result>();
|
||||||
|
private Error err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
package me.legrange.mikrotik;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exception thrown if the Api experiences a connection problem
|
||||||
|
* @author GideonLeGrange
|
||||||
|
*/
|
||||||
|
public class ApiConnectionException extends MikrotikApiException {
|
||||||
|
|
||||||
|
ApiConnectionException(String msg) {
|
||||||
|
super(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
ApiConnectionException(String msg, Throwable err) {
|
||||||
|
super(msg, err);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
package me.legrange.mikrotik;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thrown if there is a problem unpacking data from the Api.
|
||||||
|
* @author GideonLeGrange
|
||||||
|
*/
|
||||||
|
public class ApiDataException extends MikrotikApiException {
|
||||||
|
|
||||||
|
ApiDataException(String msg) {
|
||||||
|
super(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
ApiDataException(String msg, Throwable err) {
|
||||||
|
super(msg, err);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
package me.legrange.mikrotik;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thrown if the API cannot log in
|
||||||
|
* @author GideonLeGrange
|
||||||
|
*/
|
||||||
|
public class ApiLoginException extends MikrotikApiException {
|
||||||
|
|
||||||
|
ApiLoginException(String msg) {
|
||||||
|
super(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
ApiLoginException(String msg, Throwable err) {
|
||||||
|
super(msg, err);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,81 @@
|
||||||
|
package me.legrange.mikrotik;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A command sent to a Mikrotik. This internal class is used to build complex commands
|
||||||
|
* with parameters, queries and property lists.
|
||||||
|
*
|
||||||
|
* @author GideonLeGrange
|
||||||
|
*/
|
||||||
|
public class Command {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return String.format("cmd[%s] = %s, params = %s, queries = %s, props=%s ", tag, cmd, params, queries, properties);
|
||||||
|
}
|
||||||
|
|
||||||
|
Command(String cmd) {
|
||||||
|
if (!cmd.startsWith("/")) {
|
||||||
|
cmd = "/" + cmd;
|
||||||
|
}
|
||||||
|
this.cmd = cmd;
|
||||||
|
}
|
||||||
|
|
||||||
|
String getCommand() {
|
||||||
|
return cmd;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a parameter to a command.
|
||||||
|
*/
|
||||||
|
void addParameter(String name, String value) {
|
||||||
|
params.add(new Parameter(name, value));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a valueless parameter to the command
|
||||||
|
*/
|
||||||
|
void addParameter(Parameter param) {
|
||||||
|
params.add(param);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a property to include in a result
|
||||||
|
*/
|
||||||
|
void addProperty(String... names) {
|
||||||
|
properties.addAll(Arrays.asList(names));
|
||||||
|
}
|
||||||
|
|
||||||
|
void addQuery(String... queries) {
|
||||||
|
this.queries.addAll(Arrays.asList(queries));
|
||||||
|
}
|
||||||
|
|
||||||
|
void setTag(String tag) {
|
||||||
|
this.tag = tag;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> getQueries() {
|
||||||
|
return queries;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
String getTag() {
|
||||||
|
return tag;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> getProperties() {
|
||||||
|
return properties;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Parameter> getParameters() {
|
||||||
|
return params;
|
||||||
|
}
|
||||||
|
private String cmd;
|
||||||
|
private List<Parameter> params = new LinkedList<Parameter>();
|
||||||
|
private List<String> queries = new LinkedList<String>();
|
||||||
|
private List<String> properties = new LinkedList<String>();
|
||||||
|
private String tag;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
package me.legrange.mikrotik;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal representation of !done
|
||||||
|
* @author GideonLeGrange
|
||||||
|
*/
|
||||||
|
public class Done extends Response {
|
||||||
|
|
||||||
|
Done(String tag) {
|
||||||
|
super(tag);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setHash(String hash) {
|
||||||
|
this.hash = hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
String getHash() {
|
||||||
|
return hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String hash;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
package me.legrange.mikrotik;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to encapsulate API error information. We need to pass both the message and the tag (if one was used).
|
||||||
|
* @author GideonLeGrange
|
||||||
|
*/
|
||||||
|
public class Error extends Response {
|
||||||
|
|
||||||
|
Error(String tag, String message) {
|
||||||
|
super(tag);
|
||||||
|
this.message = message;
|
||||||
|
}
|
||||||
|
|
||||||
|
Error() {
|
||||||
|
super(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
String getMessage() {
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
void setMessage(String message) {
|
||||||
|
this.message = message;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String message;
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
package me.legrange.mikrotik;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thrown by the Mikrotik API to indicate errors
|
||||||
|
*
|
||||||
|
* @author GideonLeGrange
|
||||||
|
*/
|
||||||
|
public class MikrotikApiException extends Exception {
|
||||||
|
|
||||||
|
MikrotikApiException(String msg) {
|
||||||
|
super(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
MikrotikApiException(String msg, Throwable err) {
|
||||||
|
super(msg, err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,41 @@
|
||||||
|
package me.legrange.mikrotik;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A command parameter
|
||||||
|
*
|
||||||
|
* @author GideonLeGrange
|
||||||
|
*/
|
||||||
|
class Parameter {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
if (hasValue()) {
|
||||||
|
return String.format("%s=%s", name, value);
|
||||||
|
} else {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Parameter(String name, String value) {
|
||||||
|
this.name = name;
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
Parameter(String name) {
|
||||||
|
this(name, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean hasValue() {
|
||||||
|
return value != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
String getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
private String name;
|
||||||
|
private String value;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
package me.legrange.mikrotik;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exception thrown if the parser encounters an error while parsing a command line.
|
||||||
|
* @author GideonLeGrange
|
||||||
|
*/
|
||||||
|
public class ParseException extends MikrotikApiException {
|
||||||
|
|
||||||
|
ParseException(String msg) {
|
||||||
|
super(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
ParseException(String msg, Throwable err) {
|
||||||
|
super(msg, err);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,208 @@
|
||||||
|
package me.legrange.mikrotik;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.StringTokenizer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse the pseudo-command line into command objects.
|
||||||
|
* @author GideonLeGrange
|
||||||
|
*/
|
||||||
|
class Parser {
|
||||||
|
|
||||||
|
/** parse the given bit of text into a Command object */
|
||||||
|
static Command parse(String text) throws ParseException {
|
||||||
|
Parser parser = new Parser(text);
|
||||||
|
return parser.parse();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** run parse on the internal data and return the command object */
|
||||||
|
private Command parse() throws ParseException {
|
||||||
|
next();
|
||||||
|
expect(Token.COMMAND);
|
||||||
|
cmd = new Command(text);
|
||||||
|
next();
|
||||||
|
while (!((token == Token.WHERE) || (token == Token.RETURN) || (token == Token.EOL))) {
|
||||||
|
param();
|
||||||
|
}
|
||||||
|
if (token == Token.WHERE) {
|
||||||
|
where();
|
||||||
|
}
|
||||||
|
if (token == Token.RETURN) {
|
||||||
|
returns();
|
||||||
|
}
|
||||||
|
expect(Token.EOL);
|
||||||
|
return cmd;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void param() throws ParseException {
|
||||||
|
String name = text;
|
||||||
|
next();
|
||||||
|
if (token == Token.EQUALS) {
|
||||||
|
next();
|
||||||
|
expect(Token.NAME);
|
||||||
|
cmd.addParameter(new Parameter(name, text));
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
cmd.addParameter(new Parameter(name));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void where() throws ParseException {
|
||||||
|
next(); // swallow the word "where"
|
||||||
|
expr();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void expr() throws ParseException {
|
||||||
|
expect(Token.NOT, Token.NAME);
|
||||||
|
switch (token) {
|
||||||
|
case NOT:
|
||||||
|
notExpr();
|
||||||
|
break;
|
||||||
|
case NAME: {
|
||||||
|
String name = text;
|
||||||
|
next();
|
||||||
|
expect(Token.EQUALS, Token.LESS, Token.MORE);
|
||||||
|
switch (token) {
|
||||||
|
case EQUALS:
|
||||||
|
eqExpr(name);
|
||||||
|
break;
|
||||||
|
case LESS:
|
||||||
|
lessExpr(name);
|
||||||
|
break;
|
||||||
|
case MORE:
|
||||||
|
moreExpr(name);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
hasExpr(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// if you get here, you had a expression, see if you want more.
|
||||||
|
switch (token) {
|
||||||
|
case AND : andExpr();
|
||||||
|
break;
|
||||||
|
case OR : orExpr();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void andExpr() throws ParseException {
|
||||||
|
next(); // eat and
|
||||||
|
expr();
|
||||||
|
cmd.addQuery("?#&");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void orExpr() throws ParseException {
|
||||||
|
next(); // eat or
|
||||||
|
expr();
|
||||||
|
cmd.addQuery("?#|");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void notExpr() throws ParseException {
|
||||||
|
next(); // eat not
|
||||||
|
expr();
|
||||||
|
cmd.addQuery("?#!");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void eqExpr(String name) {
|
||||||
|
next(); // eat =
|
||||||
|
cmd.addQuery(String.format("?%s=%s", name, text));
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void lessExpr(String name) {
|
||||||
|
next(); // eat <
|
||||||
|
cmd.addQuery(String.format("?<%s=%s", name, text));
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void moreExpr(String name) {
|
||||||
|
next(); // eat >
|
||||||
|
cmd.addQuery(String.format("?>%s=%s", name, text));
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void hasExpr(String name) {
|
||||||
|
cmd.addQuery(String.format("?%s", name));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void returns() throws ParseException {
|
||||||
|
next();
|
||||||
|
expect(Token.NAME);
|
||||||
|
List<String> props = new LinkedList<String>();
|
||||||
|
while (!(token == Token.EOL)) {
|
||||||
|
if (token != Token.COMMA) {
|
||||||
|
props.add(text);
|
||||||
|
}
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
cmd.addProperty(props.toArray(new String[]{}));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void expect(Token...tokens) throws ParseException {
|
||||||
|
for (Token want : tokens) {
|
||||||
|
if (this.token == want) return;
|
||||||
|
}
|
||||||
|
throw new ParseException(String.format("Expected %s but found %s", Arrays.asList(tokens), this.token));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void next() {
|
||||||
|
if (!words.isEmpty()) {
|
||||||
|
text = words.remove(0);
|
||||||
|
if (text.startsWith("/")) {
|
||||||
|
token = Token.COMMAND;
|
||||||
|
} else {
|
||||||
|
Token t = lookup.get(text);
|
||||||
|
if (t != null) {
|
||||||
|
token = t;
|
||||||
|
text = "";
|
||||||
|
} else {
|
||||||
|
token = Token.NAME;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
token = Token.EOL;
|
||||||
|
text = "";
|
||||||
|
}
|
||||||
|
// System.out.printf("%s: %s\n", token, text);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Parser(String text) {
|
||||||
|
text = text.trim();
|
||||||
|
StringTokenizer st = new StringTokenizer(text, " \t,=", true);
|
||||||
|
while (st.hasMoreElements()) {
|
||||||
|
String t = st.nextToken().trim();
|
||||||
|
if (!t.equals("")) {
|
||||||
|
words.add(t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private final List<String> words = new LinkedList<String>();
|
||||||
|
private String text;
|
||||||
|
private Token token;
|
||||||
|
private Command cmd;
|
||||||
|
private static final Map<String, Token> lookup = new HashMap<String, Token>();
|
||||||
|
|
||||||
|
private enum Token {
|
||||||
|
|
||||||
|
COMMAND, WHERE, RETURN, EOL, NOT, AND, OR, NAME, EQUALS, MORE, LESS, COMMA;
|
||||||
|
}
|
||||||
|
|
||||||
|
static {
|
||||||
|
lookup.put("where", Token.WHERE);
|
||||||
|
lookup.put("return", Token.RETURN);
|
||||||
|
lookup.put("not", Token.NOT);
|
||||||
|
lookup.put("and", Token.AND);
|
||||||
|
lookup.put("or", Token.OR);
|
||||||
|
lookup.put("=", Token.EQUALS);
|
||||||
|
lookup.put(">", Token.MORE);
|
||||||
|
lookup.put("<", Token.LESS);
|
||||||
|
lookup.put(",", Token.COMMA);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
package me.legrange.mikrotik;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Super type of possible API responses
|
||||||
|
*
|
||||||
|
* @author GideonLeGrange
|
||||||
|
*/
|
||||||
|
abstract class Response {
|
||||||
|
|
||||||
|
public String getTag() {
|
||||||
|
return tag;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return String.format("%s: tag=%s", getClass().getSimpleName(), tag);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setTag(String tag) {
|
||||||
|
this.tag = tag;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Response(String tag) {
|
||||||
|
this.tag = tag;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String tag;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
package me.legrange.mikrotik;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A listener that receives life cycle command events from the Mikrotik API,
|
||||||
|
* and not just results.
|
||||||
|
* @author GideonLeGrange
|
||||||
|
*/
|
||||||
|
public interface ResponseListener extends ResultListener {
|
||||||
|
|
||||||
|
/** called if the command associated with this listener experiences a trap */
|
||||||
|
void error(Error err);
|
||||||
|
|
||||||
|
/** called when the command associated with this listener is done */
|
||||||
|
void completed(Done done);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,78 @@
|
||||||
|
package me.legrange.mikrotik;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A result from an API command.
|
||||||
|
* @author GideonLeGrange
|
||||||
|
*/
|
||||||
|
public class Result extends Response implements Map<String, String> {
|
||||||
|
|
||||||
|
public Result() {
|
||||||
|
super(null);
|
||||||
|
this.map = new HashMap<String, String>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String get(String key) {
|
||||||
|
return map.get(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isEmpty() {
|
||||||
|
return map.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return String.format("tag=%s, data=%s", getTag(), map);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int size() {
|
||||||
|
return map.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean containsKey(Object o) {
|
||||||
|
return map.containsKey(o);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean containsValue(Object o) {
|
||||||
|
return map.containsValue(o);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String get(Object o) {
|
||||||
|
return map.get(o);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String put(String k, String v) {
|
||||||
|
return map.put(k, v);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String remove(Object o) {
|
||||||
|
return map.remove(o);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void putAll(Map<? extends String, ? extends String> map) {
|
||||||
|
this.map.putAll(map);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clear() {
|
||||||
|
map.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<String> keySet() {
|
||||||
|
return map.keySet();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Collection<String> values() {
|
||||||
|
return map.values();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<Entry<String, String>> entrySet() {
|
||||||
|
return map.entrySet();
|
||||||
|
}
|
||||||
|
|
||||||
|
private final Map<String, String> map;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
package me.legrange.mikrotik;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implement this interface to receive command results from the Mikrotik Api.
|
||||||
|
* @author GideonLeGrange
|
||||||
|
*/
|
||||||
|
public interface ResultListener {
|
||||||
|
|
||||||
|
void receive(Result result);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,177 @@
|
||||||
|
package me.legrange.mikrotik;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.io.UnsupportedEncodingException;
|
||||||
|
import java.security.MessageDigest;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility library that handles the low level encoding required by the Mikrotik API.
|
||||||
|
* @author GideonLeGrange. Possibly some code by janisk left.
|
||||||
|
*/
|
||||||
|
class Util {
|
||||||
|
|
||||||
|
static void write(Command cmd, OutputStream out) throws UnsupportedEncodingException, IOException {
|
||||||
|
System.out.println("cmd = '" + cmd + "'");
|
||||||
|
encode(cmd.getCommand(), out);
|
||||||
|
for (Parameter param : cmd.getParameters()) {
|
||||||
|
encode(String.format("=%s=%s", param.getName(), param.hasValue() ? param.getValue() : "" ), out);
|
||||||
|
}
|
||||||
|
String tag = cmd.getTag();
|
||||||
|
if ((tag != null) && !tag.equals("")) {
|
||||||
|
encode(String.format(".tag=%s", tag), out);
|
||||||
|
}
|
||||||
|
List<String> props = cmd.getProperties();
|
||||||
|
if (!props.isEmpty()) {
|
||||||
|
StringBuilder buf = new StringBuilder("=.proplist=");
|
||||||
|
for (int i = 0; i < props.size(); ++i) {
|
||||||
|
if (i > 0)
|
||||||
|
buf.append(",");
|
||||||
|
buf.append(props.get(i));
|
||||||
|
}
|
||||||
|
encode(buf.toString(), out);
|
||||||
|
}
|
||||||
|
for (String query : cmd.getQueries()) {
|
||||||
|
encode(query, out);
|
||||||
|
}
|
||||||
|
out.write(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** encode text using Mikrotik's encoding scheme and write it to an output stream. */
|
||||||
|
private static void encode(String word, OutputStream out) throws UnsupportedEncodingException, IOException {
|
||||||
|
byte bytes[] = word.getBytes("US-ASCII");
|
||||||
|
int len = bytes.length;
|
||||||
|
if (len < 0x80) {
|
||||||
|
out.write(len);
|
||||||
|
} else if (len < 0x4000) {
|
||||||
|
len = len | 0x8000;
|
||||||
|
out.write(len >> 8);
|
||||||
|
out.write(len);
|
||||||
|
} else if (len < 0x20000) {
|
||||||
|
len = len | 0xC00000;
|
||||||
|
out.write(len >> 16);
|
||||||
|
out.write(len >> 8);
|
||||||
|
out.write(len);
|
||||||
|
} else if (len < 0x10000000) {
|
||||||
|
len = len | 0xE0000000;
|
||||||
|
out.write(len >> 24);
|
||||||
|
out.write(len >> 16);
|
||||||
|
out.write(len >> 8);
|
||||||
|
out.write(len);
|
||||||
|
} else {
|
||||||
|
out.write(0xF0);
|
||||||
|
out.write(len >> 24);
|
||||||
|
out.write(len >> 16);
|
||||||
|
out.write(len >> 8);
|
||||||
|
out.write(len);
|
||||||
|
}
|
||||||
|
out.write(bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** decode bytes from an input stream of Mikrotik protocol sentences into text */
|
||||||
|
static String decode(InputStream in) throws ApiDataException, ApiConnectionException {
|
||||||
|
StringBuilder res = new StringBuilder();
|
||||||
|
decode(in, res);
|
||||||
|
return res.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** decode bytes from an input stream into Mikrotik protocol sentences */
|
||||||
|
private static void decode(InputStream in, StringBuilder result) throws ApiDataException, ApiConnectionException {
|
||||||
|
try {
|
||||||
|
int len = readLen(in);
|
||||||
|
if (len > 0) {
|
||||||
|
byte buf[] = new byte[len];
|
||||||
|
for (int i = 0; i < len; ++i) {
|
||||||
|
int c = in.read();
|
||||||
|
if (c < 0) {
|
||||||
|
throw new ApiDataException("Truncated data. Expected to read more bytes");
|
||||||
|
}
|
||||||
|
buf[i] = (byte) (c & 0xFF);
|
||||||
|
}
|
||||||
|
String res = new String(buf);
|
||||||
|
if (result.length() > 0)
|
||||||
|
result.append("\n");
|
||||||
|
result.append(res);
|
||||||
|
decode(in, result);
|
||||||
|
}
|
||||||
|
} catch (IOException ex) {
|
||||||
|
throw new ApiConnectionException(ex.getMessage(), ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** read length bytes from stream and return length of coming word */
|
||||||
|
static private int readLen(InputStream in) throws IOException {
|
||||||
|
int c = in.read();
|
||||||
|
if (c > 0) {
|
||||||
|
if ((c & 0x80) == 0) {
|
||||||
|
} else if ((c & 0xC0) == 0x80) {
|
||||||
|
c = c & ~0xC0;
|
||||||
|
c = (c << 8) | in.read();
|
||||||
|
} else if ((c & 0xE0) == 0xC0) {
|
||||||
|
c = c & ~0xE0;
|
||||||
|
c = (c << 8) | in.read();
|
||||||
|
c = (c << 8) | in.read();
|
||||||
|
} else if ((c & 0xF0) == 0xE0) {
|
||||||
|
c = c & ~0xF0;
|
||||||
|
c = (c << 8) | in.read();
|
||||||
|
c = (c << 8) | in.read();
|
||||||
|
c = (c << 8) | in.read();
|
||||||
|
} else if ((c & 0xF8) == 0xF0) {
|
||||||
|
c = in.read();
|
||||||
|
c = (c << 8) | in.read();
|
||||||
|
c = (c << 8) | in.read();
|
||||||
|
c = (c << 8) | in.read();
|
||||||
|
c = (c << 8) | in.read();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* makes MD5 hash of string for use with RouterOS API
|
||||||
|
*
|
||||||
|
* @param s - variable to make hash from
|
||||||
|
* @return - the md5 hash
|
||||||
|
*/
|
||||||
|
static String hashMD5(String s) throws ApiDataException {
|
||||||
|
MessageDigest algorithm = null;
|
||||||
|
try {
|
||||||
|
algorithm = MessageDigest.getInstance("MD5");
|
||||||
|
} catch (NoSuchAlgorithmException nsae) {
|
||||||
|
throw new ApiDataException("Cannot find MD5 digest algorithm");
|
||||||
|
}
|
||||||
|
byte[] defaultBytes = new byte[s.length()];
|
||||||
|
for (int i = 0; i < s.length(); i++) {
|
||||||
|
defaultBytes[i] = (byte) (0xFF & s.charAt(i));
|
||||||
|
}
|
||||||
|
algorithm.reset();
|
||||||
|
algorithm.update(defaultBytes);
|
||||||
|
byte messageDigest[] = algorithm.digest();
|
||||||
|
StringBuilder hexString = new StringBuilder();
|
||||||
|
for (int i = 0; i < messageDigest.length; i++) {
|
||||||
|
String hex = Integer.toHexString(0xFF & messageDigest[i]);
|
||||||
|
if (hex.length() == 1) {
|
||||||
|
hexString.append('0');
|
||||||
|
}
|
||||||
|
hexString.append(hex);
|
||||||
|
}
|
||||||
|
return hexString.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* converts hex value string to normal strint for use with RouterOS API
|
||||||
|
*
|
||||||
|
* @param s - hex string to convert to
|
||||||
|
* @return - converted string.
|
||||||
|
*/
|
||||||
|
static String hexStrToStr(String s) {
|
||||||
|
String ret = "";
|
||||||
|
for (int i = 0; i < s.length(); i += 2) {
|
||||||
|
ret += (char) Integer.parseInt(s.substring(i, i + 2), 16);
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
command = action [ query ] [ return ]
|
||||||
|
action = ("/" word)+
|
||||||
|
query = "where" expr
|
||||||
|
expr = expr "and" expr | expr "or" expr | "not" expr | hasExpr | eqExpr | lessExpr | moreExpr
|
||||||
|
hasExpr = name
|
||||||
|
eqExpr = name "=" value
|
||||||
|
lessExpr = name "<" value
|
||||||
|
moreExpr = name ">" value
|
||||||
|
return = "return" (name)+
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Loading…
Reference in New Issue