Compare commits

...

256 Commits

Author SHA1 Message Date
Tal Moran cf891549bb Fixed typo in mixer docs 2018-05-10 18:07:59 +03:00
Tal Moran 21ad93f4e1 Updated to gradle 4.2.1 2017-10-16 17:02:35 +03:00
Tal Moran 73619b0c9e Better win compatibility for bb-server frontent 2017-07-09 11:09:07 +03:00
Tal Moran 252a761005 Better cross-platform support for building bb-server frontend 2017-07-06 12:00:04 +03:00
Tal Moran 931bb56378 Fixed npm_install dependency that broke clean builds 2017-07-03 02:51:02 -04:00
Tal Moran fa4656f0a9 added dependency on npmInstall to ensure protobufjs is installed in bb server frontend 2017-07-03 03:37:32 +03:00
Tal Moran 08d9bd6217 Integrated bb-server angular frontend in gradle build 2017-07-03 03:24:00 +03:00
Tal Moran 173b952e09 Added proxy to bb frontend (no need for cross-origin headers); support periodic status calls 2017-07-02 17:54:36 +03:00
Tal Moran c0b21f82ee Added toy angular application (for bulletin-board frontend) 2017-07-02 16:42:45 +03:00
Tal Moran 05c7a25bb7 Renamed proto files to use standard (lowercase-underscore) naming convention); added a separate proto for bb webapp 2017-06-30 02:28:03 +03:00
Tal Moran 68e1737f22 Dependency cleaning; more spongification; use composite build to include qilin (need qilin 1.3+ now) 2017-06-26 01:59:32 +03:00
Tal Moran 3c52eb2e8d Done refactoring scannerAPI; fixed android scanner to use modern settings api and allow URL+nonce 2017-06-26 00:22:20 +03:00
Tal Moran 0dfd2480d2 Added connection to android scanner; problems with missing crypto providers 2017-06-25 19:27:38 +03:00
Tal Moran 291f7d93c0 Scanner serverdata now tries to get a non-localhost IPV4 address if possible 2017-06-25 18:54:34 +03:00
Tal Moran 6b47387920 Code now builds 2017-06-25 18:05:52 +03:00
Tal Moran 324a079a90 Refactored and fixed NetworkVirtualPrinter 2017-06-25 17:51:25 +03:00
Tal Moran 6f35f105c5 Mostly finished with scanAPI refactoring 2017-06-25 16:20:28 +03:00
Tal Moran cb1d2343c4 More refactoring for scannerAPI 2017-06-25 10:15:09 +03:00
Tal Moran 7e4260b8d5 Added signature key generation to crypto API 2017-06-24 03:15:59 +03:00
Tal Moran ceba09e65c working on scanner API refactoring (doesn't compile yet) 2017-06-22 17:56:59 +03:00
Tal Moran c7dd5bd663 Added settings activity to android scanner; started refactoring scanner api 2017-06-22 11:50:59 +03:00
Tal Moran 24e3556320 fixed build so android-scanner compiles; changed android-scanner to use meerkat scanner API rather than custom REST library 2017-06-22 02:58:01 +03:00
Tal Moran 62349f9fe7 removed apparently unecessary file 2017-06-22 01:45:48 +03:00
Tal Moran 3b3a6a09b2 Removed debug printout 2017-06-22 01:42:24 +03:00
Tal Moran 1387de97e2 Updated gradlew 2017-06-22 01:42:08 +03:00
Tal Moran ef2e09ba6b Added appcompat dependency to scanner to allow build 2017-06-22 01:41:50 +03:00
Tal Moran c921fef055 Added fixed version of osdetector that is compatible with android plugin 2017-06-22 01:41:08 +03:00
Tal Moran ea8813bade Updated voting-booth-gui build script to work without local nexus repository 2017-06-21 17:28:07 +03:00
Tal Moran 355ea1758b Imported scanner project; doesn't compile yet 2017-06-21 13:19:00 +03:00
Tal Moran 9ad0828eb2 Removed .orig files 2017-06-20 23:41:53 +03:00
Tal Moran 33a5c493f0 Upgraded to gradle 4.0 2017-06-20 23:17:21 +03:00
Laura Radaelli 24b111a20b voting questions flow fixed (removed button cancel) 2017-06-14 19:15:13 +03:00
Laura Radaelli f813cf49ee channel single question 2017-06-14 18:17:07 +03:00
Laura Radaelli 646824de44 parse ScannedData serial number 2017-04-27 16:32:12 +03:00
Laura Radaelli 4e1b66f890 ip address set to work on tethering 2017-04-27 14:43:23 +03:00
Laura Radaelli cc3e151a26 main that starts threads added 2017-04-16 17:09:35 +03:00
Laura Radaelli 29f6b244d7 polling station server and client communicate (reading scannedData works) 2017-04-16 14:59:44 +03:00
Laura Radaelli ed7b50de23 fixed ScanErrorCallback error in ReceiverWebAPI 2017-03-20 18:20:30 +02:00
Laura Radaelli 09ee63eca4 polling station server and scanner client communicate (but missing response) 2017-03-20 18:03:08 +02:00
Laura Radaelli 11b86dd2aa new files with new names (old test passed) 2017-03-20 10:37:02 +02:00
Laura Radaelli db4cf6a734 QR code for cast (missing QR code for audit) + test interaction polling station 2017-03-20 09:34:21 +02:00
Laura Radaelli 6350da1fc2 Merge branch 'master' of https://vcs.factcenter.org/meerkat/meerkat-java into voting-booth-gui 2017-03-19 10:33:24 +02:00
Tal Moran 181f49145f Allow specifying elliptic curve name when generating keys on mixer command-line 2017-02-01 21:34:18 +02:00
Laura Radaelli 97fbfcecaf remove unused import and fixed cast/audit buttons width 2017-01-25 22:10:09 +02:00
Laura Radaelli 17d7d5eca4 Merge branch 'master' of https://vcs.factcenter.org/meerkat/meerkat-java into voting-booth-gui 2017-01-25 21:45:50 +02:00
Laura Radaelli f393825b26 printer added clean() method 2017-01-25 19:12:30 +02:00
Tal Moran b360ae81bb Fix gradle build files to work without nexus repository credentials 2017-01-25 19:10:18 +02:00
Laura Radaelli 480f4e52bb moved code for fx to voting-booth-gui 2017-01-25 16:01:13 +02:00
Laura Radaelli 0cf9390fa5 prints test qr-code to virtual printer 2017-01-25 12:50:01 +02:00
Laura Radaelli e60e76f104 changed titles of FX windows 2017-01-25 10:46:13 +02:00
Tal Moran 06526a16db Clean up build files and remove dependency on local nexus repository 2017-01-24 17:17:09 +02:00
Tal Moran 53cc13b51b Cleaning up unchecked casts 2017-01-24 16:28:57 +02:00
Tal Moran ce0e0e0d62 Fix protobuf decoding bug 2017-01-23 15:41:27 +02:00
Tal Moran 5b7fbffb09 Update to gradle 3.3 2017-01-23 15:40:45 +02:00
Tal Moran c8f82223bc Merge branch 'master' of https://vcs.factcenter.org/meerkat/meerkat-java into mixer 2017-01-23 10:46:14 +02:00
Tal Moran f930a65c48 Added mixer documentation 2017-01-22 00:24:25 +02:00
Tal Moran 2744005263 Replaced generic RandomOracle with explicit SHA-256 to make description of random oracle simpler for external verifiers 2017-01-21 23:07:20 +02:00
Tal Moran 43d4fb75b2 More refactoring, wrote command-line mixer application (no BB access as of yet) 2017-01-21 21:42:10 +02:00
Tal Moran 273338010d More refactoring 2017-01-20 23:46:51 +02:00
Tal Moran b9abd847c7 Refactoring 2017-01-19 16:00:01 +02:00
Tal Moran deeed4f20f tests pass 2017-01-19 15:24:35 +02:00
Tal Moran fc2c26d7e9 Refactoring (tests currently fail) 2017-01-19 11:08:07 +02:00
Tal Moran 78f823f31e new benes code passes tests 2017-01-19 01:18:21 +02:00
Tal Moran 37d1857f9c Working on Benes network/testing 2017-01-18 21:54:42 +02:00
Tal Moran abf4cc5e54 Add task to download protoc (protobuf compiler) to root-level build/ directory 2017-01-18 21:54:22 +02:00
Tal Moran 0d416f0018 Rewriting Benes network code (in progress) 2017-01-18 11:47:52 +02:00
Tal Moran c2da5aa464 All tests pass 2017-01-14 02:42:02 +02:00
Laura Radaelli 5386acfd1b added css 2017-01-12 18:24:38 +02:00
Laura Radaelli 72d96c4d78 virtual printer (javaFX window) added 2017-01-12 18:03:40 +02:00
Laura Radaelli b8418156b1 got to Cast or Audit screen 2017-01-12 12:32:59 +02:00
Tal Moran a5cceaa6c0 Writing tests for new ZKPs 2017-01-12 11:05:38 +02:00
Tal Moran 723d348443 Working on mixing code rewrite 2017-01-11 22:09:01 +02:00
Tal Moran b7ef2c10e1 rewriting mixing proofs 2017-01-11 17:01:14 +02:00
Laura Radaelli 29b8ef9734 javafx integration runs till channel choice 2017-01-06 17:24:38 +02:00
Laura Radaelli c8044fc93d fx gui with mainController and navigator (incomplete again) 2016-12-16 22:11:00 +02:00
Laura Radaelli 5113c2235b fx gui with MainController and Navigator (incomplete) 2016-12-16 22:10:23 +02:00
Laura Radaelli c97519c28e starting fx window and communicating with graphicalUI thread 2016-12-16 17:55:56 +02:00
Laura Radaelli 0b1d334d3c start javaFX window from voting booth ToyRun 2016-12-13 10:23:51 +02:00
Laura Radaelli 971db5d89c barcode window always on top 2016-12-08 13:13:07 +02:00
Laura Radaelli 71076f5c7e fix NullPointerException when calling votersBallot.toString and add showBallot (new window) 2016-12-08 12:11:04 +02:00
Tal Moran 9db8efd75b Updated to gradle 3.2.1 2016-11-28 13:51:24 +02:00
VladimirEliTokarev 0509bf6dda Resolved the umextendablity of the summary ballot panel 2016-11-12 17:49:48 +02:00
VladimirEliTokarev 08cc50c22a Resulted the bug with the not expendable panel 2016-11-12 17:37:42 +02:00
VladimirEliTokarev 6161898805 Changed the configuration parser
The usage of \\z delimeter is mustable
2016-11-12 17:17:17 +02:00
VladimirEliTokarev 7b937cc754 Added the creator of the jar to compile and create jar
The jar converts json configuration into normal
2016-11-12 17:05:53 +02:00
Tal Moran 97f52bfd82 Fixed fatmain bug in build.gradle-template 2016-11-09 15:21:16 +02:00
Tal Moran e611d0c721 Removed generated files from repository 2016-11-05 23:58:33 +02:00
Tal Moran f5e71dec2f Added JOpt.simple dependency for command-line parsing; fixed bug in build.gradle to support alternative fatmain 2016-11-05 23:56:19 +02:00
VladimirEliTokarev d6ea9412e3 Changed the usage of json configuration
For now we use an binary file which contains all the protobuf ballot
quesitons.
However there is a problem, the jar that suppose to convert json into
binary configuraiton file doesnt work.
2016-11-05 18:00:28 +02:00
Tal Moran aac7a50a94 Yet more mixer refactoring 2016-11-02 11:59:20 +02:00
Tal Moran 5b268cd779 More refactoring for mixer 2016-11-02 00:31:49 +02:00
Tal Moran 1baa567d8e Starting to review and refactor mixer 2016-11-01 18:03:53 +02:00
Tal Moran c4e0e5f56d Allow using resource paths for images linked in the JSON configuration file 2016-10-30 15:34:58 +02:00
Tal Moran ac4d668984 Use configuration file from bundled resource in test 2016-10-30 13:32:39 +02:00
VladimirEliTokarev b601619d39 Changed the configuraitons tests 2016-10-29 23:10:18 +03:00
VladimirEliTokarev 555783d30c Removed unnsessery panels 2016-10-29 23:06:28 +03:00
VladimirEliTokarev c5dcb9f06e Added the description of the picture of the candidate 2016-10-29 09:48:29 +03:00
VladimirEliTokarev fbc1707b37 Added the validation that the answers given are valid strings and pictures 2016-10-28 20:02:27 +03:00
VladimirEliTokarev e440bb3e76 Added configuration validation 2016-10-28 19:55:51 +03:00
Tal Moran ca89514c97 Modified JSON and BARBECUE deps to use external maven sources 2016-10-26 00:50:23 +03:00
VladimirEliTokarev d575eaf1d2 Moved the main file into outer folder 2016-10-24 17:12:10 +03:00
VladimirEliTokarev 2bf78f7c73 Added the logging to the configuration parsing and the main 2016-10-24 16:47:38 +03:00
VladimirEliTokarev 0af9d94a73 Addded logging to all of the panels. 2016-10-24 16:31:48 +03:00
VladimirEliTokarev 37f8893224 Added the randomiztaion of the answesr 2016-10-18 11:55:31 +03:00
VladimirEliTokarev c474bc5ffb Added the creation of the barcode 2016-10-05 17:12:44 +03:00
VladimirEliTokarev 5a78f723e5 Added button which takes you back 2016-10-05 16:52:28 +03:00
VladimirEliTokarev 94adef22f3 Added the thank for auditing pannel 2016-10-05 16:48:30 +03:00
VladimirEliTokarev dcaf36f8fa Added local usage of json configuration ar 2016-10-05 15:54:06 +03:00
VladimirEliTokarev ef5cd70cc9 The summiting of the voters selections working 2016-10-05 15:37:53 +03:00
VladimirEliTokarev 8e2fdb7828 Every pannel that choses somthing now saves it into VotersBallot` 2016-10-05 13:11:18 +03:00
VladimirEliTokarev 6655f2dc8e Added the usage of VotersBallot 2016-10-05 13:00:57 +03:00
VladimirEliTokarev af2622b05c Changed the project structure 2016-10-05 12:28:19 +03:00
VladimirEliTokarev f535847ee5 Added tests to conbfiguration creator 2016-10-04 19:52:08 +03:00
VladimirEliTokarev 55279e2e3d The new object which creates protobuf from jason working 2016-10-04 15:50:00 +03:00
VladimirEliTokarev 80a0a2fc2c Added the Object which creates configurations based on json file 2016-10-04 15:09:58 +03:00
VladimirEliTokarev 80a032d373 Added the selection of the channel 2016-10-03 21:15:44 +03:00
VladimirEliTokarev 6e47a4df98 Added pictures showing
Now from given configuration the the pictures showed two,
2016-10-03 20:30:43 +03:00
VladimirEliTokarev 21867fb5da Added the ability to display pictures 2016-10-03 20:11:42 +03:00
VladimirEliTokarev 91b0d947f2 Started implementing the representation of the pictures for voter 2016-10-03 12:48:16 +03:00
VladimirEliTokarev 352fb7a548 Now the text question represented in the select name panel 2016-10-03 12:15:08 +03:00
VladimirEliTokarev 4e071c83ce Createing the object that will update the new strings answers 2016-10-02 23:02:42 +03:00
VladimirEliTokarev fe5a4d7be1 Created Succesefully whole config object 2016-10-02 22:26:38 +03:00
VladimirEliTokarev ef1ff8dea1 Creating configuration file 2016-10-02 21:35:59 +03:00
VladimirEliTokarev 272e40647e Resolved the issue with the encoding 2016-10-02 21:03:42 +03:00
VladimirEliTokarev c410fc6693 Added the changes of two way node to number of clases 2016-10-02 19:11:34 +03:00
VladimirEliTokarev 5267c7026f Implementing the usage of voting booth gui config 2016-10-02 18:52:20 +03:00
Vladimir Eliezer Tokarev 68291d34f3 Added the protobuf that describes the voting booth question
And changed the panels to work with voting booth configuration object
instead of the backend.
2016-10-01 18:50:33 +03:00
Vladimir Eliezer Tokarev 5c2de6d009 Inserted the VotingBoothConfiguration into every panel
For now every panel will have the configuration object when created and
will represent the questions/answers according to it.
2016-10-01 15:56:26 +03:00
Vladimir Eliezer Tokarev c0ed5ea3fc Added the configuration object that will contains all needed data for
voting booth initilization
2016-10-01 15:41:13 +03:00
Vladimir Eliezer Tokarev 51ea368c01 Removed the packages problem 2016-10-01 11:07:43 +03:00
Vladimir Eliezer Tokarev 7cda19247e Updated the slesect candidate by pictures 2016-10-01 11:00:25 +03:00
Vladimir Eliezer Tokarev 9211c70c2e Merge branch 'voting-booth-gui' of http://cs.idc.ac.il/rhodecode/meerkat/meerkat-java into voting-booth-gui 2016-09-24 19:17:40 +03:00
Vladimir Eliezer Tokarev 16e0633b75 Removed unnesesery fields 2016-09-24 19:09:13 +03:00
Vladimir Eliezer Tokarev 37be8f3a52 Added the ability to submit questions and answers 2016-09-24 19:08:24 +03:00
Vladimir Eliezer Tokarev f1704ce16e Implemented the start new session functionality
When new session needed to be started we call the doStartNewSesison method
which generates all of the gui panels and gives to them the primary
stage.
2016-09-24 13:26:15 +03:00
Tal Moran a9fbf555d6 Refactored voting-booth-gui to use gradle and match maven directory style 2016-09-21 17:21:34 +03:00
Tal Moran 1ff490a354 Merge branch 'master' of https://cs.idc.ac.il/rhodecode/meerkat/meerkat-java into voting-booth-gui 2016-09-21 16:23:45 +03:00
Tal Moran 63e26fdc16 Some documentation for versionup.pl 2016-09-21 16:21:22 +03:00
Tal Moran 267164e996 Update to Gradle 3.1 2016-09-21 16:19:45 +03:00
Vladimir Eliezer Tokarev 0007df38b8 updated documentation 2016-09-17 13:50:06 +03:00
Vladimir Eliezer Tokarev d7fca43817 Removed unnesesary test2 class 2016-09-17 13:40:22 +03:00
Vladimir Eliezer Tokarev 210994ca75 Added the main method into VotingBoothGUIManager 2016-09-17 13:36:37 +03:00
Vladimir Eliezer Tokarev d8adf7b49e Created the VotingBoothGUIManager
This object creates a map of visual panels and can convert the
VotingBoothUI methods into actions (can execute them on wanted panels)
2016-09-17 13:29:21 +03:00
Vladimir Eliezer Tokarev 9444072f40 Created the Graphical User Interface
This objhect is the connector between the backed and the voting booth gui
also moved  number of methods from SystemConsoleUI into Utils class for
all the UI's classes be able to use those methods.
2016-09-17 12:33:40 +03:00
Vladimir Eliezer Tokarev c6f2ffff9b Merge branch 'master' of http://cs.idc.ac.il/rhodecode/meerkat/meerkat-java into voting-booth-gui 2016-09-10 14:00:14 +03:00
Vladimir Eliezer Tokarev dda2d3e55b Added Vote have been cast into flow 2016-08-27 17:06:35 +03:00
Vladimir Eliezer Tokarev 38f1624071 Added the cast or audit into the flow 2016-08-27 16:56:53 +03:00
Vladimir Eliezer Tokarev b687782f5c Added the ballot summary to the flow 2016-08-27 16:48:50 +03:00
Vladimir Eliezer Tokarev 4591c4e2e8 Added Select candidate by picture to the flow 2016-08-27 16:24:25 +03:00
Vladimir Eliezer Tokarev 36d9276ad8 Added the Select Candidate name into the flaw 2016-08-27 16:05:18 +03:00
Vladimir Eliezer Tokarev 01cda996f9 Connected the Welcome splash to straight channel section
When start voting pressed in welcome splash panel strair channel loaded
2016-08-27 15:07:18 +03:00
Vladimir Eliezer Tokarev e200bc0f8b Converted TwoWayNode into abstract class
Becuase all the classes that will extend this class will implelmett thisa
same logic, so there is no reason to implement it as interface.
2016-08-27 14:24:00 +03:00
Vladimir Eliezer Tokarev 9b636f2d2e Converted WelcomeSplash into two way link
Welcome Splash should be the first object to displayed and when the next
button pressed it shoud jump to already created straight channel section.
2016-08-27 14:07:13 +03:00
Vladimir Eliezer Tokarev e605089666 Created the basic idea polling booth gui
The idea is simple all of the fxmls panels will be connected this how
every one willknow which is the next or the previous panel
2016-08-27 13:34:03 +03:00
Vladimir Eliezer Tokarev c39d0774f9 Changed the name of fxml 2016-08-27 12:38:38 +03:00
Vladimir Eliezer Tokarev 19b11736e0 Created the vote has been casted
This was the last fxml that i needed to create now i should connect by
simple flow all of the panels.
next is implement panels logic and then work with the backend.
2016-08-20 13:01:27 +03:00
Vladimir Eliezer Tokarev 3a86e6f01a Created the thank for auditing panel
this panel returns the voter into first stage of selection.
2016-08-20 12:27:27 +03:00
Vladimir Eliezer Tokarev c26acbc3cf Created cast of audit panel
This panel lets you choose whenerver you want to cast or audit your vote
voting will set your vote as countable, and acudit is the choise for
checking the encoding process works fine.
2016-08-20 12:15:58 +03:00
Vladimir Eliezer Tokarev 86de5b07a4 Created the printing commitment barcode panel
Printing Commitment Barcode panel represent the progress of the printing
of the voters choise.
2016-08-20 11:55:14 +03:00
Vladimir Eliezer Tokarev 8a73f21f48 Created the ballot summary panel
ballot summary panel summaries the status of current voter
2016-08-20 11:48:54 +03:00
Hai Brenner f2ea4b5d15 Typo: fix typo in documentation 2016-08-11 13:36:09 +03:00
Hai Brenner 6b512e078b Merge branch 'master' into dist_dec
# Conflicts:
#	meerkat-common/src/main/java/meerkat/crypto/concrete/ECElGamalEncryption.java
#	meerkat-common/src/main/proto/meerkat/BulletinBoardAPI.proto
#	settings.gradle
2016-08-09 14:30:40 +03:00
Hai Brenner ce40a04ac7 Make code of SDKG test prettier 2016-08-07 17:04:23 +03:00
Vladimir Eliezer Tokarev bc790c3eb4 Created select candidate by picture fxml 2016-08-06 16:19:34 +03:00
Vladimir Eliezer Tokarev 99edbcf1b1 Created the candidate write name and the candidate selections uml 2016-08-06 16:01:33 +03:00
Hai Brenner 4ddd5f852a Fix: better test of the NetworkVirtualPrinter 2016-08-02 14:24:25 +03:00
Hai Brenner c78b78aa3c no change 2016-08-02 14:22:21 +03:00
Hai Brenner afed4fb510 Fix: reading protobuf BoolValues through network used to fail due to problematic dynamic casting 2016-08-02 14:13:10 +03:00
Hai Brenner aea84d0f54 Add: todo comments 2016-07-31 17:44:24 +03:00
Hai Brenner cb65103fca Fix: typo in comment 2016-07-31 17:43:58 +03:00
Vladimir Eliezer Tokarev 4f3fc40dc6 Created the voting-booth-station
The created branch is for the voting booth gui, and created the enter
splash fxml file and the straight into channel fxml.
2016-07-30 16:57:17 +03:00
Hai Brenner ae357541e8 Fix typo in documentation comment 2016-07-27 14:25:09 +03:00
Hai Brenner da7a05ecd8 Fix: UI now has command queue of size 1.
A new CommandPend class is introduced. It functions basically as
an ArrayBlockingQueue of size 1.
Difference is that it can handle two functions from two different threads
 - trample(cmd): removes the previously kept command (if there is such) and overrides it with the next command
 - offer(cmd): keeps the given command, but only if it doesn't currently keeps an older one

This new functionality is used so the UI can get commands from the controller
(but only take into account the latest one). At the same time it gets tick commands
from its clock ticker, but only keep and handle those if it doesn't have
a real controller command to handle.
2016-07-19 12:05:06 +03:00
Hai Brenner 1cf16d8386 Fix: output device now has queue of size 1. a newer command always overrides the previous one 2016-07-19 11:59:50 +03:00
Hai Brenner 88991ea9ff Refactor: some more comments, and better looking code in VotingBoothImpl.java 2016-07-12 15:32:22 +03:00
Hai Brenner 0956fa98d3 Refactor: add comments to VotingBoothImpl.java, and rename tasks to commands (because they were two names of the same thing)
Signed-off-by: Hai Brenner <haibrenner@gmail.com>
2016-07-12 15:23:46 +03:00
Hai Brenner 42d68b7ce8 Test: Add NetworkVirtualPrinterTest 2016-07-12 11:54:50 +03:00
Hai Brenner 5404bb9ed2 Fix error in setting name of output device thread 2016-07-12 11:51:16 +03:00
Hai Brenner f2836d277a Add polling-station project dependency to VB build.gradle for testing purposes 2016-07-12 11:50:21 +03:00
Hai Brenner b667de95aa Fix protobuf definition of EncryptedBallot to match the standard convention. 2016-07-12 11:49:25 +03:00
Hai Brenner 49551dc36b Merge branch 'master' into vbdev2
Conflicts:
	meerkat-common/src/main/proto/meerkat/voting.proto
2016-07-06 21:55:34 +03:00
Hai Brenner 88c4e0e644 Merge branch 'master' of https://cs.idc.ac.il/rhodecode/meerkat/meerkat-java 2016-07-06 21:45:35 +03:00
Hai Brenner d804f0dbac voting-booth gradle.build now has dependencies on jetty and RESTful API for testing purposes 2016-07-06 21:33:30 +03:00
Hai Brenner b1a033da5e Add the channel identifier to the PlaintextBallot, so it is now printed by the output device 2016-07-06 13:24:10 +03:00
Hai Brenner 853a6b5684 Another (final) Generics type fix 2016-07-04 16:05:52 +03:00
Hai Brenner 2336d44ffc reduced more compilation warnings by having better Generics type handling and better JavaDocs 2016-07-04 16:04:04 +03:00
Hai Brenner d8b766725b Removed many compilation warnings by having better handling of Generics types and better JavaDocs 2016-07-04 15:52:05 +03:00
Hai Brenner 218677fd96 added many comments and JavaDocs 2016-07-04 14:17:11 +03:00
Hai Brenner 2b56928e9a added some JavaDoc comments and documentation 2016-07-04 11:54:36 +03:00
Hai Brenner 7db6218735 fixed: access to the static system messages from the StorageManage class, rather than its instance 2016-07-04 11:53:28 +03:00
Hai Brenner 66e5db9f22 cleared some unnecessary imports 2016-07-04 11:51:49 +03:00
arbel.peled 48bf8dbe6b Ignore .arcconfig 2016-06-28 15:10:38 +03:00
arbel.peled 8aada21119 Fixed some errors in the tests.
Made Threaded Client parameterized (with respect to waiting times and thread count).
2016-06-28 15:08:36 +03:00
Hai Brenner e298ab1e76 removed two unnecessary imports 2016-06-28 11:35:36 +03:00
Hai Brenner 14fac728b3 1. ScannedData now has a Channel and a SignedEncryptedBallot attributes,
rather than just 'data' attribute
2. Implemented a NetworkVirtualPrinter output device, and now both
   this class and the previous SystemConsoleOutputDevice extend the same
   new abstract class AsyncRunnableOutputDevice which supplies default
   implementations for the interface methods.
2016-06-28 11:32:00 +03:00
Hai Brenner 3a5908ac49 A cancel by the voter is now sent to the execution flow as a Throwable
rather than an Exception.
2016-06-28 11:28:14 +03:00
Hai Brenner 438df78e36 Changed the ToySignature test file to match the new Signature interface. 2016-06-28 11:26:50 +03:00
Hai Brenner 4379921445 For some reason restful API did not appear in my gradle.build. It is fixed now. 2016-06-28 11:26:00 +03:00
arbel.peled 53d609bfee Removed database address from Bulletin Board Server init method (the address is given to the Query Provider).
Added integration tests for Single Server Bulletin Board Client.
Fixed subscription logic in the Single Server Bulletin Board Client.
Decoupled Single Server- and Simple- Bulletin Board Clients (Simple-... is now obsolete).
Fixed some bugs.
Threaded Bulletin Board Client now has some errors in integration.
2016-06-28 08:19:29 +03:00
Hai Brenner d12ad408c4 Merge branch 'master' into vbdev2
Conflicts:
	meerkat-common/src/main/proto/meerkat/voting.proto
2016-06-26 17:17:24 +03:00
Hai Brenner 06d69554d9 fixed some merge conflicts which appeared for some unknown reason 2016-06-26 17:07:03 +03:00
Arbel Deutsch Peled cc2888483d Threaded Client integration tests passing 2016-06-26 14:32:30 +03:00
Arbel Deutsch Peled d1f7413cde Working client-side Batch changes 2016-06-26 13:06:16 +03:00
Hai Brenner 42ae18df00 Just added comments as part of the process to comment all the VB files.
Currently I commented the controller callbacks and commands packages, and
also the QuestionSelector component.
2016-06-21 15:37:20 +03:00
Hai Brenner e9732561f4 Removed the last dependency on the obsolete SystemMessages class.
This dependency was left by mistake.
We now read all the system messages of the VB from a protobuf file
using the StorageManager component.
2016-06-21 15:35:42 +03:00
Hai Brenner 19deec00bb a change of variable name to suggest its insignificance 2016-06-20 16:15:41 +03:00
Hai Brenner 559c714aac testing was changed according to the new interface of storage manager 2016-06-20 15:27:39 +03:00
Hai Brenner 2bdf92b075 Fixed some stuff according to Arbel's suggestions.
Specifically:
1. now handling exceptions in the encryption process (according to voter's choice)
2. handling files in storage manager (now reading election parameters and system messages from files)
3. Controller's init() now already sets all the info and parameters. No need to call extra functions
4. some more changes to the method structure
2016-06-20 15:26:53 +03:00
Arbel Deutsch Peled 1951db546d Changed Bulletin Board Message payload to either data or message ID
Added server-generated unique batch identifiers
Changed Client-side interfaces
Refactored Client-side code for new batch mechanisms

Not tested on client-side yet
2016-06-19 22:00:43 +03:00
Arbel Deutsch Peled b501992643 Complete overhaul of batch system on server-side
Added batch methods to BulletinBoardUtils
Related changes in BulletinBoardDigest and BulletinBoardSignature

Merge remote-tracking branch 'origin/master' into Cached-Client

Conflicts:
	bulletin-board-client/src/main/java/meerkat/bulletinboard/SimpleBulletinBoardClient.java
	bulletin-board-client/src/main/java/meerkat/bulletinboard/ThreadedBulletinBoardClient.java
	bulletin-board-server/src/main/java/meerkat/bulletinboard/sqlserver/BulletinBoardSQLServer.java
	bulletin-board-server/src/main/java/meerkat/bulletinboard/sqlserver/H2QueryProvider.java
	bulletin-board-server/src/main/java/meerkat/bulletinboard/sqlserver/MySQLQueryProvider.java
	bulletin-board-server/src/main/java/meerkat/bulletinboard/webapp/BulletinBoardWebApp.java
	bulletin-board-server/src/main/proto/meerkat/bulletin_board_server.proto
	meerkat-common/src/main/java/meerkat/bulletinboard/BulletinBoardClient.java
	meerkat-common/src/main/java/meerkat/bulletinboard/BulletinBoardServer.java
2016-06-16 11:21:58 +03:00
Hai Brenner 13f8948cfb change the output class to run as a thread.
It is now runnable and has its own queue of (also new) OutputCommands, and its own shutdown flag.
2016-06-15 19:32:05 +03:00
Arbel Deutsch Peled 337a135151 Started removing dependency on CompleteBatch.
Tags of batches are now stored as a blob until the batch is complete.
2016-06-15 10:34:46 +03:00
Hai Brenner 76d3fdeac2 Fixed the Selector classes.
Fixed the crypto classes to handle signatures as well.
Some more other fixes
2016-06-07 16:15:08 +03:00
arbel.peled ffac7c1e34 Fixed all of Tal's remarks.
Switched to using the predefined BoolValue Protobuf.
2016-06-02 14:48:48 +03:00
arbel.peled 229cbfd48f Fixed some subscription functionality of the CachedClient 2016-06-02 13:01:41 +03:00
arbel.peled fe209f6b5a Removed default testing for the Bulletin Board Client. 2016-06-02 10:39:29 +03:00
arbel.peled e2f3dbe6b2 Fixed some more issues (most have to do with concurrency).
Implemented close method for the SQLServer which renders it unusable until reinitialization.
Added test for Synchronizer for the case when the remote server is unavailable (test passes).
Still need to fix Batch digest and sign issue.
2016-06-02 10:38:31 +03:00
Arbel Deutsch Peled e91a48b5e1 Fixed a few bugs.
Changed H2 Query Provider to run in-memory.
2016-06-01 22:46:51 +03:00
arbel.peled 7c60e487cc Created a test for the Synchronizer.
Not passing yet.
2016-06-01 21:34:17 +03:00
arbel.peled 347e826f73 Working integrated version of Scanner WebApp
Fully testsed
Moved BoolMsg and IntMsg to Comm package (from BulletinBoardAPI)
2016-05-31 15:26:56 +03:00
Hai Brenner 94f3920e6d Many fixes, some are still only in temporary phase, according to what Arbel told me to do so far. 2016-05-23 14:43:01 +03:00
arbel.peled 061dc69fbc File rename 2016-05-05 17:01:00 +03:00
arbel.peled b934894bc5 Created Polling Station Scanner interface
Implemented Web App for the scanner
Not tested
2016-05-05 16:55:10 +03:00
Hai Brenner c04ed42dca possible future protobufs for handling category channel selection.
Summary:
The initial code for the voting booth.
Some things are still missing:
1. comments EVERYWHERE
2. an implementation for the encryptor (program crashes when trying to encrypt PlaintexBallot)
3. the OutputDevice class should become a thread, (runnable with a queue of commands as the UI component)

Test Plan: Currently only simply run it with another main class.

Reviewers: arbel.peled

Differential Revision: https://proj-cs.idc.ac.il/D3
2016-05-04 17:58:06 +03:00
Hai Brenner e042779b15 Initial code for the Voting Booth.
Still missing components:
1. An implementation of the encryptor (currently program crashes when trying to encrypt the PlaintextBallot)
2. The output device implementation should change to a runnable thread (with commands queue as the ui)

Also needs to add comments EVERYWHERE.
2016-05-04 17:46:05 +03:00
tzlil.gon e677355040 comments: 2016-04-17 09:09:13 +03:00
tzlil.gon b4e5040814 comments 2016-04-17 09:08:21 +03:00
arbel.peled 4c33e923b2 Implemented Synchronizer and Cached Client
Not tested yet
2016-04-16 19:50:09 +03:00
arbel.peled 9ed728fca7 Added message counting ability to the server (but not to the client)
Added synchronous CompleteBatch read by the client
Started implementing the synchronizer
Added support for null callbacks
2016-04-14 09:20:11 +03:00
arbel.peled 48b2b9efa2 Merge branch 'Cached-Client' of https://cs.idc.ac.il/rhodecode/meerkat/meerkat-java into Cached-Client 2016-04-13 10:38:43 +03:00
Arbel Deutsch Peled c806e7b32a Added Deletion to Bulletin Board Server and Local Client 2016-04-13 09:46:24 +03:00
Tal Moran f5410bedf4 Typo fixes 2016-04-05 11:51:01 +03:00
Tal Moran 19e52344f5 Merged move to public qilin version 2016-04-05 11:49:34 +03:00
Tal Moran d99bf4123e Merge remote-tracking branch 'origin/master' into mixer 2016-04-05 11:32:19 +03:00
Tal Moran 6d6e4748b7 merge 2016-04-05 11:32:07 +03:00
tzlil.gon e27fddcf0c mixing test passed 2016-03-20 19:18:23 +02:00
tzlil.gon b7e543e5e8 simple rerandomize test 2016-03-20 16:15:43 +02:00
Hai Brenner d1bc0d7c84 Some small changes according to code review 2016-03-18 10:22:05 +02:00
Hai Brenner 0fa5d4094a yet another take on the VB interfaces.
Interfaces were now shortened even more.
Many changes were made according to Arbel's instructions.
Most important change is now that the controller passes ALL questions for UI
to ask voter, instead of chunks of questions and back-and-forth messages
between the controller and UI which were always quite redundant.
2016-03-13 15:35:58 +02:00
Hai Brenner 15453a772d Changes made in the initial interfaces for further code review.
1. Especially tried to fix the callback mechanism I previously used.
2. 'long sessionID' changed to 'int requestId'
3. Introduced a generic class VotingBoothResult
4. Quite some other local changes
2016-03-02 19:52:17 +02:00
Hai Brenner 77f47fe9e1 First version of Voter Booth
Summary:
Planned some basic interfaces for my revised Voting Booth componenets.
No implementation yet, though...

Test Plan: There are none, yet

Reviewers: arbel.peled

Differential Revision: https://proj-cs.idc.ac.il/D2
2016-02-28 15:10:27 +02:00
tzlil.gon efde36d869 profiling 2016-01-25 16:48:36 +02:00
tzlil.gon ae2b7c51f0 zkp speedup 2016-01-20 12:14:15 +02:00
tzlil.gon 9bb2f47b50 last version - main problem was found in RerandomizeTest 2016-01-17 20:17:04 +02:00
tzlil.gon 8a07f86c0f more tests for mixer 2016-01-01 11:37:54 +02:00
tzlil.gon 8f75bebaea testing in progress 2016-01-01 00:39:17 +02:00
tzlil.gon 026a879de3 zkp verification fails from time to time 2015-12-31 18:58:25 +02:00
tzlil.gon 75c411a5e7 integrated to AsyncBBClient 2015-12-27 13:12:17 +02:00
tzlil.gon be6449f27d before merge with master 2015-12-17 23:45:32 +02:00
tzlil.gon c8646712c0 Mixing + Mixer test 2015-12-17 19:15:48 +02:00
tzlil.gon 767d73c143 smal changes after code review 2015-12-15 16:44:50 +02:00
tzlil.gon c37d30baf6 work with qilin 2015-12-14 17:54:44 +02:00
tzlil.gon 23573666ec mixer + prover + verifier 2015-12-11 14:41:26 +02:00
tzlil.gon b502dc82d3 updated mixer code 2015-12-08 00:15:16 +02:00
tzlil.gon a834194d50 mixer project 2015-12-01 21:48:41 +02:00
tzlil.gon 12ed7a679d mixer code 2015-11-24 15:39:39 +02:00
498 changed files with 31392 additions and 8468 deletions

54
.gitignore vendored
View File

@ -1,16 +1,38 @@
.gradle
.idea
build
bin
.settings
.classpath
.project
out
*.iml
*.ipr
*.iws
**/*.swp
*.prefs
*.project
*.classpath
bulletin-board-server/local-instances/meerkat.db
.gradle
.idea
build
bin
.settings
.classpath
.project
out
*.iml
*.ipr
*.iws
**/*.swp
*.prefs
*.project
*.classpath
*.db
*.sql
.arcconfig
/meerkat_election_params_tempfile.dat
/meerkat_booth_system_messages.dat
local.properties
# Angular junk
*/tmp
*/dist
*/out-tsc
*/node_modules
*/e2e/*.js
*/e2e/*.map
*/.sass-cache
*/connect.lock
*/coverage
*/libpeerconnection.log
npm-debug.log
testem.log
*/typings
bundle.js
bundle.d.ts

View File

@ -0,0 +1,10 @@
This file describes Wombat Code & Documentation Conventions:
Code Conventions:
* Code- The good old classic java code conventions
camelCase convention, constants should be capital letters with underscore etc...
Documentation Conventions:
* Comments- The good old classic java code documentation, Block Comments (to describe
method/class/interface etc...) // comments too describe complex code
(only if the code block complicated)

View File

@ -0,0 +1,82 @@
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.3.3'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
apply plugin: 'com.android.application'
android {
compileSdkVersion 26
buildToolsVersion "26.0.0"
defaultConfig {
applicationId "com.meerkat.laura.fakescannerapp"
minSdkVersion 14
targetSdkVersion 26
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
// Enabling multidex support.
multiDexEnabled true
vectorDrawables.useSupportLibrary = true
}
buildTypes {
debug {
debuggable true
}
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
configurations.all {
resolutionStrategy.dependencySubstitution {
// Avoid Android compilation error caused by two different javax.inject dependencies.
substitute module('org.glassfish.hk2.external:javax.inject:2.4.0-b34') with module('javax.inject:javax.inject:1')
}
// Exclude non-android logback modules
exclude group:"ch.qos.logback", module:"logback-core"
exclude group:"ch.qos.logback", module:"logback-classic"
}
dependencies {
// compile fileTree(dir: 'libs', include: ['*.jar'])
compile project(':meerkat-common')
compile project(':scanner-api-common')
compile 'com.android.support:appcompat-v7:26.0.+'
compile 'com.google.protobuf:protobuf-java:3.+'
compile 'com.android.support:support-v4:26.0.+'
compile 'com.android.support:support-vector-drawable:26.0.+'
testCompile 'junit:junit:4.12'
// Android logging
compile 'com.github.tony19:logback-android-core:1.1.1-6'
compile('com.github.tony19:logback-android-classic:1.1.1-6') {
// workaround issue #73
exclude group: 'com.google.android', module: 'android'
}
}
repositories {
mavenLocal()
jcenter()
}

1
android-scanner/gradlew vendored Symbolic link
View File

@ -0,0 +1 @@
../gradlew

7
android-scanner/lint.xml Normal file
View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<lint>
<issue id="InvalidPackage">
<!-- Ignore javax.mail from logback https://github.com/tony19/logback-android/issues/140-->
<ignore regexp="javax.mail"/>
</issue>
</lint>

View File

@ -0,0 +1,13 @@
package com.meerkat.laura.fakescannerapp;
import android.app.Application;
import android.test.ApplicationTestCase;
/**
* <a href="http://d.android.com/tools/testing/testing_android.html">Testing Fundamentals</a>
*/
public class ApplicationTest extends ApplicationTestCase<Application> {
public ApplicationTest() {
super(Application.class);
}
}

View File

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.meerkat.laura.fakescannerapp">
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<activity
android:name=".SettingsActivity"
android:label="@string/title_activity_settings">
</activity>
</application>
</manifest>

View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<appender name="logcat" class="ch.qos.logback.classic.android.LogcatAppender">
<tagEncoder>
<pattern>%logger{12}</pattern>
</tagEncoder>
<encoder>
<pattern>[%-20thread] %msg</pattern>
</encoder>
</appender>
<root level="DEBUG">
<appender-ref ref="logcat" />
</root>
</configuration>

View File

@ -0,0 +1,508 @@
package com.google.zxing.integration.android;
/*
* Copyright 2009 ZXing authors
*
* 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.
*/
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Fragment;
import android.content.ActivityNotFoundException;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
/**
* <p>A utility class which helps ease integration with Barcode Scanner via {@link Intent}s. This is a simple
* way to invoke barcode scanning and receive the result, without any need to integrate, modify, or learn the
* project's source code.</p>
*
* <h2>Initiating a barcode scan</h2>
*
* <p>To integrate, create an instance of {@code IntentIntegrator} and call {@link #initiateScan()} and wait
* for the result in your app.</p>
*
* <p>It does require that the Barcode Scanner (or work-alike) application is installed. The
* {@link #initiateScan()} method will prompt the user to download the application, if needed.</p>
*
* <p>There are a few steps to using this integration. First, your {@link Activity} must implement
* the method {@link Activity#onActivityResult(int, int, Intent)} and include a line of code like this:</p>
*
* <pre>{@code
* public void onActivityResult(int requestCode, int resultCode, Intent intent) {
* IntentResult scanResult = IntentIntegrator.parseActivityResult(requestCode, resultCode, intent);
* if (scanResult != null) {
* // handle scan result
* }
* // else continue with any other code you need in the method
* ...
* }
* }</pre>
*
* <p>This is where you will handle a scan result.</p>
*
* <p>Second, just call this in response to a user action somewhere to begin the scan process:</p>
*
* <pre>{@code
* IntentIntegrator integrator = new IntentIntegrator(yourActivity);
* integrator.initiateScan();
* }</pre>
*
* <p>Note that {@link #initiateScan()} returns an {@link AlertDialog} which is non-null if the
* user was prompted to download the application. This lets the calling app potentially manage the dialog.
* In particular, ideally, the app dismisses the dialog if it's still active in its {@link Activity#onPause()}
* method.</p>
*
* <p>You can use {@link #setTitle(String)} to customize the title of this download prompt dialog (or, use
* {@link #setTitleByID(int)} to set the title by string resource ID.) Likewise, the prompt message, and
* yes/no button labels can be changed.</p>
*
* <p>Finally, you can use {@link #addExtra(String, Object)} to add more parameters to the Intent used
* to invoke the scanner. This can be used to set additional options not directly exposed by this
* simplified API.</p>
*
* <p>By default, this will only allow applications that are known to respond to this intent correctly
* do so. The apps that are allowed to response can be set with {@link #setTargetApplications(List)}.
* For example, set to {@link #TARGET_BARCODE_SCANNER_ONLY} to only target the Barcode Scanner app itself.</p>
*
* <h2>Sharing text via barcode</h2>
*
* <p>To share text, encoded as a QR Code on-screen, similarly, see {@link #shareText(CharSequence)}.</p>
*
* <p>Some code, particularly download integration, was contributed from the Anobiit application.</p>
*
* <h2>Enabling experimental barcode formats</h2>
*
* <p>Some formats are not enabled by default even when scanning with {@link #ALL_CODE_TYPES}, such as
* PDF417. Use {@link #initiateScan(Collection)} with
* a collection containing the names of formats to scan for explicitly, like "PDF_417", to use such
* formats.</p>
*
* @author Sean Owen
* @author Fred Lin
* @author Isaac Potoczny-Jones
* @author Brad Drehmer
* @author gcstang
*/
public class IntentIntegrator {
public static final int REQUEST_CODE = 0x0000c0de; // Only use bottom 16 bits
private static final String TAG = IntentIntegrator.class.getSimpleName();
public static final String DEFAULT_TITLE = "Install Barcode Scanner?";
public static final String DEFAULT_MESSAGE =
"This application requires Barcode Scanner. Would you like to install it?";
public static final String DEFAULT_YES = "Yes";
public static final String DEFAULT_NO = "No";
private static final String BS_PACKAGE = "com.google.zxing.client.android";
private static final String BSPLUS_PACKAGE = "com.srowen.bs.android";
// supported barcode formats
public static final Collection<String> PRODUCT_CODE_TYPES = list("UPC_A", "UPC_E", "EAN_8", "EAN_13", "RSS_14");
public static final Collection<String> ONE_D_CODE_TYPES =
list("UPC_A", "UPC_E", "EAN_8", "EAN_13", "CODE_39", "CODE_93", "CODE_128",
"ITF", "RSS_14", "RSS_EXPANDED");
public static final Collection<String> QR_CODE_TYPES = Collections.singleton("QR_CODE");
public static final Collection<String> DATA_MATRIX_TYPES = Collections.singleton("DATA_MATRIX");
public static final Collection<String> ALL_CODE_TYPES = null;
public static final List<String> TARGET_BARCODE_SCANNER_ONLY = Collections.singletonList(BS_PACKAGE);
public static final List<String> TARGET_ALL_KNOWN = list(
BSPLUS_PACKAGE, // Barcode Scanner+
BSPLUS_PACKAGE + ".simple", // Barcode Scanner+ Simple
BS_PACKAGE // Barcode Scanner
// What else supports this intent?
);
private final Activity activity;
private final Fragment fragment;
private String title;
private String message;
private String buttonYes;
private String buttonNo;
private List<String> targetApplications;
private final Map<String,Object> moreExtras = new HashMap<String,Object>(3);
/**
* @param activity {@link Activity} invoking the integration
*/
public IntentIntegrator(Activity activity) {
this.activity = activity;
this.fragment = null;
initializeConfiguration();
}
/**
* @param fragment {@link Fragment} invoking the integration.
* {@link #startActivityForResult(Intent, int)} will be called on the {@link Fragment} instead
* of an {@link Activity}
*/
public IntentIntegrator(Fragment fragment) {
this.activity = fragment.getActivity();
this.fragment = fragment;
initializeConfiguration();
}
private void initializeConfiguration() {
title = DEFAULT_TITLE;
message = DEFAULT_MESSAGE;
buttonYes = DEFAULT_YES;
buttonNo = DEFAULT_NO;
targetApplications = TARGET_ALL_KNOWN;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public void setTitleByID(int titleID) {
title = activity.getString(titleID);
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public void setMessageByID(int messageID) {
message = activity.getString(messageID);
}
public String getButtonYes() {
return buttonYes;
}
public void setButtonYes(String buttonYes) {
this.buttonYes = buttonYes;
}
public void setButtonYesByID(int buttonYesID) {
buttonYes = activity.getString(buttonYesID);
}
public String getButtonNo() {
return buttonNo;
}
public void setButtonNo(String buttonNo) {
this.buttonNo = buttonNo;
}
public void setButtonNoByID(int buttonNoID) {
buttonNo = activity.getString(buttonNoID);
}
public Collection<String> getTargetApplications() {
return targetApplications;
}
public final void setTargetApplications(List<String> targetApplications) {
if (targetApplications.isEmpty()) {
throw new IllegalArgumentException("No target applications");
}
this.targetApplications = targetApplications;
}
public void setSingleTargetApplication(String targetApplication) {
this.targetApplications = Collections.singletonList(targetApplication);
}
public Map<String,?> getMoreExtras() {
return moreExtras;
}
public final void addExtra(String key, Object value) {
moreExtras.put(key, value);
}
/**
* Initiates a scan for all known barcode types with the default camera.
*
* @return the {@link AlertDialog} that was shown to the user prompting them to download the app
* if a prompt was needed, or null otherwise.
*/
public final AlertDialog initiateScan() {
return initiateScan(ALL_CODE_TYPES, -1);
}
/**
* Initiates a scan for all known barcode types with the specified camera.
*
* @param cameraId camera ID of the camera to use. A negative value means "no preference".
* @return the {@link AlertDialog} that was shown to the user prompting them to download the app
* if a prompt was needed, or null otherwise.
*/
public final AlertDialog initiateScan(int cameraId) {
return initiateScan(ALL_CODE_TYPES, cameraId);
}
/**
* Initiates a scan, using the default camera, only for a certain set of barcode types, given as strings corresponding
* to their names in ZXing's {@code BarcodeFormat} class like "UPC_A". You can supply constants
* like {@link #PRODUCT_CODE_TYPES} for example.
*
* @param desiredBarcodeFormats names of {@code BarcodeFormat}s to scan for
* @return the {@link AlertDialog} that was shown to the user prompting them to download the app
* if a prompt was needed, or null otherwise.
*/
public final AlertDialog initiateScan(Collection<String> desiredBarcodeFormats) {
return initiateScan(desiredBarcodeFormats, -1);
}
/**
* Initiates a scan, using the specified camera, only for a certain set of barcode types, given as strings corresponding
* to their names in ZXing's {@code BarcodeFormat} class like "UPC_A". You can supply constants
* like {@link #PRODUCT_CODE_TYPES} for example.
*
* @param desiredBarcodeFormats names of {@code BarcodeFormat}s to scan for
* @param cameraId camera ID of the camera to use. A negative value means "no preference".
* @return the {@link AlertDialog} that was shown to the user prompting them to download the app
* if a prompt was needed, or null otherwise
*/
public final AlertDialog initiateScan(Collection<String> desiredBarcodeFormats, int cameraId) {
Intent intentScan = new Intent(BS_PACKAGE + ".SCAN");
intentScan.addCategory(Intent.CATEGORY_DEFAULT);
// check which types of codes to scan for
if (desiredBarcodeFormats != null) {
// set the desired barcode types
StringBuilder joinedByComma = new StringBuilder();
for (String format : desiredBarcodeFormats) {
if (joinedByComma.length() > 0) {
joinedByComma.append(',');
}
joinedByComma.append(format);
}
intentScan.putExtra("SCAN_FORMATS", joinedByComma.toString());
}
// check requested camera ID
if (cameraId >= 0) {
intentScan.putExtra("SCAN_CAMERA_ID", cameraId);
}
String targetAppPackage = findTargetAppPackage(intentScan);
if (targetAppPackage == null) {
return showDownloadDialog();
}
intentScan.setPackage(targetAppPackage);
intentScan.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
intentScan.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
attachMoreExtras(intentScan);
startActivityForResult(intentScan, REQUEST_CODE);
return null;
}
/**
* Start an activity. This method is defined to allow different methods of activity starting for
* newer versions of Android and for compatibility library.
*
* @param intent Intent to start.
* @param code Request code for the activity
* @see Activity#startActivityForResult(Intent, int)
* @see Fragment#startActivityForResult(Intent, int)
*/
protected void startActivityForResult(Intent intent, int code) {
if (fragment == null) {
activity.startActivityForResult(intent, code);
} else {
fragment.startActivityForResult(intent, code);
}
}
private String findTargetAppPackage(Intent intent) {
PackageManager pm = activity.getPackageManager();
List<ResolveInfo> availableApps = pm.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
if (availableApps != null) {
for (String targetApp : targetApplications) {
if (contains(availableApps, targetApp)) {
return targetApp;
}
}
}
return null;
}
private static boolean contains(Iterable<ResolveInfo> availableApps, String targetApp) {
for (ResolveInfo availableApp : availableApps) {
String packageName = availableApp.activityInfo.packageName;
if (targetApp.equals(packageName)) {
return true;
}
}
return false;
}
private AlertDialog showDownloadDialog() {
AlertDialog.Builder downloadDialog = new AlertDialog.Builder(activity);
downloadDialog.setTitle(title);
downloadDialog.setMessage(message);
downloadDialog.setPositiveButton(buttonYes, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
String packageName;
if (targetApplications.contains(BS_PACKAGE)) {
// Prefer to suggest download of BS if it's anywhere in the list
packageName = BS_PACKAGE;
} else {
// Otherwise, first option:
packageName = targetApplications.get(0);
}
Uri uri = Uri.parse("market://details?id=" + packageName);
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
try {
if (fragment == null) {
activity.startActivity(intent);
} else {
fragment.startActivity(intent);
}
} catch (ActivityNotFoundException anfe) {
// Hmm, market is not installed
Log.w(TAG, "Google Play is not installed; cannot install " + packageName);
}
}
});
downloadDialog.setNegativeButton(buttonNo, null);
downloadDialog.setCancelable(true);
return downloadDialog.show();
}
/**
* <p>Call this from your {@link Activity}'s
* {@link Activity#onActivityResult(int, int, Intent)} method.</p>
*
* @param requestCode request code from {@code onActivityResult()}
* @param resultCode result code from {@code onActivityResult()}
* @param intent {@link Intent} from {@code onActivityResult()}
* @return null if the event handled here was not related to this class, or
* else an {@link IntentResult} containing the result of the scan. If the user cancelled scanning,
* the fields will be null.
*/
public static IntentResult parseActivityResult(int requestCode, int resultCode, Intent intent) {
if (requestCode == REQUEST_CODE) {
if (resultCode == Activity.RESULT_OK) {
String contents = intent.getStringExtra("SCAN_RESULT");
String formatName = intent.getStringExtra("SCAN_RESULT_FORMAT");
byte[] rawBytes = intent.getByteArrayExtra("SCAN_RESULT_BYTES");
int intentOrientation = intent.getIntExtra("SCAN_RESULT_ORIENTATION", Integer.MIN_VALUE);
Integer orientation = intentOrientation == Integer.MIN_VALUE ? null : intentOrientation;
String errorCorrectionLevel = intent.getStringExtra("SCAN_RESULT_ERROR_CORRECTION_LEVEL");
return new IntentResult(contents,
formatName,
rawBytes,
orientation,
errorCorrectionLevel);
}
return new IntentResult();
}
return null;
}
/**
* Defaults to type "TEXT_TYPE".
*
* @param text the text string to encode as a barcode
* @return the {@link AlertDialog} that was shown to the user prompting them to download the app
* if a prompt was needed, or null otherwise
* @see #shareText(CharSequence, CharSequence)
*/
public final AlertDialog shareText(CharSequence text) {
return shareText(text, "TEXT_TYPE");
}
/**
* Shares the given text by encoding it as a barcode, such that another user can
* scan the text off the screen of the device.
*
* @param text the text string to encode as a barcode
* @param type type of data to encode. See {@code com.google.zxing.client.android.Contents.Type} constants.
* @return the {@link AlertDialog} that was shown to the user prompting them to download the app
* if a prompt was needed, or null otherwise
*/
public final AlertDialog shareText(CharSequence text, CharSequence type) {
Intent intent = new Intent();
intent.addCategory(Intent.CATEGORY_DEFAULT);
intent.setAction(BS_PACKAGE + ".ENCODE");
intent.putExtra("ENCODE_TYPE", type);
intent.putExtra("ENCODE_DATA", text);
String targetAppPackage = findTargetAppPackage(intent);
if (targetAppPackage == null) {
return showDownloadDialog();
}
intent.setPackage(targetAppPackage);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
attachMoreExtras(intent);
if (fragment == null) {
activity.startActivity(intent);
} else {
fragment.startActivity(intent);
}
return null;
}
private static List<String> list(String... values) {
return Collections.unmodifiableList(Arrays.asList(values));
}
private void attachMoreExtras(Intent intent) {
for (Map.Entry<String,Object> entry : moreExtras.entrySet()) {
String key = entry.getKey();
Object value = entry.getValue();
// Kind of hacky
if (value instanceof Integer) {
intent.putExtra(key, (Integer) value);
} else if (value instanceof Long) {
intent.putExtra(key, (Long) value);
} else if (value instanceof Boolean) {
intent.putExtra(key, (Boolean) value);
} else if (value instanceof Double) {
intent.putExtra(key, (Double) value);
} else if (value instanceof Float) {
intent.putExtra(key, (Float) value);
} else if (value instanceof Bundle) {
intent.putExtra(key, (Bundle) value);
} else {
intent.putExtra(key, value.toString());
}
}
}
}

View File

@ -0,0 +1,94 @@
/*
* Copyright 2009 ZXing authors
*
* 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 com.google.zxing.integration.android;
/**
* <p>Encapsulates the result of a barcode scan invoked through {@link IntentIntegrator}.</p>
*
* @author Sean Owen
*/
public final class IntentResult {
private final String contents;
private final String formatName;
private final byte[] rawBytes;
private final Integer orientation;
private final String errorCorrectionLevel;
IntentResult() {
this(null, null, null, null, null);
}
IntentResult(String contents,
String formatName,
byte[] rawBytes,
Integer orientation,
String errorCorrectionLevel) {
this.contents = contents;
this.formatName = formatName;
this.rawBytes = rawBytes;
this.orientation = orientation;
this.errorCorrectionLevel = errorCorrectionLevel;
}
/**
* @return raw content of barcode
*/
public String getContents() {
return contents;
}
/**
* @return name of format, like "QR_CODE", "UPC_A". See {@code BarcodeFormat} for more format names.
*/
public String getFormatName() {
return formatName;
}
/**
* @return raw bytes of the barcode content, if applicable, or null otherwise
*/
public byte[] getRawBytes() {
return rawBytes;
}
/**
* @return rotation of the image, in degrees, which resulted in a successful scan. May be null.
*/
public Integer getOrientation() {
return orientation;
}
/**
* @return name of the error correction level used in the barcode, if applicable
*/
public String getErrorCorrectionLevel() {
return errorCorrectionLevel;
}
@Override
public String toString() {
int rawBytesLength = rawBytes == null ? 0 : rawBytes.length;
return "Format: " + formatName + '\n' +
"Contents: " + contents + '\n' +
"Raw bytes: (" + rawBytesLength + " bytes)\n" +
"Orientation: " + orientation + '\n' +
"EC level: " + errorCorrectionLevel + '\n';
}
}

View File

@ -0,0 +1,109 @@
package com.meerkat.laura.fakescannerapp;
import android.content.res.Configuration;
import android.os.Bundle;
import android.preference.PreferenceActivity;
import android.support.annotation.LayoutRes;
import android.support.annotation.Nullable;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatDelegate;
import android.support.v7.widget.Toolbar;
import android.view.MenuInflater;
import android.view.View;
import android.view.ViewGroup;
/**
* A {@link android.preference.PreferenceActivity} which implements and proxies the necessary calls
* to be used with AppCompat.
*/
public abstract class AppCompatPreferenceActivity extends PreferenceActivity {
private AppCompatDelegate mDelegate;
@Override
protected void onCreate(Bundle savedInstanceState) {
getDelegate().installViewFactory();
getDelegate().onCreate(savedInstanceState);
super.onCreate(savedInstanceState);
}
@Override
protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
getDelegate().onPostCreate(savedInstanceState);
}
public ActionBar getSupportActionBar() {
return getDelegate().getSupportActionBar();
}
public void setSupportActionBar(@Nullable Toolbar toolbar) {
getDelegate().setSupportActionBar(toolbar);
}
@Override
public MenuInflater getMenuInflater() {
return getDelegate().getMenuInflater();
}
@Override
public void setContentView(@LayoutRes int layoutResID) {
getDelegate().setContentView(layoutResID);
}
@Override
public void setContentView(View view) {
getDelegate().setContentView(view);
}
@Override
public void setContentView(View view, ViewGroup.LayoutParams params) {
getDelegate().setContentView(view, params);
}
@Override
public void addContentView(View view, ViewGroup.LayoutParams params) {
getDelegate().addContentView(view, params);
}
@Override
protected void onPostResume() {
super.onPostResume();
getDelegate().onPostResume();
}
@Override
protected void onTitleChanged(CharSequence title, int color) {
super.onTitleChanged(title, color);
getDelegate().setTitle(title);
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
getDelegate().onConfigurationChanged(newConfig);
}
@Override
protected void onStop() {
super.onStop();
getDelegate().onStop();
}
@Override
protected void onDestroy() {
super.onDestroy();
getDelegate().onDestroy();
}
public void invalidateOptionsMenu() {
getDelegate().invalidateOptionsMenu();
}
private AppCompatDelegate getDelegate() {
if (mDelegate == null) {
mDelegate = AppCompatDelegate.create(this, null);
}
return mDelegate;
}
}

View File

@ -0,0 +1,252 @@
package com.meerkat.laura.fakescannerapp;
import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.Color;
import android.os.AsyncTask;
import android.os.Bundle;
import android.preference.Preference;
import android.preference.PreferenceManager;
import android.support.v7.app.AppCompatActivity;
import android.util.Base64;
import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import ch.qos.logback.classic.android.BasicLogcatConfigurator;
import com.google.protobuf.InvalidProtocolBufferException;
import com.google.protobuf.Message;
import com.google.zxing.integration.android.IntentIntegrator;
import com.google.zxing.integration.android.IntentResult;
import meerkat.crypto.DigitalSignatureGenerator;
import meerkat.crypto.concrete.ECDSADeterministicSignature;
import meerkat.pollingstation.PollingStationScanner;
import meerkat.pollingstation.ScannerClientAPI;
import meerkat.protobuf.PollingStation;
import java.math.BigInteger;
import java.util.Date;
import static com.meerkat.laura.fakescannerapp.R.xml.preferences;
public class MainActivity extends AppCompatActivity implements View.OnClickListener, SharedPreferences.OnSharedPreferenceChangeListener {
static {
BasicLogcatConfigurator.configureDefaultContext();
}
final public static String SCANNER_NAME = "AndroidScanner";
SharedPreferences sharedPref;
Button scanBtn;
TextView formatTxt, contentTxt, responseTxt, serverStatus;
PollingStationScanner.ScannerClient scannerClient;
DigitalSignatureGenerator signer;
class AsyncScanConnect extends AsyncTask<Void, Void, Boolean> {
PollingStation.ConnectionServerData serverData;
public AsyncScanConnect(PollingStation.ConnectionServerData connectionServerData) {
this.serverData = connectionServerData;
}
@Override
protected void onPreExecute() {
super.onPreExecute();
serverStatus.setTextColor(Color.BLUE);
serverStatus.setText("Connecting to " + serverData.getServerUrl());
}
@Override
protected Boolean doInBackground(Void... foo) {
try {
return scannerClient.connect(serverData);
} catch (RuntimeException e) {
Log.e("MainActivity", "Exception during connect: " + e.toString());
return false;
}
}
@Override
protected void onPostExecute(Boolean res) {
super.onPostExecute(res);
if (res) {
serverStatus.setTextColor(Color.GREEN);
serverStatus.setText("Connected to " + serverData.getServerUrl());
} else {
serverStatus.setTextColor(Color.RED);
serverStatus.setText("Connection failed: " + serverData.getServerUrl());
}
}
}
class AsyncScanSend extends AsyncTask<Void, Void, Boolean> {
Message data;
public AsyncScanSend(Message data) {
this.data = data;
}
@Override
protected void onPreExecute() {
super.onPreExecute();
serverStatus.setTextColor(Color.BLUE);
serverStatus.setText("Sending to server...");
}
@Override
protected Boolean doInBackground(Void... foo) {
try {
if (data instanceof PollingStation.ScannedBallot) {
return scannerClient.newScan((PollingStation.ScannedBallot) data);
} else if (data instanceof PollingStation.ScanError) {
return scannerClient.reportError((PollingStation.ScanError) data);
} else {
Log.e("MainActivity", "Trying to send invalid message to scanner");
return false;
}
} catch (RuntimeException e) {
Log.e("MainActivity", "Exception during send: " + e.toString());
return false;
}
}
@Override
protected void onPostExecute(Boolean res) {
super.onPostExecute(res);
if (res) {
serverStatus.setTextColor(Color.GREEN);
serverStatus.setText("Sent successfully");
Log.i("MainActivity", "post submitted to API.");
} else {
serverStatus.setTextColor(Color.RED);
serverStatus.setText("Send failed");
Log.e("MainActivity", "Unable to submit post to API.");
}
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.options_menu, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle item selection
switch (item.getItemId()) {
case R.id.action_settings:
Intent i = new Intent(this, SettingsActivity.class);
startActivity(i);
return true;
default:
return super.onOptionsItemSelected(item);
}
}
void setNewScannerClientAPI(SharedPreferences sharedPref) {
String pscUrlString = sharedPref.getString(SettingsFragment.PSC_URL, "");
long nonce = 0;
try {
nonce = Long.parseLong(sharedPref.getString(SettingsFragment.PSC_NONCE, "0"));
} catch (NumberFormatException e) {
// Ignore
}
if (pscUrlString.isEmpty())
return;
PollingStation.ConnectionServerData serverData = PollingStation.ConnectionServerData.newBuilder()
.setServerUrl(pscUrlString)
.setNonce(nonce)
.build();
new AsyncScanConnect(serverData).execute();
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
signer = new ECDSADeterministicSignature();
signer.generateSigningCertificate(BigInteger.ONE, new Date(System.currentTimeMillis()),
new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 24 * 365), SCANNER_NAME);
scannerClient = new ScannerClientAPI(signer);
PreferenceManager.setDefaultValues(this, preferences, false);
sharedPref = PreferenceManager.getDefaultSharedPreferences(this);
sharedPref.registerOnSharedPreferenceChangeListener(this);
scanBtn = (Button) findViewById(R.id.scan_button);
formatTxt = (TextView) findViewById(R.id.scan_format);
contentTxt = (TextView) findViewById(R.id.scan_content);
responseTxt = (TextView) findViewById(R.id.server_response);
serverStatus = (TextView) findViewById(R.id.serverStatus);
setNewScannerClientAPI(sharedPref);
scanBtn.setOnClickListener(this);
}
public void onClick(View v) {
if (v.getId() == R.id.scan_button) {
IntentIntegrator scanIntegrator = new IntentIntegrator(this);
formatTxt.setText("Initiating scan...");
scanIntegrator.initiateScan();
}
}
public void onActivityResult(int requestCode, int resultCode, Intent intent) {
IntentResult scanningResult = IntentIntegrator.parseActivityResult(requestCode, resultCode, intent);
if (scanningResult != null) {
String scanContent = scanningResult.getContents();
byte[] rawBytes = scanningResult.getRawBytes();
String scanFormat = scanningResult.getFormatName();
formatTxt.setText("FORMAT: " + scanFormat);
contentTxt.setText("CONTENT: " + scanContent + "sending data to server...");
// sendPost(rawBytes);
sendPost(scanContent);
} else {
Toast toast = Toast.makeText(getApplicationContext(),
"No scan data received!", Toast.LENGTH_SHORT);
toast.show();
}
}
// public void sendPost(byte[] body) {
public void sendPost(String scanContent) {
try {
PollingStation.ScannedBallot scannedBallot = PollingStation.ScannedBallot.parseFrom(Base64.decode(scanContent.getBytes(), Base64.DEFAULT));
//
// PollingStation.ScannedData scannedData = PollingStation.ScannedData.newBuilder()
// .setChannel(ByteString.copyFrom(body))
// .build();
new AsyncScanSend(scannedBallot).execute();
} catch (InvalidProtocolBufferException e) {
e.printStackTrace();
}
}
public void showResponse(String response) {
responseTxt.setText(response);
}
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key){
setNewScannerClientAPI(sharedPreferences);
}
}

View File

@ -0,0 +1,26 @@
package com.meerkat.laura.fakescannerapp;
import android.app.Activity;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.PreferenceManager;
/**
* Created by talm on 25/06/17.
*/
public class SettingsActivity extends Activity {
SettingsFragment settings;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
settings = new SettingsFragment();
PreferenceManager.getDefaultSharedPreferences(this).registerOnSharedPreferenceChangeListener(settings);
// Display the fragment as the main content.
getFragmentManager().beginTransaction()
.replace(android.R.id.content, settings)
.commit();
}
}

View File

@ -0,0 +1,34 @@
package com.meerkat.laura.fakescannerapp;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.Preference;
import android.preference.PreferenceActivity;
import android.preference.PreferenceFragment;
import android.preference.PreferenceManager;
public class SettingsFragment extends PreferenceFragment implements SharedPreferences.OnSharedPreferenceChangeListener {
public final static String PSC_URL = "psc_url";
public final static String PSC_NONCE = "psc_nonce";
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.preferences);
}
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
Preference pref = findPreference(key);
if (pref == null)
return;
switch(key) {
case SettingsFragment.PSC_URL:
pref.setSummary(sharedPreferences.getString(key, ""));
break;
case SettingsFragment.PSC_NONCE:
pref.setSummary(sharedPreferences.getString(key, ""));
break;
}
}
}

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zm1,15h-2v-6h2v6zm0,-8h-2V7h2v2z"/>
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M11.5,22c1.1,0 2,-0.9 2,-2h-4c0,1.1 0.9,2 2,2zm6.5,-6v-5.5c0,-3.07 -2.13,-5.64 -5,-6.32V3.5c0,-0.83 -0.67,-1.5 -1.5,-1.5S10,2.67 10,3.5v0.68c-2.87,0.68 -5,3.25 -5,6.32V16l-2,2v1h17v-1l-2,-2z"/>
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M12 4V1L8 5l4 4V6c3.31 0 6 2.69 6 6 0 1.01,-.25 1.97,-.7 2.8l1.46 1.46C19.54 15.03 20 13.57 20 12c0,-4.42,-3.58,-8,-8,-8zm0 14c-3.31 0,-6,-2.69,-6,-6 0,-1.01.25,-1.97.7,-2.8L5.24 7.74C4.46 8.97 4 10.43 4 12c0 4.42 3.58 8 8 8v3l4,-4,-4,-4v3z"/>
</vector>

View File

@ -0,0 +1,58 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingBottom="@dimen/activity_vertical_margin"
tools:context=".MainActivity">
<TextView android:text="@string/instructions" android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/textView" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/button_txt"
android:id="@+id/scan_button"
android:layout_below="@+id/textView"
android:layout_centerHorizontal="true" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textIsSelectable="true"
android:id="@+id/scan_format"
android:layout_below="@+id/scan_button"
android:layout_centerHorizontal="true"
android:layout_marginTop="44dp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textIsSelectable="true"
android:id="@+id/scan_content"
android:layout_below="@+id/scan_format"
android:layout_centerHorizontal="true"
android:layout_marginTop="32dp" />
<TextView
android:id="@+id/server_response"
android:layout_below="@+id/scan_content"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textIsSelectable="true"
android:layout_centerHorizontal="true"
android:layout_marginTop="44dp" />
<TextView
android:text="Not Connected"
android:layout_width="wrap_content"
android:layout_height="wrap_content" android:id="@+id/serverStatus"
android:layout_alignParentBottom="true"
android:layout_alignParentRight="true" android:layout_alignParentEnd="true"
android:layout_alignParentLeft="true" android:layout_alignParentStart="true"/>
</RelativeLayout>

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<!-- Settings, should always be in the overflow -->
<item android:id="@+id/action_settings"
android:title="@string/action_settings"
app:showAsAction="never"/>
</menu>

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View File

@ -0,0 +1,6 @@
<resources>
<!-- Example customization of dimensions originally defined in res/values/dimens.xml
(such as screen margins) for screens with more than 820dp of available width. This
would include 7" and 10" devices in landscape (~960dp and ~1280dp respectively). -->
<dimen name="activity_horizontal_margin">64dp</dimen>
</resources>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorPrimary">#3F51B5</color>
<color name="colorPrimaryDark">#303F9F</color>
<color name="colorAccent">#FF4081</color>
</resources>

View File

@ -0,0 +1,5 @@
<resources>
<!-- Default screen margins, per the Android Design guidelines. -->
<dimen name="activity_horizontal_margin">16dp</dimen>
<dimen name="activity_vertical_margin">16dp</dimen>
</resources>

View File

@ -0,0 +1,80 @@
<resources>
<string name="app_name">FakeScannerApp</string>
<string name="button_txt">SCAN</string>
<string name="instructions">Please, press the button below to scan the vote receipt.</string>
<string name="title_activity_settings">Settings</string>
<!-- Strings related to Settings -->
<string name="action_settings">Settings</string>
<!-- Example General settings -->
<string name="pref_header_general">General</string>
<string name="pref_title_social_recommendations">Enable social recommendations</string>
<string name="pref_description_social_recommendations">Recommendations for people to contact based on your message
history
</string>
<string name="pref_title_psc_url">Scanner Service URL</string>
<string name="pref_default_psc_url">http://192.168.70.10:8880/scan</string>
<string name="pref_title_add_friends_to_messages">Add friends to messages</string>
<string-array name="pref_example_list_titles">
<item>Always</item>
<item>When possible</item>
<item>Never</item>
</string-array>
<string-array name="pref_example_list_values">
<item>1</item>
<item>0</item>
<item>-1</item>
</string-array>
<!-- Example settings for Data & Sync -->
<string name="pref_header_data_sync">Data &amp; sync</string>
<string name="pref_title_sync_frequency">Sync frequency</string>
<string-array name="pref_sync_frequency_titles">
<item>15 minutes</item>
<item>30 minutes</item>
<item>1 hour</item>
<item>3 hours</item>
<item>6 hours</item>
<item>Never</item>
</string-array>
<string-array name="pref_sync_frequency_values">
<item>15</item>
<item>30</item>
<item>60</item>
<item>180</item>
<item>360</item>
<item>-1</item>
</string-array>
<string-array name="list_preference_entries">
<item>Entry 1</item>
<item>Entry 2</item>
<item>Entry 3</item>
</string-array>
<string-array name="list_preference_entry_values">
<item>1</item>
<item>2</item>
<item>3</item>
</string-array>
<string-array name="multi_select_list_preference_default_value"/>
<string name="pref_title_system_sync_settings">System sync settings</string>
<!-- Example settings for Notifications -->
<string name="pref_header_notifications">Notifications</string>
<string name="pref_title_new_message_notifications">New message notifications</string>
<string name="pref_title_ringtone">Ringtone</string>
<string name="pref_ringtone_silent">Silent</string>
<string name="pref_title_vibrate">Vibrate</string>
</resources>

View File

@ -0,0 +1,11 @@
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
</style>
</resources>

View File

@ -0,0 +1,28 @@
<PreferenceScreen xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android">
<!-- NOTE: EditTextPreference accepts EditText attributes. -->
<!-- NOTE: EditTextPreference's summary should be set to its value by the activity code. -->
<EditTextPreference
android:key="psc_url"
android:title="@string/pref_title_psc_url"
android:defaultValue="@string/pref_default_psc_url"
android:selectAllOnFocus="true"
android:singleLine="true"
android:maxLines="1"
android:inputType="textUri" />
<EditTextPreference
android:key="psc_nonce"
android:title="Nonce"
android:defaultValue="0"
android:selectAllOnFocus="true"
android:singleLine="true"
android:maxLines="1"
android:inputType="number"/>
<!-- NOTE: Hide buttons to simplify the UI. Users can touch outside the dialog to
dismiss it. -->
<!-- NOTE: ListPreference's summary should be set to its value by the activity code. -->
</PreferenceScreen>

View File

@ -0,0 +1,15 @@
package com.meerkat.laura.fakescannerapp;
import org.junit.Test;
import static org.junit.Assert.*;
/**
* To work on unit tests, switch the Test Artifact in the Build Variants view.
*/
public class ExampleUnitTest {
@Test
public void addition_isCorrect() throws Exception {
assertEquals(4, 2 + 2);
}
}

View File

@ -1,10 +1,56 @@
subprojects { proj ->
proj.afterEvaluate {
// Used to generate initial maven-dir layout
task "create-dirs" { description = "Create default maven directory structure" } << {
sourceSets*.java.srcDirs*.each { it.mkdirs() }
sourceSets*.resources.srcDirs*.each { it.mkdirs() }
}
}
}
apply plugin: com.google.gradle.osdetector.OsDetectorPlugin
subprojects { proj ->
proj.afterEvaluate {
// Used to generate initial maven-dir layout
task "create-dirs" {
description = "Create default maven directory structure"
doLast {
sourceSets*.java.srcDirs*.each { it.mkdirs() }
sourceSets*.resources.srcDirs*.each { it.mkdirs() }
}
}
}
}
// Script to create a copy of the protobuf compiler jar in the top-level build directory
// (will make it easier to configure the intellij protobuf plugin)
repositories {
mavenLocal();
mavenCentral();
}
project.ext.protocLocation = 'com.google.protobuf:protoc:3.+'
// Copied from Google's protobuf plugin code
File getProtocDep(protocLocation) {
// create a project configuration dependency for the artifact
Configuration config = project.configurations.create("protobufToolsLocator") {
visible = false
transitive = false
extendsFrom = []
}
def groupId, artifact, version
(groupId, artifact, version) = protocLocation.split(":")
def notation = [group: groupId,
name: artifact,
version: version,
classifier: project.osdetector.classifier,
ext: 'exe']
Dependency dep = project.dependencies.add(config.name, notation)
File file = config.fileCollection(dep).singleFile
return file
}
task getprotoc(type: Copy) {
from getProtocDep(project.ext.protocLocation)
rename { file -> return "protoc" }
into "${buildDir}/"
}

View File

@ -1,220 +1,212 @@
plugins {
id "us.kirchmeier.capsule" version "1.0.1"
id 'com.google.protobuf' version '0.7.0'
}
apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'idea'
apply plugin: 'maven-publish'
// Uncomment the lines below to define an application
// (this will also allow you to build a "fatCapsule" which includes
// the entire application, including all dependencies in a single jar)
//apply plugin: 'application'
//mainClassName='your.main.ApplicationClass'
// Is this a snapshot version?
ext { isSnapshot = false }
ext {
groupId = 'org.factcenter.meerkat'
nexusRepository = "https://cs.idc.ac.il/nexus/content/groups/${isSnapshot ? 'unstable' : 'public'}/"
// Credentials for IDC nexus repositories (needed only for using unstable repositories and publishing)
// Should be set in ${HOME}/.gradle/gradle.properties
nexusUser = project.hasProperty('nexusUser') ? project.property('nexusUser') : ""
nexusPassword = project.hasProperty('nexusPassword') ? project.property('nexusPassword') : ""
}
description = "TODO: Add a description"
// Your project version
version = "0.0"
version += "${isSnapshot ? '-SNAPSHOT' : ''}"
dependencies {
// Meerkat common
compile project(':meerkat-common')
// Logging
compile 'org.slf4j:slf4j-api:1.7.7'
runtime 'ch.qos.logback:logback-classic:1.1.2'
runtime 'ch.qos.logback:logback-core:1.1.2'
// Google protobufs
compile 'com.google.protobuf:protobuf-java:3.+'
testCompile 'junit:junit:4.+'
runtime 'org.codehaus.groovy:groovy:2.4.+'
}
/*==== You probably don't have to edit below this line =======*/
// Setup test configuration that can appear as a dependency in
// other subprojects
configurations {
testOutput.extendsFrom (testCompile)
}
task testJar(type: Jar, dependsOn: testClasses) {
classifier = 'tests'
from sourceSets.test.output
}
artifacts {
testOutput testJar
}
// The run task added by the application plugin
// is also of type JavaExec.
tasks.withType(JavaExec) {
// Assign all Java system properties from
// the command line to the JavaExec task.
systemProperties System.properties
}
protobuf {
// Configure the protoc executable
protoc {
// Download from repositories
artifact = 'com.google.protobuf:protoc:3.+'
}
}
idea {
module {
project.sourceSets.each { sourceSet ->
def srcDir = "${protobuf.generatedFilesBaseDir}/$sourceSet.name/java"
println "Adding $srcDir"
// add protobuf generated sources to generated source dir.
if ("test".equals(sourceSet.name)) {
testSourceDirs += file(srcDir)
} else {
sourceDirs += file(srcDir)
}
generatedSourceDirs += file(srcDir)
}
// Don't exclude build directory
excludeDirs -= file(buildDir)
}
}
/*===================================
* "Fat" Build targets
*===================================*/
if (project.hasProperty('mainClassName') && (mainClassName != null)) {
task mavenCapsule(type: MavenCapsule) {
description = "Generate a capsule jar that automatically downloads and caches dependencies when run."
applicationClass mainClassName
destinationDir = buildDir
}
task fatCapsule(type: FatCapsule) {
description = "Generate a single capsule jar containing everything. Use -Pfatmain=... to override main class"
destinationDir = buildDir
def fatMain = hasProperty('fatmain') ? fatmain : mainClassName
applicationClass fatMain
def testJar = hasProperty('test')
if (hasProperty('fatmain')) {
appendix = "fat-${fatMain}"
} else {
appendix = "fat"
}
if (testJar) {
from sourceSets.test.output
}
}
}
/*===================================
* Repositories
*===================================*/
repositories {
// Prefer the local nexus repository (it may have 3rd party artifacts not found in mavenCentral)
maven {
url nexusRepository
if (isSnapshot) {
credentials { username
password
username nexusUser
password nexusPassword
}
}
}
// Use local maven repository
mavenLocal()
// Use 'maven central' for other dependencies.
mavenCentral()
}
task "info" << {
println "Project: ${project.name}"
println "Description: ${project.description}"
println "--------------------------"
println "GroupId: $groupId"
println "Version: $version (${isSnapshot ? 'snapshot' : 'release'})"
println ""
}
info.description 'Print some information about project parameters'
/*===================================
* Publishing
*===================================*/
publishing {
publications {
mavenJava(MavenPublication) {
groupId project.groupId
pom.withXml {
asNode().appendNode('description', project.description)
}
from project.components.java
}
}
repositories {
maven {
url "https://cs.idc.ac.il/nexus/content/repositories/${project.isSnapshot ? 'snapshots' : 'releases'}"
credentials { username
password
username nexusUser
password nexusPassword
}
}
}
}
plugins {
id "us.kirchmeier.capsule" version "1.0.2"
id 'com.google.protobuf' version '0.8.1'
}
apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'idea'
apply plugin: 'maven-publish'
// Uncomment the lines below to define an application
// (this will also allow you to build a "fatCapsule" which includes
// the entire application, including all dependencies in a single jar)
//apply plugin: 'application'
//mainClassName='your.main.ApplicationClass'
// Is this a snapshot version?
ext { isSnapshot = false }
ext {
groupId = 'org.factcenter.meerkat'
nexusRepository = "https://cs.idc.ac.il/nexus/content/groups/${isSnapshot ? 'unstable' : 'public'}/"
// Credentials for IDC nexus repositories (needed only for using unstable repositories and publishing)
// Should be set in ${HOME}/.gradle/gradle.properties
// Credentials for publishing repositories
publishRepository = "https://cs.idc.ac.il/nexus/content/repositories/${project.isSnapshot ? 'snapshots' : 'releases'}"
publishUser = project.hasProperty('publishUser') ? project.property('publishUser') : ""
publishPassword = project.hasProperty('publishPassword') ? project.property('publishPassword') : ""
}
description = "TODO: Add a description"
// Your project version
version = "0.0"
version += "${isSnapshot ? '-SNAPSHOT' : ''}"
dependencies {
// Meerkat common
compile project(':meerkat-common')
// Logging
compile 'org.slf4j:slf4j-api:1.7.7'
runtime 'ch.qos.logback:logback-classic:1.1.2'
runtime 'ch.qos.logback:logback-core:1.1.2'
// Google protobufs
compile 'com.google.protobuf:protobuf-java:3.+'
testCompile 'junit:junit:4.+'
runtime 'org.codehaus.groovy:groovy:2.4.+'
}
/*==== You probably don't have to edit below this line =======*/
// Setup test configuration that can appear as a dependency in
// other subprojects
configurations {
testOutput.extendsFrom (testCompile)
}
task testJar(type: Jar, dependsOn: testClasses) {
classifier = 'tests'
from sourceSets.test.output
}
artifacts {
testOutput testJar
}
// The run task added by the application plugin
// is also of type JavaExec.
tasks.withType(JavaExec) {
// Assign all Java system properties from
// the command line to the JavaExec task.
systemProperties System.properties
}
protobuf {
// Configure the protoc executable
protoc {
// Download from repositories
artifact = 'com.google.protobuf:protoc:3.+'
}
}
idea {
module {
project.sourceSets.each { sourceSet ->
def srcDir = "${protobuf.generatedFilesBaseDir}/$sourceSet.name/java"
println "Adding $srcDir"
// add protobuf generated sources to generated source dir.
if ("test".equals(sourceSet.name)) {
testSourceDirs += file(srcDir)
} else {
sourceDirs += file(srcDir)
}
generatedSourceDirs += file(srcDir)
}
// Don't exclude build directory
excludeDirs -= file(buildDir)
}
}
/*===================================
* "Fat" Build targets
*===================================*/
if (project.hasProperty('mainClassName') && (mainClassName != null)) {
task mavenCapsule(type: MavenCapsule) {
description = "Generate a capsule jar that automatically downloads and caches dependencies when run."
applicationClass mainClassName
destinationDir = buildDir
}
task fatCapsule(type: FatCapsule) {
description = "Generate a single capsule jar containing everything. Use -Pfatmain=... to override main class"
destinationDir = buildDir
def fatMain
if (this.hasProperty('fatmain')) {
fatMain = fatmain
appendix = "fat-${fatMain}"
} else {
fatMain = mainClassName
appendix = "fat"
}
applicationClass fatMain
def testJar = this.hasProperty('test')
if (testJar) {
from sourceSets.test.output
}
}
}
/*===================================
* Repositories
*===================================*/
repositories {
// Use local maven repository
mavenLocal()
// Use 'maven central' for other dependencies.
mavenCentral()
}
task "info" {
doLast {
println "Project: ${project.name}"
println "Description: ${project.description}"
println "--------------------------"
println "GroupId: $groupId"
println "Version: $version (${isSnapshot ? 'snapshot' : 'release'})"
println ""
}
}
info.description 'Print some information about project parameters'
/*===================================
* Publishing
*===================================*/
publishing {
publications {
mavenJava(MavenPublication) {
groupId project.groupId
pom.withXml {
asNode().appendNode('description', project.description)
}
from project.components.java
}
}
repositories {
maven {
url publishRepository
credentials { username
password
username publishUser
password publishPassword
}
}
}
}

25
buildSrc/build.gradle Normal file
View File

@ -0,0 +1,25 @@
apply plugin: 'groovy'
description = 'A Gradle plugin that detects the OS name and architecture, providing a uniform\
classifier to be used in the names of native artifacts.'
group = 'com.google.gradle'
// The major and minor versions are aligned with the Maven plugin's.
version = '1.5.0-SNAPSHOT'
dependencies {
compile gradleApi(),
localGroovy(),
'kr.motd.maven:os-maven-plugin:1.5.0.Final'
// Newer versions of gradle conflict with the default guava 10. dependency of the maven
// plugin.
compile("com.google.guava:guava:19.0") {
force = true
}
testCompile 'junit:junit:4.12'
}
repositories {
mavenLocal()
mavenCentral()
}

1
buildSrc/gradlew vendored Symbolic link
View File

@ -0,0 +1 @@
../gradlew

View File

@ -0,0 +1,25 @@
/*
* Copyright 2015 Google Inc. All Rights Reserved.
*
* 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 com.google.gradle.osdetector
import org.gradle.api.Plugin
import org.gradle.api.Project
class OsDetectorPlugin implements Plugin<Project> {
void apply(final Project project) {
project.extensions.create('osdetector', OsDetector)
}
}

View File

@ -0,0 +1,120 @@
/*
* Copyright 2015 Google Inc. All Rights Reserved.
*
* 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 com.google.gradle.osdetector;
import kr.motd.maven.os.Detector;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
public class OsDetector {
private static final Logger logger = LoggerFactory.getLogger(OsDetector.class.getName());
private final List<String> classifierWithLikes = new ArrayList<String>();
private Impl impl;
public String getOs() {
return (String) getImpl().detectedProperties.get(Detector.DETECTED_NAME);
}
public String getArch() {
return (String) getImpl().detectedProperties.get(Detector.DETECTED_ARCH);
}
public String getClassifier() {
return (String) getImpl().detectedProperties.get(Detector.DETECTED_CLASSIFIER);
}
public Release getRelease() {
Impl impl = getImpl();
Object releaseId = impl.detectedProperties.get(Detector.DETECTED_RELEASE);
if (releaseId == null) {
return null;
}
return new Release(impl);
}
public synchronized void setClassifierWithLikes(List<String> classifierWithLikes) {
if (impl != null) {
throw new IllegalStateException("classifierWithLikes must be set before osdetector is read.");
}
this.classifierWithLikes.clear();
this.classifierWithLikes.addAll(classifierWithLikes);
}
private synchronized Impl getImpl() {
if (impl == null) {
impl = new Impl(classifierWithLikes);
}
return impl;
}
/**
* Accessor to information about the current OS release.
*/
public static class Release {
private final Impl impl;
private Release(Impl impl) {
this.impl = impl;
}
/**
* Returns the release ID.
*/
public String getId() {
return (String) impl.detectedProperties.get(Detector.DETECTED_RELEASE);
}
/**
* Returns the version ID.
*/
public String getVersion() {
return (String) impl.detectedProperties.get(Detector.DETECTED_RELEASE_VERSION);
}
/**
* Returns {@code true} if this release is a variant of the given base release (for example,
* ubuntu is "like" debian).
*/
public boolean isLike(String baseRelease) {
return impl.detectedProperties.containsKey(
Detector.DETECTED_RELEASE_LIKE_PREFIX + baseRelease);
}
}
private static class Impl extends Detector {
final Properties detectedProperties = System.getProperties();
@Override
protected void log(String message) {
logger.info(message);
}
@Override
protected void logProperty(String name, String value) {
logger.info(name + "=" + value);
}
Impl(List<String> classifierWithLikes) {
detect(detectedProperties, classifierWithLikes);
}
}
}

View File

@ -0,0 +1 @@
implementation-class=com.google.gradle.osdetector.OsDetectorPlugin

View File

@ -0,0 +1,62 @@
/*
* Copyright 2015 Google Inc. All Rights Reserved.
*
* 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 com.google.gradle.osdetector
import org.gradle.testfixtures.ProjectBuilder
import org.gradle.api.Project
import org.junit.Test
import static org.junit.Assert.assertEquals
import static org.junit.Assert.assertNotNull
import static org.junit.Assert.fail
class OsDetectorPluginTest {
@Test
public void pluginAddsExtensionToProject() {
Project project = ProjectBuilder.builder().build()
project.apply plugin: 'com.google.osdetector'
assertNotNull(project.osdetector)
assertNotNull(project.osdetector.os)
assertNotNull(project.osdetector.arch)
assertEquals(project.osdetector.os + '-' + project.osdetector.arch,
project.osdetector.classifier)
System.err.println('classifier=' + project.osdetector.classifier)
if (project.osdetector.os == 'linux') {
assertNotNull(project.osdetector.release.id)
assertNotNull(project.osdetector.release.version)
System.err.println('release.id=' + project.osdetector.release.id)
System.err.println('release.version=' + project.osdetector.release.version)
System.err.println('release.isLike(debian)=' + project.osdetector.release.isLike('debian'))
System.err.println('release.isLike(redhat)=' + project.osdetector.release.isLike('redhat'))
} else if (project.osdetector.release) {
fail("Should be null")
}
}
@Test
public void setClassifierWithLikes() {
Project project = ProjectBuilder.builder().build()
project.apply plugin: 'com.google.osdetector'
project.osdetector.classifierWithLikes = ['debian', 'fedora']
assertNotNull(project.osdetector.os)
assertNotNull(project.osdetector.arch)
System.err.println('classifier=' + project.osdetector.classifier)
try {
project.osdetector.classifierWithLikes = ['debian']
fail("Should throw IllegalStateException")
} catch (IllegalStateException expected) {
}
}
}

View File

@ -0,0 +1,3 @@
Manifest-Version: 1.0
Main-Class: meerkat.voting.gui.configuration.Converter

View File

@ -1,7 +1,7 @@
plugins {
id "us.kirchmeier.capsule" version "1.0.1"
id 'com.google.protobuf' version '0.7.0'
id "us.kirchmeier.capsule" version '1.0.2'
id 'com.google.protobuf' version '0.8.1'
}
apply plugin: 'java'
@ -16,18 +16,17 @@ ext { isSnapshot = false }
ext {
groupId = 'org.factcenter.meerkat'
nexusRepository = "https://cs.idc.ac.il/nexus/content/groups/${isSnapshot ? 'unstable' : 'public'}/"
// Credentials for IDC nexus repositories (needed only for using unstable repositories and publishing)
// Should be set in ${HOME}/.gradle/gradle.properties
nexusUser = project.hasProperty('nexusUser') ? project.property('nexusUser') : ""
nexusPassword = project.hasProperty('nexusPassword') ? project.property('nexusPassword') : ""
// Credentials for publishing repositories
publishRepository = "https://cs.idc.ac.il/nexus/content/repositories/${project.isSnapshot ? 'snapshots' : 'releases'}"
publishUser = project.hasProperty('publishUser') ? project.property('publishUser') : ""
publishPassword = project.hasProperty('publishPassword') ? project.property('publishPassword') : ""
}
description = "Meerkat Voting Common Library"
// Your project version
version = "0.0"
version = "0.1"
version += "${isSnapshot ? '-SNAPSHOT' : ''}"
@ -38,23 +37,10 @@ dependencies {
compile project(':meerkat-common')
compile project(':restful-api-common')
// Jersey for RESTful API
compile 'org.glassfish.jersey.containers:jersey-container-servlet:2.22.+'
// Databases
compile 'org.xerial:sqlite-jdbc:3.7.+'
// Logging
compile 'org.slf4j:slf4j-api:1.7.7'
runtime 'ch.qos.logback:logback-classic:1.1.2'
runtime 'ch.qos.logback:logback-core:1.1.2'
// Google protobufs
compile 'com.google.protobuf:protobuf-java:3.+'
// Crypto
compile 'org.factcenter.qilin:qilin:1.1+'
compile 'org.bouncycastle:bcprov-jdk15on:1.53'
compile 'org.bouncycastle:bcpkix-jdk15on:1.53'
// Depend on test resources from meerkat-common
testCompile project(path: ':meerkat-common', configuration: 'testOutput')
@ -63,13 +49,11 @@ dependencies {
testCompile 'junit:junit:4.+'
testCompile 'org.hamcrest:hamcrest-all:1.3'
runtime 'org.codehaus.groovy:groovy:2.4.+'
}
test {
exclude '**/*IntegrationTest*'
outputs.upToDateWhen { false }
// outputs.upToDateWhen { false }
}
task integrationTest(type: Test) {
@ -178,34 +162,22 @@ if (project.hasProperty('mainClassName') && (mainClassName != null)) {
*===================================*/
repositories {
// Use local repo if possible
mavenLocal();
// Prefer the local nexus repository (it may have 3rd party artifacts not found in mavenCentral)
maven {
url nexusRepository
if (isSnapshot) {
credentials { username
password
username nexusUser
password nexusPassword
}
}
}
// Use 'maven central' for other dependencies.
mavenCentral()
}
task "info" << {
println "Project: ${project.name}"
println "Description: ${project.description}"
println "--------------------------"
println "GroupId: $groupId"
println "Version: $version (${isSnapshot ? 'snapshot' : 'release'})"
println ""
task "info" {
doLast {
println "Project: ${project.name}"
println "Description: ${project.description}"
println "--------------------------"
println "GroupId: $groupId"
println "Version: $version (${isSnapshot ? 'snapshot' : 'release'})"
println ""
}
}
info.description 'Print some information about project parameters'
@ -227,12 +199,12 @@ publishing {
}
repositories {
maven {
url "https://cs.idc.ac.il/nexus/content/repositories/${project.isSnapshot ? 'snapshots' : 'releases'}"
url publishRepository
credentials { username
password
username nexusUser
password nexusPassword
username publishUser
password publishPassword
}
}
}

1
bulletin-board-client/gradlew vendored Symbolic link
View File

@ -0,0 +1 @@
../gradlew

View File

@ -1,7 +1,7 @@
package meerkat.bulletinboard;
import com.google.protobuf.ByteString;
import meerkat.protobuf.BulletinBoardAPI.BatchData;
import meerkat.protobuf.BulletinBoardApi.BatchChunk;
import meerkat.bulletinboard.AsyncBulletinBoardClient.BatchIdentifier;
import java.util.List;
@ -11,15 +11,13 @@ import java.util.List;
*/
public class BatchDataContainer {
public final byte[] signerId;
public final int batchId;
public final List<BatchData> batchDataList;
public final MultiServerBatchIdentifier batchId;
public final List<BatchChunk> batchChunkList;
public final int startPosition;
public BatchDataContainer(byte[] signerId, int batchId, List<BatchData> batchDataList, int startPosition) {
this.signerId = signerId;
public BatchDataContainer(MultiServerBatchIdentifier batchId, List<BatchChunk> batchChunkList, int startPosition) {
this.batchId = batchId;
this.batchDataList = batchDataList;
this.batchChunkList = batchChunkList;
this.startPosition = startPosition;
}
}

View File

@ -1,38 +1,38 @@
package meerkat.bulletinboard;
/**
* Created by Arbel Deutsch Peled on 09-Dec-15.
*
* This class handles bulletin client work.
* It is meant to be used in a multi-threaded environment.
*/
public abstract class BulletinClientWorker<IN> {
protected final IN payload; // Payload of the job
private int maxRetry; // Number of retries for this job; set to -1 for infinite retries
public BulletinClientWorker(IN payload, int maxRetry) {
this.payload = payload;
this.maxRetry = maxRetry;
}
public IN getPayload() {
return payload;
}
public int getMaxRetry() {
return maxRetry;
}
public void decMaxRetry(){
if (maxRetry > 0) {
maxRetry--;
}
}
public boolean isRetry(){
return (maxRetry != 0);
}
}
package meerkat.bulletinboard;
/**
* Created by Arbel Deutsch Peled on 09-Dec-15.
*
* This class handles bulletin client work.
* It is meant to be used in a multi-threaded environment.
*/
public abstract class BulletinClientWorker<IN> {
protected final IN payload; // Payload of the job
private int maxRetry; // Number of retries for this job; set to -1 for infinite retries
public BulletinClientWorker(IN payload, int maxRetry) {
this.payload = payload;
this.maxRetry = maxRetry;
}
public IN getPayload() {
return payload;
}
public int getMaxRetry() {
return maxRetry;
}
public void decMaxRetry(){
if (maxRetry > 0) {
maxRetry--;
}
}
public boolean isRetry(){
return (maxRetry != 0);
}
}

View File

@ -1,168 +1,490 @@
package meerkat.bulletinboard;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.ListeningScheduledExecutorService;
import com.google.common.util.concurrent.MoreExecutors;
import com.google.protobuf.ByteString;
import com.google.protobuf.Timestamp;
import meerkat.comm.CommunicationException;
import meerkat.protobuf.BulletinBoardAPI;
import meerkat.protobuf.BulletinBoardAPI.*;
import meerkat.protobuf.BulletinBoardApi.*;
import meerkat.protobuf.Crypto.Signature;
import meerkat.protobuf.Voting.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.Executors;
/**
* Created by Arbel Deutsch Peled on 03-Mar-16.
* This is a full-fledged implementation of a Bulletin Board Client
* It provides asynchronous access to several remote servers, as well as a local cache
* Read/write operations are performed on the local server
* Read operations are performed on the local server
* Batch reads are performed on the local server and, if they fail, also on the remote servers
* Write operations are performed on the local server
* A Synchronizer is employed in order to keep the remote server up to date
* After any read is carried out, a subscription is made for the specific query to make sure the local DB will be updated
* The database also employs a synchronizer which makes sure local data is sent to the remote servers
*/
public class CachedBulletinBoardClient implements SubscriptionAsyncBulletinBoardClient {
public class CachedBulletinBoardClient implements SubscriptionBulletinBoardClient {
private final BulletinBoardClient localClient;
private AsyncBulletinBoardClient remoteClient;
private BulletinBoardSubscriber subscriber;
private final AsyncBulletinBoardClient localClient;
private final AsyncBulletinBoardClient remoteClient;
private final AsyncBulletinBoardClient queueClient;
private final BulletinBoardSubscriber subscriber;
private final BulletinBoardSynchronizer synchronizer;
private final int threadPoolSize;
private final long failDelayInMilliseconds;
private final long subscriptionIntervalInMilliseconds;
private Thread syncThread;
public CachedBulletinBoardClient(BulletinBoardClient localClient,
int threadPoolSize,
long failDelayInMilliseconds,
long subscriptionIntervalInMilliseconds)
throws IllegalAccessException, InstantiationException {
private final static int DEFAULT_WAIT_CAP = 3000;
private final static int DEFAULT_SLEEP_INTERVAL = 3000;
private class SubscriptionStoreCallback implements FutureCallback<List<BulletinBoardMessage>> {
private final FutureCallback<List<BulletinBoardMessage>> callback;
public SubscriptionStoreCallback(){
callback = null;
}
public SubscriptionStoreCallback(FutureCallback<List<BulletinBoardMessage>> callback){
this.callback = callback;
}
@Override
public void onSuccess(List<BulletinBoardMessage> result) {
for (BulletinBoardMessage msg : result) {
try {
if (msg.getMsg().getDataTypeCase() == UnsignedBulletinBoardMessage.DataTypeCase.MSGID) {
// This is a batch message: need to upload batch data as well as the message itself
BulletinBoardMessage completeMessage = localClient.readBatchData(msg);
localClient.postMessage(completeMessage);
} else {
// This is a regular message: post it
localClient.postMessage(msg);
}
} catch (CommunicationException ignored) {
// TODO: log
}
}
}
@Override
public void onFailure(Throwable t) {
if (callback != null) {
callback.onFailure(t); // This is some hard error that cannot be dealt with
}
}
}
/**
* Creates a Cached Client
* Assumes all parameters are initialized
* @param localClient is a Client for the local instance
* @param remoteClient is a Client for the remote instance(s); Should have endless retries for post operations
* @param subscriber is a subscription service to the remote instance(s)
* @param queueClient is a client for a local deletable server to be used as a queue for not-yet-uploaded messages
*/
public CachedBulletinBoardClient(AsyncBulletinBoardClient localClient,
AsyncBulletinBoardClient remoteClient,
BulletinBoardSubscriber subscriber,
DeletableSubscriptionBulletinBoardClient queueClient,
int sleepInterval,
int waitCap) {
this.localClient = localClient;
this.threadPoolSize = threadPoolSize;
this.failDelayInMilliseconds = failDelayInMilliseconds;
this.subscriptionIntervalInMilliseconds = subscriptionIntervalInMilliseconds;
this.remoteClient = remoteClient;
this.subscriber = subscriber;
this.queueClient = queueClient;
remoteClient = new ThreadedBulletinBoardClient();
this.synchronizer = new SimpleBulletinBoardSynchronizer(sleepInterval,waitCap);
synchronizer.init(queueClient, remoteClient);
syncThread = new Thread(synchronizer);
syncThread.start();
}
/**
* Creates a Cached Client
* Used default values foe the time caps
* */
public CachedBulletinBoardClient(AsyncBulletinBoardClient localClient,
AsyncBulletinBoardClient remoteClient,
BulletinBoardSubscriber subscriber,
DeletableSubscriptionBulletinBoardClient queue) {
this(localClient, remoteClient, subscriber, queue, DEFAULT_SLEEP_INTERVAL, DEFAULT_WAIT_CAP);
}
@Override
public MessageID postMessage(final BulletinBoardMessage msg, final FutureCallback<Boolean> callback) {
return localClient.postMessage(msg, new FutureCallback<Boolean>() {
@Override
public void onSuccess(Boolean result) {
remoteClient.postMessage(msg, callback);
}
@Override
public void onFailure(Throwable t) {
if (callback != null)
callback.onFailure(t);
}
});
}
@Override
public MessageID postMessage(BulletinBoardMessage msg, FutureCallback<Boolean> callback) {
return null;
}
public MessageID postAsBatch(final BulletinBoardMessage msg, final int chunkSize, final FutureCallback<Boolean> callback) {
@Override
public MessageID postBatch(CompleteBatch completeBatch, FutureCallback<Boolean> callback) {
return null;
}
return localClient.postAsBatch(msg, chunkSize, new FutureCallback<Boolean>() {
@Override
public void onSuccess(Boolean result) {
remoteClient.postAsBatch(msg, chunkSize, callback);
}
@Override
public void beginBatch(BeginBatchMessage beginBatchMessage, FutureCallback<Boolean> callback) {
@Override
public void onFailure(Throwable t) {
if (callback != null)
callback.onFailure(t);
}
});
}
@Override
public void postBatchData(byte[] signerId, int batchId, List<BatchData> batchDataList, int startPosition, FutureCallback<Boolean> callback) {
public void beginBatch(final Iterable<String> tags, final FutureCallback<BatchIdentifier> callback) {
localClient.beginBatch(tags, new FutureCallback<BatchIdentifier>() {
private BatchIdentifier localIdentifier;
@Override
public void onSuccess(BatchIdentifier result) {
localIdentifier = result;
remoteClient.beginBatch(tags, new FutureCallback<BatchIdentifier>() {
@Override
public void onSuccess(BatchIdentifier result) {
if (callback != null)
callback.onSuccess(new CachedClientBatchIdentifier(localIdentifier, result));
}
@Override
public void onFailure(Throwable t) {
if (callback != null)
callback.onFailure(t);
}
});
}
@Override
public void onFailure(Throwable t) {
if (callback != null)
callback.onFailure(t);
}
});
}
@Override
public void postBatchData(byte[] signerId, int batchId, List<BatchData> batchDataList, FutureCallback<Boolean> callback) {
public void postBatchData(final BatchIdentifier batchIdentifier, final List<BatchChunk> batchChunkList,
final int startPosition, final FutureCallback<Boolean> callback) throws IllegalArgumentException{
if (!(batchIdentifier instanceof CachedClientBatchIdentifier)){
throw new IllegalArgumentException("Error: batch identifier supplied was not created by this class.");
}
final CachedClientBatchIdentifier identifier = (CachedClientBatchIdentifier) batchIdentifier;
localClient.postBatchData(identifier.getLocalIdentifier(), batchChunkList, startPosition, new FutureCallback<Boolean>() {
@Override
public void onSuccess(Boolean result) {
remoteClient.postBatchData(identifier.getRemoteIdentifier(), batchChunkList, startPosition, callback);
}
@Override
public void onFailure(Throwable t) {
if (callback != null)
callback.onFailure(t);
}
});
}
@Override
public void postBatchData(ByteString signerId, int batchId, List<BatchData> batchDataList, int startPosition, FutureCallback<Boolean> callback) {
public void postBatchData(final BatchIdentifier batchIdentifier, final List<BatchChunk> batchChunkList, final FutureCallback<Boolean> callback)
throws IllegalArgumentException{
if (!(batchIdentifier instanceof CachedClientBatchIdentifier)){
throw new IllegalArgumentException("Error: batch identifier supplied was not created by this class.");
}
final CachedClientBatchIdentifier identifier = (CachedClientBatchIdentifier) batchIdentifier;
localClient.postBatchData(identifier.getLocalIdentifier(), batchChunkList, new FutureCallback<Boolean>() {
@Override
public void onSuccess(Boolean result) {
remoteClient.postBatchData(identifier.getRemoteIdentifier(), batchChunkList, callback);
}
@Override
public void onFailure(Throwable t) {
if (callback != null)
callback.onFailure(t);
}
});
}
@Override
public void postBatchData(ByteString signerId, int batchId, List<BatchData> batchDataList, FutureCallback<Boolean> callback) {
public void closeBatch(final BatchIdentifier batchIdentifier, final Timestamp timestamp, final Iterable<Signature> signatures,
final FutureCallback<Boolean> callback) {
}
if (!(batchIdentifier instanceof CachedClientBatchIdentifier)){
throw new IllegalArgumentException("Error: batch identifier supplied was not created by this class.");
}
@Override
public void closeBatch(CloseBatchMessage closeBatchMessage, FutureCallback<Boolean> callback) {
final CachedClientBatchIdentifier identifier = (CachedClientBatchIdentifier) batchIdentifier;
localClient.closeBatch(identifier.getLocalIdentifier(), timestamp, signatures, new FutureCallback<Boolean>() {
@Override
public void onSuccess(Boolean result) {
remoteClient.closeBatch(identifier.getRemoteIdentifier(), timestamp, signatures, callback);
}
@Override
public void onFailure(Throwable t) {
if (callback != null)
callback.onFailure(t);
}
});
}
@Override
public void getRedundancy(MessageID id, FutureCallback<Float> callback) {
}
@Override
public void readMessages(MessageFilterList filterList, FutureCallback<List<BulletinBoardMessage>> callback) {
remoteClient.getRedundancy(id, callback);
}
@Override
public void readBatch(BatchSpecificationMessage batchSpecificationMessage, FutureCallback<CompleteBatch> callback) {
public void readMessages(MessageFilterList filterList, final FutureCallback<List<BulletinBoardMessage>> callback) {
localClient.readMessages(filterList, callback);
subscriber.subscribe(filterList, new SubscriptionStoreCallback(callback));
}
@Override
public void readMessage(final MessageID msgID, final FutureCallback<BulletinBoardMessage> callback) {
localClient.readMessage(msgID, new FutureCallback<BulletinBoardMessage>() {
@Override
public void onSuccess(BulletinBoardMessage result) {
if (callback != null)
callback.onSuccess(result); // Read from local client was successful
}
@Override
public void onFailure(Throwable t) {
// Read from local unsuccessful: try to read from remote
remoteClient.readMessage(msgID, new FutureCallback<BulletinBoardMessage>() {
@Override
public void onSuccess(BulletinBoardMessage result) {
// Read from remote was successful: store in local and return result
localClient.postMessage(result, null);
if (callback != null)
callback.onSuccess(result);
}
@Override
public void onFailure(Throwable t) {
// Read from remote was unsuccessful: report error
if (callback != null)
callback.onFailure(t);
}
});
}
});
}
@Override
public void readBatchData(final BulletinBoardMessage stub, final FutureCallback<BulletinBoardMessage> callback) throws IllegalArgumentException {
localClient.readBatchData(stub, new FutureCallback<BulletinBoardMessage>() {
@Override
public void onSuccess(BulletinBoardMessage result) {
if (callback != null)
callback.onSuccess(result); // Read from local client was successful
}
@Override
public void onFailure(Throwable t) {
// Read from local unsuccessful: try to read from remote
remoteClient.readBatchData(stub, new FutureCallback<BulletinBoardMessage>() {
@Override
public void onSuccess(BulletinBoardMessage result) {
// Read from remote was successful: store in local and return result
localClient.postMessage(result, null);
if (callback != null)
callback.onSuccess(result);
}
@Override
public void onFailure(Throwable t) {
// Read from remote was unsuccessful: report error
if (callback != null)
callback.onFailure(t);
}
});
}
});
}
@Override
public void querySync(SyncQuery syncQuery, FutureCallback<SyncQueryResponse> callback) {
localClient.querySync(syncQuery, callback);
}
@Override
public void init(BulletinBoardClientParams clientParams) {
remoteClient.init(clientParams);
ListeningScheduledExecutorService executorService = MoreExecutors.listeningDecorator(Executors.newScheduledThreadPool(threadPoolSize));
List<SubscriptionAsyncBulletinBoardClient> subscriberClients = new ArrayList<>(clientParams.getBulletinBoardAddressCount());
for (String address : clientParams.getBulletinBoardAddressList()){
SubscriptionAsyncBulletinBoardClient newClient =
new SingleServerBulletinBoardClient(executorService, failDelayInMilliseconds, subscriptionIntervalInMilliseconds);
newClient.init(clientParams.toBuilder().clearBulletinBoardAddress().addBulletinBoardAddress(address).build());
subscriberClients.add(newClient);
}
subscriber = new ThreadedBulletinBoardSubscriber(subscriberClients, localClient);
}
/**
* This is a stub method
* All resources are assumed to be initialized
*/
public void init(BulletinBoardClientParams clientParams) {}
@Override
public MessageID postMessage(BulletinBoardMessage msg) throws CommunicationException {
return null;
return localClient.postMessage(msg);
}
@Override
public float getRedundancy(MessageID id) {
return 0;
public MessageID postAsBatch(BulletinBoardMessage msg, int chunkSize) throws CommunicationException {
MessageID result = localClient.postAsBatch(msg, chunkSize);
remoteClient.postAsBatch(msg, chunkSize);
return result;
}
@Override
public List<BulletinBoardMessage> readMessages(MessageFilterList filterList) {
return null;
public float getRedundancy(MessageID id) throws CommunicationException {
return remoteClient.getRedundancy(id);
}
@Override
public SyncQuery generateSyncQuery(GenerateSyncQueryParams GenerateSyncQueryParams) throws CommunicationException {
return null;
public List<BulletinBoardMessage> readMessages(MessageFilterList filterList) throws CommunicationException {
subscriber.subscribe(filterList, new SubscriptionStoreCallback());
return localClient.readMessages(filterList);
}
@Override
public BulletinBoardMessage readMessage(MessageID msgID) throws CommunicationException {
BulletinBoardMessage result = null;
try {
result = localClient.readMessage(msgID);
} catch (CommunicationException e) {
//TODO: log
}
if (result == null){
result = remoteClient.readMessage(msgID);
if (result != null){
localClient.postMessage(result);
}
}
return result;
}
@Override
public BulletinBoardMessage readBatchData(BulletinBoardMessage stub) throws CommunicationException, IllegalArgumentException {
BulletinBoardMessage result = null;
try {
result = localClient.readBatchData(stub);
} catch (CommunicationException e) {
//TODO: log
}
if (result == null){
result = remoteClient.readBatchData(stub);
if (result != null){
localClient.postMessage(result);
}
}
return result;
}
@Override
public SyncQuery generateSyncQuery(GenerateSyncQueryParams generateSyncQueryParams) throws CommunicationException {
return localClient.generateSyncQuery(generateSyncQueryParams);
}
@Override
public void close() {
localClient.close();
remoteClient.close();
synchronizer.stop();
try {
syncThread.join();
} catch (InterruptedException e) {
//TODO: log interruption
}
}
@Override
public void subscribe(MessageFilterList filterList, FutureCallback<List<BulletinBoardMessage>> callback) {
subscriber.subscribe(filterList, new SubscriptionStoreCallback(callback));
}
@Override
public void subscribe(MessageFilterList filterList, long startEntry, FutureCallback<List<BulletinBoardMessage>> callback) {
subscriber.subscribe(filterList, startEntry, new SubscriptionStoreCallback(callback));
}
}
}

View File

@ -0,0 +1,29 @@
package meerkat.bulletinboard;
import meerkat.bulletinboard.AsyncBulletinBoardClient.BatchIdentifier;
import java.util.Arrays;
/**
* Created by Arbel Deutsch Peled on 17-Jun-16.
*/
public final class CachedClientBatchIdentifier implements BatchIdentifier {
// Per-server identifiers
private final BatchIdentifier localIdentifier;
private final BatchIdentifier remoteIdentifier;
public CachedClientBatchIdentifier(BatchIdentifier localIdentifier, BatchIdentifier remoteIdentifier) {
this.localIdentifier = localIdentifier;
this.remoteIdentifier = remoteIdentifier;
}
public BatchIdentifier getLocalIdentifier() {
return localIdentifier;
}
public BatchIdentifier getRemoteIdentifier() {
return remoteIdentifier;
}
}

View File

@ -1,20 +1,21 @@
package meerkat.bulletinboard;
import com.google.common.util.concurrent.*;
import com.google.protobuf.ByteString;
import com.google.protobuf.Int64Value;
import com.google.protobuf.Timestamp;
import meerkat.comm.CommunicationException;
import meerkat.comm.MessageInputStream;
import meerkat.comm.MessageInputStream.MessageInputStreamFactory;
import meerkat.comm.MessageOutputStream;
import meerkat.crypto.concrete.SHA256Digest;
import meerkat.protobuf.BulletinBoardAPI.*;
import meerkat.protobuf.BulletinBoardApi.*;
import meerkat.protobuf.Crypto.Signature;
import meerkat.protobuf.Voting.*;
import meerkat.util.BulletinBoardUtils;
import javax.ws.rs.NotFoundException;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.Executors;
@ -22,28 +23,27 @@ import java.util.concurrent.TimeUnit;
/**
* Created by Arbel Deutsch Peled on 15-Mar-16.
* This client is to be used mainly for testing.
* It wraps a BulletinBoardServer in an asynchronous client.
* This client wraps a BulletinBoardServer in an asynchronous client.
* It is meant to be used as a local cache handler and for testing purposes.
* This means the access to the server is direct (via method calls) instead of through a TCP connection.
* The client implements both synchronous and asynchronous method calls, but calls to the server itself are performed synchronously.
*/
public class LocalBulletinBoardClient implements SubscriptionAsyncBulletinBoardClient{
public class LocalBulletinBoardClient implements DeletableSubscriptionBulletinBoardClient {
private final BulletinBoardServer server;
private final DeletableBulletinBoardServer server;
private final ListeningScheduledExecutorService executorService;
private final BatchDigest digest;
private final int subsrciptionDelay;
private final BulletinBoardDigest digest;
private final long subsrciptionDelay;
/**
* Initializes an instance of the client
* @param server an initialized Bulletin Board Server instance which will perform the actual processing of the requests
* @param threadNum is the number of concurrent threads to allocate for the client
* @param subscriptionDelay is the required delay between subscription calls in milliseconds
*/
public LocalBulletinBoardClient(BulletinBoardServer server, int threadNum, int subscriptionDelay) {
public LocalBulletinBoardClient(DeletableBulletinBoardServer server, int threadNum, int subscriptionDelay) {
this.server = server;
this.executorService = MoreExecutors.listeningDecorator(Executors.newScheduledThreadPool(threadNum));
this.digest = new GenericBatchDigest(new SHA256Digest());
this.digest = new GenericBulletinBoardDigest(new SHA256Digest());
this.subsrciptionDelay = subscriptionDelay;
}
@ -57,7 +57,7 @@ public class LocalBulletinBoardClient implements SubscriptionAsyncBulletinBoardC
@Override
public Boolean call() throws Exception {
public Boolean call() throws CommunicationException {
return server.postMessage(msg).getValue();
}
@ -75,51 +75,57 @@ public class LocalBulletinBoardClient implements SubscriptionAsyncBulletinBoardC
private class CompleteBatchPoster implements Callable<Boolean> {
private final CompleteBatch completeBatch;
private final BulletinBoardMessage msg;
private final int chunkSize;
public CompleteBatchPoster(CompleteBatch completeBatch) {
this.completeBatch = completeBatch;
public CompleteBatchPoster(BulletinBoardMessage msg, int chunkSize) {
this.msg = msg;
this.chunkSize = chunkSize;
}
@Override
public Boolean call() throws Exception {
public Boolean call() throws CommunicationException {
if (!server.beginBatch(completeBatch.getBeginBatchMessage()).getValue())
return false;
BeginBatchMessage beginBatchMessage = BeginBatchMessage.newBuilder()
.addAllTag(msg.getMsg().getTagList())
.build();
Int64Value batchId = server.beginBatch(beginBatchMessage);
BatchMessage.Builder builder = BatchMessage.newBuilder()
.setBatchId(batchId.getValue());
List<BatchChunk> batchChunkList = BulletinBoardUtils.breakToBatch(msg, chunkSize);
int i=0;
for (BatchData data : completeBatch.getBatchDataList()){
for (BatchChunk chunk : batchChunkList){
BatchMessage message = BatchMessage.newBuilder()
.setSignerId(completeBatch.getSignature().getSignerId())
.setBatchId(completeBatch.getBeginBatchMessage().getBatchId())
.setSerialNum(i)
.setData(data)
.build();
if (!server.postBatchMessage(message).getValue())
return false;
server.postBatchMessage(builder.setSerialNum(i).setData(chunk).build());
i++;
}
return server.closeBatchMessage(completeBatch.getCloseBatchMessage()).getValue();
CloseBatchMessage closeBatchMessage = BulletinBoardUtils.generateCloseBatchMessage(batchId, batchChunkList.size(), msg);
return server.closeBatch(closeBatchMessage).getValue();
}
}
@Override
public MessageID postBatch(CompleteBatch completeBatch, FutureCallback<Boolean> callback) {
public MessageID postAsBatch(BulletinBoardMessage msg, int chunkSize, FutureCallback<Boolean> callback) {
Futures.addCallback(executorService.schedule(new CompleteBatchPoster(completeBatch), subsrciptionDelay, TimeUnit.MILLISECONDS), callback);
Futures.addCallback(executorService.submit(new CompleteBatchPoster(msg, chunkSize)), callback);
digest.update(completeBatch);
digest.reset();
digest.update(msg);
return digest.digestAsMessageID();
}
private class BatchBeginner implements Callable<Boolean> {
private class BatchBeginner implements Callable<SingleServerBatchIdentifier> {
private final BeginBatchMessage msg;
@ -129,28 +135,31 @@ public class LocalBulletinBoardClient implements SubscriptionAsyncBulletinBoardC
@Override
public Boolean call() throws Exception {
return server.beginBatch(msg).getValue();
public SingleServerBatchIdentifier call() throws Exception {
return new SingleServerBatchIdentifier(server.beginBatch(msg));
}
}
@Override
public void beginBatch(BeginBatchMessage beginBatchMessage, FutureCallback<Boolean> callback) {
public void beginBatch(Iterable<String> tags, FutureCallback<BatchIdentifier> callback) {
BeginBatchMessage beginBatchMessage = BeginBatchMessage.newBuilder()
.addAllTag(tags)
.build();
Futures.addCallback(executorService.submit(new BatchBeginner(beginBatchMessage)), callback);
}
private class BatchDataPoster implements Callable<Boolean> {
private final ByteString signerId;
private final int batchId;
private final List<BatchData> batchDataList;
private final SingleServerBatchIdentifier batchId;
private final List<BatchChunk> batchChunkList;
private final int startPosition;
public BatchDataPoster(ByteString signerId, int batchId, List<BatchData> batchDataList, int startPosition) {
this.signerId = signerId;
public BatchDataPoster(SingleServerBatchIdentifier batchId, List<BatchChunk> batchChunkList, int startPosition) {
this.batchId = batchId;
this.batchDataList = batchDataList;
this.batchChunkList = batchChunkList;
this.startPosition = startPosition;
}
@ -159,11 +168,10 @@ public class LocalBulletinBoardClient implements SubscriptionAsyncBulletinBoardC
public Boolean call() throws Exception {
BatchMessage.Builder msgBuilder = BatchMessage.newBuilder()
.setSignerId(signerId)
.setBatchId(batchId);
.setBatchId(batchId.getBatchId().getValue());
int i = startPosition;
for (BatchData data : batchDataList){
for (BatchChunk data : batchChunkList){
msgBuilder.setSerialNum(i)
.setData(data);
@ -175,6 +183,8 @@ public class LocalBulletinBoardClient implements SubscriptionAsyncBulletinBoardC
}
batchId.setLength(i);
return true;
}
@ -182,24 +192,28 @@ public class LocalBulletinBoardClient implements SubscriptionAsyncBulletinBoardC
}
@Override
public void postBatchData(byte[] signerId, int batchId, List<BatchData> batchDataList, int startPosition, FutureCallback<Boolean> callback) {
postBatchData(ByteString.copyFrom(signerId), batchId, batchDataList, startPosition, callback);
public void postBatchData(BatchIdentifier batchId, List<BatchChunk> batchChunkList, int startPosition, FutureCallback<Boolean> callback)
throws IllegalArgumentException{
// Cast identifier to usable form
if (!(batchId instanceof SingleServerBatchIdentifier)){
throw new IllegalArgumentException("Error: batch identifier supplied was not created by this class.");
}
SingleServerBatchIdentifier identifier = (SingleServerBatchIdentifier) batchId;
// Add worker
Futures.addCallback(executorService.submit(new BatchDataPoster(identifier, batchChunkList, startPosition)), callback);
}
@Override
public void postBatchData(byte[] signerId, int batchId, List<BatchData> batchDataList, FutureCallback<Boolean> callback) {
postBatchData(signerId, batchId, batchDataList, 0, callback);
public void postBatchData(BatchIdentifier batchId, List<BatchChunk> batchChunkList, FutureCallback<Boolean> callback) throws IllegalArgumentException{
postBatchData(batchId, batchChunkList, 0, callback);
}
@Override
public void postBatchData(ByteString signerId, int batchId, List<BatchData> batchDataList, int startPosition, FutureCallback<Boolean> callback) {
Futures.addCallback(executorService.submit(new BatchDataPoster(signerId, batchId, batchDataList, startPosition)), callback);
}
@Override
public void postBatchData(ByteString signerId, int batchId, List<BatchData> batchDataList, FutureCallback<Boolean> callback) {
postBatchData(signerId, batchId, batchDataList, 0, callback);
}
private class BatchCloser implements Callable<Boolean> {
@ -212,14 +226,33 @@ public class LocalBulletinBoardClient implements SubscriptionAsyncBulletinBoardC
@Override
public Boolean call() throws Exception {
return server.closeBatchMessage(msg).getValue();
return server.closeBatch(msg).getValue();
}
}
@Override
public void closeBatch(CloseBatchMessage closeBatchMessage, FutureCallback<Boolean> callback) {
public void closeBatch(BatchIdentifier batchId, Timestamp timestamp, Iterable<Signature> signatures, FutureCallback<Boolean> callback) {
// Cast identifier to usable form
if (!(batchId instanceof SingleServerBatchIdentifier)){
throw new IllegalArgumentException("Error: batch identifier supplied was not created by this class.");
}
SingleServerBatchIdentifier identifier = (SingleServerBatchIdentifier) batchId;
// Add worker
CloseBatchMessage closeBatchMessage = CloseBatchMessage.newBuilder()
.setBatchId(identifier.getBatchId().getValue())
.setBatchLength(identifier.getLength())
.setTimestamp(timestamp)
.addAllSig(signatures)
.build();
Futures.addCallback(executorService.submit(new BatchCloser(closeBatchMessage)), callback);
}
private class RedundancyGetter implements Callable<Float> {
@ -310,7 +343,8 @@ public class LocalBulletinBoardClient implements SubscriptionAsyncBulletinBoardC
public void onSuccess(List<BulletinBoardMessage> result) {
// Report new messages to user
callback.onSuccess(result);
if (callback != null)
callback.onSuccess(result);
MessageFilterList.Builder filterBuilder = filterList.toBuilder();
@ -331,7 +365,7 @@ public class LocalBulletinBoardClient implements SubscriptionAsyncBulletinBoardC
filterList = filterBuilder.build();
// Reschedule job
Futures.addCallback(executorService.submit(new MessageReader(filterList)), this);
Futures.addCallback(executorService.schedule(new MessageReader(filterList), subsrciptionDelay, TimeUnit.MILLISECONDS), this);
}
@ -339,7 +373,8 @@ public class LocalBulletinBoardClient implements SubscriptionAsyncBulletinBoardC
public void onFailure(Throwable t) {
// Notify caller about failure and terminate subscription
callback.onFailure(t);
if (callback != null)
callback.onFailure(t);
}
}
@ -364,83 +399,122 @@ public class LocalBulletinBoardClient implements SubscriptionAsyncBulletinBoardC
subscribe(filterList, 0, callback);
}
private class CompleteBatchReader implements Callable<CompleteBatch> {
private class BatchDataReader implements Callable<List<BatchChunk>> {
private final BatchSpecificationMessage batchSpecificationMessage;
private final MessageID msgID;
public CompleteBatchReader(BatchSpecificationMessage batchSpecificationMessage) {
this.batchSpecificationMessage = batchSpecificationMessage;
public BatchDataReader(MessageID msgID) {
this.msgID = msgID;
}
@Override
public List<BatchChunk> call() throws Exception {
BatchQuery batchQuery = BatchQuery.newBuilder()
.setMsgID(msgID)
.setStartPosition(0)
.build();
ByteArrayOutputStream byteOutputStream = new ByteArrayOutputStream();
MessageOutputStream<BatchChunk> batchOutputStream = new MessageOutputStream<>(byteOutputStream);
server.readBatch(batchQuery,batchOutputStream);
MessageInputStream<BatchChunk> inputStream =
MessageInputStreamFactory.createMessageInputStream(
new ByteArrayInputStream(byteOutputStream.toByteArray()),
BatchChunk.class);
return inputStream.asList();
}
}
private class CompleteBatchReader implements Callable<BulletinBoardMessage> {
private final MessageID msgID;
public CompleteBatchReader(MessageID msgID) {
this.msgID = msgID;
}
@Override
public CompleteBatch call() throws Exception {
public BulletinBoardMessage call() throws Exception {
final String[] TAGS_TO_REMOVE = {BulletinBoardConstants.BATCH_TAG, BulletinBoardConstants.BATCH_ID_TAG_PREFIX};
CompleteBatch completeBatch = new CompleteBatch(BeginBatchMessage.newBuilder()
.setSignerId(batchSpecificationMessage.getSignerId())
.setBatchId(batchSpecificationMessage.getBatchId())
.build());
ByteArrayOutputStream byteOutputStream = new ByteArrayOutputStream();
MessageOutputStream<BatchData> batchOutputStream = new MessageOutputStream<>(byteOutputStream);
server.readBatch(batchSpecificationMessage,batchOutputStream);
MessageInputStream<BatchData> batchInputStream =
MessageInputStreamFactory.createMessageInputStream(
new ByteArrayInputStream(byteOutputStream.toByteArray()),
BatchData.class);
completeBatch.appendBatchData(batchInputStream.asList());
// Read message (mat be a stub)
MessageFilterList filterList = MessageFilterList.newBuilder()
.addFilter(MessageFilter.newBuilder()
.setType(FilterType.TAG)
.setTag(BulletinBoardConstants.BATCH_TAG)
.build())
.addFilter(MessageFilter.newBuilder()
.setType(FilterType.TAG)
.setTag(BulletinBoardConstants.BATCH_ID_TAG_PREFIX + completeBatch.getBeginBatchMessage().getBatchId())
.build())
.addFilter(MessageFilter.newBuilder()
.setType(FilterType.SIGNER_ID)
.setId(completeBatch.getBeginBatchMessage().getSignerId())
.setType(FilterType.MSG_ID)
.setId(msgID.getID())
.build())
.build();
byteOutputStream = new ByteArrayOutputStream();
MessageOutputStream<BulletinBoardMessage> messageOutputStream = new MessageOutputStream<>(byteOutputStream);
server.readMessages(filterList,messageOutputStream);
MessageReader messageReader = new MessageReader(filterList);
List<BulletinBoardMessage> bulletinBoardMessages = messageReader.call();
MessageInputStream<BulletinBoardMessage> messageInputStream =
MessageInputStreamFactory.createMessageInputStream(
new ByteArrayInputStream(byteOutputStream.toByteArray()),
BulletinBoardMessage.class);
if (bulletinBoardMessages.size() <= 0) {
throw new NotFoundException("Message does not exist");
}
if (!messageInputStream.isAvailable())
throw new NotFoundException("Batch does not exist");
BulletinBoardMessage msg = bulletinBoardMessages.get(0);
BulletinBoardMessage message = messageInputStream.readMessage();
if (msg.getMsg().getDataTypeCase() == UnsignedBulletinBoardMessage.DataTypeCase.MSGID) {
completeBatch.setBeginBatchMessage(BeginBatchMessage.newBuilder()
.addAllTag(BulletinBoardUtils.removePrefixTags(message, Arrays.asList(TAGS_TO_REMOVE)))
.setSignerId(message.getSig(0).getSignerId())
.setBatchId(Integer.parseInt(BulletinBoardUtils.findTagWithPrefix(message, BulletinBoardConstants.BATCH_ID_TAG_PREFIX)))
.build());
// Read data
completeBatch.setSignature(message.getSig(0));
completeBatch.setTimestamp(message.getMsg().getTimestamp());
BatchDataReader batchDataReader = new BatchDataReader(msgID);
List<BatchChunk> batchChunkList = batchDataReader.call();
return completeBatch;
// Combine and return
return BulletinBoardUtils.gatherBatch(msg, batchChunkList);
} else {
return msg;
}
}
}
private class BatchDataCombiner implements Callable<BulletinBoardMessage> {
private final BulletinBoardMessage stub;
public BatchDataCombiner(BulletinBoardMessage stub) {
this.stub = stub;
}
@Override
public BulletinBoardMessage call() throws Exception {
MessageID msgID = MessageID.newBuilder().setID(stub.getMsg().getMsgId()).build();
BatchDataReader batchDataReader = new BatchDataReader(msgID);
List<BatchChunk> batchChunkList = batchDataReader.call();
return BulletinBoardUtils.gatherBatch(stub, batchChunkList);
}
}
@Override
public void readBatch(BatchSpecificationMessage batchSpecificationMessage, FutureCallback<CompleteBatch> callback) {
Futures.addCallback(executorService.submit(new CompleteBatchReader(batchSpecificationMessage)), callback);
public void readMessage(MessageID msgID, FutureCallback<BulletinBoardMessage> callback) {
Futures.addCallback(executorService.submit(new CompleteBatchReader(msgID)), callback);
}
@Override
public void readBatchData(BulletinBoardMessage stub, FutureCallback<BulletinBoardMessage> callback) throws IllegalArgumentException {
if (stub.getMsg().getDataTypeCase() != UnsignedBulletinBoardMessage.DataTypeCase.MSGID){
throw new IllegalArgumentException("Message is not a stub and does not contain the required message ID");
}
Futures.addCallback(executorService.submit(new BatchDataCombiner(stub)),callback);
}
private class SyncQueryHandler implements Callable<SyncQueryResponse> {
@ -474,17 +548,26 @@ public class LocalBulletinBoardClient implements SubscriptionAsyncBulletinBoardC
@Override
public MessageID postMessage(BulletinBoardMessage msg) throws CommunicationException {
try {
MessagePoster poster = new MessagePoster(msg);
poster.call();
digest.update(msg);
digest.update(msg.getMsg());
return digest.digestAsMessageID();
} catch (Exception e) {
return null;
}
}
@Override
public MessageID postAsBatch(BulletinBoardMessage msg, int chunkSize) throws CommunicationException {
CompleteBatchPoster poster = new CompleteBatchPoster(msg, chunkSize);
Boolean result = poster.call();
if (!result)
throw new CommunicationException("Batch post failed");
digest.reset();
digest.update(msg);
return digest.digestAsMessageID();
}
@ -503,7 +586,7 @@ public class LocalBulletinBoardClient implements SubscriptionAsyncBulletinBoardC
}
@Override
public List<BulletinBoardMessage> readMessages(MessageFilterList filterList) {
public List<BulletinBoardMessage> readMessages(MessageFilterList filterList) throws CommunicationException{
try {
@ -511,14 +594,89 @@ public class LocalBulletinBoardClient implements SubscriptionAsyncBulletinBoardC
return reader.call();
} catch (Exception e){
return null;
throw new CommunicationException("Error reading from server");
}
}
@Override
public SyncQuery generateSyncQuery(GenerateSyncQueryParams GenerateSyncQueryParams) throws CommunicationException {
return server.generateSyncQuery(GenerateSyncQueryParams);
public BulletinBoardMessage readMessage(MessageID msgID) throws CommunicationException {
MessageFilterList filterList = MessageFilterList.newBuilder()
.addFilter(MessageFilter.newBuilder()
.setType(FilterType.MSG_ID)
.setId(msgID.getID())
.build())
.build();
CompleteBatchReader completeBatchReader = new CompleteBatchReader(msgID);
try {
return completeBatchReader.call();
} catch (Exception e) {
throw new CommunicationException(e.getMessage() + " " + e.getMessage());
}
}
@Override
public BulletinBoardMessage readBatchData(BulletinBoardMessage stub) throws CommunicationException, IllegalArgumentException {
if (stub.getMsg().getDataTypeCase() != UnsignedBulletinBoardMessage.DataTypeCase.MSGID){
throw new IllegalArgumentException("Message is not a stub and does not contain the required message ID");
}
BatchDataCombiner combiner = new BatchDataCombiner(stub);
try {
return combiner.call();
} catch (Exception e) {
throw new CommunicationException(e.getCause() + " " + e.getMessage());
}
}
@Override
public SyncQuery generateSyncQuery(GenerateSyncQueryParams generateSyncQueryParams) throws CommunicationException {
return server.generateSyncQuery(generateSyncQueryParams);
}
@Override
public void deleteMessage(MessageID msgID, FutureCallback<Boolean> callback) {
try {
Boolean deleted = server.deleteMessage(msgID).getValue();
if (callback != null)
callback.onSuccess(deleted);
} catch (CommunicationException e) {
if (callback != null)
callback.onFailure(e);
}
}
@Override
public void deleteMessage(long entryNum, FutureCallback<Boolean> callback) {
try {
Boolean deleted = server.deleteMessage(entryNum).getValue();
if (callback != null)
callback.onSuccess(deleted);
} catch (CommunicationException e) {
if (callback != null)
callback.onFailure(e);
}
}
@Override
public boolean deleteMessage(MessageID msgID) throws CommunicationException {
return server.deleteMessage(msgID).getValue();
}
@Override
public boolean deleteMessage(long entryNum) throws CommunicationException {
return server.deleteMessage(entryNum).getValue();
}
@Override

View File

@ -0,0 +1,27 @@
package meerkat.bulletinboard;
import meerkat.bulletinboard.AsyncBulletinBoardClient.BatchIdentifier;
import java.util.Arrays;
/**
* Created by Arbel Deutsch Peled on 17-Jun-16.
*/
public final class MultiServerBatchIdentifier implements AsyncBulletinBoardClient.BatchIdentifier {
// Per-server identifiers
private final Iterable<BatchIdentifier> identifiers;
public MultiServerBatchIdentifier(Iterable<BatchIdentifier> identifiers) {
this.identifiers = identifiers;
}
public MultiServerBatchIdentifier(BatchIdentifier[] identifiers) {
this.identifiers = Arrays.asList(identifiers);
}
public Iterable<BatchIdentifier> getIdentifiers() {
return identifiers;
}
}

View File

@ -18,7 +18,7 @@ import java.util.concurrent.atomic.AtomicInteger;
*/
public abstract class MultiServerWorker<IN, OUT> extends BulletinClientWorker<IN> implements Runnable, FutureCallback<OUT>{
private final List<SingleServerBulletinBoardClient> clients;
protected final List<SingleServerBulletinBoardClient> clients;
protected AtomicInteger minServers; // The minimal number of servers the job must be successful on for the job to be completed
@ -74,7 +74,8 @@ public abstract class MultiServerWorker<IN, OUT> extends BulletinClientWorker<IN
*/
protected void succeed(OUT result){
if (returnedResult.compareAndSet(false, true)) {
futureCallback.onSuccess(result);
if (futureCallback != null)
futureCallback.onSuccess(result);
}
}
@ -85,18 +86,11 @@ public abstract class MultiServerWorker<IN, OUT> extends BulletinClientWorker<IN
*/
protected void fail(Throwable t){
if (returnedResult.compareAndSet(false, true)) {
futureCallback.onFailure(t);
if (futureCallback != null)
futureCallback.onFailure(t);
}
}
/**
* Used by implementations to get a Single Server Client iterator
* @return the requested iterator
*/
protected Iterator<SingleServerBulletinBoardClient> getClientIterator() {
return clients.iterator();
}
protected int getClientNumber() {
return clients.size();
}

View File

@ -1,198 +1,329 @@
package meerkat.bulletinboard;
import com.google.protobuf.ByteString;
import com.google.protobuf.Timestamp;
import meerkat.comm.CommunicationException;
import meerkat.comm.MessageInputStream;
import meerkat.crypto.Digest;
import meerkat.crypto.concrete.SHA256Digest;
import meerkat.protobuf.BulletinBoardAPI;
import meerkat.protobuf.BulletinBoardAPI.*;
import meerkat.protobuf.Voting.*;
import meerkat.rest.*;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.Entity;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.Response;
import static meerkat.bulletinboard.BulletinBoardConstants.*;
/**
* Created by Arbel Deutsch Peled on 05-Dec-15.
* Implements BulletinBoardClient interface in a simple, straightforward manner
*/
public class SimpleBulletinBoardClient implements BulletinBoardClient{
protected List<String> meerkatDBs;
protected Client client;
protected Digest digest;
/**
* Stores database locations and initializes the web Client
* @param clientParams contains the data needed to access the DBs
*/
@Override
public void init(BulletinBoardClientParams clientParams) {
this.meerkatDBs = clientParams.getBulletinBoardAddressList();
client = ClientBuilder.newClient();
client.register(ProtobufMessageBodyReader.class);
client.register(ProtobufMessageBodyWriter.class);
digest = new SHA256Digest();
}
/**
* Post message to all DBs
* Make only one try per DB.
* @param msg is the message,
* @return the message ID for later retrieval
* @throws CommunicationException
*/
@Override
public MessageID postMessage(BulletinBoardMessage msg) throws CommunicationException {
WebTarget webTarget;
Response response;
// Post message to all databases
try {
for (String db : meerkatDBs) {
webTarget = client.target(db).path(BULLETIN_BOARD_SERVER_PATH).path(POST_MESSAGE_PATH);
response = webTarget.request(Constants.MEDIATYPE_PROTOBUF).post(Entity.entity(msg, Constants.MEDIATYPE_PROTOBUF));
// Only consider valid responses
if (response.getStatusInfo() == Response.Status.OK
|| response.getStatusInfo() == Response.Status.CREATED) {
response.readEntity(BoolMsg.class).getValue();
}
}
} catch (Exception e) { // Occurs only when server replies with valid status but invalid data
throw new CommunicationException("Error accessing database: " + e.getMessage());
}
// Calculate the correct message ID and return it
digest.reset();
digest.update(msg.getMsg());
return MessageID.newBuilder().setID(ByteString.copyFrom(digest.digest())).build();
}
/**
* Access each database and search for a given message ID
* Return the number of databases in which the message was found
* Only try once per DB
* Ignore communication exceptions in specific databases
* @param id is the requested message ID
* @return the number of DBs in which retrieval was successful
*/
@Override
public float getRedundancy(MessageID id) {
WebTarget webTarget;
Response response;
MessageFilterList filterList = MessageFilterList.newBuilder()
.addFilter(MessageFilter.newBuilder()
.setType(FilterType.MSG_ID)
.setId(id.getID())
.build())
.build();
float count = 0;
for (String db : meerkatDBs) {
try {
webTarget = client.target(db).path(BULLETIN_BOARD_SERVER_PATH).path(READ_MESSAGES_PATH);
response = webTarget.request(Constants.MEDIATYPE_PROTOBUF).post(Entity.entity(filterList, Constants.MEDIATYPE_PROTOBUF));
if (response.readEntity(BulletinBoardMessageList.class).getMessageCount() > 0){
count++;
}
} catch (Exception e) {}
}
return count / ((float) meerkatDBs.size());
}
/**
* Go through the DBs and try to retrieve messages according to the specified filter
* If at the operation is successful for some DB: return the results and stop iterating
* If no operation is successful: return null (NOT blank list)
* @param filterList return only messages that match the filters (null means no filtering).
* @return the list of Bulletin Board messages that are returned from a server
*/
@Override
public List<BulletinBoardMessage> readMessages(MessageFilterList filterList) {
WebTarget webTarget;
Response response;
BulletinBoardMessageList messageList;
// Replace null filter list with blank one.
if (filterList == null){
filterList = MessageFilterList.newBuilder().build();
}
for (String db : meerkatDBs) {
try {
webTarget = client.target(db).path(BULLETIN_BOARD_SERVER_PATH).path(READ_MESSAGES_PATH);
response = webTarget.request(Constants.MEDIATYPE_PROTOBUF).post(Entity.entity(filterList, Constants.MEDIATYPE_PROTOBUF));
messageList = response.readEntity(BulletinBoardMessageList.class);
if (messageList != null){
return messageList.getMessageList();
}
} catch (Exception e) {}
}
return null;
}
@Override
public SyncQuery generateSyncQuery(GenerateSyncQueryParams generateSyncQueryParams) throws CommunicationException {
WebTarget webTarget;
Response response;
for (String db : meerkatDBs) {
try {
webTarget = client.target(db).path(BULLETIN_BOARD_SERVER_PATH).path(GENERATE_SYNC_QUERY_PATH);
response = webTarget.request(Constants.MEDIATYPE_PROTOBUF).post(Entity.entity(generateSyncQueryParams, Constants.MEDIATYPE_PROTOBUF));
return response.readEntity(SyncQuery.class);
} catch (Exception e) {}
}
throw new CommunicationException("Could not contact any server");
}
public void close() {
client.close();
}
}
package meerkat.bulletinboard;
import com.google.protobuf.BoolValue;
import com.google.protobuf.ByteString;
import com.google.protobuf.Int64Value;
import meerkat.bulletinboard.workers.singleserver.*;
import meerkat.comm.CommunicationException;
import meerkat.crypto.concrete.SHA256Digest;
import meerkat.protobuf.BulletinBoardApi;
import meerkat.protobuf.BulletinBoardApi.*;
import meerkat.protobuf.Voting.*;
import meerkat.rest.*;
import meerkat.util.BulletinBoardUtils;
import java.util.List;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.Entity;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.Response;
import static meerkat.bulletinboard.BulletinBoardConstants.*;
/**
* Created by Arbel Deutsch Peled on 05-Dec-15.
* Implements BulletinBoardClient interface in a simple, straightforward manner
*/
public class SimpleBulletinBoardClient implements BulletinBoardClient{
protected List<String> meerkatDBs;
protected Client client;
protected BulletinBoardDigest digest;
/**
* Stores database locations and initializes the web Client
* @param clientParams contains the data needed to access the DBs
*/
@Override
public void init(BulletinBoardClientParams clientParams) {
this.meerkatDBs = clientParams.getBulletinBoardAddressList();
client = ClientBuilder.newClient();
client.register(ProtobufMessageBodyReader.class);
client.register(ProtobufMessageBodyWriter.class);
// Wrap the Digest into a BatchDigest
digest = new GenericBulletinBoardDigest(new SHA256Digest());
}
/**
* Post message to all DBs
* Make only one try per DB.
* @param msg is the message,
* @return the message ID for later retrieval
* @throws CommunicationException
*/
@Override
public MessageID postMessage(BulletinBoardMessage msg) throws CommunicationException {
WebTarget webTarget;
Response response = null;
// Post message to all databases
try {
for (String db : meerkatDBs) {
SingleServerPostMessageWorker worker = new SingleServerPostMessageWorker(db, msg, 0);
worker.call();
}
} catch (Exception e) { // Occurs only when server replies with valid status but invalid data
throw new CommunicationException("Error accessing database: " + e.getMessage());
}
// Calculate the correct message ID and return it
digest.reset();
digest.update(msg.getMsg());
return MessageID.newBuilder().setID(ByteString.copyFrom(digest.digest())).build();
}
/**
* Access each database and search for a given message ID
* Return the number of databases in which the message was found
* Only try once per DB
* Ignore communication exceptions in specific databases
* @param id is the requested message ID
* @return the number of DBs in which retrieval was successful
*/
@Override
public float getRedundancy(MessageID id) {
WebTarget webTarget;
Response response;
MessageFilterList filterList = MessageFilterList.newBuilder()
.addFilter(MessageFilter.newBuilder()
.setType(FilterType.MSG_ID)
.setId(id.getID())
.build())
.build();
float count = 0;
for (String db : meerkatDBs) {
try {
webTarget = client.target(db).path(BULLETIN_BOARD_SERVER_PATH).path(READ_MESSAGES_PATH);
response = webTarget.request(Constants.MEDIATYPE_PROTOBUF).post(Entity.entity(filterList, Constants.MEDIATYPE_PROTOBUF));
if (response.readEntity(BulletinBoardMessageList.class).getMessageCount() > 0){
count++;
}
} catch (Exception e) {}
}
return count / ((float) meerkatDBs.size());
}
/**
* Go through the DBs and try to retrieve messages according to the specified filter
* If at the operation is successful for some DB: return the results and stop iterating
* If no operation is successful: return null (NOT blank list)
* @param filterList return only messages that match the filters (null means no filtering).
* @return the list of Bulletin Board messages that are returned from a server
*/
@Override
public List<BulletinBoardMessage> readMessages(MessageFilterList filterList) throws CommunicationException{
// Replace null filter list with blank one.
if (filterList == null){
filterList = MessageFilterList.getDefaultInstance();
}
String exceptionString = "";
for (String db : meerkatDBs) {
try {
SingleServerReadMessagesWorker worker = new SingleServerReadMessagesWorker(db, filterList, 0);
List<BulletinBoardMessage> result = worker.call();
return result;
} catch (Exception e) {
//TODO: log
exceptionString += e.getMessage() + "\n";
}
}
throw new CommunicationException("Could not find message in any DB. Errors follow:\n" + exceptionString);
}
@Override
public MessageID postAsBatch(BulletinBoardMessage msg, int chunkSize) throws CommunicationException {
List<BatchChunk> chunkList = BulletinBoardUtils.breakToBatch(msg, chunkSize);
BeginBatchMessage beginBatchMessage = BulletinBoardUtils.generateBeginBatchMessage(msg);
boolean posted = false;
// Post message to all databases
for (String db : meerkatDBs) {
try {
int pos = 0;
SingleServerBeginBatchWorker beginBatchWorker = new SingleServerBeginBatchWorker(db, beginBatchMessage, 0);
Int64Value batchId = beginBatchWorker.call();
BatchMessage.Builder builder = BatchMessage.newBuilder().setBatchId(batchId.getValue());
for (BatchChunk batchChunk : chunkList) {
SingleServerPostBatchWorker postBatchWorker =
new SingleServerPostBatchWorker(
db,
builder.setData(batchChunk).setSerialNum(pos).build(),
0);
postBatchWorker.call();
pos++;
}
CloseBatchMessage closeBatchMessage = BulletinBoardUtils.generateCloseBatchMessage(batchId, chunkList.size(), msg);
SingleServerCloseBatchWorker closeBatchWorker = new SingleServerCloseBatchWorker(db, closeBatchMessage, 0);
closeBatchWorker.call();
posted = true;
} catch(Exception ignored) {}
}
if (!posted){
throw new CommunicationException("Could not post to any server");
}
digest.reset();
digest.update(msg);
return digest.digestAsMessageID();
}
@Override
public BulletinBoardMessage readMessage(MessageID msgID) throws CommunicationException {
MessageFilterList filterList = MessageFilterList.newBuilder()
.addFilter(MessageFilter.newBuilder()
.setType(FilterType.MSG_ID)
.setId(msgID.getID())
.build())
.build();
BatchQuery batchQuery = BatchQuery.newBuilder()
.setMsgID(msgID)
.setStartPosition(0)
.build();
String exceptionString = "";
for (String db : meerkatDBs) {
try {
SingleServerReadMessagesWorker messagesWorker = new SingleServerReadMessagesWorker(db, filterList, 0);
List<BulletinBoardMessage> messages = messagesWorker.call();
if (messages == null || messages.size() < 1)
continue;
BulletinBoardMessage stub = messages.get(0);
SingleServerReadBatchWorker batchWorker = new SingleServerReadBatchWorker(db, batchQuery, 0);
List<BatchChunk> batchChunkList = batchWorker.call();
return BulletinBoardUtils.gatherBatch(stub, batchChunkList);
} catch (Exception e) {
//TODO: log
exceptionString += e.getMessage() + "\n";
}
}
throw new CommunicationException("Could not find message in any DB. Errors follow:\n" + exceptionString);
}
@Override
public BulletinBoardMessage readBatchData(BulletinBoardMessage stub) throws CommunicationException, IllegalArgumentException {
if (stub.getMsg().getDataTypeCase() != UnsignedBulletinBoardMessage.DataTypeCase.MSGID){
throw new IllegalArgumentException("Message is not a stub and does not contain the required message ID");
}
BatchQuery batchQuery = BatchQuery.newBuilder()
.setMsgID(MessageID.newBuilder()
.setID(stub.getMsg().getMsgId())
.build())
.setStartPosition(0)
.build();
String exceptionString = "";
for (String db : meerkatDBs) {
try {
SingleServerReadBatchWorker batchWorker = new SingleServerReadBatchWorker(db, batchQuery, 0);
List<BatchChunk> batchChunkList = batchWorker.call();
return BulletinBoardUtils.gatherBatch(stub, batchChunkList);
} catch (Exception e) {
//TODO: log
exceptionString += e.getMessage() + "\n";
}
}
throw new CommunicationException("Could not find message in any DB. Errors follow:\n" + exceptionString);
}
@Override
public SyncQuery generateSyncQuery(GenerateSyncQueryParams generateSyncQueryParams) throws CommunicationException {
WebTarget webTarget;
Response response;
for (String db : meerkatDBs) {
try {
webTarget = client.target(db).path(BULLETIN_BOARD_SERVER_PATH).path(GENERATE_SYNC_QUERY_PATH);
response = webTarget.request(Constants.MEDIATYPE_PROTOBUF).post(Entity.entity(generateSyncQueryParams, Constants.MEDIATYPE_PROTOBUF));
return response.readEntity(SyncQuery.class);
} catch (Exception e) {}
}
throw new CommunicationException("Could not contact any server");
}
public void close() {
client.close();
}
}

View File

@ -0,0 +1,241 @@
package meerkat.bulletinboard;
import com.google.common.util.concurrent.FutureCallback;
import com.google.protobuf.ByteString;
import meerkat.comm.CommunicationException;
import meerkat.protobuf.BulletinBoardApi.*;
import meerkat.util.BulletinBoardUtils;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* Created by Arbel on 13/04/2016.
* Simple, straightforward implementation of the {@link BulletinBoardSynchronizer} interface
*/
public class SimpleBulletinBoardSynchronizer implements BulletinBoardSynchronizer {
private DeletableSubscriptionBulletinBoardClient localClient;
private AsyncBulletinBoardClient remoteClient;
private AtomicBoolean running;
private volatile SyncStatus syncStatus;
private List<FutureCallback<Integer>> messageCountCallbacks;
private List<FutureCallback<SyncStatus>> syncStatusCallbacks;
private static final MessageFilterList EMPTY_FILTER = MessageFilterList.getDefaultInstance();
private static final int DEFAULT_SLEEP_INTERVAL = 10000; // 10 Seconds
private static final int DEFAULT_WAIT_CAP = 300000; // 5 minutes wait before deciding that the sync has failed fatally
private final int SLEEP_INTERVAL;
private final int WAIT_CAP;
private Semaphore semaphore;
private class SyncCallback implements FutureCallback<List<BulletinBoardMessage>> {
@Override
public void onSuccess(List<BulletinBoardMessage> result) {
// Notify Message Count callbacks if needed
if (syncStatus != SyncStatus.SYNCHRONIZED || result.size() > 0) {
for (FutureCallback<Integer> callback : messageCountCallbacks){
callback.onSuccess(result.size());
}
}
// Handle upload and status change
SyncStatus newStatus = SyncStatus.PENDING;
if (result.size() == 0) {
newStatus = SyncStatus.SYNCHRONIZED;
semaphore.release();
}
else{ // Upload messages
for (BulletinBoardMessage message : result){
try {
if (message.getMsg().getDataTypeCase() == UnsignedBulletinBoardMessage.DataTypeCase.MSGID) {
// This is a batch message: need to upload batch data as well as the message itself
BulletinBoardMessage completeMsg = localClient.readBatchData(message);
remoteClient.postMessage(completeMsg);
localClient.deleteMessage(completeMsg.getEntryNum());
} else {
// This is a regular message: post it
remoteClient.postMessage(message);
localClient.deleteMessage(message.getEntryNum());
}
} catch (CommunicationException e) {
// This is an error with the local server
// TODO: log
updateSyncStatus(SyncStatus.SERVER_ERROR);
}
}
}
updateSyncStatus(newStatus);
}
@Override
public void onFailure(Throwable t) {
updateSyncStatus(SyncStatus.SERVER_ERROR);
}
}
public SimpleBulletinBoardSynchronizer(int sleepInterval, int waitCap) {
this.syncStatus = SyncStatus.STOPPED;
this.SLEEP_INTERVAL = sleepInterval;
this.WAIT_CAP = waitCap;
this.running = new AtomicBoolean(false);
}
public SimpleBulletinBoardSynchronizer() {
this(DEFAULT_SLEEP_INTERVAL, DEFAULT_WAIT_CAP);
}
private synchronized void updateSyncStatus(SyncStatus newStatus) {
if (!running.get()) {
newStatus = SyncStatus.STOPPED;
}
if (newStatus != syncStatus){
syncStatus = newStatus;
for (FutureCallback<SyncStatus> callback : syncStatusCallbacks){
if (callback != null)
callback.onSuccess(syncStatus);
}
}
}
@Override
public void init(DeletableSubscriptionBulletinBoardClient localClient, AsyncBulletinBoardClient remoteClient) {
updateSyncStatus(SyncStatus.STOPPED);
this.localClient = localClient;
this.remoteClient = remoteClient;
messageCountCallbacks = new LinkedList<>();
syncStatusCallbacks = new LinkedList<>();
semaphore = new Semaphore(0);
}
@Override
public SyncStatus getSyncStatus() {
return syncStatus;
}
@Override
public void subscribeToSyncStatus(FutureCallback<SyncStatus> callback) {
syncStatusCallbacks.add(callback);
}
@Override
public List<BulletinBoardMessage> getRemainingMessages() throws CommunicationException{
return localClient.readMessages(EMPTY_FILTER);
}
@Override
public void getRemainingMessages(FutureCallback<List<BulletinBoardMessage>> callback) {
localClient.readMessages(EMPTY_FILTER, callback);
}
@Override
public long getRemainingMessagesCount() throws CommunicationException {
return localClient.readMessages(EMPTY_FILTER).size();
}
@Override
public void subscribeToRemainingMessagesCount(FutureCallback<Integer> callback) {
messageCountCallbacks.add(callback);
}
@Override
public void run() {
if (running.compareAndSet(false,true)){
updateSyncStatus(SyncStatus.PENDING);
SyncCallback callback = new SyncCallback();
while (syncStatus != SyncStatus.STOPPED) {
do {
localClient.readMessages(EMPTY_FILTER, callback);
try {
semaphore.tryAcquire(WAIT_CAP, TimeUnit.MILLISECONDS);
//TODO: log hard error. Too much time trying to upload data.
} catch (InterruptedException ignored) {
// We expect an interruption when the upload will complete
}
} while (syncStatus == SyncStatus.PENDING);
// Database is synced. Wait for new data.
try {
semaphore.tryAcquire(SLEEP_INTERVAL, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
//TODO: log (probably nudged)
}
}
}
}
@Override
public void nudge() {
semaphore.release();
}
@Override
public void stop() {
running.set(false);
updateSyncStatus(SyncStatus.STOPPED);
}
}

View File

@ -0,0 +1,42 @@
package meerkat.bulletinboard;
import com.google.protobuf.Int64Value;
/**
* Created by Arbel Deutsch Peled on 16-Jun-16.
* Single-server implementation of the BatchIdentifier interface
*/
final class SingleServerBatchIdentifier implements AsyncBulletinBoardClient.BatchIdentifier {
private final Int64Value batchId;
private int length;
public SingleServerBatchIdentifier(Int64Value batchId) {
this.batchId = batchId;
length = 0;
}
public SingleServerBatchIdentifier(long batchId) {
this(Int64Value.newBuilder().setValue(batchId).build());
}
public Int64Value getBatchId() {
return batchId;
}
/**
* Overrides the existing length with the new one only if the new length is longer
* @param newLength
*/
public void setLength(int newLength) {
if (newLength > length) {
length = newLength;
}
}
public int getLength() {
return length;
}
}

View File

@ -4,18 +4,19 @@ import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListeningScheduledExecutorService;
import com.google.common.util.concurrent.MoreExecutors;
import com.google.protobuf.ByteString;
import com.google.protobuf.Int64Value;
import com.google.protobuf.Timestamp;
import meerkat.bulletinboard.workers.singleserver.*;
import meerkat.comm.CommunicationException;
import meerkat.protobuf.BulletinBoardAPI;
import meerkat.protobuf.BulletinBoardAPI.*;
import meerkat.crypto.concrete.SHA256Digest;
import meerkat.protobuf.BulletinBoardApi.*;
import meerkat.protobuf.Crypto;
import meerkat.protobuf.Voting.BulletinBoardClientParams;
import meerkat.util.BulletinBoardUtils;
import javax.ws.rs.NotFoundException;
import java.util.Arrays;
import javax.ws.rs.client.Client;
import java.lang.Iterable;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
@ -30,19 +31,23 @@ import java.util.concurrent.atomic.AtomicInteger;
* If the list of servers contains more than one server: the server actually used is the first one
* The class further implements a delayed access to the server after a communication error occurs
*/
public class SingleServerBulletinBoardClient extends SimpleBulletinBoardClient implements SubscriptionAsyncBulletinBoardClient {
public class SingleServerBulletinBoardClient implements SubscriptionBulletinBoardClient {
protected Client client;
protected BulletinBoardDigest digest;
private String dbAddress;
private final int MAX_RETRIES = 11;
private ListeningScheduledExecutorService executorService;
protected BatchDigest batchDigest;
private final ListeningScheduledExecutorService executorService;
private long lastServerErrorTime;
private final long failDelayInMilliseconds;
private final long FAIL_DELAY_IN_MILLISECONDS;
private final long subscriptionIntervalInMilliseconds;
private final long SUBSCRIPTION_INTERVAL_IN_MILLISECONDS;
/**
* Notify the client that a job has failed
@ -55,6 +60,43 @@ public class SingleServerBulletinBoardClient extends SimpleBulletinBoardClient i
}
private class SynchronousRetry<OUT> {
private final SingleServerWorker<?,OUT> worker;
private String thrown;
public SynchronousRetry(SingleServerWorker<?,OUT> worker) {
this.worker = worker;
this.thrown = "Could not contact server. Errors follow:\n";
}
OUT run() throws CommunicationException {
do {
try {
return worker.call();
} catch (Exception e) {
thrown += e.getCause() + " " + e.getMessage() + "\n";
}
try {
Thread.sleep(FAIL_DELAY_IN_MILLISECONDS);
} catch (InterruptedException e) {
//TODO: log
}
worker.decMaxRetry();
} while (worker.isRetry());
throw new CommunicationException(thrown);
}
}
/**
* This method adds a worker to the scheduled queue of the threadpool
* If the server is in an accessible state: the job is submitted for immediate handling
@ -66,7 +108,7 @@ public class SingleServerBulletinBoardClient extends SimpleBulletinBoardClient i
long timeSinceLastServerError = System.currentTimeMillis() - lastServerErrorTime;
if (timeSinceLastServerError >= failDelayInMilliseconds) {
if (timeSinceLastServerError >= FAIL_DELAY_IN_MILLISECONDS) {
// Schedule for immediate processing
Futures.addCallback(executorService.submit(worker), callback);
@ -76,7 +118,7 @@ public class SingleServerBulletinBoardClient extends SimpleBulletinBoardClient i
// Schedule for processing immediately following delay expiry
Futures.addCallback(executorService.schedule(
worker,
failDelayInMilliseconds - timeSinceLastServerError,
FAIL_DELAY_IN_MILLISECONDS - timeSinceLastServerError,
TimeUnit.MILLISECONDS),
callback);
@ -99,7 +141,8 @@ public class SingleServerBulletinBoardClient extends SimpleBulletinBoardClient i
@Override
public void onSuccess(T result) {
futureCallback.onSuccess(result);
if (futureCallback != null)
futureCallback.onSuccess(result);
}
@Override
@ -117,7 +160,8 @@ public class SingleServerBulletinBoardClient extends SimpleBulletinBoardClient i
scheduleWorker(worker, this);
} else {
// No more retries: notify caller about failure
futureCallback.onFailure(t);
if (futureCallback != null)
futureCallback.onFailure(t);
}
}
@ -129,14 +173,14 @@ public class SingleServerBulletinBoardClient extends SimpleBulletinBoardClient i
* It reports success back to the user only if all of the batch-data were successfully posted
* If any batch-data fails to post: this callback reports failure
*/
class PostBatchDataListCallback implements FutureCallback<Boolean> {
class PostBatchChunkListCallback implements FutureCallback<Boolean> {
private final FutureCallback<Boolean> callback;
private AtomicInteger batchDataRemaining;
private AtomicBoolean aggregatedResult;
public PostBatchDataListCallback(int batchDataLength, FutureCallback<Boolean> callback) {
public PostBatchChunkListCallback(int batchDataLength, FutureCallback<Boolean> callback) {
this.callback = callback;
this.batchDataRemaining = new AtomicInteger(batchDataLength);
@ -152,7 +196,8 @@ public class SingleServerBulletinBoardClient extends SimpleBulletinBoardClient i
}
if (batchDataRemaining.decrementAndGet() == 0){
callback.onSuccess(this.aggregatedResult.get());
if (callback != null)
callback.onSuccess(this.aggregatedResult.get());
}
}
@ -160,110 +205,80 @@ public class SingleServerBulletinBoardClient extends SimpleBulletinBoardClient i
public void onFailure(Throwable t) {
// Notify caller about failure
callback.onFailure(t);
if (callback != null)
callback.onFailure(t);
}
}
private class ReadBatchCallback implements FutureCallback<List<BatchChunk>> {
private final BulletinBoardMessage stub;
private final FutureCallback<BulletinBoardMessage> callback;
public ReadBatchCallback(BulletinBoardMessage stub, FutureCallback<BulletinBoardMessage> callback) {
this.stub = stub;
this.callback = callback;
}
@Override
public void onSuccess(List<BatchChunk> result) {
callback.onSuccess(BulletinBoardUtils.gatherBatch(stub, result));
}
@Override
public void onFailure(Throwable t) {
callback.onFailure(t);
}
}
/**
* This callback ties together the different parts of a CompleteBatch as they arrive from the server
* It assembles a CompleteBatch from the parts and sends it to the user if all parts arrived
* If any part fails to arrive: it invokes the onFailure method
* This callback receives a message which may be a stub
* If the message is not a stub: it returns it as is to a callback function
* If it is a stub: it schedules a read of the batch data which will return a complete message to the callback function
*/
class CompleteBatchReadCallback {
class CompleteMessageReadCallback implements FutureCallback<List<BulletinBoardMessage>>{
private final FutureCallback<CompleteBatch> callback;
private final FutureCallback<BulletinBoardMessage> callback;
private List<BatchData> batchDataList;
private BulletinBoardMessage batchMessage;
private AtomicInteger remainingQueries;
private AtomicBoolean failed;
public CompleteBatchReadCallback(FutureCallback<CompleteBatch> callback) {
public CompleteMessageReadCallback(FutureCallback<BulletinBoardMessage> callback) {
this.callback = callback;
remainingQueries = new AtomicInteger(2);
failed = new AtomicBoolean(false);
}
protected void combineAndReturn() {
@Override
public void onSuccess(List<BulletinBoardMessage> result) {
if (result.size() <= 0) {
onFailure(new CommunicationException("Could not find required message on the server."));
} else {
final String[] prefixes = {
BulletinBoardConstants.BATCH_ID_TAG_PREFIX,
BulletinBoardConstants.BATCH_TAG};
BulletinBoardMessage msg = result.get(0);
if (remainingQueries.decrementAndGet() == 0){
if (msg.getMsg().getDataTypeCase() != UnsignedBulletinBoardMessage.DataTypeCase.MSGID) {
callback.onSuccess(msg);
} else {
String batchIdStr = BulletinBoardUtils.findTagWithPrefix(batchMessage, BulletinBoardConstants.BATCH_ID_TAG_PREFIX);
// Create job with MAX retries for retrieval of the Batch Data List
BatchQuery batchQuery = BatchQuery.newBuilder()
.setMsgID(MessageID.newBuilder()
.setID(msg.getMsg().getMsgId())
.build())
.build();
SingleServerReadBatchWorker batchWorker = new SingleServerReadBatchWorker(dbAddress, batchQuery, MAX_RETRIES);
scheduleWorker(batchWorker, new ReadBatchCallback(msg, callback));
if (batchIdStr == null){
callback.onFailure(new CommunicationException("Server returned invalid message with no Batch ID tag"));
}
BeginBatchMessage beginBatchMessage =
BeginBatchMessage.newBuilder()
.setSignerId(batchMessage.getSig(0).getSignerId())
.setBatchId(Integer.parseInt(batchIdStr))
.addAllTag(BulletinBoardUtils.removePrefixTags(batchMessage, Arrays.asList(prefixes)))
.build();
callback.onSuccess(new CompleteBatch(beginBatchMessage, batchDataList, batchMessage.getSig(0)));
}
}
protected void fail(Throwable t) {
if (failed.compareAndSet(false, true)) {
callback.onFailure(t);
}
}
/**
* @return a FutureCallback for the Batch Data List that ties to this object
*/
public FutureCallback<List<BatchData>> asBatchDataListFutureCallback() {
return new FutureCallback<List<BatchData>>() {
@Override
public void onSuccess(List<BatchData> result) {
batchDataList = result;
combineAndReturn();
}
@Override
public void onFailure(Throwable t) {
fail(t);
}
};
}
/**
* @return a FutureCallback for the Bulletin Board Message that ties to this object
*/
public FutureCallback<List<BulletinBoardMessage>> asBulletinBoardMessageListFutureCallback() {
return new FutureCallback<List<BulletinBoardMessage>>() {
@Override
public void onSuccess(List<BulletinBoardMessage> result) {
if (result.size() < 1){
onFailure(new IllegalArgumentException("Server returned empty message list"));
return;
}
batchMessage = result.get(0);
combineAndReturn();
}
@Override
public void onFailure(Throwable t) {
fail(t);
}
};
@Override
public void onFailure(Throwable t) {
callback.onFailure(t);
}
}
@ -291,22 +306,31 @@ public class SingleServerBulletinBoardClient extends SimpleBulletinBoardClient i
public void onSuccess(List<BulletinBoardMessage> result) {
// Report new messages to user
callback.onSuccess(result);
if (callback != null)
callback.onSuccess(result);
// Remove last filter from list (MIN_ENTRY one)
filterBuilder.removeFilter(filterBuilder.getFilterCount() - 1);
// Update filter if needed
// Add updated MIN_ENTRY filter (entry number is successor of last received entry's number)
filterBuilder.addFilter(MessageFilter.newBuilder()
.setType(FilterType.MIN_ENTRY)
.setEntry(result.get(result.size() - 1).getEntryNum() + 1)
.build());
if (result.size() > 0) {
// Remove last filter from list (MIN_ENTRY one)
filterBuilder.removeFilter(filterBuilder.getFilterCount() - 1);
// Add updated MIN_ENTRY filter (entry number is successor of last received entry's number)
filterBuilder.addFilter(MessageFilter.newBuilder()
.setType(FilterType.MIN_ENTRY)
.setEntry(result.get(result.size() - 1).getEntryNum() + 1)
.build());
}
// Create new worker with updated task
worker = new SingleServerReadMessagesWorker(worker.serverAddress, filterBuilder.build(), 1);
worker = new SingleServerReadMessagesWorker(worker.serverAddress, filterBuilder.build(), MAX_RETRIES);
// Schedule the worker
scheduleWorker(worker, this);
RetryCallback<List<BulletinBoardMessage>> retryCallback = new RetryCallback<>(worker, this);
// Schedule the worker to run after the given interval has elapsed
Futures.addCallback(executorService.schedule(worker, SUBSCRIPTION_INTERVAL_IN_MILLISECONDS, TimeUnit.MILLISECONDS), retryCallback);
}
@ -317,7 +341,8 @@ public class SingleServerBulletinBoardClient extends SimpleBulletinBoardClient i
fail();
// Notify caller about failure and terminate subscription
callback.onFailure(t);
if (callback != null)
callback.onFailure(t);
}
}
@ -327,8 +352,8 @@ public class SingleServerBulletinBoardClient extends SimpleBulletinBoardClient i
this.executorService = executorService;
this.failDelayInMilliseconds = failDelayInMilliseconds;
this.subscriptionIntervalInMilliseconds = subscriptionIntervalInMilliseconds;
this.FAIL_DELAY_IN_MILLISECONDS = failDelayInMilliseconds;
this.SUBSCRIPTION_INTERVAL_IN_MILLISECONDS = subscriptionIntervalInMilliseconds;
// Set server error time to a time sufficiently in the past to make new jobs go through
lastServerErrorTime = System.currentTimeMillis() - failDelayInMilliseconds;
@ -350,79 +375,289 @@ public class SingleServerBulletinBoardClient extends SimpleBulletinBoardClient i
@Override
public void init(BulletinBoardClientParams clientParams) {
// Perform usual setup
super.init(clientParams);
// Wrap the Digest into a BatchDigest
batchDigest = new GenericBatchDigest(digest);
this.digest = new GenericBulletinBoardDigest(new SHA256Digest());
// Remove all but first DB address
String dbAddress = meerkatDBs.get(0);
meerkatDBs = new LinkedList<>();
meerkatDBs.add(dbAddress);
this.dbAddress = clientParams.getBulletinBoardAddress(0);
}
// Synchronous methods
@Override
public MessageID postMessage(BulletinBoardMessage msg) throws CommunicationException {
SingleServerPostMessageWorker worker = new SingleServerPostMessageWorker(dbAddress, msg, MAX_RETRIES);
SynchronousRetry<Boolean> retry = new SynchronousRetry<>(worker);
retry.run();
digest.reset();
digest.update(msg);
return digest.digestAsMessageID();
}
@Override
public float getRedundancy(MessageID id) throws CommunicationException {
SingleServerGetRedundancyWorker worker = new SingleServerGetRedundancyWorker(dbAddress, id, MAX_RETRIES);
SynchronousRetry<Float> retry = new SynchronousRetry<>(worker);
return retry.run();
}
@Override
public List<BulletinBoardMessage> readMessages(MessageFilterList filterList) throws CommunicationException {
SingleServerReadMessagesWorker worker = new SingleServerReadMessagesWorker(dbAddress, filterList, MAX_RETRIES);
SynchronousRetry<List<BulletinBoardMessage>> retry = new SynchronousRetry<>(worker);
return retry.run();
}
@Override
public MessageID postAsBatch(BulletinBoardMessage msg, int chunkSize) throws CommunicationException {
// Begin the batch and obtain identifier
BeginBatchMessage beginBatchMessage = BeginBatchMessage.newBuilder()
.addAllTag(msg.getMsg().getTagList())
.build();
SingleServerBeginBatchWorker beginBatchWorker = new SingleServerBeginBatchWorker(dbAddress, beginBatchMessage, MAX_RETRIES);
SynchronousRetry<Int64Value> beginRetry = new SynchronousRetry<>(beginBatchWorker);
Int64Value identifier = beginRetry.run();
// Post data chunks
List<BatchChunk> batchChunkList = BulletinBoardUtils.breakToBatch(msg, chunkSize);
BatchMessage.Builder builder = BatchMessage.newBuilder().setBatchId(identifier.getValue());
int position = 0;
for (BatchChunk data : batchChunkList) {
builder.setSerialNum(position).setData(data);
SingleServerPostBatchWorker dataWorker = new SingleServerPostBatchWorker(dbAddress, builder.build(), MAX_RETRIES);
SynchronousRetry<Boolean> dataRetry = new SynchronousRetry<>(dataWorker);
dataRetry.run();
// Increment position in batch
position++;
}
// Close batch
CloseBatchMessage closeBatchMessage = CloseBatchMessage.newBuilder()
.setBatchId(identifier.getValue())
.addAllSig(msg.getSigList())
.setTimestamp(msg.getMsg().getTimestamp())
.setBatchLength(position)
.build();
SingleServerCloseBatchWorker closeBatchWorker = new SingleServerCloseBatchWorker(dbAddress, closeBatchMessage, MAX_RETRIES);
SynchronousRetry<Boolean> retry = new SynchronousRetry<>(closeBatchWorker);
retry.run();
// Calculate ID and return
digest.reset();
digest.update(msg);
return digest.digestAsMessageID();
}
@Override
public BulletinBoardMessage readMessage(MessageID msgID) throws CommunicationException {
// Retrieve message (which may be a stub)
MessageFilterList filterList = MessageFilterList.newBuilder()
.addFilter(MessageFilter.newBuilder()
.setType(FilterType.MSG_ID)
.setId(msgID.getID())
.build())
.build();
SingleServerReadMessagesWorker stubWorker = new SingleServerReadMessagesWorker(dbAddress, filterList, MAX_RETRIES);
SynchronousRetry<List<BulletinBoardMessage>> retry = new SynchronousRetry<>(stubWorker);
List<BulletinBoardMessage> messages = retry.run();
if (messages.size() <= 0) {
throw new CommunicationException("Could not find message in database.");
}
BulletinBoardMessage msg = messages.get(0);
if (msg.getMsg().getDataTypeCase() != UnsignedBulletinBoardMessage.DataTypeCase.MSGID) {
// We retrieved a complete message. Return it.
return msg;
} else {
// We retrieved a stub. Retrieve data.
return readBatchData(msg);
}
}
@Override
public BulletinBoardMessage readBatchData(BulletinBoardMessage stub) throws CommunicationException, IllegalArgumentException {
BatchQuery batchQuery = BatchQuery.newBuilder()
.setMsgID(MessageID.newBuilder()
.setID(stub.getMsg().getMsgId())
.build())
.setStartPosition(0)
.build();
SingleServerReadBatchWorker readBatchWorker = new SingleServerReadBatchWorker(dbAddress, batchQuery, MAX_RETRIES);
SynchronousRetry<List<BatchChunk>> batchRetry = new SynchronousRetry<>(readBatchWorker);
List<BatchChunk> batchChunkList = batchRetry.run();
return BulletinBoardUtils.gatherBatch(stub, batchChunkList);
}
@Override
public SyncQuery generateSyncQuery(GenerateSyncQueryParams generateSyncQueryParams) throws CommunicationException {
SingleServerGenerateSyncQueryWorker worker =
new SingleServerGenerateSyncQueryWorker(dbAddress, generateSyncQueryParams, MAX_RETRIES);
SynchronousRetry<SyncQuery> retry = new SynchronousRetry<>(worker);
return retry.run();
}
// Asynchronous methods
@Override
public MessageID postMessage(BulletinBoardMessage msg, FutureCallback<Boolean> callback) {
// Create worker with redundancy 1 and MAX_RETRIES retries
SingleServerPostMessageWorker worker = new SingleServerPostMessageWorker(meerkatDBs.get(0), msg, MAX_RETRIES);
SingleServerPostMessageWorker worker = new SingleServerPostMessageWorker(dbAddress, msg, MAX_RETRIES);
// Submit worker and create callback
scheduleWorker(worker, new RetryCallback<>(worker, callback));
// Calculate the correct message ID and return it
batchDigest.reset();
batchDigest.update(msg.getMsg());
return batchDigest.digestAsMessageID();
digest.reset();
digest.update(msg.getMsg());
return digest.digestAsMessageID();
}
private class PostBatchDataCallback implements FutureCallback<Boolean> {
private final CompleteBatch completeBatch;
private final BulletinBoardMessage msg;
private final BatchIdentifier identifier;
private final FutureCallback<Boolean> callback;
public PostBatchDataCallback(CompleteBatch completeBatch, FutureCallback<Boolean> callback) {
this.completeBatch = completeBatch;
public PostBatchDataCallback(BulletinBoardMessage msg, BatchIdentifier identifier, FutureCallback<Boolean> callback) {
this.msg = msg;
this.identifier = identifier;
this.callback = callback;
}
@Override
public void onSuccess(Boolean msg) {
public void onSuccess(Boolean result) {
closeBatch(
completeBatch.getCloseBatchMessage(),
identifier,
msg.getMsg().getTimestamp(),
msg.getSigList(),
callback
);
}
@Override
public void onFailure(Throwable t) {
callback.onFailure(t);
if (callback != null)
callback.onFailure(t);
}
}
private class BeginBatchCallback implements FutureCallback<Boolean> {
private class ContinueBatchCallback implements FutureCallback<BatchIdentifier> {
private final CompleteBatch completeBatch;
private final BulletinBoardMessage msg;
private final int chunkSize;
private final FutureCallback<Boolean> callback;
public BeginBatchCallback(CompleteBatch completeBatch, FutureCallback<Boolean> callback) {
this.completeBatch = completeBatch;
public ContinueBatchCallback(BulletinBoardMessage msg, int chunkSize, FutureCallback<Boolean> callback) {
this.msg = msg;
this.chunkSize = chunkSize;
this.callback = callback;
}
@Override
public void onSuccess(Boolean msg) {
public void onSuccess(BatchIdentifier identifier) {
List<BatchChunk> batchChunkList = BulletinBoardUtils.breakToBatch(msg, chunkSize);
postBatchData(
completeBatch.getBeginBatchMessage().getSignerId(),
completeBatch.getBeginBatchMessage().getBatchId(),
completeBatch.getBatchDataList(),
identifier,
batchChunkList,
0,
new PostBatchDataCallback(completeBatch,callback));
new PostBatchDataCallback(msg, identifier, callback));
}
@Override
public void onFailure(Throwable t) {
if (callback != null)
callback.onFailure(t);
}
}
@Override
public MessageID postAsBatch(BulletinBoardMessage msg, int chunkSize, FutureCallback<Boolean> callback) {
beginBatch(
msg.getMsg().getTagList(),
new ContinueBatchCallback(msg, chunkSize, callback)
);
digest.update(msg);
return digest.digestAsMessageID();
}
private class BeginBatchCallback implements FutureCallback<Int64Value> {
private final FutureCallback<BatchIdentifier> callback;
public BeginBatchCallback(FutureCallback<BatchIdentifier> callback) {
this.callback = callback;
}
@Override
public void onSuccess(Int64Value result) {
callback.onSuccess(new SingleServerBatchIdentifier(result));
}
@Override
@ -432,51 +667,53 @@ public class SingleServerBulletinBoardClient extends SimpleBulletinBoardClient i
}
@Override
public MessageID postBatch(CompleteBatch completeBatch, FutureCallback<Boolean> callback) {
public void beginBatch(Iterable<String> tags, FutureCallback<BatchIdentifier> callback) {
beginBatch(
completeBatch.getBeginBatchMessage(),
new BeginBatchCallback(completeBatch, callback)
);
batchDigest.update(completeBatch);
return batchDigest.digestAsMessageID();
}
@Override
public void beginBatch(BeginBatchMessage beginBatchMessage, FutureCallback<Boolean> callback) {
BeginBatchMessage beginBatchMessage = BeginBatchMessage.newBuilder()
.addAllTag(tags)
.build();
// Create worker with redundancy 1 and MAX_RETRIES retries
SingleServerBeginBatchWorker worker =
new SingleServerBeginBatchWorker(meerkatDBs.get(0), beginBatchMessage, MAX_RETRIES);
new SingleServerBeginBatchWorker(dbAddress, beginBatchMessage, MAX_RETRIES);
// Submit worker and create callback
scheduleWorker(worker, new RetryCallback<>(worker, callback));
scheduleWorker(worker, new RetryCallback<>(worker, new BeginBatchCallback(callback)));
}
@Override
public void postBatchData(ByteString signerId, int batchId, List<BatchData> batchDataList,
int startPosition, FutureCallback<Boolean> callback) {
BatchMessage.Builder builder = BatchMessage.newBuilder()
.setSignerId(signerId)
.setBatchId(batchId);
@Override
public void postBatchData(BatchIdentifier batchIdentifier, List<BatchChunk> batchChunkList,
int startPosition, FutureCallback<Boolean> callback) throws IllegalArgumentException{
// Cast identifier to usable form
if (!(batchIdentifier instanceof SingleServerBatchIdentifier)){
throw new IllegalArgumentException("Error: batch identifier supplied was not created by this class.");
}
SingleServerBatchIdentifier identifier = (SingleServerBatchIdentifier) batchIdentifier;
// Update batch size
identifier.setLength(startPosition + batchChunkList.size());
// Create a unified callback to aggregate successful posts
PostBatchDataListCallback listCallback = new PostBatchDataListCallback(batchDataList.size(), callback);
PostBatchChunkListCallback listCallback = new PostBatchChunkListCallback(batchChunkList.size(), callback);
// Iterate through data list
for (BatchData data : batchDataList) {
BatchMessage.Builder builder = BatchMessage.newBuilder()
.setBatchId(identifier.getBatchId().getValue());
for (BatchChunk data : batchChunkList) {
builder.setSerialNum(startPosition).setData(data);
// Create worker with redundancy 1 and MAX_RETRIES retries
SingleServerPostBatchWorker worker =
new SingleServerPostBatchWorker(meerkatDBs.get(0), builder.build(), MAX_RETRIES);
new SingleServerPostBatchWorker(dbAddress, builder.build(), MAX_RETRIES);
// Create worker with redundancy 1 and MAX_RETRIES retries
scheduleWorker(worker, new RetryCallback<>(worker, listCallback));
@ -488,33 +725,33 @@ public class SingleServerBulletinBoardClient extends SimpleBulletinBoardClient i
}
@Override
public void postBatchData(ByteString signerId, int batchId, List<BatchData> batchDataList, FutureCallback<Boolean> callback) {
public void postBatchData(BatchIdentifier batchIdentifier, List<BatchChunk> batchChunkList, FutureCallback<Boolean> callback)
throws IllegalArgumentException {
postBatchData(signerId, batchId, batchDataList, 0, callback);
postBatchData(batchIdentifier, batchChunkList, 0, callback);
}
@Override
public void postBatchData(byte[] signerId, int batchId, List<BatchData> batchDataList,
int startPosition, FutureCallback<Boolean> callback) {
public void closeBatch(BatchIdentifier batchIdentifier, Timestamp timestamp, Iterable<Crypto.Signature> signatures, FutureCallback<Boolean> callback)
throws IllegalArgumentException {
postBatchData(ByteString.copyFrom(signerId), batchId, batchDataList, startPosition, callback);
if (!(batchIdentifier instanceof SingleServerBatchIdentifier)){
throw new IllegalArgumentException("Error: batch identifier supplied was not created by this class.");
}
}
SingleServerBatchIdentifier identifier = (SingleServerBatchIdentifier) batchIdentifier;
@Override
public void postBatchData(byte[] signerId, int batchId, List<BatchData> batchDataList, FutureCallback<Boolean> callback) {
postBatchData(signerId, batchId, batchDataList, 0, callback);
}
@Override
public void closeBatch(CloseBatchMessage closeBatchMessage, FutureCallback<Boolean> callback) {
CloseBatchMessage closeBatchMessage = CloseBatchMessage.newBuilder()
.setBatchId(identifier.getBatchId().getValue())
.setBatchLength(identifier.getLength())
.setTimestamp(timestamp)
.addAllSig(signatures)
.build();
// Create worker with redundancy 1 and MAX_RETRIES retries
SingleServerCloseBatchWorker worker =
new SingleServerCloseBatchWorker(meerkatDBs.get(0), closeBatchMessage, MAX_RETRIES);
new SingleServerCloseBatchWorker(dbAddress, closeBatchMessage, MAX_RETRIES);
// Submit worker and create callback
scheduleWorker(worker, new RetryCallback<>(worker, callback));
@ -525,7 +762,7 @@ public class SingleServerBulletinBoardClient extends SimpleBulletinBoardClient i
public void getRedundancy(MessageID id, FutureCallback<Float> callback) {
// Create worker with no retries
SingleServerGetRedundancyWorker worker = new SingleServerGetRedundancyWorker(meerkatDBs.get(0), id, 1);
SingleServerGetRedundancyWorker worker = new SingleServerGetRedundancyWorker(dbAddress, id, 1);
// Submit job and create callback
scheduleWorker(worker, new RetryCallback<>(worker, callback));
@ -536,7 +773,7 @@ public class SingleServerBulletinBoardClient extends SimpleBulletinBoardClient i
public void readMessages(MessageFilterList filterList, FutureCallback<List<BulletinBoardMessage>> callback) {
// Create job with no retries
SingleServerReadMessagesWorker worker = new SingleServerReadMessagesWorker(meerkatDBs.get(0), filterList, 1);
SingleServerReadMessagesWorker worker = new SingleServerReadMessagesWorker(dbAddress, filterList, 1);
// Submit job and create callback
scheduleWorker(worker, new RetryCallback<>(worker, callback));
@ -544,43 +781,55 @@ public class SingleServerBulletinBoardClient extends SimpleBulletinBoardClient i
}
@Override
public void readBatch(BatchSpecificationMessage batchSpecificationMessage, FutureCallback<CompleteBatch> callback) {
public void readMessage(MessageID msgID, FutureCallback<BulletinBoardMessage> callback) {
// Create job with no retries for retrieval of the Bulletin Board Message that defines the batch
// Create job with MAX retries for retrieval of the Bulletin Board Message (which may be a stub)
MessageFilterList filterList = MessageFilterList.newBuilder()
.addFilter(MessageFilter.newBuilder()
.setType(FilterType.TAG)
.setTag(BulletinBoardConstants.BATCH_TAG)
.build())
.addFilter(MessageFilter.newBuilder()
.setType(FilterType.TAG)
.setTag(BulletinBoardConstants.BATCH_ID_TAG_PREFIX + batchSpecificationMessage.getBatchId())
.build())
.addFilter(MessageFilter.newBuilder()
.setType(FilterType.SIGNER_ID)
.setId(batchSpecificationMessage.getSignerId())
.setType(FilterType.MSG_ID)
.setId(msgID.getID())
.build())
.build();
SingleServerReadMessagesWorker messageWorker = new SingleServerReadMessagesWorker(meerkatDBs.get(0), filterList, 1);
BatchQuery batchQuery = BatchQuery.newBuilder()
.setMsgID(msgID)
.setStartPosition(0)
.build();
// Create job with no retries for retrieval of the Batch Data List
SingleServerReadBatchWorker batchWorker = new SingleServerReadBatchWorker(meerkatDBs.get(0), batchSpecificationMessage, 1);
// Create callback that will combine the two worker products
CompleteBatchReadCallback completeBatchReadCallback = new CompleteBatchReadCallback(callback);
SingleServerReadMessagesWorker messageWorker = new SingleServerReadMessagesWorker(dbAddress, filterList, MAX_RETRIES);
// Submit jobs with wrapped callbacks
scheduleWorker(messageWorker, new RetryCallback<>(messageWorker, completeBatchReadCallback.asBulletinBoardMessageListFutureCallback()));
scheduleWorker(batchWorker, new RetryCallback<>(batchWorker, completeBatchReadCallback.asBatchDataListFutureCallback()));
scheduleWorker(messageWorker, new RetryCallback<>(messageWorker, new CompleteMessageReadCallback(callback)));
}
@Override
public void readBatchData(BulletinBoardMessage stub, FutureCallback<BulletinBoardMessage> callback) throws IllegalArgumentException{
if (stub.getMsg().getDataTypeCase() != UnsignedBulletinBoardMessage.DataTypeCase.MSGID) {
throw new IllegalArgumentException("Message is not a stub and does not contain the required message ID");
}
// Create job with MAX retries for retrieval of the Batch Data List
BatchQuery batchQuery = BatchQuery.newBuilder()
.setMsgID(MessageID.newBuilder()
.setID(stub.getMsg().getMsgId())
.build())
.setStartPosition(0)
.build();
SingleServerReadBatchWorker batchWorker = new SingleServerReadBatchWorker(dbAddress, batchQuery, MAX_RETRIES);
scheduleWorker(batchWorker, new RetryCallback<>(batchWorker, new ReadBatchCallback(stub, callback)));
}
@Override
public void querySync(SyncQuery syncQuery, FutureCallback<SyncQueryResponse> callback) {
SingleServerQuerySyncWorker worker = new SingleServerQuerySyncWorker(meerkatDBs.get(0), syncQuery, MAX_RETRIES);
SingleServerQuerySyncWorker worker = new SingleServerQuerySyncWorker(dbAddress, syncQuery, MAX_RETRIES);
scheduleWorker(worker, new RetryCallback<>(worker, callback));
@ -606,7 +855,7 @@ public class SingleServerBulletinBoardClient extends SimpleBulletinBoardClient i
.build());
// Create job with no retries
SingleServerReadMessagesWorker worker = new SingleServerReadMessagesWorker(meerkatDBs.get(0), filterListBuilder.build(), MAX_RETRIES);
SingleServerReadMessagesWorker worker = new SingleServerReadMessagesWorker(dbAddress, filterListBuilder.build(), MAX_RETRIES);
// Submit job and create callback that retries on failure and handles repeated subscription
scheduleWorker(worker, new RetryCallback<>(worker, new SubscriptionCallback(worker, callback)));
@ -620,8 +869,6 @@ public class SingleServerBulletinBoardClient extends SimpleBulletinBoardClient i
@Override
public void close() {
super.close();
executorService.shutdown();
}

View File

@ -1,255 +1,286 @@
package meerkat.bulletinboard;
import com.google.common.util.concurrent.FutureCallback;
import com.google.protobuf.ByteString;
import meerkat.bulletinboard.workers.multiserver.*;
import meerkat.comm.CommunicationException;
import meerkat.protobuf.BulletinBoardAPI;
import meerkat.protobuf.BulletinBoardAPI.*;
import meerkat.protobuf.Voting.*;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
/**
* Created by Arbel Deutsch Peled on 05-Dec-15.
* Thread-based implementation of a Async Bulletin Board Client.
* Features:
* 1. Handles tasks concurrently.
* 2. Retries submitting
*/
public class ThreadedBulletinBoardClient extends SimpleBulletinBoardClient implements AsyncBulletinBoardClient {
// Executor service for handling jobs
private final static int JOBS_THREAD_NUM = 5;
private ExecutorService executorService;
// Per-server clients
private List<SingleServerBulletinBoardClient> clients;
private BatchDigest batchDigest;
private final static int POST_MESSAGE_RETRY_NUM = 3;
private final static int READ_MESSAGES_RETRY_NUM = 1;
private final static int GET_REDUNDANCY_RETRY_NUM = 1;
private static final int SERVER_THREADPOOL_SIZE = 5;
private static final long FAIL_DELAY = 5000;
private static final long SUBSCRIPTION_INTERVAL = 10000;
private int minAbsoluteRedundancy;
/**
* Stores database locations and initializes the web Client
* Stores the required minimum redundancy.
* Starts the Thread Pool.
* @param clientParams contains the required information
*/
@Override
public void init(BulletinBoardClientParams clientParams) {
super.init(clientParams);
batchDigest = new GenericBatchDigest(digest);
minAbsoluteRedundancy = (int) (clientParams.getMinRedundancy() * (float) clientParams.getBulletinBoardAddressCount());
executorService = Executors.newFixedThreadPool(JOBS_THREAD_NUM);
clients = new ArrayList<>(clientParams.getBulletinBoardAddressCount());
for (String address : clientParams.getBulletinBoardAddressList()){
SingleServerBulletinBoardClient client =
new SingleServerBulletinBoardClient(SERVER_THREADPOOL_SIZE, FAIL_DELAY, SUBSCRIPTION_INTERVAL);
client.init(BulletinBoardClientParams.newBuilder()
.addBulletinBoardAddress(address)
.build());
clients.add(client);
}
}
/**
* Post message to all DBs
* Retry failed DBs
* @param msg is the message,
* @return the message ID for later retrieval
*/
@Override
public MessageID postMessage(BulletinBoardMessage msg, FutureCallback<Boolean> callback){
// Create job
MultiServerPostMessageWorker worker =
new MultiServerPostMessageWorker(clients, minAbsoluteRedundancy, msg, POST_MESSAGE_RETRY_NUM, callback);
// Submit job
executorService.submit(worker);
// Calculate the correct message ID and return it
batchDigest.reset();
batchDigest.update(msg.getMsg());
return batchDigest.digestAsMessageID();
}
@Override
public MessageID postBatch(CompleteBatch completeBatch, FutureCallback<Boolean> callback) {
// Create job
MultiServerPostBatchWorker worker =
new MultiServerPostBatchWorker(clients, minAbsoluteRedundancy, completeBatch, POST_MESSAGE_RETRY_NUM, callback);
// Submit job
executorService.submit(worker);
// Calculate the correct message ID and return it
batchDigest.reset();
batchDigest.update(completeBatch);
return batchDigest.digestAsMessageID();
}
@Override
public void beginBatch(BeginBatchMessage beginBatchMessage, FutureCallback<Boolean> callback) {
// Create job
MultiServerBeginBatchWorker worker =
new MultiServerBeginBatchWorker(clients, minAbsoluteRedundancy, beginBatchMessage, POST_MESSAGE_RETRY_NUM, callback);
// Submit job
executorService.submit(worker);
}
@Override
public void postBatchData(byte[] signerId, int batchId, List<BatchData> batchDataList,
int startPosition, FutureCallback<Boolean> callback) {
BatchDataContainer batchDataContainer = new BatchDataContainer(signerId, batchId, batchDataList, startPosition);
// Create job
MultiServerPostBatchDataWorker worker =
new MultiServerPostBatchDataWorker(clients, minAbsoluteRedundancy, batchDataContainer, POST_MESSAGE_RETRY_NUM, callback);
// Submit job
executorService.submit(worker);
}
@Override
public void postBatchData(byte[] signerId, int batchId, List<BatchData> batchDataList, FutureCallback<Boolean> callback) {
postBatchData(signerId, batchId, batchDataList, 0, callback);
}
@Override
public void postBatchData(ByteString signerId, int batchId, List<BatchData> batchDataList,
int startPosition, FutureCallback<Boolean> callback) {
postBatchData(signerId.toByteArray(), batchId, batchDataList, startPosition, callback);
}
@Override
public void postBatchData(ByteString signerId, int batchId, List<BatchData> batchDataList, FutureCallback<Boolean> callback) {
postBatchData(signerId, batchId, batchDataList, 0, callback);
}
@Override
public void closeBatch(CloseBatchMessage closeBatchMessage, FutureCallback<Boolean> callback) {
// Create job
MultiServerCloseBatchWorker worker =
new MultiServerCloseBatchWorker(clients, minAbsoluteRedundancy, closeBatchMessage, POST_MESSAGE_RETRY_NUM, callback);
// Submit job
executorService.submit(worker);
}
/**
* Access each database and search for a given message ID
* Return the number of databases in which the message was found
* Only try once per DB
* Ignore communication exceptions in specific databases
*/
@Override
public void getRedundancy(MessageID id, FutureCallback<Float> callback) {
// Create job
MultiServerGetRedundancyWorker worker =
new MultiServerGetRedundancyWorker(clients, minAbsoluteRedundancy, id, GET_REDUNDANCY_RETRY_NUM, callback);
// Submit job
executorService.submit(worker);
}
/**
* Go through the DBs and try to retrieve messages according to the specified filter
* If at the operation is successful for some DB: return the results and stop iterating
* If no operation is successful: return null (NOT blank list)
*/
@Override
public void readMessages(MessageFilterList filterList, FutureCallback<List<BulletinBoardMessage>> callback) {
// Create job
MultiServerReadMessagesWorker worker =
new MultiServerReadMessagesWorker(clients, minAbsoluteRedundancy, filterList, READ_MESSAGES_RETRY_NUM, callback);
// Submit job
executorService.submit(worker);
}
@Override
public void readBatch(BatchSpecificationMessage batchSpecificationMessage, FutureCallback<CompleteBatch> callback) {
// Create job
MultiServerReadBatchWorker worker =
new MultiServerReadBatchWorker(clients, minAbsoluteRedundancy, batchSpecificationMessage, READ_MESSAGES_RETRY_NUM, callback);
// Submit job
executorService.submit(worker);
}
/**
* This method is not supported by this class!
* This is because it has no meaning when considering more than one server without knowing which server will be contacted
*/
@Override
public void querySync(SyncQuery syncQuery, FutureCallback<SyncQueryResponse> callback) {
callback.onFailure(new IllegalAccessError("querySync is not supported by this class"));
}
@Override
public void close() {
super.close();
try {
for (SingleServerBulletinBoardClient client : clients){
client.close();
}
executorService.shutdown();
while (! executorService.isShutdown()) {
executorService.awaitTermination(10, TimeUnit.SECONDS);
}
} catch (InterruptedException e) {
System.err.println(e.getCause() + " " + e.getMessage());
}
}
}
package meerkat.bulletinboard;
import com.google.common.util.concurrent.FutureCallback;
import com.google.protobuf.Timestamp;
import meerkat.bulletinboard.workers.multiserver.*;
import meerkat.protobuf.BulletinBoardApi.*;
import meerkat.protobuf.Crypto.Signature;
import meerkat.protobuf.Voting.*;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
/**
* Created by Arbel Deutsch Peled on 05-Dec-15.
* Thread-based implementation of a Async Bulletin Board Client.
* Features:
* 1. Handles tasks concurrently.
* 2. Retries submitting
*/
public class ThreadedBulletinBoardClient extends SimpleBulletinBoardClient implements AsyncBulletinBoardClient {
// Executor service for handling jobs
private final static int JOBS_THREAD_NUM = 5;
private ExecutorService executorService;
// Per-server clients
private List<SingleServerBulletinBoardClient> clients;
private BulletinBoardDigest batchDigest;
private final static int POST_MESSAGE_RETRY_NUM = 3;
private final static int READ_MESSAGES_RETRY_NUM = 1;
private final static int GET_REDUNDANCY_RETRY_NUM = 1;
private final int SERVER_THREADPOOL_SIZE;
private final long FAIL_DELAY;
private final long SUBSCRIPTION_INTERVAL;
private static final int DEFAULT_SERVER_THREADPOOL_SIZE = 5;
private static final long DEFAULT_FAIL_DELAY = 5000;
private static final long DEFAULT_SUBSCRIPTION_INTERVAL = 10000;
private int minAbsoluteRedundancy;
public ThreadedBulletinBoardClient(int serverThreadpoolSize, long failDelay, long subscriptionInterval) {
SERVER_THREADPOOL_SIZE = serverThreadpoolSize;
FAIL_DELAY = failDelay;
SUBSCRIPTION_INTERVAL = subscriptionInterval;
}
public ThreadedBulletinBoardClient() {
this(DEFAULT_SERVER_THREADPOOL_SIZE, DEFAULT_FAIL_DELAY, DEFAULT_SUBSCRIPTION_INTERVAL);
}
/**
* Stores database locations and initializes the web Client
* Stores the required minimum redundancy.
* Starts the Thread Pool.
* @param clientParams contains the required information
*/
@Override
public void init(BulletinBoardClientParams clientParams) {
super.init(clientParams);
batchDigest = new GenericBulletinBoardDigest(digest);
minAbsoluteRedundancy = (int) (clientParams.getMinRedundancy() * (float) clientParams.getBulletinBoardAddressCount());
executorService = Executors.newFixedThreadPool(JOBS_THREAD_NUM);
clients = new ArrayList<>(clientParams.getBulletinBoardAddressCount());
for (String address : clientParams.getBulletinBoardAddressList()){
SingleServerBulletinBoardClient client =
new SingleServerBulletinBoardClient(SERVER_THREADPOOL_SIZE, FAIL_DELAY, SUBSCRIPTION_INTERVAL);
client.init(BulletinBoardClientParams.newBuilder()
.addBulletinBoardAddress(address)
.build());
clients.add(client);
}
}
/**
* Post message to all DBs
* Retry failed DBs
* @param msg is the message,
* @return the message ID for later retrieval
*/
@Override
public MessageID postMessage(BulletinBoardMessage msg, FutureCallback<Boolean> callback){
// Create job
MultiServerPostMessageWorker worker =
new MultiServerPostMessageWorker(clients, minAbsoluteRedundancy, msg, POST_MESSAGE_RETRY_NUM, callback);
// Submit job
executorService.submit(worker);
// Calculate the correct message ID and return it
batchDigest.reset();
batchDigest.update(msg.getMsg());
return batchDigest.digestAsMessageID();
}
@Override
public MessageID postAsBatch(BulletinBoardMessage msg, int chunkSize, FutureCallback<Boolean> callback) {
// Create job
MultiServerPostBatchWorker worker =
new MultiServerPostBatchWorker(clients, minAbsoluteRedundancy, msg, chunkSize, POST_MESSAGE_RETRY_NUM, callback);
// Submit job
executorService.submit(worker);
// Calculate the correct message ID and return it
batchDigest.reset();
batchDigest.update(msg);
return batchDigest.digestAsMessageID();
}
@Override
public void beginBatch(Iterable<String> tags, FutureCallback<BatchIdentifier> callback) {
// Create job
MultiServerBeginBatchWorker worker =
new MultiServerBeginBatchWorker(clients, minAbsoluteRedundancy, tags, POST_MESSAGE_RETRY_NUM, callback);
// Submit job
executorService.submit(worker);
}
@Override
public void postBatchData(BatchIdentifier batchIdentifier, List<BatchChunk> batchChunkList,
int startPosition, FutureCallback<Boolean> callback) throws IllegalArgumentException {
// Cast identifier to usable form
if (!(batchIdentifier instanceof MultiServerBatchIdentifier)){
throw new IllegalArgumentException("Error: batch identifier supplied was not created by this class.");
}
MultiServerBatchIdentifier identifier = (MultiServerBatchIdentifier) batchIdentifier;
BatchDataContainer batchDataContainer = new BatchDataContainer(identifier, batchChunkList, startPosition);
// Create job
MultiServerPostBatchDataWorker worker =
new MultiServerPostBatchDataWorker(clients, minAbsoluteRedundancy, batchDataContainer, POST_MESSAGE_RETRY_NUM, callback);
// Submit job
executorService.submit(worker);
}
@Override
public void postBatchData(BatchIdentifier batchIdentifier, List<BatchChunk> batchChunkList, FutureCallback<Boolean> callback)
throws IllegalArgumentException {
postBatchData(batchIdentifier, batchChunkList, 0, callback);
}
@Override
public void closeBatch(BatchIdentifier payload, Timestamp timestamp, Iterable<Signature> signatures, FutureCallback<Boolean> callback)
throws IllegalArgumentException{
if (!(payload instanceof MultiServerBatchIdentifier)) {
throw new IllegalArgumentException("Error: batch identifier supplied was not created by this class.");
}
MultiServerBatchIdentifier identifier = (MultiServerBatchIdentifier) payload;
// Create job
MultiServerCloseBatchWorker worker =
new MultiServerCloseBatchWorker(clients, minAbsoluteRedundancy, identifier, timestamp, signatures, POST_MESSAGE_RETRY_NUM, callback);
// Submit job
executorService.submit(worker);
}
/**
* Access each database and search for a given message ID
* Return the number of databases in which the message was found
* Only try once per DB
* Ignore communication exceptions in specific databases
*/
@Override
public void getRedundancy(MessageID id, FutureCallback<Float> callback) {
// Create job
MultiServerGetRedundancyWorker worker =
new MultiServerGetRedundancyWorker(clients, minAbsoluteRedundancy, id, GET_REDUNDANCY_RETRY_NUM, callback);
// Submit job
executorService.submit(worker);
}
/**
* Go through the DBs and try to retrieve messages according to the specified filter
* If at the operation is successful for some DB: return the results and stop iterating
* If no operation is successful: return null (NOT blank list)
*/
@Override
public void readMessages(MessageFilterList filterList, FutureCallback<List<BulletinBoardMessage>> callback) {
// Create job
MultiServerReadMessagesWorker worker =
new MultiServerReadMessagesWorker(clients, minAbsoluteRedundancy, filterList, READ_MESSAGES_RETRY_NUM, callback);
// Submit job
executorService.submit(worker);
}
@Override
public void readMessage(MessageID msgID, FutureCallback<BulletinBoardMessage> callback) {
//Create job
MultiServerReadMessageWorker worker =
new MultiServerReadMessageWorker(clients, minAbsoluteRedundancy, msgID, READ_MESSAGES_RETRY_NUM, callback);
// Submit job
executorService.submit(worker);
}
@Override
public void readBatchData(BulletinBoardMessage stub, FutureCallback<BulletinBoardMessage> callback) throws IllegalArgumentException {
if (stub.getMsg().getDataTypeCase() != UnsignedBulletinBoardMessage.DataTypeCase.MSGID) {
throw new IllegalArgumentException("Message is not a stub and does not contain the required message ID");
}
// Create job
MultiServerReadBatchDataWorker worker =
new MultiServerReadBatchDataWorker(clients, minAbsoluteRedundancy, stub, READ_MESSAGES_RETRY_NUM, callback);
// Submit job
executorService.submit(worker);
}
/**
* This method is not supported by this class!
* This is because it has no meaning when considering more than one server without knowing which server will be contacted
*/
@Override
public void querySync(SyncQuery syncQuery, FutureCallback<SyncQueryResponse> callback) {
callback.onFailure(new IllegalAccessError("querySync is not supported by this class"));
}
@Override
public void close() {
super.close();
try {
for (SingleServerBulletinBoardClient client : clients){
client.close();
}
executorService.shutdown();
while (! executorService.isShutdown()) {
executorService.awaitTermination(10, TimeUnit.SECONDS);
}
} catch (InterruptedException e) {
System.err.println(e.getCause() + " " + e.getMessage());
}
}
}

View File

@ -3,12 +3,11 @@ package meerkat.bulletinboard;
import com.google.common.util.concurrent.FutureCallback;
import com.google.protobuf.Timestamp;
import meerkat.comm.CommunicationException;
import meerkat.protobuf.BulletinBoardAPI.*;
import meerkat.protobuf.BulletinBoardApi.*;
import meerkat.util.BulletinBoardUtils;
import static meerkat.protobuf.BulletinBoardAPI.FilterType.*;
import static meerkat.protobuf.BulletinBoardApi.FilterType.*;
import java.sql.Time;
import java.util.*;
import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.AtomicBoolean;
@ -19,20 +18,22 @@ import java.util.concurrent.atomic.AtomicBoolean;
*/
public class ThreadedBulletinBoardSubscriber implements BulletinBoardSubscriber {
protected final Collection<SubscriptionAsyncBulletinBoardClient> clients;
protected final Collection<SubscriptionBulletinBoardClient> clients;
protected final BulletinBoardClient localClient;
protected Iterator<SubscriptionAsyncBulletinBoardClient> clientIterator;
protected SubscriptionAsyncBulletinBoardClient currentClient;
protected Iterator<SubscriptionBulletinBoardClient> clientIterator;
protected SubscriptionBulletinBoardClient currentClient;
private long lastServerSwitchTime;
private AtomicBoolean isSyncInProgress;
private Semaphore rescheduleSemaphore;
private AtomicBoolean stopped;
private static final Float[] BREAKPOINTS = {0.5f, 0.75f, 0.9f, 0.95f, 0.99f, 0.999f};
public ThreadedBulletinBoardSubscriber(Collection<SubscriptionAsyncBulletinBoardClient> clients, BulletinBoardClient localClient) {
public ThreadedBulletinBoardSubscriber(Collection<SubscriptionBulletinBoardClient> clients, BulletinBoardClient localClient) {
this.clients = clients;
this.localClient = localClient;
@ -45,6 +46,8 @@ public class ThreadedBulletinBoardSubscriber implements BulletinBoardSubscriber
isSyncInProgress = new AtomicBoolean(false);
rescheduleSemaphore = new Semaphore(1);
stopped = new AtomicBoolean(false);
}
/**
@ -132,7 +135,8 @@ public class ThreadedBulletinBoardSubscriber implements BulletinBoardSubscriber
//TODO: log
callback.onFailure(e); // Hard error: Cannot guarantee subscription safety
if (callback != null)
callback.onFailure(e); // Hard error: Cannot guarantee subscription safety
}
@ -218,7 +222,8 @@ public class ThreadedBulletinBoardSubscriber implements BulletinBoardSubscriber
public void onSuccess(List<BulletinBoardMessage> result) {
// Propagate result to caller
callback.onSuccess(result);
if (callback != null)
callback.onSuccess(result);
// Renew subscription
@ -245,12 +250,12 @@ public class ThreadedBulletinBoardSubscriber implements BulletinBoardSubscriber
super(filterList, callback);
}
@Override
public void onSuccess(List<BulletinBoardMessage> result) {
// Propagate result to caller
callback.onSuccess(result);
if (callback != null)
callback.onSuccess(result);
}
@ -268,5 +273,4 @@ public class ThreadedBulletinBoardSubscriber implements BulletinBoardSubscriber
subscribe(filterList, 0, callback);
}
}

View File

@ -1,28 +1,96 @@
package meerkat.bulletinboard.workers.multiserver;
import com.google.common.util.concurrent.FutureCallback;
import meerkat.bulletinboard.MultiServerBatchIdentifier;
import meerkat.bulletinboard.MultiServerWorker;
import meerkat.bulletinboard.SingleServerBulletinBoardClient;
import meerkat.protobuf.BulletinBoardAPI.BeginBatchMessage;
import meerkat.bulletinboard.AsyncBulletinBoardClient.BatchIdentifier;
import meerkat.comm.CommunicationException;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Created by Arbel Deutsch Peled on 27-Dec-15.
*/
public class MultiServerBeginBatchWorker extends MultiServerGenericPostWorker<BeginBatchMessage> {
public class MultiServerBeginBatchWorker extends MultiServerWorker<Iterable<String>, BatchIdentifier> {
private BatchIdentifier[] identifiers;
private AtomicInteger remainingServers;
public MultiServerBeginBatchWorker(List<SingleServerBulletinBoardClient> clients,
int minServers, BeginBatchMessage payload, int maxRetry,
FutureCallback<Boolean> futureCallback) {
int minServers, Iterable<String> payload, int maxRetry,
FutureCallback<BatchIdentifier> futureCallback) {
super(clients, minServers, payload, maxRetry, futureCallback);
identifiers = new BatchIdentifier[clients.size()];
for (int i = 0 ; i < identifiers.length ; i++) {
identifiers[i] = null;
}
remainingServers = new AtomicInteger(clients.size());
}
private class BeginBatchCallback implements FutureCallback<BatchIdentifier> {
private final int clientNum;
public BeginBatchCallback(int clientNum) {
this.clientNum = clientNum;
}
private void finishPost() {
if (remainingServers.decrementAndGet() <= 0){
if (minServers.decrementAndGet() <= 0) {
MultiServerBeginBatchWorker.this.onSuccess(new MultiServerBatchIdentifier(identifiers));
} else {
MultiServerBeginBatchWorker.this.onFailure(new CommunicationException("Could not open batch in enough servers"));
}
}
}
@Override
public void onSuccess(BatchIdentifier result) {
identifiers[clientNum] = result;
finishPost();
}
@Override
public void onFailure(Throwable t) {
finishPost();
}
}
@Override
protected void doPost(SingleServerBulletinBoardClient client, BeginBatchMessage payload) {
client.beginBatch(payload, this);
public void onSuccess(BatchIdentifier result) {
succeed(result);
}
@Override
public void onFailure(Throwable t) {
fail(t);
}
@Override
public void run() {
int clientNum = 0;
for (SingleServerBulletinBoardClient client : clients){
client.beginBatch(payload, new BeginBatchCallback(clientNum));
clientNum++;
}
}
}

View File

@ -1,27 +1,83 @@
package meerkat.bulletinboard.workers.multiserver;
import com.google.common.util.concurrent.FutureCallback;
import com.google.protobuf.Timestamp;
import meerkat.bulletinboard.AsyncBulletinBoardClient.BatchIdentifier;
import meerkat.bulletinboard.BatchDataContainer;
import meerkat.bulletinboard.MultiServerBatchIdentifier;
import meerkat.bulletinboard.MultiServerWorker;
import meerkat.bulletinboard.SingleServerBulletinBoardClient;
import meerkat.protobuf.BulletinBoardAPI.CloseBatchMessage;
import meerkat.crypto.DigitalSignature;
import meerkat.protobuf.Crypto;
import meerkat.protobuf.Crypto.Signature;
import java.util.Iterator;
import java.util.List;
/**
* Created by Arbel Deutsch Peled on 27-Dec-15.
*/
public class MultiServerCloseBatchWorker extends MultiServerGenericPostWorker<CloseBatchMessage> {
public class MultiServerCloseBatchWorker extends MultiServerWorker<MultiServerBatchIdentifier, Boolean> {
private final Timestamp timestamp;
private final Iterable<Crypto.Signature> signatures;
public MultiServerCloseBatchWorker(List<SingleServerBulletinBoardClient> clients,
int minServers, CloseBatchMessage payload, int maxRetry,
FutureCallback<Boolean> futureCallback) {
int minServers, MultiServerBatchIdentifier payload, Timestamp timestamp, Iterable<Crypto.Signature> signatures,
int maxRetry, FutureCallback<Boolean> futureCallback) {
super(clients, minServers, payload, maxRetry, futureCallback);
this.timestamp = timestamp;
this.signatures = signatures;
}
@Override
protected void doPost(SingleServerBulletinBoardClient client, CloseBatchMessage payload) {
client.closeBatch(payload, this);
public void run() {
Iterator<BatchIdentifier> identifierIterator = payload.getIdentifiers().iterator();
// Iterate through client
for (SingleServerBulletinBoardClient client : clients) {
if (identifierIterator.hasNext()) {
// Fetch the batch identifier supplied by the specific client (may be null if batch open failed on client
BatchIdentifier identifier = identifierIterator.next();
if (identifier != null) {
// Post the data with the matching identifier to the client
client.closeBatch(identifier, timestamp, signatures, this);
} else {
// Count servers with no batch identifier as failed
maxFailedServers.decrementAndGet();
}
}
}
}
@Override
public void onSuccess(Boolean result) {
if (minServers.decrementAndGet() <= 0){
succeed(result);
}
}
@Override
public void onFailure(Throwable t) {
if (maxFailedServers.decrementAndGet() <= 0){
fail(t);
}
}

View File

@ -35,14 +35,9 @@ public abstract class MultiServerGenericPostWorker<T> extends MultiServerWorker<
public void run() {
// Iterate through servers
Iterator<SingleServerBulletinBoardClient> clientIterator = getClientIterator();
while (clientIterator.hasNext()) {
for (SingleServerBulletinBoardClient client : clients) {
// Send request to Server
SingleServerBulletinBoardClient client = clientIterator.next();
doPost(client, payload);
}

View File

@ -14,7 +14,9 @@ import java.util.List;
*/
public abstract class MultiServerGenericReadWorker<IN, OUT> extends MultiServerWorker<IN, OUT>{
private final Iterator<SingleServerBulletinBoardClient> clientIterator;
private Iterator<SingleServerBulletinBoardClient> clientIterator;
private String errorString;
public MultiServerGenericReadWorker(List<SingleServerBulletinBoardClient> clients,
int minServers, IN payload, int maxRetry,
@ -22,7 +24,8 @@ public abstract class MultiServerGenericReadWorker<IN, OUT> extends MultiServerW
super(clients, true, minServers, payload, maxRetry, futureCallback); // Shuffle clients on creation to balance load
clientIterator = getClientIterator();
clientIterator = clients.iterator();
errorString = "";
}
@ -46,7 +49,7 @@ public abstract class MultiServerGenericReadWorker<IN, OUT> extends MultiServerW
doRead(payload, client);
} else {
fail(new CommunicationException("Could not contact any server"));
fail(new CommunicationException("Could not contact any server. Errors follow:\n" + errorString));
}
}
@ -58,6 +61,8 @@ public abstract class MultiServerGenericReadWorker<IN, OUT> extends MultiServerW
@Override
public void onFailure(Throwable t) {
//TODO: log
errorString += t.getCause() + " " + t.getMessage() + "\n";
run(); // Retry with next server
}

View File

@ -4,7 +4,7 @@ import com.google.common.util.concurrent.FutureCallback;
import meerkat.bulletinboard.MultiServerWorker;
import meerkat.bulletinboard.SingleServerBulletinBoardClient;
import meerkat.comm.CommunicationException;
import meerkat.protobuf.BulletinBoardAPI.*;
import meerkat.protobuf.BulletinBoardApi.*;
import java.util.Iterator;
import java.util.List;
@ -36,13 +36,8 @@ public class MultiServerGetRedundancyWorker extends MultiServerWorker<MessageID,
*/
public void run(){
Iterator<SingleServerBulletinBoardClient> clientIterator = getClientIterator();
// Iterate through clients
while (clientIterator.hasNext()) {
SingleServerBulletinBoardClient client = clientIterator.next();
for (SingleServerBulletinBoardClient client : clients) {
// Send request to client
client.getRedundancy(payload,this);

View File

@ -1,15 +1,18 @@
package meerkat.bulletinboard.workers.multiserver;
import com.google.common.util.concurrent.FutureCallback;
import meerkat.bulletinboard.AsyncBulletinBoardClient.BatchIdentifier;
import meerkat.bulletinboard.MultiServerWorker;
import meerkat.bulletinboard.SingleServerBulletinBoardClient;
import meerkat.bulletinboard.BatchDataContainer;
import java.util.Iterator;
import java.util.List;
/**
* Created by Arbel Deutsch Peled on 27-Dec-15.
*/
public class MultiServerPostBatchDataWorker extends MultiServerGenericPostWorker<BatchDataContainer> {
public class MultiServerPostBatchDataWorker extends MultiServerWorker<BatchDataContainer, Boolean> {
public MultiServerPostBatchDataWorker(List<SingleServerBulletinBoardClient> clients,
int minServers, BatchDataContainer payload, int maxRetry,
@ -20,9 +23,50 @@ public class MultiServerPostBatchDataWorker extends MultiServerGenericPostWorker
}
@Override
protected void doPost(SingleServerBulletinBoardClient client, BatchDataContainer payload) {
client.postBatchData(payload.signerId, payload.batchId, payload.batchDataList, payload.startPosition, this);
public void run() {
Iterator<BatchIdentifier> identifierIterator = payload.batchId.getIdentifiers().iterator();
// Iterate through client
for (SingleServerBulletinBoardClient client : clients) {
if (identifierIterator.hasNext()) {
// Fetch the batch identifier supplied by the specific client (may be null if batch open failed on client
BatchIdentifier identifier = identifierIterator.next();
if (identifier != null) {
// Post the data with the matching identifier to the client
client.postBatchData(identifier, payload.batchChunkList, payload.startPosition, this);
} else {
// Count servers with no batch identifier as failed
maxFailedServers.decrementAndGet();
}
}
}
}
@Override
public void onSuccess(Boolean result) {
if (minServers.decrementAndGet() <= 0){
succeed(result);
}
}
@Override
public void onFailure(Throwable t) {
if (maxFailedServers.decrementAndGet() <= 0){
fail(t);
}
}
}

View File

@ -1,27 +1,33 @@
package meerkat.bulletinboard.workers.multiserver;
import com.google.common.util.concurrent.FutureCallback;
import meerkat.bulletinboard.CompleteBatch;
import meerkat.bulletinboard.SingleServerBulletinBoardClient;
import meerkat.protobuf.BulletinBoardApi.BulletinBoardMessage;
import java.util.List;
/**
* Created by Arbel Deutsch Peled on 27-Dec-15.
*/
public class MultiServerPostBatchWorker extends MultiServerGenericPostWorker<CompleteBatch> {
public class MultiServerPostBatchWorker extends MultiServerGenericPostWorker<BulletinBoardMessage> {
private final int chunkSize;
public MultiServerPostBatchWorker(List<SingleServerBulletinBoardClient> clients,
int minServers, CompleteBatch payload, int maxRetry,
int minServers, BulletinBoardMessage payload, int chunkSize, int maxRetry,
FutureCallback<Boolean> futureCallback) {
super(clients, minServers, payload, maxRetry, futureCallback);
this.chunkSize = chunkSize;
}
@Override
protected void doPost(SingleServerBulletinBoardClient client, CompleteBatch payload) {
client.postBatch(payload, this);
protected void doPost(SingleServerBulletinBoardClient client, BulletinBoardMessage payload) {
client.postAsBatch(payload, chunkSize, this);
}

View File

@ -2,7 +2,7 @@ package meerkat.bulletinboard.workers.multiserver;
import com.google.common.util.concurrent.FutureCallback;
import meerkat.bulletinboard.SingleServerBulletinBoardClient;
import meerkat.protobuf.BulletinBoardAPI.*;
import meerkat.protobuf.BulletinBoardApi.*;
import java.util.List;

View File

@ -0,0 +1,29 @@
package meerkat.bulletinboard.workers.multiserver;
import com.google.common.util.concurrent.FutureCallback;
import meerkat.bulletinboard.SingleServerBulletinBoardClient;
import meerkat.protobuf.BulletinBoardApi.*;
import java.util.List;
/**
* Created by Arbel Deutsch Peled on 27-Dec-15.
*/
public class MultiServerReadBatchDataWorker extends MultiServerGenericReadWorker<BulletinBoardMessage, BulletinBoardMessage> {
public MultiServerReadBatchDataWorker(List<SingleServerBulletinBoardClient> clients,
int minServers, BulletinBoardMessage payload, int maxRetry,
FutureCallback<BulletinBoardMessage> futureCallback) {
super(clients, minServers, payload, maxRetry, futureCallback);
}
@Override
protected void doRead(BulletinBoardMessage payload, SingleServerBulletinBoardClient client) {
client.readBatchData(payload, this);
}
}

View File

@ -1,30 +0,0 @@
package meerkat.bulletinboard.workers.multiserver;
import com.google.common.util.concurrent.FutureCallback;
import meerkat.bulletinboard.CompleteBatch;
import meerkat.bulletinboard.SingleServerBulletinBoardClient;
import meerkat.protobuf.BulletinBoardAPI.BatchSpecificationMessage;
import java.util.List;
/**
* Created by Arbel Deutsch Peled on 27-Dec-15.
*/
public class MultiServerReadBatchWorker extends MultiServerGenericReadWorker<BatchSpecificationMessage, CompleteBatch> {
public MultiServerReadBatchWorker(List<SingleServerBulletinBoardClient> clients,
int minServers, BatchSpecificationMessage payload, int maxRetry,
FutureCallback<CompleteBatch> futureCallback) {
super(clients, minServers, payload, maxRetry, futureCallback);
}
@Override
protected void doRead(BatchSpecificationMessage payload, SingleServerBulletinBoardClient client) {
client.readBatch(payload, this);
}
}

View File

@ -0,0 +1,30 @@
package meerkat.bulletinboard.workers.multiserver;
import com.google.common.util.concurrent.FutureCallback;
import meerkat.bulletinboard.SingleServerBulletinBoardClient;
import meerkat.protobuf.BulletinBoardApi.BulletinBoardMessage;
import meerkat.protobuf.BulletinBoardApi.MessageID;
import java.util.List;
/**
* Created by Arbel Deutsch Peled on 27-Dec-15.
*/
public class MultiServerReadMessageWorker extends MultiServerGenericReadWorker<MessageID, BulletinBoardMessage> {
public MultiServerReadMessageWorker(List<SingleServerBulletinBoardClient> clients,
int minServers, MessageID payload, int maxRetry,
FutureCallback<BulletinBoardMessage> futureCallback) {
super(clients, minServers, payload, maxRetry, futureCallback);
}
@Override
protected void doRead(MessageID payload, SingleServerBulletinBoardClient client) {
client.readMessage(payload, this);
}
}

View File

@ -2,7 +2,7 @@ package meerkat.bulletinboard.workers.multiserver;
import com.google.common.util.concurrent.FutureCallback;
import meerkat.bulletinboard.SingleServerBulletinBoardClient;
import meerkat.protobuf.BulletinBoardAPI.*;
import meerkat.protobuf.BulletinBoardApi.*;
import java.util.List;

View File

@ -1,17 +1,53 @@
package meerkat.bulletinboard.workers.singleserver;
import meerkat.protobuf.BulletinBoardAPI.BeginBatchMessage;
import com.google.protobuf.Int64Value;
import meerkat.bulletinboard.SingleServerWorker;
import meerkat.comm.CommunicationException;
import meerkat.protobuf.BulletinBoardApi.BeginBatchMessage;
import meerkat.rest.Constants;
import javax.ws.rs.ProcessingException;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.Entity;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.Response;
import static meerkat.bulletinboard.BulletinBoardConstants.BEGIN_BATCH_PATH;
import static meerkat.bulletinboard.BulletinBoardConstants.BULLETIN_BOARD_SERVER_PATH;
/**
* Created by Arbel Deutsch Peled on 27-Dec-15.
* Tries to contact server once and perform a post operation
*/
public class SingleServerBeginBatchWorker extends SingleServerGenericPostWorker<BeginBatchMessage> {
public class SingleServerBeginBatchWorker extends SingleServerWorker<BeginBatchMessage,Int64Value> {
public SingleServerBeginBatchWorker(String serverAddress, BeginBatchMessage payload, int maxRetry) {
super(serverAddress, BEGIN_BATCH_PATH, payload, maxRetry);
super(serverAddress, payload, maxRetry);
}
@Override
public Int64Value call() throws Exception {
Client client = clientLocal.get();
WebTarget webTarget = client.target(serverAddress).path(BULLETIN_BOARD_SERVER_PATH).path(BEGIN_BATCH_PATH);
Response response = webTarget.request(Constants.MEDIATYPE_PROTOBUF).post(
Entity.entity(payload, Constants.MEDIATYPE_PROTOBUF));
try {
Int64Value result = response.readEntity(Int64Value.class);
return result;
} catch (ProcessingException | IllegalStateException e) {
// Post to this server failed
throw new CommunicationException("Could not contact the server. Original error: " + e.getMessage());
} catch (Exception e) {
throw new CommunicationException("Could not contact the server. Original error: " + e.getMessage());
}
finally {
response.close();
}
}
}

View File

@ -1,12 +1,12 @@
package meerkat.bulletinboard.workers.singleserver;
import meerkat.protobuf.BulletinBoardAPI.CloseBatchMessage;
import meerkat.protobuf.BulletinBoardApi.CloseBatchMessage;
import static meerkat.bulletinboard.BulletinBoardConstants.CLOSE_BATCH_PATH;
/**
* Created by Arbel Deutsch Peled on 27-Dec-15.
* Tries to contact server once and perform a close batch operation
* Tries to contact server once and perform a stop batch operation
*/
public class SingleServerCloseBatchWorker extends SingleServerGenericPostWorker<CloseBatchMessage> {

View File

@ -0,0 +1,55 @@
package meerkat.bulletinboard.workers.singleserver;
import com.google.protobuf.Int64Value;
import meerkat.bulletinboard.SingleServerWorker;
import meerkat.comm.CommunicationException;
import meerkat.protobuf.BulletinBoardApi.SyncQuery;
import meerkat.protobuf.BulletinBoardApi.GenerateSyncQueryParams;
import meerkat.rest.Constants;
import javax.ws.rs.ProcessingException;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.Entity;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.Response;
import static meerkat.bulletinboard.BulletinBoardConstants.BULLETIN_BOARD_SERVER_PATH;
import static meerkat.bulletinboard.BulletinBoardConstants.GENERATE_SYNC_QUERY_PATH;
/**
* Created by Arbel Deutsch Peled on 27-Dec-15.
* Tries to contact server once and perform a Sync Query Generation operation
*/
public class SingleServerGenerateSyncQueryWorker extends SingleServerWorker<GenerateSyncQueryParams,SyncQuery> {
public SingleServerGenerateSyncQueryWorker(String serverAddress, GenerateSyncQueryParams payload, int maxRetry) {
super(serverAddress, payload, maxRetry);
}
@Override
public SyncQuery call() throws Exception {
Client client = clientLocal.get();
WebTarget webTarget = client.target(serverAddress).path(BULLETIN_BOARD_SERVER_PATH).path(GENERATE_SYNC_QUERY_PATH);
Response response = webTarget.request(Constants.MEDIATYPE_PROTOBUF).post(Entity.entity(payload, Constants.MEDIATYPE_PROTOBUF));
try {
SyncQuery result = response.readEntity(SyncQuery.class);
return result;
} catch (ProcessingException | IllegalStateException e) {
// Post to this server failed
throw new CommunicationException("Could not contact the server. Original error: " + e.getMessage());
} catch (Exception e) {
throw new CommunicationException("Could not contact the server. Original error: " + e.getMessage());
}
finally {
response.close();
}
}
}

View File

@ -1,8 +1,9 @@
package meerkat.bulletinboard.workers.singleserver;
import com.google.protobuf.BoolValue;
import meerkat.bulletinboard.SingleServerWorker;
import meerkat.comm.CommunicationException;
import meerkat.protobuf.BulletinBoardAPI.BoolMsg;
import meerkat.protobuf.Comm.*;
import meerkat.rest.Constants;
import javax.ws.rs.ProcessingException;
@ -43,8 +44,8 @@ public class SingleServerGenericPostWorker<T> extends SingleServerWorker<T, Bool
try {
// If a BoolMsg entity is returned: the post was successful
response.readEntity(BoolMsg.class);
// If a BoolValue entity is returned: the post was successful
response.readEntity(BoolValue.class);
return Boolean.TRUE;
} catch (ProcessingException | IllegalStateException e) {

View File

@ -3,10 +3,9 @@ package meerkat.bulletinboard.workers.singleserver;
import meerkat.bulletinboard.SingleServerWorker;
import meerkat.comm.CommunicationException;
import meerkat.comm.MessageInputStream;
import meerkat.protobuf.BulletinBoardAPI.*;
import meerkat.protobuf.BulletinBoardApi.*;
import meerkat.rest.Constants;
import javax.ws.rs.ProcessingException;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.Entity;
import javax.ws.rs.client.WebTarget;
@ -14,7 +13,6 @@ import javax.ws.rs.core.Response;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import static meerkat.bulletinboard.BulletinBoardConstants.BULLETIN_BOARD_SERVER_PATH;
import static meerkat.bulletinboard.BulletinBoardConstants.READ_MESSAGES_PATH;

View File

@ -1,6 +1,6 @@
package meerkat.bulletinboard.workers.singleserver;
import meerkat.protobuf.BulletinBoardAPI.BatchMessage;
import meerkat.protobuf.BulletinBoardApi.BatchMessage;
import static meerkat.bulletinboard.BulletinBoardConstants.POST_BATCH_PATH;

View File

@ -1,6 +1,6 @@
package meerkat.bulletinboard.workers.singleserver;
import meerkat.protobuf.BulletinBoardAPI.BulletinBoardMessage;
import meerkat.protobuf.BulletinBoardApi.BulletinBoardMessage;
import static meerkat.bulletinboard.BulletinBoardConstants.POST_MESSAGE_PATH;

View File

@ -2,8 +2,8 @@ package meerkat.bulletinboard.workers.singleserver;
import meerkat.bulletinboard.SingleServerWorker;
import meerkat.comm.CommunicationException;
import meerkat.protobuf.BulletinBoardAPI.SyncQuery;
import meerkat.protobuf.BulletinBoardAPI.SyncQueryResponse;
import meerkat.protobuf.BulletinBoardApi.SyncQuery;
import meerkat.protobuf.BulletinBoardApi.SyncQueryResponse;
import meerkat.rest.Constants;
import javax.ws.rs.ProcessingException;

View File

@ -1,35 +1,28 @@
package meerkat.bulletinboard.workers.singleserver;
import meerkat.bulletinboard.CompleteBatch;
import meerkat.bulletinboard.SingleServerWorker;
import meerkat.comm.CommunicationException;
import meerkat.comm.MessageInputStream;
import meerkat.protobuf.BulletinBoardAPI.*;
import meerkat.protobuf.BulletinBoardApi.*;
import meerkat.rest.Constants;
import javax.ws.rs.ProcessingException;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.Entity;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.GenericType;
import javax.ws.rs.core.Response;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.util.List;
import static meerkat.bulletinboard.BulletinBoardConstants.BULLETIN_BOARD_SERVER_PATH;
import static meerkat.bulletinboard.BulletinBoardConstants.READ_MESSAGES_PATH;
import static meerkat.bulletinboard.BulletinBoardConstants.READ_BATCH_PATH;
import static meerkat.bulletinboard.BulletinBoardConstants.BATCH_ID_TAG_PREFIX;
/**
* Created by Arbel Deutsch Peled on 27-Dec-15.
*/
public class SingleServerReadBatchWorker extends SingleServerWorker<BatchSpecificationMessage, List<BatchData>> {
public class SingleServerReadBatchWorker extends SingleServerWorker<BatchQuery, List<BatchChunk>> {
public SingleServerReadBatchWorker(String serverAddress, BatchSpecificationMessage payload, int maxRetry) {
public SingleServerReadBatchWorker(String serverAddress, BatchQuery payload, int maxRetry) {
super(serverAddress, payload, maxRetry);
}
@ -39,7 +32,7 @@ public class SingleServerReadBatchWorker extends SingleServerWorker<BatchSpecifi
* @return the complete batch as read from the server
* @throws CommunicationException if the server's response is invalid
*/
public List<BatchData> call() throws CommunicationException{
public List<BatchChunk> call() throws CommunicationException{
Client client = clientLocal.get();
@ -50,11 +43,11 @@ public class SingleServerReadBatchWorker extends SingleServerWorker<BatchSpecifi
webTarget = client.target(serverAddress).path(BULLETIN_BOARD_SERVER_PATH).path(READ_BATCH_PATH);
InputStream in = webTarget.request(Constants.MEDIATYPE_PROTOBUF).post(Entity.entity(payload, Constants.MEDIATYPE_PROTOBUF), InputStream.class);
MessageInputStream<BatchData> inputStream = null;
MessageInputStream<BatchChunk> inputStream = null;
try {
inputStream = MessageInputStream.MessageInputStreamFactory.createMessageInputStream(in, BatchData.class);
inputStream = MessageInputStream.MessageInputStreamFactory.createMessageInputStream(in, BatchChunk.class);
return inputStream.asList();

View File

@ -3,10 +3,10 @@ package meerkat.bulletinboard.workers.singleserver;
import meerkat.bulletinboard.SingleServerWorker;
import meerkat.comm.CommunicationException;
import meerkat.comm.MessageInputStream;
import meerkat.protobuf.BulletinBoardAPI;
import meerkat.protobuf.BulletinBoardAPI.BulletinBoardMessageList;
import meerkat.protobuf.BulletinBoardAPI.MessageFilterList;
import meerkat.protobuf.BulletinBoardAPI.BulletinBoardMessage;
import meerkat.protobuf.BulletinBoardApi;
import meerkat.protobuf.BulletinBoardApi.BulletinBoardMessageList;
import meerkat.protobuf.BulletinBoardApi.MessageFilterList;
import meerkat.protobuf.BulletinBoardApi.BulletinBoardMessage;
import meerkat.rest.Constants;
import javax.ws.rs.ProcessingException;

View File

@ -0,0 +1,318 @@
package meerkat.bulletinboard;
import com.google.common.util.concurrent.FutureCallback;
import com.google.protobuf.*;
import com.google.protobuf.Timestamp;
import static meerkat.bulletinboard.BulletinBoardSynchronizer.SyncStatus;
import meerkat.bulletinboard.sqlserver.BulletinBoardSQLServer;
import meerkat.bulletinboard.sqlserver.H2QueryProvider;
import meerkat.comm.CommunicationException;
import meerkat.crypto.concrete.ECDSASignature;
import meerkat.protobuf.BulletinBoardApi.*;
import meerkat.util.BulletinBoardMessageComparator;
import meerkat.util.BulletinBoardMessageGenerator;
import org.junit.*;
import java.io.IOException;
import java.io.InputStream;
import java.security.*;
import java.security.cert.CertificateException;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.fail;
/**
* Created by Arbel on 6/1/2016.
*/
public class BulletinBoardSynchronizerTest {
private static final String REMOTE_SERVER_ADDRESS = "remoteDB";
private static final String LOCAL_SERVER_ADDRESS = "localDB";
private static int testCount;
private static final int THREAD_NUM = 3;
private static final int SUBSCRIPTION_INTERVAL = 1000;
private static final int SYNC_SLEEP_INTERVAL = 500;
private static final int SYNC_WAIT_CAP = 1000;
private DeletableSubscriptionBulletinBoardClient localClient;
private AsyncBulletinBoardClient remoteClient;
private BulletinBoardSynchronizer synchronizer;
private static BulletinBoardMessageGenerator messageGenerator;
private static BulletinBoardMessageComparator messageComparator;
private static String KEYFILE_EXAMPLE = "/certs/enduser-certs/user1-key-with-password-secret.p12";
private static String KEYFILE_PASSWORD1 = "secret";
private static String CERT1_PEM_EXAMPLE = "/certs/enduser-certs/user1.crt";
private static BulletinBoardSignature[] signers;
private static ByteString[] signerIDs;
private Semaphore semaphore;
private List<Throwable> thrown;
@BeforeClass
public static void build() {
messageGenerator = new BulletinBoardMessageGenerator(new Random(0));
messageComparator = new BulletinBoardMessageComparator();
signers = new BulletinBoardSignature[1];
signerIDs = new ByteString[1];
signers[0] = new GenericBulletinBoardSignature(new ECDSASignature());
signerIDs[0] = signers[0].getSignerID();
InputStream keyStream = BulletinBoardSynchronizerTest.class.getResourceAsStream(KEYFILE_EXAMPLE);
char[] password = KEYFILE_PASSWORD1.toCharArray();
try {
KeyStore.Builder keyStoreBuilder = signers[0].getPKCS12KeyStoreBuilder(keyStream, password);
signers[0].loadSigningCertificate(keyStoreBuilder);
signers[0].loadVerificationCertificates(BulletinBoardSynchronizerTest.class.getResourceAsStream(CERT1_PEM_EXAMPLE));
} catch (IOException e) {
System.err.println("Failed reading from signature file " + e.getMessage());
fail("Failed reading from signature file " + e.getMessage());
} catch (CertificateException e) {
System.err.println("Failed reading certificate " + e.getMessage());
fail("Failed reading certificate " + e.getMessage());
} catch (KeyStoreException e) {
System.err.println("Failed reading keystore " + e.getMessage());
fail("Failed reading keystore " + e.getMessage());
} catch (NoSuchAlgorithmException e) {
System.err.println("Couldn't find signing algorithm " + e.getMessage());
fail("Couldn't find signing algorithm " + e.getMessage());
} catch (UnrecoverableKeyException e) {
System.err.println("Couldn't find signing key " + e.getMessage());
fail("Couldn't find signing key " + e.getMessage());
}
signerIDs[0] = signers[0].getSignerID();
testCount = 0;
}
@Before
public void init() throws CommunicationException {
DeletableBulletinBoardServer remoteServer = new BulletinBoardSQLServer(new H2QueryProvider(REMOTE_SERVER_ADDRESS + testCount));
remoteServer.init();
remoteClient = new LocalBulletinBoardClient(
remoteServer,
THREAD_NUM,
SUBSCRIPTION_INTERVAL);
DeletableBulletinBoardServer localServer = new BulletinBoardSQLServer(new H2QueryProvider(LOCAL_SERVER_ADDRESS + testCount));
localServer.init();
localClient = new LocalBulletinBoardClient(
localServer,
THREAD_NUM,
SUBSCRIPTION_INTERVAL);
synchronizer = new SimpleBulletinBoardSynchronizer(SYNC_SLEEP_INTERVAL, SYNC_WAIT_CAP);
synchronizer.init(localClient, remoteClient);
semaphore = new Semaphore(0);
thrown = new LinkedList<>();
testCount++;
}
private class SyncStatusCallback implements FutureCallback<SyncStatus> {
private final SyncStatus statusToWaitFor;
private AtomicBoolean stillWaiting;
public SyncStatusCallback(SyncStatus statusToWaitFor) {
this.statusToWaitFor = statusToWaitFor;
stillWaiting = new AtomicBoolean(true);
}
@Override
public void onSuccess(SyncStatus result) {
if (result == statusToWaitFor && stillWaiting.compareAndSet(true, false)){
semaphore.release();
}
}
@Override
public void onFailure(Throwable t) {
thrown.add(t);
if (stillWaiting.compareAndSet(true,false)) {
semaphore.release();
}
}
}
private class MessageCountCallback implements FutureCallback<Integer> {
private int[] expectedCounts;
private int currentIteration;
public MessageCountCallback(int[] expectedCounts) {
this.expectedCounts = expectedCounts;
this.currentIteration = 0;
}
@Override
public void onSuccess(Integer result) {
if (currentIteration < expectedCounts.length){
if (result != expectedCounts[currentIteration]){
onFailure(new AssertionError("Wrong message count. Expected " + expectedCounts[currentIteration] + " but received " + result));
currentIteration = expectedCounts.length;
return;
}
}
currentIteration++;
if (currentIteration == expectedCounts.length)
semaphore.release();
}
@Override
public void onFailure(Throwable t) {
thrown.add(t);
semaphore.release();
}
}
@Test
public void testSync() throws SignatureException, CommunicationException, InterruptedException {
Timestamp timestamp = Timestamp.newBuilder()
.setSeconds(15252162)
.setNanos(85914)
.build();
BulletinBoardMessage msg = messageGenerator.generateRandomMessage(signers, timestamp, 10, 10);
MessageID msgID = localClient.postMessage(msg);
timestamp = Timestamp.newBuilder()
.setSeconds(51511653)
.setNanos(3625)
.build();
BulletinBoardMessage batchMessage = messageGenerator.generateRandomMessage(signers,timestamp, 100, 10);
MessageID batchMsgID = localClient.postAsBatch(batchMessage, 10);
BulletinBoardMessage test = localClient.readMessage(batchMsgID);
BulletinBoardMessage stub = localClient.readMessages(MessageFilterList.newBuilder()
.addFilter(MessageFilter.newBuilder()
.setType(FilterType.MSG_ID)
.setId(batchMsgID.getID())
.build())
.build()).get(0);
BulletinBoardMessage test2 = localClient.readBatchData(stub);
synchronizer.subscribeToSyncStatus(new SyncStatusCallback(SyncStatus.SYNCHRONIZED));
int[] expectedCounts = {2,0};
synchronizer.subscribeToRemainingMessagesCount(new MessageCountCallback(expectedCounts));
Thread syncThread = new Thread(synchronizer);
syncThread.start();
if (!semaphore.tryAcquire(2, 4000, TimeUnit.MILLISECONDS)) {
thrown.add(new TimeoutException("Timeout occurred while waiting for synchronizer to sync."));
}
synchronizer.stop();
syncThread.join();
if (thrown.size() > 0) {
for (Throwable t : thrown)
System.err.println(t.getMessage());
assertThat("Exception thrown by Synchronizer: " + thrown.get(0).getMessage(), false);
}
List<BulletinBoardMessage> msgList = remoteClient.readMessages(MessageFilterList.newBuilder()
.addFilter(MessageFilter.newBuilder()
.setType(FilterType.MSG_ID)
.setId(msgID.getID())
.build())
.build());
assertThat("Wrong number of messages returned.", msgList.size() == 1);
assertThat("Returned message is not equal to original one", messageComparator.compare(msgList.get(0),msg) == 0);
BulletinBoardMessage returnedBatchMsg = remoteClient.readMessage(batchMsgID);
assertThat("Returned batch does not equal original one.", messageComparator.compare(returnedBatchMsg, batchMessage) == 0);
}
@Test
public void testServerError() throws SignatureException, CommunicationException, InterruptedException {
Timestamp timestamp = Timestamp.newBuilder()
.setSeconds(945736256)
.setNanos(276788)
.build();
BulletinBoardMessage msg = messageGenerator.generateRandomMessage(signers, timestamp, 10, 10);
remoteClient.close();
synchronizer.subscribeToSyncStatus(new SyncStatusCallback(SyncStatus.SERVER_ERROR));
localClient.postMessage(msg);
Thread thread = new Thread(synchronizer);
thread.start();
if (!semaphore.tryAcquire(4000, TimeUnit.MILLISECONDS)) {
thrown.add(new TimeoutException("Timeout occurred while waiting for synchronizer to sync."));
}
synchronizer.stop();
thread.join();
}
@After
public void close() {
if (thrown.size() > 0) {
for (Throwable t : thrown) {
System.err.println(t.getMessage());
}
assertThat("Exception thrown by Synchronizer: " + thrown.get(0).getMessage(), false);
}
synchronizer.stop();
localClient.close();
remoteClient.close();
}
}

View File

@ -0,0 +1,111 @@
package meerkat.bulletinboard;
import meerkat.bulletinboard.sqlserver.BulletinBoardSQLServer;
import meerkat.bulletinboard.sqlserver.H2QueryProvider;
import meerkat.comm.CommunicationException;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.security.SignatureException;
import java.util.LinkedList;
import java.util.List;
/**
* Created by Arbel on 6/27/2016.
*/
public class CachedBulletinBoardClientTest {
private static final int THREAD_NUM = 3;
private static final String LOCAL_DB_NAME = "localDB";
private static final String REMOTE_DB_NAME = "remoteDB";
private static final String QUEUE_DB_NAME = "queueDB";
private static final int SUBSRCIPTION_DELAY = 500;
private static final int SYNC_DELAY = 500;
// Testers
private CachedBulletinBoardClient cachedClient;
private GenericBulletinBoardClientTester clientTest;
private GenericSubscriptionClientTester subscriptionTester;
public CachedBulletinBoardClientTest() throws CommunicationException {
DeletableBulletinBoardServer localServer = new BulletinBoardSQLServer(new H2QueryProvider(LOCAL_DB_NAME));
localServer.init();
LocalBulletinBoardClient localClient = new LocalBulletinBoardClient(localServer, THREAD_NUM, SUBSRCIPTION_DELAY);
DeletableBulletinBoardServer remoteServer = new BulletinBoardSQLServer(new H2QueryProvider(REMOTE_DB_NAME));
remoteServer.init();
LocalBulletinBoardClient remoteClient = new LocalBulletinBoardClient(remoteServer, THREAD_NUM, SUBSRCIPTION_DELAY);
DeletableBulletinBoardServer queueServer = new BulletinBoardSQLServer(new H2QueryProvider(QUEUE_DB_NAME));
queueServer.init();
LocalBulletinBoardClient queueClient = new LocalBulletinBoardClient(queueServer, THREAD_NUM, SUBSRCIPTION_DELAY);
List<SubscriptionBulletinBoardClient> clientList = new LinkedList<>();
clientList.add(remoteClient);
BulletinBoardSubscriber subscriber = new ThreadedBulletinBoardSubscriber(clientList, localClient);
cachedClient = new CachedBulletinBoardClient(localClient, remoteClient, subscriber, queueClient, SYNC_DELAY, SYNC_DELAY);
subscriptionTester = new GenericSubscriptionClientTester(cachedClient);
clientTest = new GenericBulletinBoardClientTester(cachedClient, 87351);
}
// Test methods
/**
* Takes care of initializing the client and the test resources
*/
@Before
public void init(){
clientTest.init();
}
/**
* Closes the client and makes sure the test fails when an exception occurred in a separate thread
*/
@After
public void close() {
cachedClient.close();
clientTest.close();
}
@Test
public void testPost() {
clientTest.testPost();
}
@Test
public void testBatchPost() throws CommunicationException, SignatureException, InterruptedException {
clientTest.testBatchPost();
}
@Test
public void testCompleteBatchPost() throws CommunicationException, SignatureException, InterruptedException {
clientTest.testCompleteBatchPost();
}
@Test
public void testSubscription() throws SignatureException, CommunicationException {
// subscriptionTester.init();
// subscriptionTester.subscriptionTest();
// subscriptionTester.close();
}
}

View File

@ -5,10 +5,13 @@ import com.google.protobuf.ByteString;
import com.google.protobuf.Timestamp;
import meerkat.comm.CommunicationException;
import meerkat.crypto.concrete.ECDSASignature;
import meerkat.protobuf.BulletinBoardAPI.*;
import meerkat.crypto.concrete.SHA256Digest;
import meerkat.protobuf.BulletinBoardApi.*;
import meerkat.protobuf.Crypto;
import meerkat.util.BulletinBoardMessageComparator;
import meerkat.util.BulletinBoardMessageGenerator;
import meerkat.util.BulletinBoardUtils;
import meerkat.bulletinboard.AsyncBulletinBoardClient.BatchIdentifier;
import java.io.IOException;
import java.io.InputStream;
@ -28,7 +31,7 @@ public class GenericBulletinBoardClientTester {
// Signature resources
private GenericBatchDigitalSignature signers[];
private BulletinBoardSignature signers[];
private ByteString[] signerIDs;
private static String KEYFILE_EXAMPLE = "/certs/enduser-certs/user1-key-with-password-secret.p12";
@ -45,28 +48,29 @@ public class GenericBulletinBoardClientTester {
private AsyncBulletinBoardClient bulletinBoardClient;
private PostCallback postCallback;
private PostCallback failPostCallback = new PostCallback(true,false);
private RedundancyCallback redundancyCallback;
private ReadCallback readCallback;
private ReadBatchCallback readBatchCallback;
// Sync and misc
private Semaphore jobSemaphore;
private Vector<Throwable> thrown;
private Random random;
private BulletinBoardMessageGenerator generator;
private BulletinBoardDigest digest;
// Constructor
public GenericBulletinBoardClientTester(AsyncBulletinBoardClient bulletinBoardClient){
public GenericBulletinBoardClientTester(AsyncBulletinBoardClient bulletinBoardClient, int seed){
this.bulletinBoardClient = bulletinBoardClient;
signers = new GenericBatchDigitalSignature[2];
signers = new GenericBulletinBoardSignature[2];
signerIDs = new ByteString[signers.length];
signers[0] = new GenericBatchDigitalSignature(new ECDSASignature());
signers[1] = new GenericBatchDigitalSignature(new ECDSASignature());
signers[0] = new GenericBulletinBoardSignature(new ECDSASignature());
signers[1] = new GenericBulletinBoardSignature(new ECDSASignature());
InputStream keyStream = getClass().getResourceAsStream(KEYFILE_EXAMPLE);
char[] password = KEYFILE_PASSWORD1.toCharArray();
@ -108,6 +112,10 @@ public class GenericBulletinBoardClientTester {
fail("Couldn't find signing key " + e.getMessage());
}
this.random = new Random(seed);
this.generator = new BulletinBoardMessageGenerator(random);
this.digest = new GenericBulletinBoardDigest(new SHA256Digest());
}
// Callback definitions
@ -138,16 +146,21 @@ public class GenericBulletinBoardClientTester {
@Override
public void onSuccess(Boolean msg) {
System.err.println("Post operation completed");
jobSemaphore.release();
//TODO: Change Assert mechanism to exception one
if (isAssert) {
if (assertValue) {
assertThat("Post operation failed", msg, is(Boolean.TRUE));
if (assertValue && !msg) {
genericHandleFailure(new AssertionError("Post operation failed"));
} else if (!assertValue && msg){
genericHandleFailure(new AssertionError("Post operation succeeded unexpectedly"));
} else {
assertThat("Post operation succeeded unexpectedly", msg, is(Boolean.FALSE));
jobSemaphore.release();
}
} else {
jobSemaphore.release();
}
}
@Override
@ -210,21 +223,24 @@ public class GenericBulletinBoardClientTester {
}
}
private class ReadBatchCallback implements FutureCallback<CompleteBatch> {
private class ReadBatchCallback implements FutureCallback<BulletinBoardMessage>{
private CompleteBatch expectedBatch;
private BulletinBoardMessage expectedMsg;
public ReadBatchCallback(CompleteBatch expectedBatch) {
this.expectedBatch = expectedBatch;
public ReadBatchCallback(BulletinBoardMessage expectedMsg) {
this.expectedMsg = expectedMsg;
}
@Override
public void onSuccess(CompleteBatch batch) {
public void onSuccess(BulletinBoardMessage msg) {
System.err.println(batch);
jobSemaphore.release();
BulletinBoardMessageComparator msgComparator = new BulletinBoardMessageComparator();
assertThat("Batch returned is incorrect", batch, is(equalTo(expectedBatch)));
if (msgComparator.compare(msg, expectedMsg) != 0) {
genericHandleFailure(new AssertionError("Batch read returned different message.\nExpected:" + expectedMsg + "\nRecieved:" + msg + "\n"));
} else {
jobSemaphore.release();
}
}
@ -234,59 +250,6 @@ public class GenericBulletinBoardClientTester {
}
}
// Randomness generators
private byte randomByte(){
return (byte) random.nextInt();
}
private byte[] randomByteArray(int length) {
byte[] randomBytes = new byte[length];
for (int i = 0; i < length ; i++){
randomBytes[i] = randomByte();
}
return randomBytes;
}
private CompleteBatch createRandomBatch(int signer, int batchId, int length) throws SignatureException {
CompleteBatch completeBatch = new CompleteBatch();
// Create data
completeBatch.setBeginBatchMessage(BeginBatchMessage.newBuilder()
.setSignerId(signerIDs[signer])
.setBatchId(batchId)
.addTag("Test")
.build());
for (int i = 0 ; i < length ; i++){
BatchData batchData = BatchData.newBuilder()
.setData(ByteString.copyFrom(randomByteArray(i)))
.build();
completeBatch.appendBatchData(batchData);
}
completeBatch.setTimestamp(Timestamp.newBuilder()
.setSeconds(Math.abs(90))
.setNanos(50)
.build());
signers[signer].updateContent(completeBatch);
completeBatch.setSignature(signers[signer].sign());
return completeBatch;
}
// Test methods
/**
@ -311,7 +274,13 @@ public class GenericBulletinBoardClientTester {
public void close() {
if (thrown.size() > 0) {
for (Throwable t : thrown){
System.err.println(t.getMessage());
}
assert false;
}
}
@ -319,7 +288,7 @@ public class GenericBulletinBoardClientTester {
/**
* Tests the standard post, redundancy and read methods
*/
public void postTest() {
public void testPost() {
byte[] b1 = {(byte) 1, (byte) 2, (byte) 3, (byte) 4};
byte[] b2 = {(byte) 11, (byte) 12, (byte) 13, (byte) 14};
@ -395,59 +364,69 @@ public class GenericBulletinBoardClientTester {
/**
* Tests posting a batch by parts
* Also tests not being able to post to a closed batch
* @throws CommunicationException, SignatureException, InterruptedException
*/
public void testBatchPost() throws CommunicationException, SignatureException, InterruptedException {
final int SIGNER = 1;
final int BATCH_ID = 100;
final int BATCH_LENGTH = 100;
final int CHUNK_SIZE = 10;
final int TAG_NUM = 10;
CompleteBatch completeBatch = createRandomBatch(SIGNER, BATCH_ID, BATCH_LENGTH);
final Timestamp timestamp = Timestamp.newBuilder()
.setSeconds(141515)
.setNanos(859018)
.build();
final BulletinBoardMessage msg = generator.generateRandomMessage(signers, timestamp, BATCH_LENGTH, TAG_NUM);
// Begin batch
bulletinBoardClient.beginBatch(completeBatch.getBeginBatchMessage(), postCallback);
bulletinBoardClient.beginBatch(msg.getMsg().getTagList(), new FutureCallback<BatchIdentifier>() {
@Override
public void onSuccess(final BatchIdentifier identifier) {
bulletinBoardClient.postBatchData(identifier, BulletinBoardUtils.breakToBatch(msg, CHUNK_SIZE), new FutureCallback<Boolean>() {
@Override
public void onSuccess(Boolean result) {
bulletinBoardClient.closeBatch(identifier, msg.getMsg().getTimestamp(), msg.getSigList(), new FutureCallback<Boolean>() {
@Override
public void onSuccess(Boolean result) {
jobSemaphore.release();
}
@Override
public void onFailure(Throwable t) {
genericHandleFailure(t);
}
});
}
@Override
public void onFailure(Throwable t) {
genericHandleFailure(t);
}
});
}
@Override
public void onFailure(Throwable t) {
genericHandleFailure(t);
}
});
jobSemaphore.acquire();
// Post data
digest.reset();
digest.update(msg);
bulletinBoardClient.postBatchData(signerIDs[SIGNER], BATCH_ID, completeBatch.getBatchDataList(), postCallback);
jobSemaphore.acquire();
// Close batch
CloseBatchMessage closeBatchMessage = completeBatch.getCloseBatchMessage();
bulletinBoardClient.closeBatch(closeBatchMessage, postCallback);
jobSemaphore.acquire();
// Attempt to open batch again
bulletinBoardClient.beginBatch(completeBatch.getBeginBatchMessage(), failPostCallback);
// Attempt to add batch data
bulletinBoardClient.postBatchData(signerIDs[SIGNER], BATCH_ID, completeBatch.getBatchDataList(), failPostCallback);
jobSemaphore.acquire(2);
// Read batch data
BatchSpecificationMessage batchSpecificationMessage =
BatchSpecificationMessage.newBuilder()
.setSignerId(signerIDs[SIGNER])
.setBatchId(BATCH_ID)
.setStartPosition(0)
.build();
readBatchCallback = new ReadBatchCallback(completeBatch);
bulletinBoardClient.readBatch(batchSpecificationMessage, readBatchCallback);
bulletinBoardClient.readMessage(digest.digestAsMessageID(), new ReadBatchCallback(msg));
jobSemaphore.acquire();
@ -455,62 +434,61 @@ public class GenericBulletinBoardClientTester {
/**
* Posts a complete batch message
* Checks reading of the message
* Checks reading of the message in two parts
* @throws CommunicationException, SignatureException, InterruptedException
*/
public void testCompleteBatchPost() throws CommunicationException, SignatureException, InterruptedException {
final int SIGNER = 0;
final int BATCH_ID = 101;
final int BATCH_LENGTH = 50;
final int BATCH_LENGTH = 100;
final int CHUNK_SIZE = 99;
final int TAG_NUM = 8;
final Timestamp timestamp = Timestamp.newBuilder()
.setSeconds(7776151)
.setNanos(252616)
.build();
final BulletinBoardMessage msg = generator.generateRandomMessage(signers, timestamp, BATCH_LENGTH, TAG_NUM);
// Post batch
CompleteBatch completeBatch = createRandomBatch(SIGNER, BATCH_ID, BATCH_LENGTH);
bulletinBoardClient.postBatch(completeBatch,postCallback);
MessageID msgID = bulletinBoardClient.postAsBatch(msg, CHUNK_SIZE, postCallback);
jobSemaphore.acquire();
// Read batch
BatchSpecificationMessage batchSpecificationMessage =
BatchSpecificationMessage.newBuilder()
.setSignerId(signerIDs[SIGNER])
.setBatchId(BATCH_ID)
.setStartPosition(0)
.build();
MessageFilterList filterList = MessageFilterList.newBuilder()
.addFilter(MessageFilter.newBuilder()
.setType(FilterType.MSG_ID)
.setId(msgID.getID())
.build())
.build();
readBatchCallback = new ReadBatchCallback(completeBatch);
bulletinBoardClient.readMessages(filterList, new FutureCallback<List<BulletinBoardMessage>>() {
bulletinBoardClient.readBatch(batchSpecificationMessage, readBatchCallback);
@Override
public void onSuccess(List<BulletinBoardMessage> msgList) {
jobSemaphore.acquire();
if (msgList.size() != 1) {
}
genericHandleFailure(new AssertionError("Wrong number of stubs returned. Expected: 1; Found: " + msgList.size()));
/**
* Tests that an unopened batch cannot be closed
* @throws CommunicationException, InterruptedException
*/
public void testInvalidBatchClose() throws CommunicationException, InterruptedException {
} else {
final int NON_EXISTENT_BATCH_ID = 999;
BulletinBoardMessage retrievedMsg = msgList.get(0);
bulletinBoardClient.readBatchData(retrievedMsg, new ReadBatchCallback(msg));
CloseBatchMessage closeBatchMessage =
CloseBatchMessage.newBuilder()
.setBatchId(NON_EXISTENT_BATCH_ID)
.setBatchLength(1)
.setSig(Crypto.Signature.getDefaultInstance())
.setTimestamp(Timestamp.newBuilder()
.setSeconds(9)
.setNanos(12)
.build())
.build();
}
// Try to close the (unopened) batch;
}
bulletinBoardClient.closeBatch(closeBatchMessage, failPostCallback);
@Override
public void onFailure(Throwable t) {
genericHandleFailure(t);
}
});
jobSemaphore.acquire();

View File

@ -5,7 +5,7 @@ import com.google.protobuf.ByteString;
import com.google.protobuf.Timestamp;
import meerkat.comm.CommunicationException;
import meerkat.crypto.concrete.ECDSASignature;
import meerkat.protobuf.BulletinBoardAPI.*;
import meerkat.protobuf.BulletinBoardApi.*;
import meerkat.util.BulletinBoardMessageComparator;
import meerkat.util.BulletinBoardMessageGenerator;
@ -16,9 +16,6 @@ import java.security.cert.CertificateException;
import java.util.*;
import java.util.concurrent.Semaphore;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.startsWith;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;
/**
@ -26,7 +23,7 @@ import static org.junit.Assert.fail;
*/
public class GenericSubscriptionClientTester {
private GenericBatchDigitalSignature signers[];
private BulletinBoardSignature signers[];
private ByteString[] signerIDs;
private static String KEYFILE_EXAMPLE = "/certs/enduser-certs/user1-key-with-password-secret.p12";
@ -38,7 +35,7 @@ public class GenericSubscriptionClientTester {
private static String CERT1_PEM_EXAMPLE = "/certs/enduser-certs/user1.crt";
private static String CERT3_PEM_EXAMPLE = "/certs/enduser-certs/user3.crt";
private SubscriptionAsyncBulletinBoardClient bulletinBoardClient;
private SubscriptionBulletinBoardClient bulletinBoardClient;
private Random random;
private BulletinBoardMessageGenerator generator;
@ -46,14 +43,14 @@ public class GenericSubscriptionClientTester {
private Semaphore jobSemaphore;
private Vector<Throwable> thrown;
public GenericSubscriptionClientTester(SubscriptionAsyncBulletinBoardClient bulletinBoardClient){
public GenericSubscriptionClientTester(SubscriptionBulletinBoardClient bulletinBoardClient){
this.bulletinBoardClient = bulletinBoardClient;
signers = new GenericBatchDigitalSignature[2];
signers = new BulletinBoardSignature[2];
signerIDs = new ByteString[signers.length];
signers[0] = new GenericBatchDigitalSignature(new ECDSASignature());
signers[1] = new GenericBatchDigitalSignature(new ECDSASignature());
signers[0] = new GenericBulletinBoardSignature(new ECDSASignature());
signers[1] = new GenericBulletinBoardSignature(new ECDSASignature());
InputStream keyStream = getClass().getResourceAsStream(KEYFILE_EXAMPLE);
char[] password = KEYFILE_PASSWORD1.toCharArray();
@ -181,17 +178,15 @@ public class GenericSubscriptionClientTester {
public void onFailure(Throwable t) {
System.err.println(t.getCause() + " " + t.getMessage());
thrown.add(t);
jobSemaphore.release(expectedMessages.size());
jobSemaphore.release();
stage = expectedMessages.size();
}
}
public void subscriptionTest() throws SignatureException, CommunicationException {
final int FIRST_POST_ID = 201;
final int SECOND_POST_ID = 202;
final String COMMON_TAG = "SUBSCRIPTION_TEST";
List<String> tags = new LinkedList<>();
tags.add(COMMON_TAG);
@ -207,9 +202,9 @@ public class GenericSubscriptionClientTester {
.build();
List<List<BulletinBoardMessage>> expectedMessages = new ArrayList<>(3);
expectedMessages.add(new LinkedList<BulletinBoardMessage>());
expectedMessages.add(new LinkedList<BulletinBoardMessage>());
expectedMessages.add(new LinkedList<BulletinBoardMessage>());
expectedMessages.add(new LinkedList<>());
expectedMessages.add(new LinkedList<>());
expectedMessages.add(new LinkedList<>());
expectedMessages.get(0).add(msg1);
expectedMessages.get(2).add(msg3);

View File

@ -7,12 +7,6 @@ import org.junit.Before;
import org.junit.Test;
import java.security.SignatureException;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.List;
import static org.junit.Assert.fail;
/**
* Created by Arbel Deutsch Peled on 05-Dec-15.
@ -30,30 +24,14 @@ public class LocalBulletinBoardClientTest {
public LocalBulletinBoardClientTest() throws CommunicationException {
H2QueryProvider queryProvider = new H2QueryProvider(DB_NAME) ;
H2QueryProvider queryProvider = new H2QueryProvider(DB_NAME);
try {
Connection conn = queryProvider.getDataSource().getConnection();
Statement stmt = conn.createStatement();
List<String> deletionQueries = queryProvider.getSchemaDeletionCommands();
for (String deletionQuery : deletionQueries) {
stmt.execute(deletionQuery);
}
} catch (SQLException e) {
System.err.println(e.getMessage());
throw new CommunicationException(e.getCause() + " " + e.getMessage());
}
BulletinBoardServer server = new BulletinBoardSQLServer(queryProvider);
server.init(DB_NAME);
DeletableBulletinBoardServer server = new BulletinBoardSQLServer(queryProvider);
server.init();
LocalBulletinBoardClient client = new LocalBulletinBoardClient(server, THREAD_NUM, SUBSRCIPTION_DELAY);
subscriptionTester = new GenericSubscriptionClientTester(client);
clientTest = new GenericBulletinBoardClientTester(client);
clientTest = new GenericBulletinBoardClientTester(client, 98354);
}
@ -81,9 +59,9 @@ public class LocalBulletinBoardClientTest {
}
@Test
public void postTest() {
public void testPost() {
clientTest.postTest();
clientTest.testPost();
}
@ -100,15 +78,9 @@ public class LocalBulletinBoardClientTest {
}
@Test
public void testInvalidBatchClose() throws CommunicationException, InterruptedException {
clientTest.testInvalidBatchClose();
}
@Test
public void testSubscription() throws SignatureException, CommunicationException {
subscriptionTester.init();
subscriptionTester.subscriptionTest();
subscriptionTester.close();

View File

@ -0,0 +1,104 @@
package meerkat.bulletinboard;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;
import meerkat.comm.CommunicationException;
import meerkat.protobuf.Voting.BulletinBoardClientParams;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.security.SignatureException;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.Executors;
/**
* Created by Arbel Deutsch Peled on 05-Dec-15.
*/
public class SingleServerBulletinBoardClientIntegrationTest {
// Server data
private static final String PROP_GETTY_URL = "gretty.httpBaseURI";
private static final String DEFAULT_BASE_URL = "http://localhost:8081";
private static final String BASE_URL = System.getProperty(PROP_GETTY_URL, DEFAULT_BASE_URL);
private static final int THREAD_NUM = 3;
private static final long FAIL_DELAY = 3000;
private static final long SUBSCRIPTION_INTERVAL = 500;
// Testers
private GenericBulletinBoardClientTester clientTest;
private GenericSubscriptionClientTester subscriptionTester;
public SingleServerBulletinBoardClientIntegrationTest(){
SingleServerBulletinBoardClient client = new SingleServerBulletinBoardClient(THREAD_NUM, FAIL_DELAY, SUBSCRIPTION_INTERVAL);
List<String> testDB = new LinkedList<>();
testDB.add(BASE_URL);
client.init(BulletinBoardClientParams.newBuilder()
.addAllBulletinBoardAddress(testDB)
.setMinRedundancy((float) 1.0)
.build());
clientTest = new GenericBulletinBoardClientTester(client, 981541);
subscriptionTester = new GenericSubscriptionClientTester(client);
}
// Test methods
/**
* Takes care of initializing the client and the test resources
*/
@Before
public void init(){
clientTest.init();
}
/**
* Closes the client and makes sure the test fails when an exception occurred in a separate thread
*/
@After
public void close() {
clientTest.close();
}
@Test
public void testPost() {
clientTest.testPost();
}
@Test
public void testBatchPost() throws CommunicationException, SignatureException, InterruptedException {
clientTest.testBatchPost();
}
@Test
public void testCompleteBatchPost() throws CommunicationException, SignatureException, InterruptedException {
clientTest.testCompleteBatchPost();
}
@Test
public void testSubscription() throws SignatureException, CommunicationException {
subscriptionTester.init();
subscriptionTester.subscriptionTest();
subscriptionTester.close();
}
}

View File

@ -28,7 +28,7 @@ public class ThreadedBulletinBoardClientIntegrationTest {
public ThreadedBulletinBoardClientIntegrationTest(){
ThreadedBulletinBoardClient client = new ThreadedBulletinBoardClient();
ThreadedBulletinBoardClient client = new ThreadedBulletinBoardClient(3,0,500);
List<String> testDB = new LinkedList<>();
testDB.add(BASE_URL);
@ -38,7 +38,7 @@ public class ThreadedBulletinBoardClientIntegrationTest {
.setMinRedundancy((float) 1.0)
.build());
clientTest = new GenericBulletinBoardClientTester(client);
clientTest = new GenericBulletinBoardClientTester(client, 52351);
}
@ -66,9 +66,9 @@ public class ThreadedBulletinBoardClientIntegrationTest {
}
@Test
public void postTest() {
public void testPost() {
clientTest.postTest();
clientTest.testPost();
}
@ -76,6 +76,7 @@ public class ThreadedBulletinBoardClientIntegrationTest {
public void testBatchPost() throws CommunicationException, SignatureException, InterruptedException {
clientTest.testBatchPost();
}
@Test
@ -85,11 +86,4 @@ public class ThreadedBulletinBoardClientIntegrationTest {
}
@Test
public void testInvalidBatchClose() throws CommunicationException, InterruptedException {
clientTest.testInvalidBatchClose();
}
}

View File

@ -0,0 +1,57 @@
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"project": {
"name": "bulletin-board-server-frontend"
},
"apps": [
{
"root": "src",
"outDir": "dist",
"assets": [
"assets",
"favicon.ico"
],
"index": "index.html",
"main": "main.ts",
"polyfills": "polyfills.ts",
"test": "test.ts",
"tsconfig": "tsconfig.app.json",
"testTsconfig": "tsconfig.spec.json",
"prefix": "app",
"styles": [
"styles.css"
],
"scripts": [],
"environmentSource": "environments/environment.ts",
"environments": {
"dev": "environments/environment.ts",
"prod": "environments/environment.prod.ts"
}
}
],
"e2e": {
"protractor": {
"config": "./protractor.conf.js"
}
},
"lint": [
{
"project": "src/tsconfig.app.json"
},
{
"project": "src/tsconfig.spec.json"
},
{
"project": "e2e/tsconfig.e2e.json"
}
],
"test": {
"karma": {
"config": "./karma.conf.js"
}
},
"defaults": {
"styleExt": "css",
"component": {}
}
}

View File

@ -0,0 +1,13 @@
# Editor configuration, see http://editorconfig.org
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true
[*.md]
max_line_length = off
trim_trailing_whitespace = false

View File

@ -0,0 +1,42 @@
# See http://help.github.com/ignore-files/ for more about ignoring files.
# compiled output
/dist
/tmp
/out-tsc
# dependencies
/node_modules
# IDEs and editors
/.idea
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
# IDE - VSCode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
# misc
/.sass-cache
/connect.lock
/coverage
/libpeerconnection.log
npm-debug.log
testem.log
/typings
# e2e
/e2e/*.js
/e2e/*.map
# System Files
.DS_Store
Thumbs.db

View File

@ -0,0 +1,28 @@
# BulletinBoardServerFrontend
This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 1.2.0.
## Development server
Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files.
## Code scaffolding
Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|module`.
## Build
Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `-prod` flag for a production build.
## Running unit tests
Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).
## Running end-to-end tests
Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/).
Before running the tests make sure you are serving the app via `ng serve`.
## Further help
To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md).

View File

@ -0,0 +1,60 @@
plugins {
id "com.moowork.node" version "1.2.0"
}
node {
download = true;
}
ext {
bundlejs = "src/app/bundle.js"
bundlets = "src/app/bundle.d.ts"
}
//task installAngularCli(type: NpmTask) {
// args = ['install', '@angular/cli']
//}
//npm_install.dependsOn(installAngularCli);
task printProtos {
doLast {
println getProtoFiles().join(" ")
}
}
printProtos.description = "List all the .proto files we can find."
def getProtoFiles() {
def protoFiles = []
rootProject.subprojects { proj ->
if (proj.hasProperty('sourceSets') && proj.sourceSets.hasProperty('main') &&
proj.sourceSets.main.hasProperty('proto')) {
protoFiles = protoFiles + proj.sourceSets.main.proto.getFiles();
}
}
return protoFiles;
}
task generateProtobufBundle(type: NpmTask) {
doFirst {
// We need do this in doFirst since getProtoFiles won't find all subprojects
// until after configuration is complete
def protoFiles = getProtoFiles()
generateProtobufBundle.args = ['run', 'protoc', '--' ] + protoFiles;
}
dependsOn npm_install
}
generateProtobufBundle.description = "Generate the bundle.js/bundle.d.ts files containing compiled protobufs"
npm_run_build.dependsOn(npm_install)
npm_run_build.dependsOn(generateProtobufBundle)
npm_start.dependsOn(generateProtobufBundle)
// It would be nice if this worked, but it seems to get stuck:
//npm_start.dependsOn(project(':bulletin-board-server').jettyStart);

View File

@ -0,0 +1,14 @@
import { BulletinBoardServerFrontendPage } from './app.po';
describe('bulletin-board-server-frontend App', () => {
let page: BulletinBoardServerFrontendPage;
beforeEach(() => {
page = new BulletinBoardServerFrontendPage();
});
it('should display welcome message', () => {
page.navigateTo();
expect(page.getParagraphText()).toEqual('Welcome to app!!');
});
});

View File

@ -0,0 +1,11 @@
import { browser, by, element } from 'protractor';
export class BulletinBoardServerFrontendPage {
navigateTo() {
return browser.get('/');
}
getParagraphText() {
return element(by.css('app-root h1')).getText();
}
}

View File

@ -0,0 +1,13 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"outDir": "../out-tsc/e2e",
"module": "commonjs",
"target": "es5",
"types": [
"jasmine",
"jasminewd2",
"node"
]
}
}

View File

@ -0,0 +1,32 @@
#!/usr/bin/env node
const { execFileSync } = require('child_process');
const path = require('path');
const isWin = (process.platform === 'win32');
const bundlejs = 'src/app/bundle.js'
const bundlets = 'src/app/bundle.d.ts'
const npm = isWin ? 'npm.cmd' : 'npm';
var args = process.argv.slice(2).map(function(x) {
return path.normalize(x);
});
var pbjs_args = [
'-t', 'static-module', '-w', 'commonjs', '-o', bundlejs
].concat(args);
console.log("Running pbjs " + pbjs_args.join(" "));
var out = execFileSync(npm, ['run', 'pbjs', '--'].concat(pbjs_args));
console.log("" + out);
var pbts_args = [
'-o', bundlets, bundlejs
];
console.log("Running pbts " + pbts_args.join(" "));
out = execFileSync(npm, ['run', 'pbts', '--'].concat(pbts_args));
console.log("" + out);

1
bulletin-board-server-frontend/gradlew vendored Symbolic link
View File

@ -0,0 +1 @@
../gradlew

View File

@ -0,0 +1,33 @@
// Karma configuration file, see link for more information
// https://karma-runner.github.io/0.13/config/configuration-file.html
module.exports = function (config) {
config.set({
basePath: '',
frameworks: ['jasmine', '@angular/cli'],
plugins: [
require('karma-jasmine'),
require('karma-chrome-launcher'),
require('karma-jasmine-html-reporter'),
require('karma-coverage-istanbul-reporter'),
require('@angular/cli/plugins/karma')
],
client:{
clearContext: false // leave Jasmine Spec Runner output visible in browser
},
coverageIstanbulReporter: {
reports: [ 'html', 'lcovonly' ],
fixWebpackSourcePaths: true
},
angularCli: {
environment: 'dev'
},
reporters: ['progress', 'kjhtml'],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,
browsers: ['Chrome'],
singleRun: false
});
};

View File

@ -0,0 +1,53 @@
{
"name": "bulletin-board-server-frontend",
"version": "0.0.0",
"license": "MIT",
"scripts": {
"ng": "ng",
"start": "ng serve --proxy-config proxy.conf.json",
"build": "ng build",
"test": "ng test",
"lint": "ng e2e",
"pbts": "pbts",
"pbjs": "pbjs",
"protoc" : "node ./generate-protobuf.js"
},
"private": true,
"dependencies": {
"@angular/animations": "^4.0.0",
"@angular/common": "^4.0.0",
"@angular/compiler": "^4.0.0",
"@angular/core": "^4.0.0",
"@angular/forms": "^4.0.0",
"@angular/http": "^4.0.0",
"@angular/platform-browser": "^4.0.0",
"@angular/platform-browser-dynamic": "^4.0.0",
"@angular/router": "^4.0.0",
"@types/long": "^3.0.31",
"core-js": "^2.4.1",
"protobufjs": "^6.8.0",
"rxjs": "^5.1.0",
"zone.js": "^0.8.4"
},
"devDependencies": {
"@angular/cli": "^1.2.0",
"@angular/compiler-cli": "^4.0.0",
"@angular/language-service": "^4.0.0",
"@types/jasmine": "~2.5.53",
"@types/jasminewd2": "~2.0.2",
"@types/node": "~6.0.60",
"codelyzer": "~3.0.1",
"jasmine-core": "~2.6.2",
"jasmine-spec-reporter": "~4.1.0",
"karma": "~1.7.0",
"karma-chrome-launcher": "~2.1.1",
"karma-cli": "~1.0.1",
"karma-coverage-istanbul-reporter": "^1.2.1",
"karma-jasmine": "~1.1.0",
"karma-jasmine-html-reporter": "^0.2.2",
"protractor": "~5.1.2",
"ts-node": "~3.0.4",
"tslint": "~5.3.2",
"typescript": "~2.3.3"
}
}

View File

@ -0,0 +1,28 @@
// Protractor configuration file, see link for more information
// https://github.com/angular/protractor/blob/master/lib/config.ts
const { SpecReporter } = require('jasmine-spec-reporter');
exports.config = {
allScriptsTimeout: 11000,
specs: [
'./e2e/**/*.e2e-spec.ts'
],
capabilities: {
'browserName': 'chrome'
},
directConnect: true,
baseUrl: 'http://localhost:4200/',
framework: 'jasmine',
jasmineNodeOpts: {
showColors: true,
defaultTimeoutInterval: 30000,
print: function() {}
},
onPrepare() {
require('ts-node').register({
project: 'e2e/tsconfig.e2e.json'
});
jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));
}
};

View File

@ -0,0 +1,6 @@
{
"/bbserver" : {
"target" : "http://localhost:8081/",
"secure" : false
}
}

View File

@ -0,0 +1,12 @@
<!--The content below is only a placeholder and can be replaced.-->
<div style="text-align:center">
<h1>
Welcome to {{title}}!!
</h1>
<h3>Current status: {{status}}</h3>
<img width="300" src="assets/meerkat-logo.svg">
<hr>
{{errorMessage}}
</div>

Some files were not shown because too many files have changed in this diff Show More