Refactored to split public API from implementation
This commit is contained in:
parent
93e5eda09d
commit
daf1ff9518
|
|
@ -1,4 +1,7 @@
|
||||||
package me.legrange.mikrotik;
|
package me.legrange.mikrotik.impl;
|
||||||
|
|
||||||
|
import me.legrange.mikrotik.MikrotikApiException;
|
||||||
|
import me.legrange.mikrotik.impl.Error;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Thrown when the Mikrotik returns an error when receiving our command.
|
* Thrown when the Mikrotik returns an error when receiving our command.
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,8 @@
|
||||||
package me.legrange.mikrotik;
|
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.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.LinkedBlockingQueue;
|
import me.legrange.mikrotik.impl.ApiConnectionImpl;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The Mikrotik API connection. This is the class used to connect to a remote
|
* The Mikrotik API connection. This is the class used to connect to a remote
|
||||||
|
|
@ -20,7 +10,10 @@ import java.util.concurrent.LinkedBlockingQueue;
|
||||||
*
|
*
|
||||||
* @author GideonLeGrange
|
* @author GideonLeGrange
|
||||||
*/
|
*/
|
||||||
public class ApiConnection {
|
public abstract class ApiConnection {
|
||||||
|
|
||||||
|
public static final int DEFAULT_PORT = 8728;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new API connection to the give device on the supplied port
|
* Create a new API connection to the give device on the supplied port
|
||||||
|
|
@ -28,10 +21,8 @@ public class ApiConnection {
|
||||||
* @param port The TCP port to use.
|
* @param port The TCP port to use.
|
||||||
* @return The ApiConnection
|
* @return The ApiConnection
|
||||||
*/
|
*/
|
||||||
public static ApiConnection connect(String host, int port) throws ApiConnectionException {
|
public static ApiConnection connect(String host, int port) throws MikrotikApiException {
|
||||||
ApiConnection con = new ApiConnection();
|
return ApiConnectionImpl.connect(host, port);
|
||||||
con.open(host, port);
|
|
||||||
return con;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -39,7 +30,7 @@ public class ApiConnection {
|
||||||
* @param host The host to which to connect.
|
* @param host The host to which to connect.
|
||||||
* @return The ApiConnection
|
* @return The ApiConnection
|
||||||
*/
|
*/
|
||||||
public static ApiConnection connect(String host) throws ApiConnectionException {
|
public static ApiConnection connect(String host) throws MikrotikApiException {
|
||||||
return connect(host, DEFAULT_PORT);
|
return connect(host, DEFAULT_PORT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -48,25 +39,12 @@ public class ApiConnection {
|
||||||
*
|
*
|
||||||
* @return if connection is established to router it returns true.
|
* @return if connection is established to router it returns true.
|
||||||
*/
|
*/
|
||||||
public boolean isConnected() {
|
public abstract boolean isConnected();
|
||||||
return connected;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Disconnect from the remote API
|
* Disconnect from the remote API
|
||||||
*/
|
*/
|
||||||
public void disconnect() throws ApiConnectionException {
|
public abstract void disconnect() throws MikrotikApiException;
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Log in to the remote router.
|
* Log in to the remote router.
|
||||||
|
|
@ -74,22 +52,13 @@ public class ApiConnection {
|
||||||
* @param username - username of the user on the router
|
* @param username - username of the user on the router
|
||||||
* @param password - password for the user
|
* @param password - password for the user
|
||||||
*/
|
*/
|
||||||
public void login(String username, String password) throws MikrotikApiException, ApiCommandException, InterruptedException {
|
public abstract void login(String username, String password) throws MikrotikApiException, InterruptedException;
|
||||||
List<Map<String, String>> list = execute("/login");
|
|
||||||
Map<String, String> res = list.get(0);
|
|
||||||
String hash = res.get("ret");
|
|
||||||
String chal = Util.hexStrToStr("00") + new String(makePass(password)) + Util.hexStrToStr(hash);
|
|
||||||
chal = Util.hashMD5(chal);
|
|
||||||
execute("/login name=" + username + " response=00" + chal);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** execute a command and return a list of results.
|
/** execute a command and return a list of results.
|
||||||
* @param cmd Command to execute
|
* @param cmd Command to execute
|
||||||
* @return The list of results
|
* @return The list of results
|
||||||
*/
|
*/
|
||||||
public List<Map<String, String>> execute(String cmd) throws MikrotikApiException {
|
public abstract List<Map<String, String>> execute(String cmd) throws MikrotikApiException;
|
||||||
return execute(Parser.parse(cmd));
|
|
||||||
}
|
|
||||||
|
|
||||||
/** execute a command and attach a result listener to receive it's results.
|
/** execute a command and attach a result listener to receive it's results.
|
||||||
*
|
*
|
||||||
|
|
@ -98,359 +67,9 @@ public class ApiConnection {
|
||||||
* @return A command object that can be used to cancel the command.
|
* @return A command object that can be used to cancel the command.
|
||||||
* @throws MikrotikApiException
|
* @throws MikrotikApiException
|
||||||
*/
|
*/
|
||||||
public String execute(String cmd, ResultListener lis) throws MikrotikApiException {
|
public abstract String execute(String cmd, ResultListener lis) throws MikrotikApiException;
|
||||||
return execute(Parser.parse(cmd), lis);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** cancel a command */
|
/** cancel a command */
|
||||||
public void cancel(String tag) throws MikrotikApiException {
|
public abstract void cancel(String tag) throws MikrotikApiException;
|
||||||
execute(String.format("/cancel tag=%s", tag)) ;
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<Map<String, String>> execute(Command cmd) throws MikrotikApiException {
|
|
||||||
SyncListener l = new SyncListener();
|
|
||||||
execute(cmd, l);
|
|
||||||
return l.getResults();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private String 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 tag;
|
|
||||||
}
|
|
||||||
|
|
||||||
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) {
|
|
||||||
if (rl instanceof SyncListener) {
|
|
||||||
((SyncListener)rl).completed((Done)res);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
rl.completed();
|
|
||||||
}
|
|
||||||
} else if (res instanceof Error) {
|
|
||||||
rl.error(new ApiCommandException((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(MikrotikApiException ex) {
|
|
||||||
this.err = ex;
|
|
||||||
notify();
|
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized void completed() {
|
|
||||||
notify();
|
|
||||||
}
|
|
||||||
|
|
||||||
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(Map<String, String> result) {
|
|
||||||
results.add(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<Map<String, String>> getResults() throws MikrotikApiException {
|
|
||||||
try {
|
|
||||||
synchronized (this) { // don't wait if we already have a result.
|
|
||||||
if ((err == null) && results.isEmpty()) {
|
|
||||||
wait();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (InterruptedException ex) {
|
|
||||||
throw new ApiConnectionException(ex.getMessage(), ex);
|
|
||||||
}
|
|
||||||
if (err != null) {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
return results;
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<Map<String, String>> results = new LinkedList<Map<String, String>>();
|
|
||||||
private MikrotikApiException err;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -6,11 +6,11 @@ package me.legrange.mikrotik;
|
||||||
*/
|
*/
|
||||||
public class ApiConnectionException extends MikrotikApiException {
|
public class ApiConnectionException extends MikrotikApiException {
|
||||||
|
|
||||||
ApiConnectionException(String msg) {
|
public ApiConnectionException(String msg) {
|
||||||
super(msg);
|
super(msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
ApiConnectionException(String msg, Throwable err) {
|
public ApiConnectionException(String msg, Throwable err) {
|
||||||
super(msg, err);
|
super(msg, err);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,11 +7,11 @@ package me.legrange.mikrotik;
|
||||||
*/
|
*/
|
||||||
public class MikrotikApiException extends Exception {
|
public class MikrotikApiException extends Exception {
|
||||||
|
|
||||||
MikrotikApiException(String msg) {
|
public MikrotikApiException(String msg) {
|
||||||
super(msg);
|
super(msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
MikrotikApiException(String msg, Throwable err) {
|
public MikrotikApiException(String msg, Throwable err) {
|
||||||
super(msg, err);
|
super(msg, err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
package me.legrange.mikrotik;
|
package me.legrange.mikrotik.impl;
|
||||||
|
|
||||||
|
import me.legrange.mikrotik.*;
|
||||||
import java.io.DataInputStream;
|
import java.io.DataInputStream;
|
||||||
import java.io.DataOutputStream;
|
import java.io.DataOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
@ -15,12 +16,12 @@ import java.util.Map;
|
||||||
import java.util.concurrent.LinkedBlockingQueue;
|
import java.util.concurrent.LinkedBlockingQueue;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The Mikrotik API connection. This is the class used to connect to a remote
|
* The Mikrotik API connection implementation. This is the class used to connect to a remote
|
||||||
* Mikrotik and send commands to it.
|
* Mikrotik and send commands to it.
|
||||||
*
|
*
|
||||||
* @author GideonLeGrange
|
* @author GideonLeGrange
|
||||||
*/
|
*/
|
||||||
public class ApiConnection {
|
public final class ApiConnectionImpl extends ApiConnection {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new API connection to the give device on the supplied port
|
* Create a new API connection to the give device on the supplied port
|
||||||
|
|
@ -29,20 +30,11 @@ public class ApiConnection {
|
||||||
* @return The ApiConnection
|
* @return The ApiConnection
|
||||||
*/
|
*/
|
||||||
public static ApiConnection connect(String host, int port) throws ApiConnectionException {
|
public static ApiConnection connect(String host, int port) throws ApiConnectionException {
|
||||||
ApiConnection con = new ApiConnection();
|
ApiConnectionImpl con = new ApiConnectionImpl();
|
||||||
con.open(host, port);
|
con.open(host, port);
|
||||||
return con;
|
return con;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new API connection to the give device on the default API port..
|
|
||||||
* @param host The host to which to connect.
|
|
||||||
* @return The ApiConnection
|
|
||||||
*/
|
|
||||||
public static ApiConnection connect(String host) throws ApiConnectionException {
|
|
||||||
return connect(host, DEFAULT_PORT);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check the state of connection.
|
* Check the state of connection.
|
||||||
*
|
*
|
||||||
|
|
@ -74,7 +66,7 @@ public class ApiConnection {
|
||||||
* @param username - username of the user on the router
|
* @param username - username of the user on the router
|
||||||
* @param password - password for the user
|
* @param password - password for the user
|
||||||
*/
|
*/
|
||||||
public void login(String username, String password) throws MikrotikApiException, ApiCommandException, InterruptedException {
|
public void login(String username, String password) throws MikrotikApiException, InterruptedException {
|
||||||
List<Map<String, String>> list = execute("/login");
|
List<Map<String, String>> list = execute("/login");
|
||||||
Map<String, String> res = list.get(0);
|
Map<String, String> res = list.get(0);
|
||||||
String hash = res.get("ret");
|
String hash = res.get("ret");
|
||||||
|
|
@ -128,7 +120,7 @@ public class ApiConnection {
|
||||||
return tag;
|
return tag;
|
||||||
}
|
}
|
||||||
|
|
||||||
private ApiConnection() {
|
private ApiConnectionImpl() {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
package me.legrange.mikrotik;
|
package me.legrange.mikrotik.impl;
|
||||||
|
|
||||||
|
import me.legrange.mikrotik.MikrotikApiException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Thrown if there is a problem unpacking data from the Api.
|
* Thrown if there is a problem unpacking data from the Api.
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package me.legrange.mikrotik;
|
package me.legrange.mikrotik.impl;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package me.legrange.mikrotik;
|
package me.legrange.mikrotik.impl;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Internal representation of !done
|
* Internal representation of !done
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package me.legrange.mikrotik;
|
package me.legrange.mikrotik.impl;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used to encapsulate API error information. We need to pass both the message and the tag (if one was used).
|
* Used to encapsulate API error information. We need to pass both the message and the tag (if one was used).
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package me.legrange.mikrotik;
|
package me.legrange.mikrotik.impl;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A command parameter
|
* A command parameter
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
package me.legrange.mikrotik;
|
package me.legrange.mikrotik.impl;
|
||||||
|
|
||||||
|
import me.legrange.mikrotik.MikrotikApiException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Exception thrown if the parser encounters an error while parsing a command line.
|
* Exception thrown if the parser encounters an error while parsing a command line.
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package me.legrange.mikrotik;
|
package me.legrange.mikrotik.impl;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package me.legrange.mikrotik;
|
package me.legrange.mikrotik.impl;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Super type of possible API responses
|
* Super type of possible API responses
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package me.legrange.mikrotik;
|
package me.legrange.mikrotik.impl;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package me.legrange.mikrotik;
|
package me.legrange.mikrotik.impl;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
|
@ -7,17 +7,23 @@ import java.io.UnsupportedEncodingException;
|
||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import me.legrange.mikrotik.ApiConnectionException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utility library that handles the low level encoding required by the Mikrotik API.
|
* Utility library that handles the low level encoding required by the Mikrotik
|
||||||
|
* API.
|
||||||
|
*
|
||||||
* @author GideonLeGrange. Possibly some code by janisk left.
|
* @author GideonLeGrange. Possibly some code by janisk left.
|
||||||
*/
|
*/
|
||||||
class Util {
|
final class Util {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* write a command to the output stream
|
||||||
|
*/
|
||||||
static void write(Command cmd, OutputStream out) throws UnsupportedEncodingException, IOException {
|
static void write(Command cmd, OutputStream out) throws UnsupportedEncodingException, IOException {
|
||||||
encode(cmd.getCommand(), out);
|
encode(cmd.getCommand(), out);
|
||||||
for (Parameter param : cmd.getParameters()) {
|
for (Parameter param : cmd.getParameters()) {
|
||||||
encode(String.format("=%s=%s", param.getName(), param.hasValue() ? param.getValue() : "" ), out);
|
encode(String.format("=%s=%s", param.getName(), param.hasValue() ? param.getValue() : ""), out);
|
||||||
}
|
}
|
||||||
String tag = cmd.getTag();
|
String tag = cmd.getTag();
|
||||||
if ((tag != null) && !tag.equals("")) {
|
if ((tag != null) && !tag.equals("")) {
|
||||||
|
|
@ -27,8 +33,9 @@ class Util {
|
||||||
if (!props.isEmpty()) {
|
if (!props.isEmpty()) {
|
||||||
StringBuilder buf = new StringBuilder("=.proplist=");
|
StringBuilder buf = new StringBuilder("=.proplist=");
|
||||||
for (int i = 0; i < props.size(); ++i) {
|
for (int i = 0; i < props.size(); ++i) {
|
||||||
if (i > 0)
|
if (i > 0) {
|
||||||
buf.append(",");
|
buf.append(",");
|
||||||
|
}
|
||||||
buf.append(props.get(i));
|
buf.append(props.get(i));
|
||||||
}
|
}
|
||||||
encode(buf.toString(), out);
|
encode(buf.toString(), out);
|
||||||
|
|
@ -39,45 +46,19 @@ class Util {
|
||||||
out.write(0);
|
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 {
|
* decode bytes from an input stream of Mikrotik protocol sentences into
|
||||||
byte bytes[] = word.getBytes("US-ASCII");
|
* text
|
||||||
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 {
|
static String decode(InputStream in) throws ApiDataException, ApiConnectionException {
|
||||||
StringBuilder res = new StringBuilder();
|
StringBuilder res = new StringBuilder();
|
||||||
decode(in, res);
|
decode(in, res);
|
||||||
return res.toString();
|
return res.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** decode bytes from an input stream into Mikrotik protocol sentences */
|
/**
|
||||||
|
* decode bytes from an input stream into Mikrotik protocol sentences
|
||||||
|
*/
|
||||||
private static void decode(InputStream in, StringBuilder result) throws ApiDataException, ApiConnectionException {
|
private static void decode(InputStream in, StringBuilder result) throws ApiDataException, ApiConnectionException {
|
||||||
try {
|
try {
|
||||||
int len = readLen(in);
|
int len = readLen(in);
|
||||||
|
|
@ -91,8 +72,9 @@ class Util {
|
||||||
buf[i] = (byte) (c & 0xFF);
|
buf[i] = (byte) (c & 0xFF);
|
||||||
}
|
}
|
||||||
String res = new String(buf);
|
String res = new String(buf);
|
||||||
if (result.length() > 0)
|
if (result.length() > 0) {
|
||||||
result.append("\n");
|
result.append("\n");
|
||||||
|
}
|
||||||
result.append(res);
|
result.append(res);
|
||||||
decode(in, result);
|
decode(in, result);
|
||||||
}
|
}
|
||||||
|
|
@ -101,34 +83,6 @@ class Util {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 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
|
* makes MD5 hash of string for use with RouterOS API
|
||||||
*
|
*
|
||||||
|
|
@ -173,4 +127,68 @@ class Util {
|
||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* read length bytes from stream and return length of coming word
|
||||||
|
*/
|
||||||
|
private static 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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue