Compare commits

..

230 Commits
v1.1 ... master

Author SHA1 Message Date
kirillius a3d9be54f7 bump javadoc plugin 2026-07-05 07:29:16 +00:00
kirillius 8d056a7cea hotfix: bump compiler target version 2026-07-05 07:20:10 +00:00
kirillius 60fe180b81 Merge pull request 'added support for !empty response' (#1) from kirillius-patch-1 into master
Reviewed-on: #1
2026-07-05 07:16:16 +00:00
kirillius 0243318870 Обновить pom.xml 2026-07-05 07:15:58 +00:00
kirillius 4939c65f57 added support for !empty response 2026-07-05 07:14:30 +00:00
Gideon Le Grange ec5f665081 Updated README 2024-09-26 14:41:43 +02:00
Gideon Le Grange e3cf6a933a Fixed NPE (Bumped to 3.0.8) 2024-09-26 14:36:54 +02:00
Gideon le Grange ed0468dbf9
Merge pull request #88 from unlimmitted/master
Fix NullPointerException
2024-09-26 14:32:03 +02:00
unlimmitted bfbe50732d Fix NullPointerException 2024-09-21 16:50:56 +03:00
Gideon Le Grange 75a140f79f Merge branch 'master' of github.com:GideonLeGrange/mikrotik-java 2021-01-18 09:31:58 +02:00
Gideon Le Grange 3424482aab Backup 2021-01-18 09:31:34 +02:00
Gideon le Grange 7a629d3b85
Merge pull request #80 from GideonLeGrange/dependabot/maven/junit-junit-4.13.1
Bump junit from 3.8.1 to 4.13.1
2020-10-13 08:06:54 +02:00
dependabot[bot] 603f34ca6e
Bump junit from 3.8.1 to 4.13.1
Bumps [junit](https://github.com/junit-team/junit4) from 3.8.1 to 4.13.1.
- [Release notes](https://github.com/junit-team/junit4/releases)
- [Changelog](https://github.com/junit-team/junit4/blob/main/doc/ReleaseNotes4.13.1.md)
- [Commits](https://github.com/junit-team/junit4/commits/r4.13.1)

Signed-off-by: dependabot[bot] <support@github.com>
2020-10-13 03:31:10 +00:00
Gideon le Grange 0f312c2232
Update README.md 2020-08-24 17:35:19 +02:00
Gideon le Grange 5bc5263aaf
Create maven.yml 2020-08-24 17:34:42 +02:00
Gideon Le Grange 283b3f68ef Updated pom.xml for 3.0.7 2020-01-08 10:57:34 +02:00
Gideon Le Grange 6654c1f629 Updated README for 3.0.7 2020-01-08 10:16:42 +02:00
Gideon le Grange 15e3f75ca0
Update Config.java 2020-01-04 20:16:15 +02:00
Gideon Le Grange 42c281ab88 Fixed bug in handling nested expressions. Added simple excample class 2020-01-04 20:10:14 +02:00
Gideon Le Grange 8fa8711ec5 Added support for error category to ApiCommandException which was weirdly missing. Not complete 2020-01-03 09:03:22 +02:00
Gideon Le Grange 1f31a6f3b5 Cleanup to parser 2020-01-03 08:52:01 +02:00
Gideon le Grange 7d29f9265e
Update grammar.txt 2020-01-03 08:49:58 +02:00
Gideon Le Grange 2fea5e3a6b Added support for parentheses around expressions as per #72 2020-01-03 08:46:09 +02:00
Gideon le Grange 95c0abfec4
Update README.md 2020-01-03 08:14:04 +02:00
Gideon Le Grange 223cb9ef66 Bumped version to 3.0.6 2020-01-03 08:12:35 +02:00
Gideon le Grange 7aac1b95e2
Merge pull request #67 from Metori/master
Fixed issue with special chars in login credentials
2019-10-04 15:04:51 +02:00
Artem Pinchuk 20a5f3fdf6 Fixed issue with special chars in login credentials 2019-04-18 16:16:05 +10:00
Gideon le Grange 7e9c0000a5
Removed obsolete documentation 2018-11-13 08:56:37 +02:00
Gideon le Grange d33f798510
Change order in documentation 2018-11-13 08:55:02 +02:00
Gideon le Grange af9fc52336 Release 3.0.5 2018-11-08 06:39:24 +02:00
Gideon le Grange acefe40f25 Release 3.0.5 2018-11-08 06:38:39 +02:00
Gideon le Grange 8cba90814f
Merge pull request #60 from GideonLeGrange/login-change
Updated login code to support RouterOS 6.43+
2018-11-08 06:35:57 +02:00
Gideon le Grange 81efcd6c73 Implemented support for new login method used from 6.43 2018-11-08 06:30:58 +02:00
Gideon le Grange afad1e6d30 Implemented support for new login method used from 6.43 2018-11-08 06:27:35 +02:00
Gideon le Grange bd969f0d69
Fixed LICENCE.md in README 2018-03-20 12:04:05 +02:00
Gideon le Grange 6d9141a6d2 Updated README for 3.0.4 2017-08-07 21:09:18 +02:00
Gideon le Grange b7a162099f Merge branch 'master' of github.com:GideonLeGrange/mikrotik-java 2017-08-07 21:02:04 +02:00
Gideon le Grange 80947044c6 Removed more stray Kotlin. v3.0.4 2017-08-07 21:01:48 +02:00
Gideon le Grange 3b45c541db Merge pull request #51 from GideonLeGrange/3.0.4-cleanup
Code cleanups
2017-08-07 20:59:36 +02:00
Gideon le Grange 4060e73131 Added travis 2017-08-07 20:58:21 +02:00
Gideon le Grange 0116183b7d Update CONTRIBUTING.md 2017-08-07 17:26:23 +02:00
Gideon le Grange 2f679ecebf Changed README on help & contribution 2017-08-07 17:20:42 +02:00
Gideon le Grange 0deb1df41e Create CONTRIBUTING.md 2017-08-07 17:11:55 +02:00
Gideon le Grange 293c9b36fb Code cleanups 2017-08-07 15:23:06 +02:00
Gideon le Grange 9e90653bc8 Removed stray kotlin plugin from POM 2017-08-03 04:59:49 +02:00
Gideon le Grange 5ab42ddf80 Added un-set with value-time example 2017-06-19 17:47:44 +02:00
Gideon le Grange 1f9eef7d02 Fixed README typos 2017-01-05 15:23:52 +02:00
Gideon le Grange 6440ca0290 Merge branch 'master' of github.com:GideonLeGrange/mikrotik-java 2017-01-05 15:20:37 +02:00
Gideon le Grange aae18ec248 Fixed typo in README 2017-01-05 15:19:49 +02:00
Gideon le Grange f9c7db8ae5 Added try with resources example 2017-01-05 15:19:49 +02:00
Matthew Buckett 6b1b397704 Fixed example code to use close() 2017-01-05 15:19:49 +02:00
Gideon le Grange 36820ec411 Removed stray example 2017-01-05 15:08:42 +02:00
Gideon le Grange ee8d1ba609 Added thread names for easier debug 2017-01-05 15:07:48 +02:00
Gideon le Grange b52c80b600 Merge branch 'master' of github.com:GideonLeGrange/mikrotik-java 2016-10-08 20:03:12 +02:00
Gideon le Grange 40ebed669a Added CharSets example with reference to #40 2016-10-08 20:02:54 +02:00
Gideon le Grange 56bd38672e Update README.md 2016-10-08 19:34:25 +02:00
Gideon le Grange b606fa9574 Preparing for 3.0.3 release 2016-10-08 19:32:40 +02:00
Gideon le Grange e6b714b23a Removed debug example 2016-10-08 19:28:36 +02:00
Gideon le Grange 7f5423dcec Merge branch 'master' of github.com:GideonLeGrange/mikrotik-java 2016-10-08 19:05:41 +02:00
Gideon le Grange a6230a82bf Fix #40. Changed string encoding to UTF8 when sending bytes accross the network 2016-10-08 19:05:09 +02:00
Gideon le Grange 6fe6aeb511 Update README.md 2016-10-07 16:21:15 +02:00
Gideon le Grange e627fc7212 Fix of README for 3.0.2 2016-10-06 22:43:35 +02:00
Gideon le Grange a2437f1afe Updated README & pom for 3.0.2 2016-10-06 22:40:17 +02:00
Gideon le Grange 4b87d3b660 Updateed DownloadConfig example to correctly delete file 2016-10-06 22:33:05 +02:00
Gideon le Grange 2b5ade26fa Merge pull request #37 from TFyre/master
Processing multi-line contents
2016-10-06 22:26:41 +02:00
Gideon le Grange 9efcfe6edc Update README.md 2016-08-04 21:55:10 +02:00
Gideon le Grange 96969bd3cb Update README.md 2016-08-04 21:53:07 +02:00
Gideon le Grange 245214641e Update README.md 2016-06-23 12:52:56 +02:00
Francois Steyn 7ef283df90 Process multi-line "contents" variable when printing a file 2016-05-09 13:03:41 +02:00
Francois 4f0b3208a8 Rename file to remove trailing space 2016-05-09 13:00:23 +02:00
Gideon le Grange 024188f28a Update README.md 2016-04-25 13:23:14 +02:00
Gideon le Grange 6d7a711ce2 Removed mention of semver 2016-02-19 11:29:28 +02:00
Gideon le Grange 30906dc2a9 Version 3.0.1 2016-02-10 21:59:53 +02:00
Gideon le Grange 5ec7940f7b Version 3.0.1 2016-02-10 21:53:12 +02:00
Gideon le Grange 5bb2042fd7 Removed logging imports 2016-02-08 22:19:12 +02:00
Gideon le Grange b6921f8f85 Merge branch 'master' of github.com:GideonLeGrange/mikrotik-java 2016-02-08 22:16:44 +02:00
Gideon le Grange 387da6e6cd Change to fix spurious stack trace on connection close, #34 2016-02-08 22:16:09 +02:00
Gideon le Grange 9d32ae054c Updated README for accuracy 2016-02-04 10:59:06 +02:00
Gideon le Grange c0e97f8138 Version 3.0 2016-02-03 21:52:49 +02:00
Gideon le Grange 7b3c12e9d3 Corrected typo in README 2016-02-03 21:49:03 +02:00
Gideon le Grange d7e058a626 Removed spurios throws 2016-02-03 21:43:22 +02:00
Gideon le Grange 2510c1211c More README updates for 3.0 2016-02-03 21:37:31 +02:00
Gideon le Grange ee8ce0c40a More README updates for 3.0 2016-02-03 21:30:50 +02:00
Gideon le Grange e55cb54569 More README updates for 3.0 2016-02-02 22:52:59 +02:00
Gideon le Grange d36e75f8cb More README updates for 3.0 2016-02-02 22:50:50 +02:00
Gideon le Grange 09cc7dbda8 Removed overloaded connect() methods 2016-02-02 22:42:21 +02:00
Gideon le Grange bbe2909411 Updated example for removed connect() methods 2016-02-02 22:41:51 +02:00
Gideon le Grange 689f59312f Updated example for removed connect() methods 2016-02-02 22:41:37 +02:00
Gideon le Grange d7ca193625 Updated README for 3.0 2016-02-02 22:40:54 +02:00
Gideon le Grange 8882226684 Merge branch 'tls' of github.com:GideonLeGrange/mikrotik-java into tls 2016-02-02 22:15:45 +02:00
Gideon le Grange 9f531220bf Updated anonymous TLS example 2016-02-02 22:14:44 +02:00
Gideon le Grange b23bcdbf4a Working on documentation for 3.0 2016-01-21 17:52:48 +02:00
Gideon le Grange 1559ae26cd Cleaning up for 3.0 2016-01-06 12:48:47 +02:00
Gideon le Grange b87b7bbf8c Updated README for 3.0 2016-01-04 09:40:27 +02:00
Gideon le Grange 46b4a32315 Removed comment reference to connectTLS() 2016-01-04 09:38:15 +02:00
Gideon le Grange 3399ca2619 Created working Anonymous TLS example 2016-01-03 20:43:02 +02:00
Gideon le Grange 3ea5b1ec06 Working on TLS - bumped version 2016-01-03 17:42:51 +02:00
Gideon le Grange c1b8469989 Working on TLS - bumped version 2016-01-03 17:42:07 +02:00
Gideon le Grange 238fa8683d Working on TLS 2016-01-03 17:39:15 +02:00
Gideon le Grange 380ccda45d Working on cleaning up TLS support 2016-01-03 17:37:42 +02:00
Gideon le Grange 84204938e9 Added ConnectTLS example 2016-01-01 10:03:37 +02:00
Gideon le Grange 7390be0aa1 Added ConnectTLS example 2016-01-01 10:03:04 +02:00
Gideon le Grange 0b80064e46 Renamed examples 2015-12-31 18:30:02 +02:00
Gideon le Grange 5d081c8c88 Added missing @throws 2015-05-05 13:44:12 +02:00
Gideon le Grange 56d10d6937 Added missing @throws 2015-05-05 13:41:41 +02:00
Gideon le Grange 2218bacead Merge pull request #23 from GideonLeGrange/pre-2.2
Merge in documentation changes for 2.20
2015-05-05 13:36:32 +02:00
Gideon le Grange 138d93bcb3 Preparing for 2.2 2015-05-05 13:23:35 +02:00
Gideon le Grange 702aa45a18 Preparing for 2.2 release 2015-05-05 13:20:35 +02:00
Gideon le Grange 158386ef44 Corrected example error in README 2015-05-04 13:20:29 +02:00
Gideon le Grange 1bfc4fbb70 Deprecated disconnect() in favour of close() 2015-04-28 14:09:26 +02:00
Gideon le Grange 61691cb9a6 Implemented java.lang.AutoCloseable 2015-04-28 09:52:31 +02:00
GideonLeGrange db373c41d8 Fixed error in README (version in Maven artifact) 2015-04-07 17:08:05 +02:00
Gideon le Grange 69633f3390 Updated README and pom.xml for version 2.1 2015-04-06 21:26:12 +02:00
Gideon le Grange d753a306fd Updated README and pom.xml for version 2.1 2015-04-06 21:25:03 +02:00
GideonLeGrange f9a166afaa Improved README formatting a bit 2015-03-30 17:03:44 +02:00
Gideon le Grange 0219fe43d8 Merge pull request #19 from GideonLeGrange/bug#16
Changes to support connection and command timeouts
2015-03-30 16:49:24 +02:00
GideonLeGrange d86e59c340 Removed getTimeout() until somebody wants it. Don't want bloat 2015-03-30 16:47:35 +02:00
GideonLeGrange eac62b0945 Updated README with information about the timeouts 2015-03-30 16:43:00 +02:00
GideonLeGrange b02bf2eaee Removed old debug printf and improved disconnect() 2015-03-30 16:27:21 +02:00
Gideon le Grange afeeec67db Some cleanups. Fixed timeout corner case in #16 2015-03-29 23:14:32 +02:00
Gideon le Grange 6a73b28b59 Added command timeouts as requested in #16 2015-03-29 22:40:01 +02:00
Gideon le Grange c297aba43e Added command timeouts as requested in #16 2015-03-29 22:34:42 +02:00
Gideon le Grange a2b109ebb7 Fixed mistake in README 2015-03-29 21:14:08 +02:00
Gideon le Grange 9aa06f3380 Working on fix for issue #16 2015-03-29 13:06:34 +02:00
Gideon le Grange 5d05b76fe8 Working on fix for issue #16 2015-03-29 13:05:50 +02:00
Gideon le Grange 60b1bd16f2 Working on fix for issue #16 2015-03-29 12:56:23 +02:00
Gideon le Grange 8bde47a9ad Updated for version 2.0.3 2015-03-28 17:25:08 +02:00
Gideon le Grange 0408db9b61 Updated for version 2.0.3 2015-03-28 17:19:05 +02:00
Gideon le Grange ae639d323f Fixed bug #18 - login with empty username 2015-03-28 17:03:05 +02:00
GideonLeGrange cddb19d11f Changed parsing of =var variables to handle multi-line results better 2014-12-22 15:56:35 +02:00
GideonLeGrange 2a450e260c Changed parsing of =var variables to handle multi-line results better 2014-12-22 15:41:02 +02:00
GideonLeGrange 047cd4246b Added trim() to cope with spaces around host nameS 2014-12-22 12:55:59 +02:00
GideonLeGrange 3f6a930a4d Merge branch 'master' of github.com:GideonLeGrange/mikrotik-java 2014-12-22 12:51:09 +02:00
GideonLeGrange 42569fe1f1 Added IntelliJ exclusions 2014-12-17 11:49:19 +02:00
GideonLeGrange ab6c9d326c Added IntelliJ exclusions 2014-12-17 11:48:58 +02:00
GideonLeGrange 5f90815ddc Added IntelliJ exclusions 2014-12-17 11:44:20 +02:00
Gideon le Grange c3136a7ade Update README.md 2014-11-26 21:50:38 +02:00
Gideon le Grange 040cbb361d Update README.md
Removed inaccurate text from README
2014-11-26 21:45:18 +02:00
GideonLeGrange fc72c58372 Merge branch 'master' of github.com:GideonLeGrange/mikrotik-java 2014-11-26 09:46:09 +02:00
GideonLeGrange 57534fac63 Temp debugging to hunt #14, will remove in 2.0.3 2014-11-26 09:45:53 +02:00
Gideon le Grange 71bf6995c8 Update README.md 2014-11-17 23:59:33 +02:00
Gideon le Grange 599ec6d6f5 Updated README 2014-11-17 23:55:40 +02:00
Gideon le Grange 0efe62750b Update to pom.xml 2014-11-17 23:49:39 +02:00
Gideon le Grange adaca0413e Merge branch 'master' of github.com:GideonLeGrange/mikrotik-java 2014-11-17 22:58:26 +02:00
Gideon le Grange ab750e1379 Updated pom.xml for 2.0.2 2014-11-17 22:55:54 +02:00
Gideon le Grange e6a3643774 Update README.md
Explained version 2.0.2
2014-11-17 22:43:25 +02:00
Gideon le Grange 746e37f6cd Fixed bug #13 - Processor thread not stopped on disconnect 2014-11-17 22:35:48 +02:00
Gideon le Grange 882503b602 Update README.md
Explained the use of maps slightly better.
2014-11-11 03:24:00 +02:00
Gideon le Grange 0c48b49f23 Update README.md 2014-11-06 16:52:51 +02:00
Gideon le Grange 139b39d64a Update README.md 2014-11-06 16:50:19 +02:00
Gideon le Grange 598a0acd35 Update README.md
A note about the examples.
2014-11-06 16:49:46 +02:00
Gideon le Grange 4f9a091058 Update README.md
Added note about semantic versioning
2014-11-06 16:46:47 +02:00
Gideon le Grange 07c925fc51 Updated POM 2014-10-09 21:41:11 +02:00
GideonLeGrange 1d2fb19ef5 Updated version in POM 2014-10-08 08:04:23 +02:00
Gideon le Grange 4af6339ae1 Fixed gpg version in POM 2014-10-08 06:25:03 +02:00
Gideon le Grange 51435187cb Merge branch 'master' of github.com:GideonLeGrange/mikrotik-java 2014-10-08 06:08:42 +02:00
Gideon le Grange 00275d61a4 Fixed regression to #7 caused by fix for #11 2014-10-08 06:08:19 +02:00
Gideon le Grange 5aee8e7292 Updated README for 2.0.1 2014-10-08 05:46:48 +02:00
Gideon le Grange 935c76ae34 Fixed bug #11 2014-10-08 04:40:27 +02:00
Gideon le Grange 8ce5fce77f Cleanups 2014-10-07 15:39:07 +02:00
Gideon le Grange ee10dddb81 Cleanups 2014-10-07 15:38:16 +02:00
Gideon le Grange 82befa76fc Added != support to parser. #11 2014-10-07 11:37:27 +02:00
Gideon le Grange 196131dac1 Added != support to scanner. #11 2014-10-07 11:18:27 +02:00
Gideon le Grange 91b95e8466 Fixed scanning of < and > operators in where clauses. #12 2014-10-07 09:04:27 +02:00
GideonLeGrange 2e431894bf Some small cleanups and refactoring. Fixed minor potential concurrency issue 2014-09-08 13:10:18 +02:00
Gideon le Grange a111646c1f Update README.md 2014-09-08 12:02:25 +02:00
GideonLeGrange 70db71c8cc POM changed for Central 2014-08-29 09:13:58 +02:00
GideonLeGrange bda4629fba Preparing for 2.0.0 2014-08-29 08:29:04 +02:00
GideonLeGrange 9f8b97d421 Code documentation fixes 2014-08-29 08:26:59 +02:00
Gideon le Grange 5fdddcd0c1 Updated with information about 2.0.0 2014-08-29 08:24:54 +02:00
Gideon le Grange e7bb8a4f42 Merge pull request #9 from GideonLeGrange/2.0.0
2.0.0
2014-08-29 08:23:19 +02:00
GideonLeGrange 9e0f38149d Modified README to explain version 2.0.0 2014-06-23 11:26:15 +02:00
GideonLeGrange 3d04b20dca Created version 2.0.0 branch to fix a few non-backwards compatibile problems.
- Added error() and completed() to ResultListener
- Removed ResponseListener
- Refactored internals and examples to account for these changes.
2014-06-23 11:19:15 +02:00
Gideon le Grange f2f3614ff8 Added link to Licence.md 2014-06-17 09:15:53 +02:00
Gideon le Grange af93bbed3c Updated README for version 1.1.6 2014-04-30 15:25:08 +02:00
GideonLeGrange 2ea8ddc349 [maven-release-plugin] prepare for next development iteration 2014-04-30 15:08:45 +02:00
GideonLeGrange b89a91084a [maven-release-plugin] prepare release mikrotik-1.1.6 2014-04-30 15:08:36 +02:00
GideonLeGrange 56a9f7d8f3 [maven-release-plugin] prepare for next development iteration 2014-04-30 14:55:18 +02:00
GideonLeGrange 7621b64bf1 [maven-release-plugin] prepare release mikrotik-1.1.6 2014-04-30 14:55:10 +02:00
GideonLeGrange 80c1c0ef41 [maven-release-plugin] prepare for next development iteration 2014-04-30 14:40:11 +02:00
GideonLeGrange 688b86e0fa [maven-release-plugin] prepare release mikrotik-1.1.6 2014-04-30 14:40:01 +02:00
GideonLeGrange b1f6443f22 Preparing for Maven 2014-04-30 14:36:35 +02:00
GideonLeGrange 3a96bee808 [maven-release-plugin] prepare for next development iteration 2014-04-30 14:29:39 +02:00
GideonLeGrange 33feda40d3 [maven-release-plugin] prepare release mikrotik-1.1.6 2014-04-30 14:29:31 +02:00
GideonLeGrange 2bac9a5b21 Preparing for Maven 2014-04-30 14:29:05 +02:00
GideonLeGrange a33948f807 [maven-release-plugin] prepare release mikrotik-1.1.6 2014-04-30 14:22:37 +02:00
GideonLeGrange 1cb3b113a5 Maven release 2014-04-30 14:20:36 +02:00
GideonLeGrange 2415da9368 [maven-release-plugin] prepare release mikrotik-1.1.6 2014-04-30 14:17:07 +02:00
GideonLeGrange d3949477bf [maven-release-plugin] prepare for next development iteration 2014-04-30 13:50:00 +02:00
GideonLeGrange 5eabf89533 [maven-release-plugin] prepare release mikrotik-1.1.6 2014-04-30 13:49:52 +02:00
GideonLeGrange 78720eda09 Preparing Maven release 2014-04-30 13:48:41 +02:00
GideonLeGrange 0df72f5240 [maven-release-plugin] prepare for next development iteration 2014-04-30 13:44:08 +02:00
GideonLeGrange d49cd1ae47 [maven-release-plugin] prepare release 1.1.6 2014-04-30 13:44:00 +02:00
GideonLeGrange d1f8565dff [maven-release-plugin] prepare release 1.1.5 2014-04-30 13:42:57 +02:00
GideonLeGrange 22032d7f71 [maven-release-plugin] prepare for next development iteration 2014-04-30 13:28:48 +02:00
GideonLeGrange c107267a63 [maven-release-plugin] prepare release mikrotik-1.1.5 2014-04-30 13:28:39 +02:00
Gideon le Grange 015090c8db Fixed spelling mistake 2014-04-26 08:03:20 +02:00
Gideon le Grange 267cb151f7 Updated for version 1.1.5 2014-04-24 15:01:47 +02:00
GideonLeGrange 6503eefa38 Fixed issue #7 2014-04-24 14:51:58 +02:00
Gideon le Grange 160b7f5152 Updated for 1.1.4 2014-04-21 20:45:05 +02:00
Gideon le Grange 16eb6cb17e Fixed scanner bug causing .id=*nn values not to be scanned correctly. 2014-04-21 20:36:59 +02:00
Gideon le Grange bd08b964ee Update README.md 2014-04-15 07:17:27 +02:00
Gideon le Grange 11e7db75d5 Update README.md 2014-04-14 20:23:25 +02:00
Gideon le Grange a115355d22 Merge pull request #6 from GideonLeGrange/parser-fix
Fixed parser problems described in issues #3, #4 and #5
2014-04-14 20:17:57 +02:00
Gideon le Grange 15c48def68 Fixed quoting in parser 2014-04-14 20:16:55 +02:00
Gideon le Grange 11e9a5805e Update README.md
Explained about bugs
2014-04-14 16:31:13 +02:00
Gideon le Grange 351f233def Update README.md 2014-04-14 16:25:33 +02:00
GideonLeGrange a78ad48d39 Slight scanner and parser error handling improvements 2014-04-14 16:19:20 +02:00
GideonLeGrange 10f88ad9f0 Slight scanner and parser error handling improvements 2014-04-14 16:19:03 +02:00
GideonLeGrange c44c02294b More parser fixes and testing 2014-04-14 16:01:52 +02:00
GideonLeGrange b5eb058f25 Slight scanner and parser error handling improvements 2014-04-14 14:52:47 +02:00
GideonLeGrange 3d7fee5a4b Removed stray antlr 2014-04-14 14:44:32 +02:00
GideonLeGrange 99fa3149fd Re-wrote scanning and parsing to handle problems from issues #3 and #4 2014-04-14 14:43:03 +02:00
GideonLeGrange 7c73559bb1 Re-wrote scanning and parsing to handle problems from issues #3 and #4 2014-04-14 14:42:51 +02:00
Gideon le Grange 2633088303 Working out ANTLR grammar 2014-04-11 09:01:02 +02:00
GideonLeGrange c4f2b9ae65 Switched to ANTLR parser branch 2014-04-10 16:35:18 +02:00
Gideon le Grange a8dae95e49 Update README.md
Updated README.md with information about TLS issues
2014-03-25 20:34:34 +02:00
Gideon le Grange f30d28ffab Merge pull request #1 from aro1976/master
first junit tests
2014-03-25 20:28:24 +02:00
aro1976@gmail.com b5ba142a5e adding .gitignore 2014-03-24 22:45:38 -03:00
aro1976@gmail.com 072e3c4fd7 connection tests 2014-03-24 22:41:54 -03:00
GideonLeGrange 2e074e5c53 Modified to explain version 1.1.2 2014-01-29 11:10:53 +02:00
GideonLeGrange 500fa4c699 Modified file download example 2014-01-29 11:08:34 +02:00
GideonLeGrange afe511b593 Added support for multi-line results 2014-01-29 11:05:59 +02:00
GideonLeGrange 666c6c4e41 Merge branch 'master' of https://github.com/GideonLeGrange/mikrotik-java 2014-01-20 15:44:00 +02:00
GideonLeGrange e1caaf2b31 Fixed to not check for router reachability before connecting 2014-01-20 15:43:42 +02:00
Gideon le Grange 6950dc6592 Updated to explain v1.1.1 2014-01-20 15:33:11 +02:00
GideonLeGrange 74c77bf4d8 Merge branch 'master' of https://github.com/GideonLeGrange/mikrotik-java 2014-01-20 14:50:21 +02:00
GideonLeGrange 044e4e76a0 Fixed to not check for router reachability before connecting 2014-01-20 14:48:53 +02:00
Gideon le Grange 3aded5419f Readme typo fix 2014-01-15 07:43:14 +02:00
Gideon le Grange 3a9238b41a Updated readme to explain 1.1 2014-01-07 21:30:27 +02:00
38 changed files with 1490 additions and 526 deletions

24
.github/workflows/maven.yml vendored Normal file
View File

@ -0,0 +1,24 @@
# This workflow will build a Java project with Maven
# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven
name: Java CI with Maven
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up JDK 1.8
uses: actions/setup-java@v1
with:
java-version: 1.8
- name: Build with Maven
run: mvn -B package --file pom.xml

32
.gitignore vendored
View File

@ -1,23 +1,4 @@
# 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/
@ -30,12 +11,17 @@ nbactions.xml
# IntelliJ
.idea/
*.iml
# Eclipse
.settings/
# Project
Mtik.java
.classpath
# Jekyll
_site/
.project
# Maven
pom.xml.versionsBackup
# MacOS
.DS_Store

1
.travis.yml Normal file
View File

@ -0,0 +1 @@
language: java

54
CONTRIBUTING.md Normal file
View File

@ -0,0 +1,54 @@
Thank you for considering contributing to this project. This library is one tiny way I am giving back to the open source community, and I am
always amazed and humbled when users contribute back.
There are several ways of contributing to this small open source library:
* You can ask for help
* You can log a bug
* You can contribute code
# Ask for help
If you experience a problem using the mikrotik-java library, the best way to get in touch with me is to either send me an
email or open an issue on GitHub. While you can seek help on StackOverflow or the Mikrotik Forums (both of which I've seen
people do), I do not read those sites often. If you wish to log your question there (it makes sense for it to be searchable or
in a place where other interested people can find it) and want me to take a look at it, please send me an email pointing to
the question.
I find that it is very easy to help users of this library if they include the following things which are usually quite relevant:
* A snipped of sample code showing what they're doing
* The stack trace (if there is one) when it goes wrong
* Version of the library they're using
# Log a bug
If you are pretty sure you have found a bug in the library, please log a bug by opening an issue on GithHub. When things get buggy,
the following information (mostly the same as for asking help) becomes important:
* A snipped of sample code showing what they're doing
* The stack trace (if there is one) when it goes wrong
* Version of the library they're using
* The version of RouterOS they're connecting to
I take bug reports very seriously, and will usually bring out immediate minor versions to fix show stoppers.
# Contribute code
If you want to contribute code to this library, you're welcome. Please read this section before submitting pull requests.
## If you're fixing a bug
If you've fixed a bug, please submit the pull request on GitHub. If there is an already existing issue being fixed, please
reference that issue so I can look at what you're fixing, and if not, please explain the issue you're fixing in the pull request.
## If you want new functionality or API changes
Please contact me via email before you create a pull request for feature additions or API changes. The mikrotik-java library is stable and,
for it's current purpose, feature complete. The situations in which I will add features to the library or change it's interface are:
* If Mikrotik adds new RouterOS functionality that requires an API change to remain feature complete
* If some very bad design flaw in the interface is discovered
* If somebody has a truly compelling reason for adding to the library. To be compelling means the added functionality must be useful
for more users than just the submitter
* If some new pattern or convention arises in the Java standard libraries that is useful
I *really* hate declining people's PRs, but I will do so if I don't think they add value as explained above, or if I can't figure out why
they want the change. For this reason, if you are at all unsure if your PR will be accepted, please contact me and discuss what you want
before submitting a PR.
Gideon

178
README.md
View File

@ -1,50 +1,119 @@
mikrotik-java
=============
# mikrotik-java
A Java client library implementation for the Mikrotik RouterOS API.
This project provides a Java client to manipulate Mikrotik routers using the remote API. Simple things must be easy to do, and complex things must be possible.
Versions
--------
## Versions
The current stable version is 1.0. The master branch is on version 1.1, which is the currently being tested and will be released soon.
![Java CI with Maven](https://github.com/GideonLeGrange/mikrotik-java/workflows/Java%20CI%20with%20Maven/badge.svg)
Version 1.1 will add:
* TLS (SSL) support to encrypt API traffic.
**The current stable version is 3.0.8**
Version 3.0.8 fixes a null pointer error
Examples
========
### Changes in version 3.0:
Version 3.0 addresses the problems the API had around TLS encryption. The way secure connections are implemented is changed so that the user has complete control over the creation of TLS sockets. To this end:
* A new method, `connect(SocketFactory fact, String host, int port, int timeout)`, was added to allow for better user control over sockets and especially encryption.
* The `connectTLS()` API methods were removed.
* Most of the overloaded `connect()` methods were removed.
* Added a pre-built `jar` file to the downloads.
Further changes include:
* The previously deprecated `disconnect()` method is removed.
#### Versions 1.x and 2.x
Versions 1 and 2 are considered *obsolete* and will no longer be supported or patched.
## Getting the API
Maven users can use the artifact from Maven Central with this dependency:
```xml
<dependency>
<groupId>me.legrange</groupId>
<artifactId>mikrotik</artifactId>
<version>3.0.8</version>
</dependency>
```
You can also download the pre-built jar file, or a zip or tar.gz file with the source for the latest release [here](https://github.com/GideonLeGrange/mikrotik-java/releases/latest)
## Asking for help or contributing
I welcome contributions, be it bug fixes or other improvements.
Please read [CONTRIBUTING.md](CONTRIBUTING.md) for information on how to contribute to this project.
# Using the API
How to use the API is best illustrated by examples.
These examples should illustrate how to use this library. Please note that I assume that the user is proficient in Java and understands the Mikrotik command line syntax. The command line syntax gives you an indication of what commands you can pass, but the RouterOS API used by this library does not support everyting.
Some things to consider when debugging your API calls are:
* The RouterOS API does not support auto-completion. You need to write out command and parameter names. For example, you can't say `/ip/hotspot/user/add name=john add=10.0.0.1`, you need to write out `address`.
* You need to quote values with spaces in. You can't say `name=Joe Blogs`, you need to use `name="Joe Blogs"`
* Exceptions with a root cause of `ApiCommandException` are errors received from the remote RouterOS device and contain the error message received.
## Opening a connection
Here is a simple example: Connect to a router and reboot it.
```java
ApiConnection con = ApiConnection.connect("10.0.1.1"); // connect to router
con.login("admin","password"); // log in to router
con.execute("/system/reboot"); // execute a command
con.disconnect(); // disconnect from router
con.close(); // disconnect from router
```
If your router does not use the default API port, the port can be specified when connecting:
The above example shows a easy way of creating an unencrypted connection using the default API port and timeout, which is useful for development and testing.
### TLS encryption
For production environments, encrypting API traffic is recommended. To do this you need to open a TLS connection to the router by passing an instance of the `SocketFactory` you wish to use to construct the TLS socket to the API:
```java
ApiConnection con = ApiConnection.connect("10.0.1.1", 1337); // connect to router on port 1337
ApiConnection con = ApiConnection.connect(SSLSocketFactory.getDefault(), "10.0.1.1", ApiConnection.DEFAULT_TLS_PORT, ApiConnection.DEFAULT_CONNECTION_TIMEOUT);
```
To open an encrypted (TLS) connection is as simple, assuming the default API-SSL port is used:
Above an instance of the default SSL socket factory is passed to the API. This will work as long as the router's certificate has been added to the local key store. Besides allowing the user to specify the socket factory, the above method also gives full control over the TCP Port and connection timeout.
RouterOS also supports anonymous TLS. An example showing how to create a socket factory for anonymous TLS is `AnonymousSocketFactory` in the examples directory.
### Connection timeouts
By default, the API will generate an exception if it cannot connect to the specified router. This can take place immediately (typically if the OS returns a 'Connection refused' error), but can also take up to 60 seconds if the router host is firewalled or if there are other network problems. This 60 seconds is the 'default connection timeout' an can be overridded by passing the preferred timeout to the APi as last parameter in a ```connect()``` call. For example:
```java
ApiConnection con = ApiConnection.connectTLS("10.0.1.1"); // connect to router using TLS
ApiConnection con = ApiConnection.connect(SSLSocketFactory.getDefault(), "10.0.1.1", ApiConnection.DEFAULT_TLS_PORT, 2000); // connect to router on the default API port and fail in 2 seconds
```
Currently only anonymous TLS is supported, not certificates.
### Constants
Some constants are provided in `ApiConnection` to make it easier for users to construct connections with default ports and timeouts:
In following examples the connection, logging and disconnection code will not be repeated.
Constant | Use for | Value
---------|---------|------
DEFAULT_PORT | Default TCP `port` value for unencrypyted connections | 8728
DEFAULT_TLS_PORT | Default TCP `port` value for encrypyted connections | 8729
DEFAULT_CONNECTION_TIMEOUT | Default connection `timeout` value (ms) | 60000
Reading data
------------
### Try with resources
A simple example that returns a result: Print all interfaces.
The API can also be used in a "try with resources" statement which will ensure that the connection is closed:
```java
try (ApiConnection con = ApiConnection.connect(SocketFactory.getDefault(), Config.HOST, ApiConnection.DEFAULT_PORT, 2000)) {
con.login(Config.USERNAME, Config.PASSWORD);
con.execute("/user/add name=eric");
}
```
In following examples the connection, login and disconnection code will not be repeated. In all cases it is assumed that an `ApiConnection` has been established, `login()` has been called, and that the connection is called `con`.
## Reading data
A simple example that returns a result - Print all interfaces:
```java
@ -54,6 +123,14 @@ for (Map<String,String> r : rs) {
}
```
Results are returned as a list of maps of String key/value pairs. The reason for this is that a command can return multiple results, which have multpile variables. For example, to print the names of all the interfaces returned in the command above, do:
```java
for (Map<String, String> map : rs) {
System.out.println(map.get("name"));
}
```
### Filtering results
The same query, but with the results filtered: Print all interfaces of type 'vlan'.
@ -64,14 +141,13 @@ List<Map<String, String>> rs = con.execute("/interface/print where type=vlan");
### Selecting returned fields
The same query, but we only want certain result fields names Print all interfaces of type 'vlan' and return their name.
The same query, but we only want certain result fields names: Print all interfaces of type 'vlan' and return just their name:
```java
List<Map<String, String>> rs = con.execute("/interface/print where type=vlan return name");
```
Writing data
------------
## Writing data
Creating, modifying and deleting configuration objects is of course possible.
@ -99,12 +175,24 @@ And now remove the object:
con.execute("/interface/gre/remove .id=gre1");
```
Asynchronous commands
---------------------
### Un-setting a variable on an object
Un-setting a variable is a bit different, and you need to use a parameter called `value-name`. This isn't well documented. Let's say you have a firewall rule that was set up like this:
```java
con.execute("/ip/firewall/filter/add action=accept chain=forward time=00:00:01-01,mon")
```
Assuming the rule can be accessed as `.id=*1`, you un-set it by using `value-name` as seen below:
```java
con.execute("/ip/firewall/filter/unset .id=*1 value-name=time");
```
## Asynchronous commands
We can run some commands asynchronously in order to continue receiving updates:
This example shows how to run '/interface wireless monitor' and have the result sent to a listener object, which prints it
This example shows how to run '/interface wireless monitor' and have the result sent to a listener object, which prints it:
```java
String tag = con.execute("/interface/wireless/monitor .id=wlan1 return signal-to-noise",
@ -114,23 +202,49 @@ String tag = con.execute("/interface/wireless/monitor .id=wlan1 return signal-to
System.out.println(result);
}
public void error(MikrotikApiException e) {
System.out.println("An error occurred: " + e.getMessage());
}
public void completed() {
System.out.println("Asynchronous command has finished");
}
}
);
```
The above command will run and send results asynchronously as they become available, until it is canceled. The command (identified by the unique String retruned)
is canceled like this:
The `ResultListener` interface has three methods the user needs to implement:
* `receive()` is called to receive results produced by the router from the API.
* `error()` is called when an exception is raised based on a 'trap' received from the router or another (typically connection) problem.
* `completed()` is called when the router has indicated that the command has completed or has been cancelled.
The above command will run and send results asynchronously as they become available, until it is canceled. The command (identified by the unique String returned) is canceled like this:
```java
con.cancel(tag);
```
References
==========
## Command timeouts
Command timeouts can be used to make sure that synchronous commands either return or fail within a specific time. Command timeouts are separate from the connection timeout used in ```connect()```, and can be set using ```setTimeout()```. Here is an example:
```java
ApiConnection con = ApiConnection.connect("10.0.1.1"); // connect to router
con.setTimeout(5000); // set command timeout to 5 seconds
con.login("admin","password"); // log in to router
con.execute("/system/reboot"); // execute a command
```
It is important to note that command timeouts can be set before ```login()``` is called, and can therefore influence the behaviour of login.
The default command timeout, if none is set by the user, is 60 seconds.
# References
The RouterOS API is documented here: http://wiki.mikrotik.com/wiki/Manual:API
Licence
=======
# Licence
This library is released under the Apache 2.0 licence. See the [LICENCE.md](LICENCE.md) file
This library is released under the Apache 2.0 licence. See the Licence.md file

246
pom.xml
View File

@ -1,86 +1,168 @@
<?xml version="1.0" encoding="UTF-8"?>
<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">
<parent>
<groupId>org.sonatype.oss</groupId>
<artifactId>oss-parent</artifactId>
<version>7</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>me.legrange</groupId>
<artifactId>mikrotik</artifactId>
<version>1.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>Mikrotik API Java Client Library</name>
<url>https://github.com/GideonLeGrange/mikrotik-java</url>
<licenses>
<license>
<name>The Apache Software License, Version 2.0</name>
<url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
<distribution>repo</distribution>
</license>
</licenses>
<scm>
<url>git@github.com:GideonLeGrange/mikrotik-java.git</url>
<connection>scm:git:git@github.com:GideonLeGrange/mikrotik-java.git</connection>
<developerConnection>scm:git:git@github.com:GideonLeGrange/mikrotik-java.git</developerConnection>
</scm>
<build>
<parent>
<groupId>org.sonatype.oss</groupId>
<artifactId>oss-parent</artifactId>
<version>9</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>me.legrange</groupId>
<artifactId>mikrotik</artifactId>
<version>3.0.8.1</version>
<packaging>jar</packaging>
<name>Mikrotik API Java Client Library</name>
<url>https://github.com/GideonLeGrange/mikrotik-java</url>
<licenses>
<license>
<name>The Apache Software License, Version 2.0</name>
<url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
<distribution>repo</distribution>
</license>
</licenses>
<scm>
<url>git@github.com:GideonLeGrange/mikrotik-java.git</url>
<connection>scm:git:git@github.com:GideonLeGrange/mikrotik-java.git</connection>
<developerConnection>scm:git:git@github.com:GideonLeGrange/mikrotik-java.git</developerConnection>
</scm>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.3.2</version>
<configuration>
<showDeprecation>true</showDeprecation>
<source>8</source>
<target>8</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.3.2</version>
<executions>
<execution>
<id>default-jar</id>
<phase>package</phase>
<goals>
<goal>jar</goal>
</goals>
<configuration>
<excludes>
<exclude>**/examples</exclude>
<exclude>**/examples/*</exclude>
</excludes>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>2.2.1</version>
<executions>
<execution>
<id>attach-sources</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.7.0</version>
<configuration>
<excludePackageNames>example;*.impl</excludePackageNames>
</configuration>
<executions>
<execution>
<id>attach-javadocs</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>release</id>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.3.2</version>
<configuration>
<showDeprecation>true</showDeprecation>
<source>1.5</source>
<target>1.5</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.3.2</version>
<executions>
<execution>
<id>default-jar</id>
<phase>package</phase>
<goals>
<goal>jar</goal>
</goals>
<configuration>
<excludes>
<exclude>**/examples</exclude>
<exclude>**/examples/*</exclude>
</excludes>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>2.9.1</version>
<configuration>
<excludePackageNames>example;*.impl</excludePackageNames>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>2.4</version>
<executions>
<execution>
<id>attach-sources</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>2.10.1</version>
<executions>
<execution>
<id>attach-javadocs</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-gpg-plugin</artifactId>
<version>1.6</version>
<executions>
<execution>
<id>sign-artifacts</id>
<phase>verify</phase>
<goals>
<goal>sign</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.sonatype.plugins</groupId>
<artifactId>nexus-staging-maven-plugin</artifactId>
<version>1.6.5</version>
<extensions>true</extensions>
<configuration>
<serverId>ossrh</serverId>
<nexusUrl>https://oss.sonatype.org/</nexusUrl>
<autoReleaseAfterClose>true</autoReleaseAfterClose>
</configuration>
</plugin>
</plugins>
</build>
<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>
</build>
<distributionManagement>
<snapshotRepository>
<id>ossrh</id>
<url>https://oss.sonatype.org/content/repositories/snapshots</url>
</snapshotRepository>
</distributionManagement>
</profile>
</profiles>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.1</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@ -7,18 +7,23 @@ import me.legrange.mikrotik.MikrotikApiException;
*
* @author gideon
*/
public class Example6 extends Example {
public class AddAndModify extends Example {
public static void main(String... args) throws Exception {
Example6 ex = new Example6();
AddAndModify ex = new AddAndModify();
ex.connect();
ex.test();
ex.disconnect();
}
private void test() throws MikrotikApiException, InterruptedException {
con.execute("/interface/gre/add remote-address=10.0.1.1 name=gre1 keepalive=10");
System.out.println("Creating interface gre1");
con.execute("/interface/gre/add remote-address=1.2.3.4 name=gre1 keepalive=10 comment='test comment'");
System.out.println("Adding firewall rule for interface gre1");
con.execute("/ip/firewall/filter/add action=drop chain=forward in-interface=gre1 protocol=udp dst-port=78,80");//,80,32");
System.out.println("Waiting 10 seconds");
Thread.sleep(10000); // 10 seconds for the user to look on the router to see the interface with /interface gre print
System.out.println("Changing IP for interface gre1");
con.execute("/interface/gre/set remote-address=172.16.1.1 .id=gre1");
// now look again and the IP has changed
}

View File

@ -0,0 +1,86 @@
/*
* Copyright 2016 gideon.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package examples;
import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.LinkedList;
import java.util.List;
import javax.net.SocketFactory;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
/**
*
* @since 3.0
* @author Gideon le Grange https://github.com/GideonLeGrange
*/
public class AnonymousSocketFactory extends SocketFactory {
@Override
public Socket createSocket() throws IOException {
return fixSocket((SSLSocket) SSLSocketFactory.getDefault().createSocket());
}
@Override
public Socket createSocket(String host, int port) throws IOException, UnknownHostException {
return fixSocket((SSLSocket) SSLSocketFactory.getDefault().createSocket(host, port));
}
@Override
public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException, UnknownHostException {
return fixSocket((SSLSocket) SSLSocketFactory.getDefault().createSocket(host, port, localHost, localPort));
}
@Override
public Socket createSocket(InetAddress address, int port) throws IOException {
return fixSocket((SSLSocket) SSLSocketFactory.getDefault().createSocket(address, port));
}
@Override
public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {
return fixSocket((SSLSocket) SSLSocketFactory.getDefault().createSocket(address, port, localAddress, localPort));
}
private Socket fixSocket(SSLSocket ssl) {
List<String> cs = new LinkedList<>();
// not happy with this code. Without it, SSL throws a "Remote host closed connection during handshake" error
// caused by a "SSL peer shut down incorrectly" error
for (String s : ssl.getSupportedCipherSuites()) {
if (s.startsWith("TLS_DH_anon")) {
cs.add(s);
}
}
ssl.setEnabledCipherSuites(cs.toArray(new String[]{}));
return ssl;
}
public static SocketFactory getDefault() {
if (fact == null) {
fact = new AnonymousSocketFactory();
}
return fact;
}
private AnonymousSocketFactory() {
}
private static AnonymousSocketFactory fact;
}

View File

@ -9,24 +9,34 @@ import me.legrange.mikrotik.ResultListener;
*
* @author gideon
*/
public class Example4 extends Example {
public class AsyncCommand extends Example {
public static void main(String... args) throws Exception {
Example4 ex = new Example4();
AsyncCommand ex = new AsyncCommand();
ex.connect();
ex.test();
ex.disconnect();
}
private void test() throws MikrotikApiException, InterruptedException {
String id = con.execute("/interface/wireless/monitor .id=wlan1", new ResultListener() {
String id = con.execute("/interface/wireless/monitor .id=wlan1 .proplist=signal-strength", new ResultListener() {
private int prev = 0;
public void receive(Map<String, String> result) {
int val = Integer.parseInt(result.get("signal-strength"));
System.out.println(result);
/* int val = Integer.parseInt(result.get("signal-strength"));
String sym = (val == prev) ? " " : ((val < prev) ? "-" : "+");
System.out.printf("%d %s\n", val, sym);
prev = val;
*/ }
@Override
public void error(MikrotikApiException ex) {
throw new RuntimeException(ex.getMessage(), ex);
}
@Override
public void completed() {
}

View File

@ -2,17 +2,17 @@ package examples;
import java.util.Map;
import me.legrange.mikrotik.MikrotikApiException;
import me.legrange.mikrotik.ResponseListener;
import me.legrange.mikrotik.ResultListener;
/**
* Example 5: Asynchronous results, with error and completion. Run a command and receive results, errors and completion notification for it asynchronously with a ResponseListener
*
* @author gideon
*/
public class Example5 extends Example {
public class AsyncWithErrorHandling extends Example {
public static void main(String... args) throws Exception {
Example5 ex = new Example5();
AsyncWithErrorHandling ex = new AsyncWithErrorHandling();
ex.connect();
ex.test();
ex.disconnect();
@ -20,9 +20,10 @@ public class Example5 extends Example {
private void test() throws MikrotikApiException, InterruptedException {
boolean completed = false;
String id = con.execute("/interface/wireless/monitor .id=wlan1", new ResponseListener() {
String id = con.execute("/interface/wireless/monitor .id=wlan1", new ResultListener() {
private int prev = 0;
@Override
public void receive(Map<String, String> result) {
int val = Integer.parseInt(result.get("signal-strength"));
String sym = (val == prev) ? " " : ((val < prev) ? "-" : "+");
@ -30,11 +31,13 @@ public class Example5 extends Example {
prev = val;
}
@Override
public void error(MikrotikApiException ex) {
System.out.printf("An error ocurred: %s\n", ex.getMessage());
ex.printStackTrace();
}
@Override
public void completed() {
System.out.printf("The request has been completed\n");
}

View File

@ -0,0 +1,34 @@
package examples;
import java.util.List;
import java.util.Map;
import me.legrange.mikrotik.MikrotikApiException;
/**
* Example to show that different character sets may work some times.
*
* @author gideon
*/
public class CharacterSets extends Example {
private static final String JAPANESE = "事報ハヤ久送とゅ歳用ト候新放すルドう二5春園ユヲロ然納レ部悲と被状クヘ芸一ーあぽだ野健い産隊ず";
private static final String CRYLLIC = "Лорем ипсум долор сит амет, легере елояуентиам хис ид. Елигенди нолуиссе вих ут. Нихил";
private static final String ARABIC = "تجهيز والمانيا تم قام. وحتّى المتاخمة ما وقد. أسر أمدها تكبّد عل. فقد بسبب ترتيب استدعى أم, مما مع غرّة، لأداء. الشتاء، عسكرياً";
public static void main(String... args) throws Exception {
CharacterSets ex = new CharacterSets();
ex.connect();
ex.test();
ex.disconnect();
}
private void test() throws MikrotikApiException {
con.execute("/ip/hotspot/user/add name=userJ comment='" + JAPANESE + "'");
con.execute("/ip/hotspot/user/add name=userC comment='" + CRYLLIC + "'");
con.execute("/ip/hotspot/user/add name=userA comment='" + ARABIC + "'");
for (Map<String, String> res : con.execute("/ip/hotspot/user/print return name,comment")) {
System.out.printf("%s : %s\n", res.get("name"), res.get("comment"));
}
}
}

View File

@ -9,10 +9,10 @@ import me.legrange.mikrotik.MikrotikApiException;
*
* @author gideon
*/
public class Example3 extends Example {
public class CommandWithWhere extends Example {
public static void main(String... args) throws Exception {
Example3 ex = new Example3();
CommandWithWhere ex = new CommandWithWhere();
ex.connect();
ex.test();
ex.disconnect();

View File

@ -2,14 +2,13 @@ package examples;
/**
* Config class for examples
*
* @author gideon
*/
public class Config {
public static final String HOST = "10.0.1.3";
public static final String USERNAME = "admin";
public static final String PASSWORD = "";
public static final String HOST = "ce2.ter.cpt.adept.za.net";
public static final String USERNAME = "adept";
public static final String PASSWORD = "34ffp9";
}

View File

@ -0,0 +1,54 @@
/*
* Copyright 2015 gideon.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package examples;
import java.util.List;
import java.util.Map;
import me.legrange.mikrotik.ApiConnection;
import me.legrange.mikrotik.MikrotikApiException;
/**
* Example: Open an Anonymous TLS connection
*
* @author gideon
*/
public class ConnectTLSAnonymous {
public static void main(String... args) throws Exception {
ConnectTLSAnonymous ex = new ConnectTLSAnonymous();
ex.connect();
ex.test();
ex.disconnect();
}
private void test() throws MikrotikApiException {
List<Map<String, String>> results = con.execute("/interface/print");
for (Map<String, String> result : results) {
System.out.println(result);
}
}
protected void connect() throws Exception {
con = ApiConnection.connect(AnonymousSocketFactory.getDefault(), Config.HOST, ApiConnection.DEFAULT_TLS_PORT, ApiConnection.DEFAULT_CONNECTION_TIMEOUT);
con.login(Config.USERNAME, Config.PASSWORD);
}
protected void disconnect() throws Exception {
con.close();
}
private ApiConnection con;
}

View File

@ -0,0 +1,55 @@
/*
* Copyright 2015 gideon.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package examples;
import java.util.List;
import java.util.Map;
import javax.net.ssl.SSLSocketFactory;
import me.legrange.mikrotik.ApiConnection;
import me.legrange.mikrotik.MikrotikApiException;
/**
* Example: Open a TLS connection
*
* @author gideon
*/
public class ConnectTLSCertificate {
public static void main(String... args) throws Exception {
ConnectTLSCertificate ex = new ConnectTLSCertificate();
ex.connect();
ex.test();
ex.disconnect();
}
private void test() throws MikrotikApiException {
List<Map<String, String>> results = con.execute("/interface/print");
for (Map<String, String> result : results) {
System.out.println(result);
}
}
protected void connect() throws Exception {
con = ApiConnection.connect(SSLSocketFactory.getDefault(), Config.HOST, ApiConnection.DEFAULT_TLS_PORT, ApiConnection.DEFAULT_CONNECTION_TIMEOUT);
con.login(Config.USERNAME, Config.PASSWORD);
}
protected void disconnect() throws Exception {
con.close();
}
private ApiConnection con;
}

View File

@ -0,0 +1,30 @@
package examples;
import java.util.List;
import java.util.Map;
import me.legrange.mikrotik.MikrotikApiException;
/**
* Example 7: Dump your complete config
*
* @author gideon
*/
public class DownloadConfig extends Example {
public static void main(String... args) throws Exception {
DownloadConfig ex = new DownloadConfig();
ex.connect();
ex.test();
ex.disconnect();
}
private void test() throws MikrotikApiException, InterruptedException {
con.execute("/export file=conf");
List<Map<String, String>> res = con.execute("/file/print detail where name=conf.rsc");
con.execute("/file/remove .id=conf.rsc");
String text = res.get(0).get("contents");
for (String line : text.split("\r")) {
System.out.println(line);
}
}
}

View File

@ -1,5 +1,6 @@
package examples;
import javax.net.SocketFactory;
import me.legrange.mikrotik.ApiConnection;
/**
@ -9,12 +10,12 @@ import me.legrange.mikrotik.ApiConnection;
abstract class Example {
protected void connect() throws Exception {
con = ApiConnection.connect(Config.HOST);
con = ApiConnection.connect(SocketFactory.getDefault(), Config.HOST, ApiConnection.DEFAULT_PORT, 2000);
con.login(Config.USERNAME, Config.PASSWORD);
}
protected void disconnect() throws Exception {
con.disconnect();
con.close();
}
protected ApiConnection con;

View File

@ -0,0 +1,33 @@
package examples;
import me.legrange.mikrotik.MikrotikApiException;
import java.util.List;
import java.util.Map;
/**
* Example 2: A command that returns results. Print all interfaces
*
* @author gideon
*/
public class NestedExpressions extends Example {
public static void main(String... args) throws Exception {
NestedExpressions ex = new NestedExpressions();
ex.connect();
ex.test("/ip/firewall/nat/print where (src-address=\"192.168.15.52\" or src-address=\"192.168.15.53\")");
ex.test("/ip/firewall/nat/print where chain=api_test and (src-address=192.168.15.52) and action=log ");
ex.test("/ip/firewall/nat/print where chain=api_test and (src-address=192.168.15.53 or src-address=192.168.15.52) and action=log ");
ex.test("/ip/firewall/nat/print where chain=api_test and (src-address=\"192.168.15.53\" or src-address=\"192.168.15.52\") and action=log ");
ex.disconnect();
}
private void test(String cmd) throws MikrotikApiException {
System.out.println("Command: " + cmd);
List<Map<String, String>> results = con.execute(cmd);
for (Map<String, String> result : results) {
System.out.println(result);
}
System.out.println();
}
}

View File

@ -0,0 +1,28 @@
package examples;
import me.legrange.mikrotik.MikrotikApiException;
/**
* Example 1: A very simple command: Reboot the remote router
* @author gideon
*/
public class ScriptCommand extends Example {
public static void main(String...args) throws Exception {
ScriptCommand ex = new ScriptCommand();
ex.connect();
ex.test();
ex.disconnect();
}
private void test() throws MikrotikApiException {
con.execute("/ip/hotspot/user/profile/add name=\"cahyo-cek-2\" idle-timeout=none keepalive-timeout=2m status-autorefresh=1m shared-users=1 add-mac-cookie=true mac-cookie-timeout=3d parent-queue=none rate-limit=512k/512k on-login=:put(\",re,3000,12d,3000,,Enable,\")");
}
///
/*
/ip/hotspot/user/profile/add name="cahyo-cek-2" idle-timeout=none keepalive-timeout=2m status-autorefresh=1m shared-users=1 add-mac-cookie=true mac-cookie-timeout=3d parent-queue=none rate-limit=512k/512k on-login=:put (",re,3000,12d,3000,,Enable,");
/system scheduler add name="usercahyo-cek-2-$user" interval=12:00:00 on-even="/ip hotspot active remove [find user=\"$user\"] ; /ip hotspot user set \"$user\" disable=yes ; /system scheduler remove [find name=usercahyo-cek-2-$user]"
*/
}

View File

@ -6,10 +6,10 @@ import me.legrange.mikrotik.MikrotikApiException;
* Example 1: A very simple command: Reboot the remote router
* @author gideon
*/
public class Example1 extends Example {
public class SimpleCommand extends Example {
public static void main(String...args) throws Exception {
Example1 ex = new Example1();
SimpleCommand ex = new SimpleCommand();
ex.connect();
ex.test();
ex.disconnect();

View File

@ -9,10 +9,10 @@ import me.legrange.mikrotik.MikrotikApiException;
*
* @author gideon
*/
public class Example2 extends Example {
public class SimpleCommandWithResults extends Example {
public static void main(String... args) throws Exception {
Example2 ex = new Example2();
SimpleCommandWithResults ex = new SimpleCommandWithResults();
ex.connect();
ex.test();
ex.disconnect();

View File

@ -0,0 +1,25 @@
package examples;
import javax.net.SocketFactory;
import me.legrange.mikrotik.ApiConnection;
import me.legrange.mikrotik.MikrotikApiException;
/**
* Example 9: Try with resources
*
* @author gideon
*/
public class TryWithResources {
public static void main(String... args) throws Exception {
TryWithResources ex = new TryWithResources();
ex.test();
}
private void test() throws MikrotikApiException, InterruptedException {
try (ApiConnection con = ApiConnection.connect(SocketFactory.getDefault(), Config.HOST, ApiConnection.DEFAULT_PORT, 2000)) {
con.login(Config.USERNAME, Config.PASSWORD);
con.execute("/user/add name=eric");
}
}
}

View File

@ -2,6 +2,7 @@ package me.legrange.mikrotik;
import java.util.List;
import java.util.Map;
import javax.net.SocketFactory;
import me.legrange.mikrotik.impl.ApiConnectionImpl;
/**
@ -10,46 +11,53 @@ import me.legrange.mikrotik.impl.ApiConnectionImpl;
*
* @author GideonLeGrange
*/
public abstract class ApiConnection {
public abstract class ApiConnection implements AutoCloseable {
/**
* Create a new API connection to the give device on the supplied port, using anonymous TLS for encryption.
* default TCP port used by Mikrotik API
*/
public static final int DEFAULT_PORT = 8728;
/**
* default TCP TLS port used by Mikrotik API
*/
public static final int DEFAULT_TLS_PORT = 8729;
/**
* default connection timeout to use when opening the connection
*/
public static final int DEFAULT_CONNECTION_TIMEOUT = 60000;
/**
* default command timeout used for synchronous commands
*/
public static final int DEFAULT_COMMAND_TIMEOUT = 60000;
/**
* Create a new API connection to the give device on the supplied port using
* the supplied socket factory to create the socket.
*
* @param fact SocketFactory to use for TCP socket creation.
* @param host The host to which to connect.
* @param port The TCP port to use.
* @param timeout The connection timeout to use when opening the connection.
* @return The ApiConnection
* @throws me.legrange.mikrotik.MikrotikApiException Thrown if there is a
* problem connecting
* @since 3.0
*/
public static ApiConnection connectTLS(String host, int port) throws MikrotikApiException {
return ApiConnectionImpl.connect(host, port, true);
}
/**
* Create a new API connection to the give device on the default API port, using anonymous TLS for encryption.
* @param host The host to which to connect.
* @return The ApiConnection
*/
public static ApiConnection connectTLS(String host) throws MikrotikApiException {
return ApiConnectionImpl.connect(host, DEFAULT_TLS_PORT, true);
}
/**
* Create a new API connection to the give device on the supplied port
* @param host The host to which to connect.
* @param port The TCP port to use.
* @return The ApiConnection
*/
public static ApiConnection connect(String host, int port) throws MikrotikApiException {
return ApiConnectionImpl.connect(host, port, false);
public static ApiConnection connect(SocketFactory fact, String host, int port, int timeout) throws MikrotikApiException {
return ApiConnectionImpl.connect(fact, host, port, timeout);
}
/**
* 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
* @throws me.legrange.mikrotik.MikrotikApiException Thrown if there is a
* problem connecting
*/
public static ApiConnection connect(String host) throws MikrotikApiException {
return connect(host, DEFAULT_PORT);
public static ApiConnection connect(String host) throws MikrotikApiException {
return connect(SocketFactory.getDefault(), host, DEFAULT_PORT, DEFAULT_COMMAND_TIMEOUT);
}
/**
@ -59,40 +67,65 @@ public abstract class ApiConnection {
*/
public abstract boolean isConnected();
/**
* Disconnect from the remote API
*/
public abstract void disconnect() throws MikrotikApiException;
/**
* Log in to the remote router.
*
* @param username - username of the user on the router
* @param password - password for the user
* @throws me.legrange.mikrotik.MikrotikApiException Thrown if the API encounters an error on login.
*/
public abstract void login(String username, String password) throws MikrotikApiException, InterruptedException;
public abstract void login(String username, String password) throws MikrotikApiException;
/** execute a command and return a list of results.
/**
* execute a command and return a list of results.
*
* @param cmd Command to execute
* @return The list of results
* @throws me.legrange.mikrotik.MikrotikApiException Thrown if the API encounters an error executing a command.
*/
public abstract List<Map<String, String>> execute(String cmd) throws MikrotikApiException;
/** 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.
*
* @param cmd Command to execute
* @param lis ResultListener that will receive the results
* @return A command object that can be used to cancel the command.
* @throws MikrotikApiException
* @throws me.legrange.mikrotik.MikrotikApiException Thrown if the API encounters an error executing a command.
*/
public abstract String execute(String cmd, ResultListener lis) throws MikrotikApiException;
/** cancel a command */
/**
* cancel a command
*
* @param tag The tag of the command to cancel
* @throws me.legrange.mikrotik.MikrotikApiException Thrown if there is a
* problem cancelling the command
*/
public abstract void cancel(String tag) throws MikrotikApiException;
/** default TCP port used by Mikrotik API */
private static final int DEFAULT_PORT = 8728;
/** default TCP TLS port used by Mikrotik API */
private static final int DEFAULT_TLS_PORT = 8729;
/**
* set the command timeout. The command timeout is used to time out API
* commands after a specific time.
*
* Note: This is not the same as the timeout value passed in the connect()
* methods. This timeout is specific to synchronous
* commands, that timeout is applied to opening the API socket.
*
* @param timeout The time out in milliseconds.
* @throws MikrotikApiException Thrown if the timeout specified is invalid.
* @since 2.1
*/
public abstract void setTimeout(int timeout) throws MikrotikApiException;
/**
* Disconnect from the remote API
*
* @throws me.legrange.mikrotik.ApiConnectionException Thrown if there is a
* problem closing the connection.
* @since 2.2
*/
@Override
public abstract void close() throws ApiConnectionException;
}

View File

@ -6,10 +6,20 @@ package me.legrange.mikrotik;
*/
public class ApiConnectionException extends MikrotikApiException {
/**
* Create a new exception.
*
* @param msg The message
*/
public ApiConnectionException(String msg) {
super(msg);
}
/**
* Create a new exception
* @param msg The message
* @param err The underlying cause
*/
public ApiConnectionException(String msg, Throwable err) {
super(msg, err);
}

View File

@ -7,10 +7,21 @@ package me.legrange.mikrotik;
*/
public class MikrotikApiException extends Exception {
/**
* Create a new exception
* @param msg The message
*/
public MikrotikApiException(String msg) {
super(msg);
}
/**
* Create a new exception
* @param msg The message
* @param err The underlying cause
*/
public MikrotikApiException(String msg, Throwable err) {
super(msg, err);
}

View File

@ -1,17 +0,0 @@
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 an error */
void error(MikrotikApiException ex);
/** called when the command associated with this listener is done */
void completed();
}

View File

@ -8,6 +8,15 @@ import java.util.Map;
*/
public interface ResultListener {
/** receive data from router
* @param result The data received */
void receive(Map<String, String> result);
/** called if the command associated with this listener experiences an error
* @param ex Exception encountered */
void error(MikrotikApiException ex);
/** called when the command associated with this listener is done */
void completed();
}

View File

@ -1,11 +1,13 @@
command = action [ query ] [ return ]
action = ("/" word)+
query = "where" expr
expr = expr "and" expr | expr "or" expr | "not" expr | hasExpr | eqExpr | lessExpr | moreExpr
expr = expr "and" expr | expr "or" expr | "not" expr | hasExpr | eqExpr | lessExpr | moreExpr | notExpr | nestedExpr
hasExpr = name
eqExpr = name "=" value
lessExpr = name "<" value
moreExpr = name ">" value
notExpr = name "!=" value
nestedExpr = "(" expr ")"
return = "return" (name)+

View File

@ -4,30 +4,40 @@ import me.legrange.mikrotik.MikrotikApiException;
/**
* Thrown when the Mikrotik returns an error when receiving our command.
*
* @author GideonLeGrange
*/
public class ApiCommandException extends MikrotikApiException {
private String tag = null;
private int category = 0;
/** return the tag associated with this exception, if there is one */
/**
* return the tag associated with this exception, if there is one
*
* @return the tag associated with this exception. Null if there is no tag
*/
public String getTag() {
return tag;
}
ApiCommandException(String msg) {
ApiCommandException(String msg) {
super(msg);
}
ApiCommandException(String msg, Throwable err) {
ApiCommandException(String msg, Throwable err) {
super(msg, err);
}
public int getCategory() {
return category;
}
ApiCommandException(Error err) {
super(err.getMessage());
tag = err.getTag();
category = err.getCategory();
}
private String tag = null;
}

View File

@ -1,24 +1,25 @@
package me.legrange.mikrotik.impl;
import 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.InetSocketAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.security.Provider;
import java.security.Security;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.LinkedBlockingQueue;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
//import org.bouncycastle.jce.provider.BouncyCastleProvider;
import java.util.concurrent.atomic.AtomicInteger;
import javax.net.SocketFactory;
import me.legrange.mikrotik.ApiConnection;
import me.legrange.mikrotik.ApiConnectionException;
import me.legrange.mikrotik.MikrotikApiException;
import me.legrange.mikrotik.ResultListener;
/**
* The Mikrotik API connection implementation. This is the class used to connect
@ -31,89 +32,90 @@ public final class ApiConnectionImpl extends ApiConnection {
/**
* Create a new API connection to the give device on the supplied port
*
* @param fact The socket factory used to construct the connection socket.
* @param host The host to which to connect.
* @param port The TCP port to use.
* @param timeOut The connection timeout
* @return The ApiConnection
* @throws me.legrange.mikrotik.ApiConnectionException Thrown if there is a
* problem connecting
*/
public static ApiConnection connect(String host, int port, boolean secure) throws ApiConnectionException {
public static ApiConnection connect(SocketFactory fact, String host, int port, int timeOut) throws ApiConnectionException {
ApiConnectionImpl con = new ApiConnectionImpl();
con.open(host, port, secure);
con.open(host, port, fact, timeOut);
return con;
}
/**
* Check the state of connection.
*
* @return if connection is established to router it returns true.
*/
@Override
public boolean isConnected() {
return connected;
}
/**
* Disconnect from the remote API
*/
public void disconnect() throws ApiConnectionException {
@Override
public void login(String username, String password) throws MikrotikApiException {
if (username.trim().isEmpty()) {
throw new ApiConnectionException("API username cannot be empty");
}
Command cmd = new Command("/login");
cmd.addParameter("name", username);
cmd.addParameter("password", password);
List<Map<String, String>> list = execute(cmd, timeout);
if (!list.isEmpty()) {
Map<String, String> res = list.get(0);
if (res.containsKey("ret")) {
String hash = res.get("ret");
String chal = Util.hexStrToStr("00") + new String(password.toCharArray()) + Util.hexStrToStr(hash);
chal = Util.hashMD5(chal);
execute("/login name=" + username + " response=00" + chal);
}
}
}
@Override
public List<Map<String, String>> execute(String cmd) throws MikrotikApiException {
return execute(Parser.parse(cmd), timeout);
}
@Override
public String execute(String cmd, ResultListener lis) throws MikrotikApiException {
return execute(Parser.parse(cmd), lis);
}
@Override
public void cancel(String tag) throws MikrotikApiException {
execute(String.format("/cancel tag=%s", tag));
}
@Override
public void setTimeout(int timeout) throws MikrotikApiException {
if (timeout > 0) {
this.timeout = timeout;
} else {
throw new MikrotikApiException(String.format("Invalid timeout value '%d'; must be postive", timeout));
}
}
@Override
public void close() throws ApiConnectionException {
if (!connected) {
throw new ApiConnectionException(("Not/no longer connected to remote Mikrotik"));
}
connected = false;
processor.interrupt();
reader.interrupt();
try {
in.close();
out.close();
sock.close();
} catch (IOException ex) {
throw new ApiConnectionException(String.format("Error closing socket: %s", ex.getMessage()), ex);
}
}
/**
* Log in to the remote router.
*
* @param username - username of the user on the router
* @param password - password for the user
*/
public 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.
*
* @param cmd Command to execute
* @return The list of results
*/
public 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.
*
* @param cmd Command to execute
* @param lis ResultListener that will receive the results
* @return A command object that can be used to cancel the command.
* @throws MikrotikApiException
*/
public String execute(String cmd, ResultListener lis) throws MikrotikApiException {
return execute(Parser.parse(cmd), lis);
}
/**
* cancel a command
*/
public void cancel(String tag) throws MikrotikApiException {
execute(String.format("/cancel tag=%s", tag));
}
private List<Map<String, String>> execute(Command cmd) throws MikrotikApiException {
private List<Map<String, String>> execute(Command cmd, int timeout) throws MikrotikApiException {
SyncListener l = new SyncListener();
execute(cmd, l);
return l.getResults();
return l.getResults(timeout);
}
private String execute(Command cmd, ResultListener lis) throws MikrotikApiException {
@ -131,94 +133,60 @@ public final class ApiConnectionImpl extends ApiConnection {
}
private ApiConnectionImpl() {
}
/**
* Start the API. Connects to the Mikrotik without using encryption
*/
private void open(String host, int port) throws ApiConnectionException {
open(host, port, false);
this.listeners = new ConcurrentHashMap<>();
}
/**
* Start the API. Connects to the Mikrotik
*/
private void open(String host, int port, boolean secure) throws ApiConnectionException {
private void open(String host, int port, SocketFactory fact, int conTimeout) throws ApiConnectionException {
try {
InetAddress ia = InetAddress.getByName(host);
if (ia.isReachable(1000)) {
if (secure) {
sock = openSSLSocket(ia, port);
} else {
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));
}
InetAddress ia = InetAddress.getByName(host.trim());
sock = fact.createSocket();
sock.connect(new InetSocketAddress(ia, port), conTimeout);
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();
} 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);
throw new ApiConnectionException(String.format("Error connecting to %s:%d : %s", host, port, ex.getMessage()), ex);
}
}
/**
* open and configure a SSL socket.
*/
private Socket openSSLSocket(InetAddress ia, int port) throws IOException {
SSLSocket ssl = (SSLSocket) SSLSocketFactory.getDefault().createSocket(ia, port);
List<String> cs = new LinkedList<String>();
// not happy with this code. Without it, SSL throws a "Remote host closed connection during handshake" error
// caused by a "SSL peer shut down incorrectly" error
for (String s : ssl.getSupportedCipherSuites()) {
if (s.startsWith("TLS_DH_anon")) cs.add(s);
}
ssl.setEnabledCipherSuites(cs.toArray(new String[]{}));
return ssl;
}
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);
return Integer.toHexString(_tag.incrementAndGet());
}
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;
private final Map<String, ResultListener> listeners;
private final AtomicInteger _tag = new AtomicInteger(0);
private int timeout = ApiConnection.DEFAULT_COMMAND_TIMEOUT;
/**
* thread to read data from the socket and process it into Strings
*/
private class Reader extends Thread {
private Reader() {
super("Mikrotik API Reader");
}
private String take() throws ApiConnectionException, ApiDataException {
Object val = null;
Object val;
try {
val = queue.take();
} catch (InterruptedException ex) {
@ -241,20 +209,25 @@ public final class ApiConnectionImpl extends ApiConnection {
while (connected) {
try {
String s = Util.decode(in);
if (s != null) {
queue.put(s);
}
put(s);
} catch (ApiDataException ex) {
try {
queue.put(ex);
} catch (InterruptedException ex2) {
}
put(ex);
} catch (ApiConnectionException ex) {
} catch (InterruptedException ex1) {
if (connected || !sock.isClosed()) {
put(ex);
}
}
}
}
private LinkedBlockingQueue queue = new LinkedBlockingQueue(40);
private void put(Object data) {
try {
queue.put(data);
} catch (InterruptedException ignored) {
}
}
private final LinkedBlockingQueue queue = new LinkedBlockingQueue(40);
}
/**
@ -262,6 +235,10 @@ public final class ApiConnectionImpl extends ApiConnection {
*/
private class Processor extends Thread {
private Processor() {
super("Mikrotik API Result Processor");
}
@Override
public void run() {
while (connected) {
@ -271,35 +248,31 @@ public final class ApiConnectionImpl extends ApiConnection {
} catch (ApiCommandException ex) {
String tag = ex.getTag();
if (tag != null) {
res = new Error(tag, ex.getMessage());
res = new Error(tag, ex.getMessage(), ex.getCategory());
} 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));
if (res.getTag() != null) {
ResultListener l = listeners.get(res.getTag());
if (l != null) {
if (res instanceof Result) {
l.receive((Result) res);
} else if (res instanceof Done) {
if (l instanceof SyncListener) {
((SyncListener) l).completed((Done) res);
} else {
l.completed();
}
listeners.remove(res.getTag());
} else if (res instanceof Error) {
l.error(new ApiCommandException((Error) res));
}
}
} else {
nextTag();
}
}
}
@ -307,7 +280,7 @@ public final class ApiConnectionImpl extends ApiConnection {
private void nextLine() throws ApiConnectionException, ApiDataException {
if (lines.isEmpty()) {
String block = reader.take();
String parts[] = block.split("\n");
String[] parts = block.split("\n");
lines.addAll(Arrays.asList(parts));
}
line = lines.remove(0);
@ -317,38 +290,57 @@ public final class ApiConnectionImpl extends ApiConnection {
return !lines.isEmpty() || !reader.isEmpty();
}
private String peekLine() throws ApiConnectionException, ApiDataException {
if (lines.isEmpty()) {
String block = reader.take();
String[] parts = block.split("\n");
lines.addAll(Arrays.asList(parts));
}
return lines.get(0);
}
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));
switch (line) {
case "!re":
return unpackRe();
case "!empty":
case "!done":
return unpackDone();
case "!trap":
case "!halt":
return unpackError();
case "":
default:
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);
String[] parts = line.split("=", 3);
if (parts.length == 3) {
res.put(parts[1], parts[2]);
if (!parts[2].endsWith("\r")) {
res.put(parts[1], unpackResult(parts[2]));
} else {
final StringBuilder sb = new StringBuilder();
sb.append(parts[2]);
while (!lines.isEmpty()) {
nextLine();
sb.append(line);
}
res.put(parts[1], sb.toString());
}
} else {
throw new ApiDataException(String.format("Malformed line '%s'", line));
}
} else if (line.startsWith(".tag=")) {
String parts[] = line.split("=", 2);
String[] parts = line.split("=", 2);
if (parts.length == 2) {
res.setTag(parts[1]);
}
@ -365,6 +357,23 @@ public final class ApiConnectionImpl extends ApiConnection {
return res;
}
private String unpackResult(String first) throws ApiConnectionException, ApiDataException {
StringBuilder buf = new StringBuilder(first);
line = null;
while (hasNextLine()) {
String peek = peekLine();
if (!(peek.startsWith("!") || peek.startsWith("=") || peek.startsWith(".tag="))) {
nextLine();
buf.append("\n");
buf.append(line);
} else {
break;
}
}
return buf.toString();
}
private Done unpackDone() throws MikrotikApiException {
Done done = new Done(null);
if (hasNextLine()) {
@ -372,12 +381,12 @@ public final class ApiConnectionImpl extends ApiConnection {
while (!line.startsWith("!")) {
if (line.startsWith(".tag=")) {
String parts[] = line.split("=", 2);
String[] parts = line.split("=", 2);
if (parts.length == 2) {
done.setTag(parts[1]);
}
} else if (line.startsWith(("=ret"))) {
String parts[] = line.split("=", 3);
String[] parts = line.split("=", 3);
if (parts.length == 3) {
done.setHash(parts[2]);
} else {
@ -401,13 +410,16 @@ public final class ApiConnectionImpl extends ApiConnection {
if (hasNextLine()) {
while (!line.startsWith("!")) {
if (line.startsWith(".tag=")) {
String parts[] = line.split("=", 2);
String[] parts = line.split("=", 2);
if (parts.length == 2) {
err.setTag(parts[1]);
}
} else if (line.startsWith("=message=")) {
err.setMessage(line.split("=", 3)[2]);
}
else if (line.startsWith("=category=")) {
err.setCategory(Integer.parseInt(line.split("=", 3)[2]));
}
if (hasNextLine()) {
nextLine();
} else {
@ -419,32 +431,22 @@ public final class ApiConnectionImpl extends ApiConnection {
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 final List<String> lines = new LinkedList<>();
private String line;
}
private class SyncListener implements ResponseListener {
private static class SyncListener implements ResultListener {
@Override
public synchronized void error(MikrotikApiException ex) {
this.err = ex;
notify();
notifyAll();
}
@Override
public synchronized void completed() {
notify();
complete = true;
notifyAll();
}
synchronized void completed(Done done) {
@ -453,29 +455,39 @@ public final class ApiConnectionImpl extends ApiConnection {
res.put("ret", done.getHash());
results.add(res);
}
notify();
complete = true;
notifyAll();
}
@Override
public void receive(Map<String, String> result) {
results.add(result);
}
private List<Map<String, String>> getResults() throws MikrotikApiException {
private List<Map<String, String>> getResults(int timeout) throws MikrotikApiException {
try {
synchronized (this) { // don't wait if we already have a result.
if ((err == null) && results.isEmpty()) {
wait();
int waitTime = timeout;
while (!complete && (waitTime > 0)) {
long start = System.currentTimeMillis();
wait(waitTime);
waitTime = waitTime - (int) (System.currentTimeMillis() - start);
if ((waitTime <= 0) && !complete) {
err = new ApiConnectionException(String.format("Command timed out after %d ms", timeout));
}
}
}
} catch (InterruptedException ex) {
throw new ApiConnectionException(ex.getMessage(), ex);
}
if (err != null) {
throw err;
throw new MikrotikApiException(err.getMessage(), err);
}
return results;
}
private List<Map<String, String>> results = new LinkedList<Map<String, String>>();
private final List<Map<String, String>> results = new LinkedList<>();
private MikrotikApiException err;
private boolean complete = false;
}
}

View File

@ -73,9 +73,9 @@ class Command {
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 final String cmd;
private final List<Parameter> params = new LinkedList<>();
private final List<String> queries = new LinkedList<>();
private final List<String> properties = new LinkedList<>();
private String tag;
}

View File

@ -2,11 +2,15 @@ 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).
*
* @author GideonLeGrange
*/
class Error extends Response {
Error(String tag, String message) {
private String message;
private int category;
Error(String tag, String message, int category) {
super(tag);
this.message = message;
}
@ -18,11 +22,16 @@ class Error extends Response {
String getMessage() {
return message;
}
void setMessage(String message) {
this.message = message;
}
private String message;
int getCategory() {
return category;
}
void setCategory(int category) {
this.category = category;
}
}

View File

@ -1,31 +1,32 @@
package me.legrange.mikrotik.impl;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import me.legrange.mikrotik.impl.Scanner.Token;
/**
* Parse the pseudo-command line into command objects.
*
* @author GideonLeGrange
*/
class Parser {
/** parse the given bit of text into a Command object */
/**
* 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 */
/**
* 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))) {
command();
while (!is(Token.WHERE, Token.RETURN, Token.EOL)) {
param();
}
if (token == Token.WHERE) {
@ -38,16 +39,41 @@ class Parser {
return cmd;
}
private void command() throws ParseException {
StringBuilder path = new StringBuilder();
do {
expect(Token.SLASH);
path.append("/");
next();
expect(Token.TEXT);
path.append(text);
next();
} while (token == Token.SLASH);
cmd = new Command(path.toString());
}
private void param() throws ParseException {
String name = text;
next();
if (token == Token.EQUALS) {
next();
expect(Token.NAME);
cmd.addParameter(new Parameter(name, text));
StringBuilder val = new StringBuilder();
if (token == Token.PIPE) { // handle cases like hotspot=!auth
val.append(token);
next();
}
expect(Token.TEXT);
val.append(text);
next();
}
else {
while (is(Token.COMMA, Token.SLASH)) {
val.append(token);
next();
expect(Token.TEXT);
val.append(text);
next();
}
cmd.addParameter(new Parameter(name, val.toString()));
} else {
cmd.addParameter(new Parameter(name));
}
}
@ -58,19 +84,22 @@ class Parser {
}
private void expr() throws ParseException {
expect(Token.NOT, Token.NAME);
expect(Token.NOT, Token.TEXT, Token.LEFT_BRACKET);
switch (token) {
case NOT:
notExpr();
break;
case NAME: {
case TEXT: {
String name = text;
next();
expect(Token.EQUALS, Token.LESS, Token.MORE);
expect(Token.EQUALS, Token.LESS, Token.MORE, Token.NOT_EQUALS);
switch (token) {
case EQUALS:
eqExpr(name);
break;
case NOT_EQUALS:
notExpr(name);
break;
case LESS:
lessExpr(name);
break;
@ -82,22 +111,36 @@ class Parser {
}
}
break;
case LEFT_BRACKET:
nestedExpr();
break;
}
// if you get here, you had a expression, see if you want more.
switch (token) {
case AND : andExpr();
case AND:
andExpr();
break;
case OR:
orExpr();
break;
case OR : orExpr();
}
}
private void nestedExpr() throws ParseException {
expect(Token.LEFT_BRACKET);
next();
expr();
expect(Token.RIGHT_BRACKET);
next();
}
private void andExpr() throws ParseException {
next(); // eat and
expr();
cmd.addQuery("?#&");
}
private void orExpr() throws ParseException {
private void orExpr() throws ParseException {
next(); // eat or
expr();
cmd.addQuery("?#|");
@ -110,19 +153,27 @@ class Parser {
cmd.addQuery("?#!");
}
private void eqExpr(String name) {
private void eqExpr(String name) throws ParseException {
next(); // eat =
expect(Token.TEXT);
cmd.addQuery(String.format("?%s=%s", name, text));
next();
}
private void lessExpr(String name) {
private void lessExpr(String name) throws ScanException {
next(); // eat <
cmd.addQuery(String.format("?<%s=%s", name, text));
next();
}
private void moreExpr(String name) {
private void notExpr(String name) throws ScanException {
next(); // eat !=
cmd.addQuery(String.format("?%s=%s", name, text));
cmd.addQuery("?#!");
next();
}
private void moreExpr(String name) throws ScanException {
next(); // eat >
cmd.addQuery(String.format("?>%s=%s", name, text));
next();
@ -134,75 +185,49 @@ class Parser {
private void returns() throws ParseException {
next();
expect(Token.NAME);
List<String> props = new LinkedList<String>();
expect(Token.TEXT);
List<String> props = new LinkedList<>();
while (!(token == Token.EOL)) {
if (token != Token.COMMA) {
props.add(text);
}
next();
}
cmd.addProperty(props.toArray(new String[]{}));
cmd.addProperty(props.toArray(new String[props.size()]));
}
private void expect(Token...tokens) throws ParseException {
private void expect(Token... tokens) throws ParseException {
if (!is(tokens))
throw new ParseException(String.format("Expected %s but found %s at position %d", Arrays.asList(tokens), this.token, scanner.pos()));
}
private boolean is(Token... tokens) {
for (Token want : tokens) {
if (this.token == want) return;
if (this.token == want) return true;
}
throw new ParseException(String.format("Expected %s but found %s", Arrays.asList(tokens), this.token));
return false;
}
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 = "";
/**
* move to the next token returned by the scanner
*/
private void next() throws ScanException {
token = scanner.next();
while (token == Token.WS) {
token = scanner.next();
}
// System.out.printf("%s: %s\n", token, text);
text = scanner.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 Parser(String line) throws ScanException {
line = line.trim();
scanner = new Scanner(line);
next();
}
private final List<String> words = new LinkedList<String>();
private String text;
private final Scanner scanner;
private Token token;
private String text;
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);
}
}

View File

@ -24,5 +24,5 @@ abstract class Response {
this.tag = tag;
}
private String tag;
private String tag;
}

View File

@ -1,4 +1,4 @@
package me.legrange.mikrotik.impl;
package me.legrange.mikrotik.impl;
import java.util.Collection;
import java.util.HashMap;
@ -15,6 +15,7 @@ class Result extends Response implements Map<String, String> {
return map.get(key);
}
@Override
public boolean isEmpty() {
return map.isEmpty();
}
@ -24,53 +25,64 @@ class Result extends Response implements Map<String, String> {
return String.format("tag=%s, data=%s", getTag(), map);
}
@Override
public int size() {
return map.size();
}
@Override
public boolean containsKey(Object o) {
return map.containsKey(o);
}
@Override
public boolean containsValue(Object o) {
return map.containsValue(o);
}
@Override
public String get(Object o) {
return map.get(o);
}
@Override
public String put(String k, String v) {
return map.put(k, v);
}
@Override
public String remove(Object o) {
return map.remove(o);
}
@Override
public void putAll(Map<? extends String, ? extends String> map) {
this.map.putAll(map);
}
@Override
public void clear() {
map.clear();
}
@Override
public Set<String> keySet() {
return map.keySet();
}
@Override
public Collection<String> values() {
return map.values();
}
@Override
public Set<Entry<String, String>> entrySet() {
return map.entrySet();
}
Result() {
super(null);
this.map = new HashMap<String, String>();
this.map = new HashMap<>();
}
private final Map<String, String> map;

View File

@ -0,0 +1,17 @@
package me.legrange.mikrotik.impl;
/**
* Exception thrown if the scanner encounters an error while scanning a command line.
* @author GideonLeGrange
*/
public class ScanException extends ParseException {
ScanException(String msg) {
super(msg);
}
ScanException(String msg, Throwable err) {
super(msg, err);
}
}

View File

@ -0,0 +1,206 @@
/*
* Copyright 2014 GideonLeGrange.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package me.legrange.mikrotik.impl;
import java.util.Locale;
import static me.legrange.mikrotik.impl.Scanner.Token.*;
/**
* A simple scanner.
*
* @author gideon
*/
class Scanner {
enum Token {
SLASH("/"), COMMA(","), EOL(), WS, TEXT,
LESS("<"), MORE(">"), EQUALS("="), NOT_EQUALS("!="), PIPE("!"),
LEFT_BRACKET("("), RIGHT_BRACKET(")"),
WHERE, NOT, AND, OR, RETURN;
@Override
public String toString() {
return (symb == null) ? name() : symb;
}
private Token(String symb) {
this.symb = symb;
}
private Token() {
symb = null;
}
private final String symb;
}
/**
* create a scanner for the given line of text
*/
Scanner(String line) {
this.line = line;
nextChar();
}
/**
* return the next token from the text
*/
Token next() throws ScanException {
text = null;
switch (c) {
case '\n':
return EOL;
case ' ':
case '\t':
return whiteSpace();
case ',':
nextChar();
return COMMA;
case '/':
nextChar();
return SLASH;
case '<':
nextChar();
return LESS;
case '>':
nextChar();
return MORE;
case '=':
nextChar();
return EQUALS;
case '(' :
nextChar();
return LEFT_BRACKET;
case ')' :
nextChar();
return RIGHT_BRACKET;
case '!':
return pipe();
case '"':
return quotedText('"');
case '\'':
return quotedText('\'');
default:
return name();
}
}
/**
* return the text associated with the last token returned
*/
String text() {
if (text != null) {
return text.toString();
}
return "";
}
/**
* return the position of the scanner
*/
int pos() {
return pos;
}
/**
* process 'name' tokens which could be key words or text
*/
private Token name() throws ScanException {
text = new StringBuilder();
while (!in(c, "[ \t\r\n=<>!)]")) {
text.append(c);
nextChar();
}
String val = text.toString().toLowerCase(Locale.getDefault());
switch (val) {
case "where":
return WHERE;
case "not":
return NOT;
case "and":
return AND;
case "or":
return OR;
case "return":
return RETURN;
}
return TEXT;
}
/**
* process quoted text
*/
private Token quotedText(char quote) throws ScanException {
nextChar(); // eat the '"'
text = new StringBuilder();
while (c != quote) {
if (c == '\n') {
throw new ScanException("Unclosed quoted text, reached end of line.");
}
text.append(c);
nextChar();
}
nextChar(); // eat the '"'
return TEXT;
}
/**
* process notEquals !
*/
private Token pipe() {
nextChar(); // eat !
if (c == '=') {
nextChar(); // eat =
return NOT_EQUALS;
}
return PIPE;
}
/**
* process white space
*/
private Token whiteSpace() {
while ((c == ' ') || (c == '\t')) {
nextChar();
}
return WS;
}
/**
* return the next character from the line of text
*/
private void nextChar() {
if (pos < line.length()) {
c = line.charAt(pos);
pos++;
} else {
c = '\n';
}
}
/**
* check if the character matches the give expression
*/
private boolean in(char c, String cs) {
return ("" + c).matches(cs);
}
private final String line;
private int pos = 0;
private char c;
private StringBuilder text;
}

View File

@ -4,6 +4,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.List;
@ -71,7 +72,7 @@ final class Util {
}
buf[i] = (byte) (c & 0xFF);
}
String res = new String(buf);
String res = new String(buf, Charset.forName("UTF-8"));
if (result.length() > 0) {
result.append("\n");
}
@ -121,11 +122,11 @@ final class Util {
* @return - converted string.
*/
static String hexStrToStr(String s) {
String ret = "";
StringBuilder ret = new StringBuilder();
for (int i = 0; i < s.length(); i += 2) {
ret += (char) Integer.parseInt(s.substring(i, i + 2), 16);
ret.append((char) Integer.parseInt(s.substring(i, i + 2), 16));
}
return ret;
return ret.toString();
}
/**
@ -133,7 +134,7 @@ final class Util {
* stream.
*/
private static void encode(String word, OutputStream out) throws UnsupportedEncodingException, IOException {
byte bytes[] = word.getBytes("US-ASCII");
byte bytes[] = word.getBytes("UTF-8");
int len = bytes.length;
if (len < 0x80) {
out.write(len);