From 8f2cd2d576d466dd791db72a6c54348d69af8541 Mon Sep 17 00:00:00 2001 From: Ali Younis <ayounis@uci.edu> Date: Sat, 7 Jan 2017 15:23:39 -0800 Subject: [PATCH] Block Chain Transactions, Commits multiple parts version --- version2/backup/doc/iotcloud.aux | 18 + version2/backup/doc/iotcloud.log | 589 ++++ version2/backup/doc/iotcloud.pdf | Bin 0 -> 245596 bytes version2/{ => backup}/doc/iotcloud.tex | 0 version2/{ => backup}/doc/makefile | 0 version2/backup/src/java/.dir-locals.el | 2 + version2/backup/src/java/iotcloud/Abort.java | 63 + .../backup/src/java/iotcloud/CloudComm.java | 261 ++ version2/backup/src/java/iotcloud/Commit.java | 125 + version2/backup/src/java/iotcloud/Entry.java | 119 + .../src/java/iotcloud/Guard.java_backup | 163 + .../backup/src/java/iotcloud/IoTString.java | 105 + .../backup/src/java/iotcloud/KeyValue.java | 76 + .../backup/src/java/iotcloud/LastMessage.java | 55 + .../backup/src/java/iotcloud/Liveness.java | 11 + .../backup/src/java/iotcloud/LocalComm.java | 23 + version2/backup/src/java/iotcloud/Makefile | 17 + version2/backup/src/java/iotcloud/NewKey.java | 57 + version2/backup/src/java/iotcloud/Pair.java | 23 + .../src/java/iotcloud/PendingTransaction.java | 138 + .../src/java/iotcloud/RejectedMessage.java | 88 + .../src/java/iotcloud/ServerException.java | 8 + version2/backup/src/java/iotcloud/Slot.java | 222 ++ .../backup/src/java/iotcloud/SlotBuffer.java | 122 + .../backup/src/java/iotcloud/SlotIndexer.java | 31 + version2/backup/src/java/iotcloud/Table.java | 1728 ++++++++++ .../backup/src/java/iotcloud/TableStatus.java | 45 + version2/backup/src/java/iotcloud/Test.java | 1878 ++++++++++ .../backup/src/java/iotcloud/ThreeTuple.java | 29 + .../backup/src/java/iotcloud/Transaction.java | 154 + .../src/java/iotcloud/TransactionStatus.java | 53 + .../{ => backup}/src/java/iotcloud/issues.txt | 0 version2/{ => backup}/src/script/C.cfg | 0 version2/{ => backup}/src/script/java.cfg | 0 version2/{ => backup}/src/script/makefile | 0 version2/backup/src/server/.dir-locals.el | 2 + version2/backup/src/server/Makefile | 15 + version2/backup/src/server/README.txt | 32 + version2/backup/src/server/iotcloud.cpp | 40 + version2/backup/src/server/iotquery.cpp | 517 +++ version2/backup/src/server/iotquery.h | 68 + version2/src/java/iotcloud/Abort.java | 86 +- version2/src/java/iotcloud/CloudComm.java | 136 +- version2/src/java/iotcloud/Commit.java | 307 +- version2/src/java/iotcloud/CommitPart.java | 128 + version2/src/java/iotcloud/Entry.java | 27 +- version2/src/java/iotcloud/LocalComm.java | 10 +- version2/src/java/iotcloud/NewKey.java | 4 + version2/src/java/iotcloud/Pair.java | 28 +- .../src/java/iotcloud/PendingTransaction.java | 108 +- .../src/java/iotcloud/ServerException.java | 14 +- version2/src/java/iotcloud/Table.java | 2446 ++++++------- version2/src/java/iotcloud/Test.java | 3011 +++++++---------- version2/src/java/iotcloud/Transaction.java | 329 +- .../src/java/iotcloud/TransactionPart.java | 140 + .../src/java/iotcloud/TransactionStatus.java | 24 +- 56 files changed, 10274 insertions(+), 3401 deletions(-) create mode 100644 version2/backup/doc/iotcloud.aux create mode 100644 version2/backup/doc/iotcloud.log create mode 100644 version2/backup/doc/iotcloud.pdf rename version2/{ => backup}/doc/iotcloud.tex (100%) rename version2/{ => backup}/doc/makefile (100%) create mode 100644 version2/backup/src/java/.dir-locals.el create mode 100644 version2/backup/src/java/iotcloud/Abort.java create mode 100644 version2/backup/src/java/iotcloud/CloudComm.java create mode 100644 version2/backup/src/java/iotcloud/Commit.java create mode 100644 version2/backup/src/java/iotcloud/Entry.java create mode 100644 version2/backup/src/java/iotcloud/Guard.java_backup create mode 100644 version2/backup/src/java/iotcloud/IoTString.java create mode 100644 version2/backup/src/java/iotcloud/KeyValue.java create mode 100644 version2/backup/src/java/iotcloud/LastMessage.java create mode 100644 version2/backup/src/java/iotcloud/Liveness.java create mode 100644 version2/backup/src/java/iotcloud/LocalComm.java create mode 100644 version2/backup/src/java/iotcloud/Makefile create mode 100644 version2/backup/src/java/iotcloud/NewKey.java create mode 100644 version2/backup/src/java/iotcloud/Pair.java create mode 100644 version2/backup/src/java/iotcloud/PendingTransaction.java create mode 100644 version2/backup/src/java/iotcloud/RejectedMessage.java create mode 100644 version2/backup/src/java/iotcloud/ServerException.java create mode 100644 version2/backup/src/java/iotcloud/Slot.java create mode 100644 version2/backup/src/java/iotcloud/SlotBuffer.java create mode 100644 version2/backup/src/java/iotcloud/SlotIndexer.java create mode 100644 version2/backup/src/java/iotcloud/Table.java create mode 100644 version2/backup/src/java/iotcloud/TableStatus.java create mode 100644 version2/backup/src/java/iotcloud/Test.java create mode 100644 version2/backup/src/java/iotcloud/ThreeTuple.java create mode 100644 version2/backup/src/java/iotcloud/Transaction.java create mode 100644 version2/backup/src/java/iotcloud/TransactionStatus.java rename version2/{ => backup}/src/java/iotcloud/issues.txt (100%) rename version2/{ => backup}/src/script/C.cfg (100%) rename version2/{ => backup}/src/script/java.cfg (100%) rename version2/{ => backup}/src/script/makefile (100%) create mode 100644 version2/backup/src/server/.dir-locals.el create mode 100644 version2/backup/src/server/Makefile create mode 100644 version2/backup/src/server/README.txt create mode 100644 version2/backup/src/server/iotcloud.cpp create mode 100644 version2/backup/src/server/iotquery.cpp create mode 100644 version2/backup/src/server/iotquery.h create mode 100644 version2/src/java/iotcloud/CommitPart.java create mode 100644 version2/src/java/iotcloud/TransactionPart.java diff --git a/version2/backup/doc/iotcloud.aux b/version2/backup/doc/iotcloud.aux new file mode 100644 index 0000000..134fcf3 --- /dev/null +++ b/version2/backup/doc/iotcloud.aux @@ -0,0 +1,18 @@ +\relax +\@writefile{toc}{\contentsline {section}{\numberline {1}\textbf {Introduction}}{1}} +\@writefile{toc}{\contentsline {section}{\numberline {2}Approach}{1}} +\@writefile{toc}{\contentsline {subsection}{\numberline {2.1}Keys}{1}} +\@writefile{toc}{\contentsline {subsection}{\numberline {2.2}Entry layout}{1}} +\@writefile{toc}{\contentsline {subsection}{\numberline {2.3}Live status}{3}} +\@writefile{toc}{\contentsline {paragraph}{Validation procedure on client:}{4}} +\@writefile{toc}{\contentsline {subsection}{\numberline {2.4}Resizing Queue}{4}} +\@writefile{toc}{\contentsline {subsection}{\numberline {2.5}The Arbitrator}{4}} +\@writefile{toc}{\contentsline {section}{\numberline {3}Server Algorithm}{4}} +\@writefile{toc}{\contentsline {section}{\numberline {4}\textbf {Client}}{6}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.1}\textbf {Client Notation Conventions}}{6}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.2}\textbf {Client State}}{7}} +\@writefile{toc}{\contentsline {subsubsection}{\numberline {4.2.1}Constants}{7}} +\@writefile{toc}{\contentsline {subsubsection}{\numberline {4.2.2}Primitive Variables}{7}} +\@writefile{toc}{\contentsline {subsubsection}{\numberline {4.2.3}Sets and Lists}{7}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.3}Helper Functions}{8}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.4}Client Interfaces}{37}} diff --git a/version2/backup/doc/iotcloud.log b/version2/backup/doc/iotcloud.log new file mode 100644 index 0000000..f9f7b1e --- /dev/null +++ b/version2/backup/doc/iotcloud.log @@ -0,0 +1,589 @@ +This is pdfTeX, Version 3.14159265-2.6-1.40.16 (TeX Live 2015) (preloaded format=pdflatex 2015.5.24) 21 DEC 2016 15:40 +entering extended mode + restricted \write18 enabled. + %&-line parsing enabled. +**iotcloud.tex +(./iotcloud.tex +LaTeX2e <2015/01/01> +Babel <3.9l> and hyphenation patterns for 79 languages loaded. +(/usr/local/texlive/2015/texmf-dist/tex/latex/base/article.cls +Document Class: article 2014/09/29 v1.4h Standard LaTeX document class +(/usr/local/texlive/2015/texmf-dist/tex/latex/base/size11.clo +File: size11.clo 2014/09/29 v1.4h Standard LaTeX file (size option) +) +\c@part=\count79 +\c@section=\count80 +\c@subsection=\count81 +\c@subsubsection=\count82 +\c@paragraph=\count83 +\c@subparagraph=\count84 +\c@figure=\count85 +\c@table=\count86 +\abovecaptionskip=\skip41 +\belowcaptionskip=\skip42 +\bibindent=\dimen102 +) +(/usr/local/texlive/2015/texmf-dist/tex/latex/graphics/color.sty +Package: color 2014/10/28 v1.1a Standard LaTeX Color (DPC) + +(/usr/local/texlive/2015/texmf-dist/tex/latex/latexconfig/color.cfg +File: color.cfg 2007/01/18 v1.5 color configuration of teTeX/TeXLive +) +Package color Info: Driver file: pdftex.def on input line 142. + +(/usr/local/texlive/2015/texmf-dist/tex/latex/pdftex-def/pdftex.def +File: pdftex.def 2011/05/27 v0.06d Graphics/color for pdfTeX + +(/usr/local/texlive/2015/texmf-dist/tex/generic/oberdiek/infwarerr.sty +Package: infwarerr 2010/04/08 v1.3 Providing info/warning/error messages (HO) +) +(/usr/local/texlive/2015/texmf-dist/tex/generic/oberdiek/ltxcmds.sty +Package: ltxcmds 2011/11/09 v1.22 LaTeX kernel commands for general use (HO) +) +\Gread@gobject=\count87 +)) +(/usr/local/texlive/2015/texmf-dist/tex/latex/amscls/amsthm.sty +Package: amsthm 2015/03/04 v2.20.2 +\thm@style=\toks14 +\thm@bodyfont=\toks15 +\thm@headfont=\toks16 +\thm@notefont=\toks17 +\thm@headpunct=\toks18 +\thm@preskip=\skip43 +\thm@postskip=\skip44 +\thm@headsep=\skip45 +\dth@everypar=\toks19 +) +(/usr/local/texlive/2015/texmf-dist/tex/latex/amsmath/amsmath.sty +Package: amsmath 2013/01/14 v2.14 AMS math features +\@mathmargin=\skip46 + +For additional information on amsmath, use the `?' option. +(/usr/local/texlive/2015/texmf-dist/tex/latex/amsmath/amstext.sty +Package: amstext 2000/06/29 v2.01 + +(/usr/local/texlive/2015/texmf-dist/tex/latex/amsmath/amsgen.sty +File: amsgen.sty 1999/11/30 v2.0 +\@emptytoks=\toks20 +\ex@=\dimen103 +)) +(/usr/local/texlive/2015/texmf-dist/tex/latex/amsmath/amsbsy.sty +Package: amsbsy 1999/11/29 v1.2d +\pmbraise@=\dimen104 +) +(/usr/local/texlive/2015/texmf-dist/tex/latex/amsmath/amsopn.sty +Package: amsopn 1999/12/14 v2.01 operator names +) +\inf@bad=\count88 +LaTeX Info: Redefining \frac on input line 210. +\uproot@=\count89 +\leftroot@=\count90 +LaTeX Info: Redefining \overline on input line 306. +\classnum@=\count91 +\DOTSCASE@=\count92 +LaTeX Info: Redefining \ldots on input line 378. +LaTeX Info: Redefining \dots on input line 381. +LaTeX Info: Redefining \cdots on input line 466. +\Mathstrutbox@=\box26 +\strutbox@=\box27 +\big@size=\dimen105 +LaTeX Font Info: Redeclaring font encoding OML on input line 566. +LaTeX Font Info: Redeclaring font encoding OMS on input line 567. +\macc@depth=\count93 +\c@MaxMatrixCols=\count94 +\dotsspace@=\muskip10 +\c@parentequation=\count95 +\dspbrk@lvl=\count96 +\tag@help=\toks21 +\row@=\count97 +\column@=\count98 +\maxfields@=\count99 +\andhelp@=\toks22 +\eqnshift@=\dimen106 +\alignsep@=\dimen107 +\tagshift@=\dimen108 +\tagwidth@=\dimen109 +\totwidth@=\dimen110 +\lineht@=\dimen111 +\@envbody=\toks23 +\multlinegap=\skip47 +\multlinetaggap=\skip48 +\mathdisplay@stack=\toks24 +LaTeX Info: Redefining \[ on input line 2665. +LaTeX Info: Redefining \] on input line 2666. +) +(/usr/local/texlive/2015/texmf-dist/tex/latex/graphics/graphicx.sty +Package: graphicx 2014/10/28 v1.0g Enhanced LaTeX Graphics (DPC,SPQR) + +(/usr/local/texlive/2015/texmf-dist/tex/latex/graphics/keyval.sty +Package: keyval 2014/10/28 v1.15 key=value parser (DPC) +\KV@toks@=\toks25 +) +(/usr/local/texlive/2015/texmf-dist/tex/latex/graphics/graphics.sty +Package: graphics 2014/10/28 v1.0p Standard LaTeX Graphics (DPC,SPQR) + +(/usr/local/texlive/2015/texmf-dist/tex/latex/graphics/trig.sty +Package: trig 1999/03/16 v1.09 sin cos tan (DPC) +) +(/usr/local/texlive/2015/texmf-dist/tex/latex/latexconfig/graphics.cfg +File: graphics.cfg 2010/04/23 v1.9 graphics configuration of TeX Live +) +Package graphics Info: Driver file: pdftex.def on input line 94. +) +\Gin@req@height=\dimen112 +\Gin@req@width=\dimen113 +) +(/usr/local/texlive/2015/texmf-dist/tex/latex/jknapltx/mathrsfs.sty +Package: mathrsfs 1996/01/01 Math RSFS package v1.0 (jk) +\symrsfs=\mathgroup4 +) +(/usr/local/texlive/2015/texmf-dist/tex/latex/amsfonts/amssymb.sty +Package: amssymb 2013/01/14 v3.01 AMS font symbols + +(/usr/local/texlive/2015/texmf-dist/tex/latex/amsfonts/amsfonts.sty +Package: amsfonts 2013/01/14 v3.01 Basic AMSFonts support +\symAMSa=\mathgroup5 +\symAMSb=\mathgroup6 +LaTeX Font Info: Overwriting math alphabet `\mathfrak' in version `bold' +(Font) U/euf/m/n --> U/euf/b/n on input line 106. +)) +(/usr/local/texlive/2015/texmf-dist/tex/latex/algorithmicx/algpseudocode.sty +Package: algpseudocode + +(/usr/local/texlive/2015/texmf-dist/tex/latex/base/ifthen.sty +Package: ifthen 2014/09/29 v1.1c Standard LaTeX ifthen package (DPC) +) +(/usr/local/texlive/2015/texmf-dist/tex/latex/algorithmicx/algorithmicx.sty +Package: algorithmicx 2005/04/27 v1.2 Algorithmicx + +Document Style algorithmicx 1.2 - a greatly improved `algorithmic' style +\c@ALG@line=\count100 +\c@ALG@rem=\count101 +\c@ALG@nested=\count102 +\ALG@tlm=\skip49 +\ALG@thistlm=\skip50 +\c@ALG@Lnr=\count103 +\c@ALG@blocknr=\count104 +\c@ALG@storecount=\count105 +\c@ALG@tmpcounter=\count106 +\ALG@tmplength=\skip51 +) +Document Style - pseudocode environments for use with the `algorithmicx' style +) (/usr/local/texlive/2015/texmf-dist/tex/generic/xypic/xy.sty +(/usr/local/texlive/2015/texmf-dist/tex/generic/xypic/xy.tex Bootstrap'ing: +catcodes, docmode, +(/usr/local/texlive/2015/texmf-dist/tex/generic/xypic/xyrecat.tex) +(/usr/local/texlive/2015/texmf-dist/tex/generic/xypic/xyidioms.tex) + + Xy-pic version 3.8.9 <2013/10/06> + Copyright (c) 1991-2013 by Kristoffer H. Rose <krisrose@tug.org> and others + Xy-pic is free software: see the User's Guide for details. + +Loading kernel: messages; fonts; allocations: state, +\X@c=\dimen114 +\Y@c=\dimen115 +\U@c=\dimen116 +\D@c=\dimen117 +\L@c=\dimen118 +\R@c=\dimen119 +\Edge@c=\toks26 +\X@p=\dimen120 +\Y@p=\dimen121 +\U@p=\dimen122 +\D@p=\dimen123 +\L@p=\dimen124 +\R@p=\dimen125 +\Edge@p=\toks27 +\X@origin=\dimen126 +\Y@origin=\dimen127 +\X@xbase=\dimen128 +\Y@xbase=\dimen129 +\X@ybase=\dimen130 +\Y@ybase=\dimen131 +\X@min=\dimen132 +\Y@min=\dimen133 +\X@max=\dimen134 +\Y@max=\dimen135 +\lastobjectbox@=\box28 +\zerodotbox@=\box29 +\almostz@=\dimen136 + direction, +\d@X=\dimen137 +\d@Y=\dimen138 +\K@=\count107 +\KK@=\count108 +\Direction=\count109 +\K@dXdY=\dimen139 +\K@dYdX=\dimen140 +\xyread@=\read1 +\xywrite@=\write3 +\csp@=\count110 +\quotPTK@=\dimen141 + +utility macros; pictures: \xy, positions, +\swaptoks@@=\toks28 +\connectobjectbox@@=\box30 + objects, +\styletoks@=\toks29 + decorations; +kernel objects: directionals, circles, text; options; algorithms: directions, +edges, connections; Xy-pic loaded) +(/usr/local/texlive/2015/texmf-dist/tex/generic/oberdiek/ifpdf.sty +Package: ifpdf 2011/01/30 v2.3 Provides the ifpdf switch (HO) +Package ifpdf Info: pdfTeX in PDF mode is detected. +) +Package: xy 2013/10/06 Xy-pic version 3.8.9 + +(/usr/local/texlive/2015/texmf-dist/tex/generic/xypic/xyall.tex + Xy-pic option: All features v.3.8 +(/usr/local/texlive/2015/texmf-dist/tex/generic/xypic/xycurve.tex + Xy-pic option: Curve and Spline extension v.3.12 curve, +\crv@cnt@=\count111 +\crvpts@=\toks30 +\splinebox@=\box31 +\splineval@=\dimen142 +\splinedepth@=\dimen143 +\splinetol@=\dimen144 +\splinelength@=\dimen145 + circles, +\L@=\dimen146 + loaded) +(/usr/local/texlive/2015/texmf-dist/tex/generic/xypic/xyframe.tex + Xy-pic option: Frame and Bracket extension v.3.14 loaded) +(/usr/local/texlive/2015/texmf-dist/tex/generic/xypic/xycmtip.tex + Xy-pic option: Computer Modern tip extension v.3.7 +(/usr/local/texlive/2015/texmf-dist/tex/generic/xypic/xytips.tex + Xy-pic option: More Tips extension v.3.11 loaded) loaded) +(/usr/local/texlive/2015/texmf-dist/tex/generic/xypic/xyline.tex + Xy-pic option: Line styles extension v.3.10 +\xylinethick@=\dimen147 + loaded) +(/usr/local/texlive/2015/texmf-dist/tex/generic/xypic/xyrotate.tex + Xy-pic option: Rotate and Scale extension v.3.8 loaded) +(/usr/local/texlive/2015/texmf-dist/tex/generic/xypic/xycolor.tex + Xy-pic option: Colour extension v.3.11 loaded) +(/usr/local/texlive/2015/texmf-dist/tex/generic/xypic/xymatrix.tex + Xy-pic option: Matrix feature v.3.14 +\Row=\count112 +\Col=\count113 +\queue@=\toks31 +\queue@@=\toks32 +\qcount@=\count114 +\qcount@@=\count115 +\matrixsize@=\count116 + loaded) +(/usr/local/texlive/2015/texmf-dist/tex/generic/xypic/xyarrow.tex + Xy-pic option: Arrow and Path feature v.3.9 path, \ar, loaded) +(/usr/local/texlive/2015/texmf-dist/tex/generic/xypic/xygraph.tex + Xy-pic option: Graph feature v.3.11 loaded) loaded) +(/usr/local/texlive/2015/texmf-dist/tex/generic/xypic/xypdf.tex + Xy-pic option: PDF driver v.1.7 Xy-pic pdf driver: `color' extension support +(/usr/local/texlive/2015/texmf-dist/tex/generic/xypic/xypdf-co.tex loaded) +Xy-pic pdf driver: `curve' extension support +(/usr/local/texlive/2015/texmf-dist/tex/generic/xypic/xypdf-cu.tex loaded) +Xy-pic pdf driver: `frame' extension support +(/usr/local/texlive/2015/texmf-dist/tex/generic/xypic/xypdf-fr.tex loaded) +Xy-pic pdf driver: `line' extension support +(/usr/local/texlive/2015/texmf-dist/tex/generic/xypic/xypdf-li.tex loaded) +Xy-pic pdf driver: `rotate' extension support +(/usr/local/texlive/2015/texmf-dist/tex/generic/xypic/xypdf-ro.tex loaded) +loaded)) (/usr/local/texlive/2015/texmf-dist/tex/latex/varwidth/varwidth.sty +Package: varwidth 2009/03/30 ver 0.92; Variable-width minipages +\@vwid@box=\box32 +\sift@deathcycles=\count117 +\@vwid@loff=\dimen148 +\@vwid@roff=\dimen149 +) +\c@theorem=\count118 +\c@prop=\count119 +\c@lem=\count120 +\c@defn=\count121 + +No file iotcloud.aux. +\openout1 = `iotcloud.aux'. + +LaTeX Font Info: Checking defaults for OML/cmm/m/it on input line 21. +LaTeX Font Info: ... okay on input line 21. +LaTeX Font Info: Checking defaults for T1/cmr/m/n on input line 21. +LaTeX Font Info: ... okay on input line 21. +LaTeX Font Info: Checking defaults for OT1/cmr/m/n on input line 21. +LaTeX Font Info: ... okay on input line 21. +LaTeX Font Info: Checking defaults for OMS/cmsy/m/n on input line 21. +LaTeX Font Info: ... okay on input line 21. +LaTeX Font Info: Checking defaults for OMX/cmex/m/n on input line 21. +LaTeX Font Info: ... okay on input line 21. +LaTeX Font Info: Checking defaults for U/cmr/m/n on input line 21. +LaTeX Font Info: ... okay on input line 21. +(/usr/local/texlive/2015/texmf-dist/tex/context/base/supp-pdf.mkii +[Loading MPS to PDF converter (version 2006.09.02).] +\scratchcounter=\count122 +\scratchdimen=\dimen150 +\scratchbox=\box33 +\nofMPsegments=\count123 +\nofMParguments=\count124 +\everyMPshowfont=\toks33 +\MPscratchCnt=\count125 +\MPscratchDim=\dimen151 +\MPnumerator=\count126 +\makeMPintoPDFobject=\count127 +\everyMPtoPDFconversion=\toks34 +) (/usr/local/texlive/2015/texmf-dist/tex/generic/oberdiek/pdftexcmds.sty +Package: pdftexcmds 2011/11/29 v0.20 Utility functions of pdfTeX for LuaTeX (HO +) + +(/usr/local/texlive/2015/texmf-dist/tex/generic/oberdiek/ifluatex.sty +Package: ifluatex 2010/03/01 v1.3 Provides the ifluatex switch (HO) +Package ifluatex Info: LuaTeX not detected. +) +Package pdftexcmds Info: LuaTeX not detected. +Package pdftexcmds Info: \pdf@primitive is available. +Package pdftexcmds Info: \pdf@ifprimitive is available. +Package pdftexcmds Info: \pdfdraftmode found. +) +(/usr/local/texlive/2015/texmf-dist/tex/latex/oberdiek/epstopdf-base.sty +Package: epstopdf-base 2010/02/09 v2.5 Base part for package epstopdf + +(/usr/local/texlive/2015/texmf-dist/tex/latex/oberdiek/grfext.sty +Package: grfext 2010/08/19 v1.1 Manage graphics extensions (HO) + +(/usr/local/texlive/2015/texmf-dist/tex/generic/oberdiek/kvdefinekeys.sty +Package: kvdefinekeys 2011/04/07 v1.3 Define keys (HO) +)) +(/usr/local/texlive/2015/texmf-dist/tex/latex/oberdiek/kvoptions.sty +Package: kvoptions 2011/06/30 v3.11 Key value format for package options (HO) + +(/usr/local/texlive/2015/texmf-dist/tex/generic/oberdiek/kvsetkeys.sty +Package: kvsetkeys 2012/04/25 v1.16 Key value parser (HO) + +(/usr/local/texlive/2015/texmf-dist/tex/generic/oberdiek/etexcmds.sty +Package: etexcmds 2011/02/16 v1.5 Avoid name clashes with e-TeX commands (HO) +Package etexcmds Info: Could not find \expanded. +(etexcmds) That can mean that you are not using pdfTeX 1.50 or +(etexcmds) that some package has redefined \expanded. +(etexcmds) In the latter case, load this package earlier. +))) +Package grfext Info: Graphics extension search list: +(grfext) [.png,.pdf,.jpg,.mps,.jpeg,.jbig2,.jb2,.PNG,.PDF,.JPG,.JPE +G,.JBIG2,.JB2,.eps] +(grfext) \AppendGraphicsExtensions on input line 452. + +(/usr/local/texlive/2015/texmf-dist/tex/latex/latexconfig/epstopdf-sys.cfg +File: epstopdf-sys.cfg 2010/07/13 v1.3 Configuration of (r)epstopdf for TeX Liv +e +)) +LaTeX Font Info: Try loading font information for U+rsfs on input line 21. + +(/usr/local/texlive/2015/texmf-dist/tex/latex/jknapltx/ursfs.fd +File: ursfs.fd 1998/03/24 rsfs font definition file (jk) +) +LaTeX Font Info: Try loading font information for U+msa on input line 21. + +(/usr/local/texlive/2015/texmf-dist/tex/latex/amsfonts/umsa.fd +File: umsa.fd 2013/01/14 v3.01 AMS symbols A +) +LaTeX Font Info: Try loading font information for U+msb on input line 21. + +(/usr/local/texlive/2015/texmf-dist/tex/latex/amsfonts/umsb.fd +File: umsb.fd 2013/01/14 v3.01 AMS symbols B +) +Package xypdf Info: Line width: 0.43799pt on input line 21. +LaTeX Font Info: Try loading font information for OMS+cmr on input line 67. + +(/usr/local/texlive/2015/texmf-dist/tex/latex/base/omscmr.fd +File: omscmr.fd 2014/09/29 v2.5h Standard LaTeX font definitions +) +LaTeX Font Info: Font shape `OMS/cmr/m/n' in size <10.95> not available +(Font) Font shape `OMS/cmsy/m/n' tried instead on input line 67. + [1 + +{/usr/local/texlive/2015/texmf-var/fonts/map/pdftex/updmap/pdftex.map}] [2] [3] + +Underfull \hbox (badness 10000) in paragraph at lines 145--159 + + [] + + +Underfull \hbox (badness 10000) in paragraph at lines 145--159 + + [] + + +Underfull \hbox (badness 10000) in paragraph at lines 145--159 + + [] + +[4] [5] +Underfull \hbox (badness 10000) in paragraph at lines 206--216 + + [] + + +Underfull \hbox (badness 10000) in paragraph at lines 217--224 + + [] + +[6] +Underfull \hbox (badness 10000) in paragraph at lines 226--228 + + [] + + +Underfull \hbox (badness 10000) in paragraph at lines 229--231 + + [] + + +Underfull \hbox (badness 10000) in paragraph at lines 235--241 + + [] + + +Underfull \hbox (badness 10000) in paragraph at lines 243--244 + + [] + +[7] +Underfull \hbox (badness 10000) in paragraph at lines 257--258 + + [] + + +Underfull \hbox (badness 10000) in paragraph at lines 259--261 + + [] + +[8] [9] [10] [11] [12] [13] [14] [15] [16] [17] [18] [19] [20] [21] [22] +[23] [24] [25] +Underfull \hbox (badness 1603) in paragraph at lines 1227--1227 +[] $\OML/cmm/m/it/10.95 LSDelete \OMS/cmsy/m/n/10.95 fh\OML/cmm/m/it/10.95 se +q[]; Dat[]\OMS/cmsy/m/n/10.95 ijh\OML/cmm/m/it/10.95 seq[]; Dat[]\OMS/cmsy/m/n/ +10.95 i 2 \OML/cmm/m/it/10.95 LocalSlots; seq[] > + [] + + +Underfull \hbox (badness 1603) detected at line 1227 +[] $\OML/cmm/m/it/10.95 LSDelete \OMS/cmsy/m/n/10.95 fh\OML/cmm/m/it/10.95 se +q[]; Dat[]\OMS/cmsy/m/n/10.95 ijh\OML/cmm/m/it/10.95 seq[]; Dat[]\OMS/cmsy/m/n/ +10.95 i 2 \OML/cmm/m/it/10.95 LocalSlots; seq[] > + [] + +[26] +Underfull \hbox (badness 10000) in paragraph at lines 1301--1301 +[] $\OML/cmm/m/it/10.95 LstSlt[] \OMS/cmsy/m/n/10.95 $ \OT1/cmr/m/sc/10.95 Up- +date-LastMes- + [] + + +Underfull \hbox (badness 10000) detected at line 1301 +[] $\OML/cmm/m/it/10.95 LstSlt[] \OMS/cmsy/m/n/10.95 $ \OT1/cmr/m/sc/10.95 Up- +date-LastMes- + [] + +[27] +Overfull \vbox (27.29413pt too high) has occurred while \output is active [] + + +[28] +Underfull \hbox (badness 2376) in paragraph at lines 1385--1385 +[] $\OML/cmm/m/it/10.95 smallestseq \OMS/cmsy/m/n/10.95 \OML/cmm/m/it/10.95 s +eq$ \OT1/cmr/m/n/10.95 such that $\OMS/cmsy/m/n/10.95 h\OML/cmm/m/it/10.95 seq; + DE\OMS/cmsy/m/n/10.95 i 2 \OML/cmm/m/it/10.95 LocalSlots \OMS/cmsy/m/n/10.95 ^ + + [] + + +Underfull \hbox (badness 2359) detected at line 1385 +[] $\OML/cmm/m/it/10.95 smallestseq \OMS/cmsy/m/n/10.95 \OML/cmm/m/it/10.95 s +eq$ \OT1/cmr/m/n/10.95 such that $\OMS/cmsy/m/n/10.95 h\OML/cmm/m/it/10.95 seq; + DE\OMS/cmsy/m/n/10.95 i 2 \OML/cmm/m/it/10.95 LocalSlots \OMS/cmsy/m/n/10.95 ^ + + [] + +[29] +Underfull \hbox (badness 2376) in paragraph at lines 1430--1430 +[] $\OML/cmm/m/it/10.95 smallestseq \OMS/cmsy/m/n/10.95 \OML/cmm/m/it/10.95 s +eq$ \OT1/cmr/m/n/10.95 such that $\OMS/cmsy/m/n/10.95 h\OML/cmm/m/it/10.95 seq; + DE\OMS/cmsy/m/n/10.95 i 2 \OML/cmm/m/it/10.95 LocalSlots \OMS/cmsy/m/n/10.95 ^ + + [] + + +Underfull \hbox (badness 3482) in paragraph at lines 1430--1430 +[] $\OML/cmm/m/it/10.95 largestseq \OMS/cmsy/m/n/10.95 \OML/cmm/m/it/10.95 se +q$ \OT1/cmr/m/n/10.95 such that $\OMS/cmsy/m/n/10.95 h\OML/cmm/m/it/10.95 seq; +DE\OMS/cmsy/m/n/10.95 i 2 \OML/cmm/m/it/10.95 LocalSlots \OMS/cmsy/m/n/10.95 ^ + [] + + +Underfull \hbox (badness 3482) detected at line 1430 +[] $\OML/cmm/m/it/10.95 largestseq \OMS/cmsy/m/n/10.95 \OML/cmm/m/it/10.95 se +q$ \OT1/cmr/m/n/10.95 such that $\OMS/cmsy/m/n/10.95 h\OML/cmm/m/it/10.95 seq; +DE\OMS/cmsy/m/n/10.95 i 2 \OML/cmm/m/it/10.95 LocalSlots \OMS/cmsy/m/n/10.95 ^ + [] + + +Underfull \hbox (badness 2376) detected at line 1430 +[] $\OML/cmm/m/it/10.95 smallestseq \OMS/cmsy/m/n/10.95 \OML/cmm/m/it/10.95 s +eq$ \OT1/cmr/m/n/10.95 such that $\OMS/cmsy/m/n/10.95 h\OML/cmm/m/it/10.95 seq; + DE\OMS/cmsy/m/n/10.95 i 2 \OML/cmm/m/it/10.95 LocalSlots \OMS/cmsy/m/n/10.95 ^ + + [] + +[30] +Overfull \vbox (13.69412pt too high) has occurred while \output is active [] + + +[31] +Underfull \hbox (badness 10000) in paragraph at lines 1471--1471 +[] $\OML/cmm/m/it/10.95 DE[] \OMS/cmsy/m/n/10.95 \OML/cmm/m/it/10.95 DE[]\OMS +/cmsy/m/n/10.95 [$ \OT1/cmr/m/sc/10.95 Cre-ate-Col- + [] + + +Underfull \hbox (badness 10000) detected at line 1471 +[] $\OML/cmm/m/it/10.95 DE[] \OMS/cmsy/m/n/10.95 \OML/cmm/m/it/10.95 DE[]\OMS +/cmsy/m/n/10.95 [$ \OT1/cmr/m/sc/10.95 Cre-ate-Col- + [] + +[32] +Overfull \vbox (149.69418pt too high) has occurred while \output is active [] + + +[33] [34] +Overfull \vbox (95.29416pt too high) has occurred while \output is active [] + + +[35] +Overfull \vbox (95.20291pt too high) has occurred while \output is active [] + + +[36] [37] [38] [39] [40] (./iotcloud.aux) ) +Here is how much of TeX's memory you used: + 5802 strings out of 493089 + 75548 string characters out of 6134842 + 202852 words of memory out of 5000000 + 9157 multiletter control sequences out of 15000+600000 + 13316 words of font info for 54 fonts, out of 8000000 for 9000 + 1141 hyphenation exceptions out of 8191 + 38i,11n,26p,3240b,312s stack positions out of 5000i,500n,10000p,200000b,80000s +</usr/local/texlive/2015/texmf-dist/ +fonts/type1/public/amsfonts/cm/cmbx10.pfb></usr/local/texlive/2015/texmf-dist/f +onts/type1/public/amsfonts/cm/cmbx12.pfb></usr/local/texlive/2015/texmf-dist/fo +nts/type1/public/amsfonts/cm/cmcsc10.pfb></usr/local/texlive/2015/texmf-dist/fo +nts/type1/public/amsfonts/cm/cmmi10.pfb></usr/local/texlive/2015/texmf-dist/fon +ts/type1/public/amsfonts/cm/cmmi6.pfb></usr/local/texlive/2015/texmf-dist/fonts +/type1/public/amsfonts/cm/cmmi8.pfb></usr/local/texlive/2015/texmf-dist/fonts/t +ype1/public/amsfonts/cm/cmr10.pfb></usr/local/texlive/2015/texmf-dist/fonts/typ +e1/public/amsfonts/cm/cmr6.pfb></usr/local/texlive/2015/texmf-dist/fonts/type1/ +public/amsfonts/cm/cmr8.pfb></usr/local/texlive/2015/texmf-dist/fonts/type1/pub +lic/amsfonts/cm/cmr9.pfb></usr/local/texlive/2015/texmf-dist/fonts/type1/public +/amsfonts/cm/cmsy10.pfb></usr/local/texlive/2015/texmf-dist/fonts/type1/public/ +amsfonts/cm/cmsy8.pfb></usr/local/texlive/2015/texmf-dist/fonts/type1/public/am +sfonts/cm/cmti10.pfb> +Output written on iotcloud.pdf (40 pages, 245596 bytes). +PDF statistics: + 187 PDF objects out of 1000 (max. 8388607) + 130 compressed objects within 2 object streams + 0 named destinations out of 1000 (max. 500000) + 1 words of extra memory for PDF output out of 10000 (max. 10000000) + diff --git a/version2/backup/doc/iotcloud.pdf b/version2/backup/doc/iotcloud.pdf new file mode 100644 index 0000000000000000000000000000000000000000..6feabc27b7293d93eb28f42e65669bdd87fa2469 GIT binary patch literal 245596 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 KEvYPp{NDh?rro*# literal 0 HcmV?d00001 diff --git a/version2/doc/iotcloud.tex b/version2/backup/doc/iotcloud.tex similarity index 100% rename from version2/doc/iotcloud.tex rename to version2/backup/doc/iotcloud.tex diff --git a/version2/doc/makefile b/version2/backup/doc/makefile similarity index 100% rename from version2/doc/makefile rename to version2/backup/doc/makefile diff --git a/version2/backup/src/java/.dir-locals.el b/version2/backup/src/java/.dir-locals.el new file mode 100644 index 0000000..e166a2e --- /dev/null +++ b/version2/backup/src/java/.dir-locals.el @@ -0,0 +1,2 @@ +((nil . ((indent-tabs-mode . t)))) + diff --git a/version2/backup/src/java/iotcloud/Abort.java b/version2/backup/src/java/iotcloud/Abort.java new file mode 100644 index 0000000..327ce33 --- /dev/null +++ b/version2/backup/src/java/iotcloud/Abort.java @@ -0,0 +1,63 @@ +package iotcloud; + +import java.nio.ByteBuffer; + +/** + * This Entry records the abort sent by a given machine. + * @author Ali Younis <ayounis@uci.edu> + * @version 1.0 + */ + + +class Abort extends Entry { + private long seqnumtrans; + private long machineid; + private long transarbitrator; + + + public Abort(Slot slot, long _seqnumtrans, long _machineid, long _transarbitrator) { + super(slot); + seqnumtrans = _seqnumtrans; + machineid = _machineid; + transarbitrator = _transarbitrator; + } + + public long getMachineID() { + return machineid; + } + + public long getTransSequenceNumber() { + return seqnumtrans; + } + + + public long getTransArbitrator() { + return transarbitrator; + } + + static Entry decode(Slot slot, ByteBuffer bb) { + long seqnumtrans = bb.getLong(); + long machineid = bb.getLong(); + long transarbitrator = bb.getLong(); + return new Abort(slot, seqnumtrans, machineid, transarbitrator); + } + + public void encode(ByteBuffer bb) { + bb.put(Entry.TypeAbort); + bb.putLong(seqnumtrans); + bb.putLong(machineid); + bb.putLong(transarbitrator); + } + + public int getSize() { + return (3 * Long.BYTES) + Byte.BYTES; + } + + public byte getType() { + return Entry.TypeAbort; + } + + public Entry getCopy(Slot s) { + return new Abort(s, seqnumtrans, machineid, transarbitrator); + } +} \ No newline at end of file diff --git a/version2/backup/src/java/iotcloud/CloudComm.java b/version2/backup/src/java/iotcloud/CloudComm.java new file mode 100644 index 0000000..6f548af --- /dev/null +++ b/version2/backup/src/java/iotcloud/CloudComm.java @@ -0,0 +1,261 @@ +package iotcloud; +import java.io.*; +import java.net.*; +import java.util.Arrays; +import javax.crypto.*; +import javax.crypto.spec.*; +import java.security.SecureRandom; + +/** + * This class provides a communication API to the webserver. It also + * validates the HMACs on the slots and handles encryption. + * @author Brian Demsky <bdemsky@uci.edu> + * @version 1.0 + */ + + +class CloudComm { + String hostname; + String baseurl; + Cipher encryptCipher; + Cipher decryptCipher; + Mac mac; + String password; + SecureRandom random; + static final int SALT_SIZE = 8; + static final int TIMEOUT_MILLIS = 100; + byte salt[]; + Table table; + + /** + * Empty Constructor needed for child class. + */ + CloudComm() { + } + + /** + * Constructor for actual use. Takes in the url and password. + */ + CloudComm(Table _table, String _hostname, String _baseurl, String _password) { + this.table = _table; + this.hostname = _hostname; + this.baseurl = _baseurl; + this.password = _password; + this.random = new SecureRandom(); + } + + /** + * Generates Key from password. + */ + private SecretKeySpec initKey() { + try { + PBEKeySpec keyspec = new PBEKeySpec(password.toCharArray(), salt, 65536, 128); + SecretKey tmpkey = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256").generateSecret(keyspec); + return new SecretKeySpec(tmpkey.getEncoded(), "AES"); + } catch (Exception e) { + e.printStackTrace(); + throw new Error("Failed generating key."); + } + } + + /** + * Inits the HMAC generator. + */ + private void initCrypt() { + try { + SecretKeySpec key = initKey(); + password = null; // drop password + mac = Mac.getInstance("HmacSHA256"); + mac.init(key); + encryptCipher = Cipher.getInstance("AES/ECB/PKCS5Padding"); + encryptCipher.init(Cipher.ENCRYPT_MODE, key); + decryptCipher = Cipher.getInstance("AES/ECB/PKCS5Padding"); + decryptCipher.init(Cipher.DECRYPT_MODE, key); + } catch (Exception e) { + e.printStackTrace(); + throw new Error("Failed To Initialize Ciphers"); + } + } + + /* + * Builds the URL for the given request. + */ + private URL buildRequest(boolean isput, long sequencenumber, long maxentries) throws IOException { + String reqstring = isput ? "req=putslot" : "req=getslot"; + String urlstr = baseurl + "?" + reqstring + "&seq=" + sequencenumber; + if (maxentries != 0) + urlstr += "&max=" + maxentries; + return new URL(urlstr); + } + + public void setSalt() throws ServerException { + try { + byte[] saltTmp = new byte[SALT_SIZE]; + random.nextBytes(saltTmp); + URL url = new URL(baseurl + "?req=setsalt"); + URLConnection con = url.openConnection(); + HttpURLConnection http = (HttpURLConnection) con; + http.setRequestMethod("POST"); + http.setFixedLengthStreamingMode(saltTmp.length); + http.setDoOutput(true); + http.setConnectTimeout(TIMEOUT_MILLIS); + http.connect(); + OutputStream os = http.getOutputStream(); + os.write(saltTmp); + int responsecode = http.getResponseCode(); + if (responsecode != HttpURLConnection.HTTP_OK) { + // TODO: Remove this print + // System.out.println(responsecode); + throw new Error("Invalid response"); + } + + salt = saltTmp; + } catch (Exception e) { + throw new ServerException("Failed setting salt"); + } + initCrypt(); + } + + private void getSalt() throws Exception { + URL url = new URL(baseurl + "?req=getsalt"); + URLConnection con = url.openConnection(); + HttpURLConnection http = (HttpURLConnection) con; + http.setRequestMethod("POST"); + http.connect(); + + InputStream is = http.getInputStream(); + DataInputStream dis = new DataInputStream(is); + int salt_length = dis.readInt(); + byte [] tmp = new byte[salt_length]; + dis.readFully(tmp); + salt = tmp; + } + + /* + * API for putting a slot into the queue. Returns null on success. + * On failure, the server will send slots with newer sequence + * numbers. + */ + + Slot[] putSlot(Slot slot, int max) throws ServerException { + try { + if (salt == null) { + getSalt(); + initCrypt(); + } + + long sequencenumber = slot.getSequenceNumber(); + byte[] bytes = slot.encode(mac); + bytes = encryptCipher.doFinal(bytes); + + + URL url = buildRequest(true, sequencenumber, max); + URLConnection con = url.openConnection(); + HttpURLConnection http = (HttpURLConnection) con; + + http.setRequestMethod("POST"); + http.setFixedLengthStreamingMode(bytes.length); + http.setDoOutput(true); + http.setConnectTimeout(TIMEOUT_MILLIS); + // http.setReadTimeout(TIMEOUT_MILLIS); + http.connect(); + + OutputStream os = http.getOutputStream(); + os.write(bytes); + os.flush(); + + + InputStream is = http.getInputStream(); + DataInputStream dis = new DataInputStream(is); + byte[] resptype = new byte[7]; + dis.readFully(resptype); + + if (Arrays.equals(resptype, "getslot".getBytes())) + return processSlots(dis); + else if (Arrays.equals(resptype, "putslot".getBytes())) + return null; + else + throw new Error("Bad response to putslot"); + + } catch (Exception e) { + throw new ServerException("putSlot failed"); + } + } + + + /** + * Request the server to send all slots with the given + * sequencenumber or newer. + */ + Slot[] getSlots(long sequencenumber) throws ServerException { + try { + if (salt == null) { + getSalt(); + initCrypt(); + } + + URL url = buildRequest(false, sequencenumber, 0); + URLConnection con = url.openConnection(); + HttpURLConnection http = (HttpURLConnection) con; + http.setRequestMethod("POST"); + http.setConnectTimeout(TIMEOUT_MILLIS); + // http.setReadTimeout(TIMEOUT_MILLIS); + http.connect(); + InputStream is = http.getInputStream(); + DataInputStream dis = new DataInputStream(is); + + int responsecode = http.getResponseCode(); + if (responsecode != HttpURLConnection.HTTP_OK) { + // TODO: Remove this print + // System.out.println("Code: " + responsecode); + throw new ServerException("getSlots failed"); + } + + byte[] resptype = new byte[7]; + dis.readFully(resptype); + if (!Arrays.equals(resptype, "getslot".getBytes())) + throw new Error("Bad Response: " + new String(resptype)); + else + return processSlots(dis); + } catch (Exception e) { + // e.printStackTrace(); + throw new ServerException("getSlots failed"); + } + } + + public boolean hasConnection() { + try { + InetAddress address = InetAddress.getByName(hostname); + return address.isReachable(TIMEOUT_MILLIS); + } catch (Exception e) { + return false; + } + } + + /** + * Method that actually handles building Slot objects from the + * server response. Shared by both putSlot and getSlots. + */ + private Slot[] processSlots(DataInputStream dis) throws Exception { + int numberofslots = dis.readInt(); + int[] sizesofslots = new int[numberofslots]; + Slot[] slots = new Slot[numberofslots]; + for (int i = 0; i < numberofslots; i++) + sizesofslots[i] = dis.readInt(); + + for (int i = 0; i < numberofslots; i++) { + byte[] data = new byte[sizesofslots[i]]; + dis.readFully(data); + + data = decryptCipher.doFinal(data); + + slots[i] = Slot.decode(table, data, mac); + } + dis.close(); + return slots; + } + + + + +} diff --git a/version2/backup/src/java/iotcloud/Commit.java b/version2/backup/src/java/iotcloud/Commit.java new file mode 100644 index 0000000..fb52e67 --- /dev/null +++ b/version2/backup/src/java/iotcloud/Commit.java @@ -0,0 +1,125 @@ +package iotcloud; + +import java.nio.ByteBuffer; +import java.util.Set; +import java.util.HashSet; +import java.util.Iterator; + +/** + * This Entry records the commit of a transaction. + * @author Ali Younis <ayounis@uci.edu> + * @version 1.0 + */ + + +class Commit extends Entry { + private long seqnumtrans; + private long seqnumcommit; + private long transarbitrator; + + private Set<KeyValue> keyValueUpdateSet = null; + + + public Commit(Slot slot, long _seqnumtrans, long _seqnumcommit, long _transarbitrator, Set<KeyValue> _keyValueUpdateSet) { + super(slot); + seqnumtrans = _seqnumtrans; + seqnumcommit = _seqnumcommit; + transarbitrator = _transarbitrator; + + keyValueUpdateSet = new HashSet<KeyValue>(); + + for (KeyValue kv : _keyValueUpdateSet) { + KeyValue kvCopy = kv.getCopy(); + keyValueUpdateSet.add(kvCopy); + } + } + + public long getTransSequenceNumber() { + return seqnumtrans; + } + public long getSequenceNumber() { + return seqnumcommit; + } + + public long getTransArbitrator() { + return transarbitrator; + } + + public Set<KeyValue> getkeyValueUpdateSet() { + return keyValueUpdateSet; + } + + public byte getType() { + return Entry.TypeCommit; + } + + public int getSize() { + int size = 3 * Long.BYTES + Byte.BYTES; // seq id, entry type + size += Integer.BYTES; // number of KV's + + // Size of each KV + for (KeyValue kv : keyValueUpdateSet) { + size += kv.getSize(); + } + + return size; + } + + static Entry decode(Slot slot, ByteBuffer bb) { + long seqnumtrans = bb.getLong(); + long seqnumcommit = bb.getLong(); + long transarbitrator = bb.getLong(); + int numberOfKeys = bb.getInt(); + + Set<KeyValue> kvSet = new HashSet<KeyValue>(); + for (int i = 0; i < numberOfKeys; i++) { + KeyValue kv = KeyValue.decode(bb); + kvSet.add(kv); + } + + return new Commit(slot, seqnumtrans, seqnumcommit, transarbitrator, kvSet); + } + + public void encode(ByteBuffer bb) { + bb.put(Entry.TypeCommit); + bb.putLong(seqnumtrans); + bb.putLong(seqnumcommit); + bb.putLong(transarbitrator); + + bb.putInt(keyValueUpdateSet.size()); + + for (KeyValue kv : keyValueUpdateSet) { + kv.encode(bb); + } + } + + public Entry getCopy(Slot s) { + return new Commit(s, seqnumtrans, seqnumcommit, transarbitrator, keyValueUpdateSet); + } + + public Set<KeyValue> updateLiveKeys(Set<KeyValue> kvSet) { + + if (!this.isLive()) + return new HashSet<KeyValue>(); + + Set<KeyValue> toDelete = new HashSet<KeyValue>(); + + for (KeyValue kv1 : kvSet) { + for (Iterator<KeyValue> i = keyValueUpdateSet.iterator(); i.hasNext();) { + KeyValue kv2 = i.next(); + + if (kv1.getKey().equals(kv2.getKey())) { + toDelete.add(kv2); + i.remove(); + break; + } + } + } + + if (keyValueUpdateSet.size() == 0) { + this.setDead(); + } + + return toDelete; + } +} \ No newline at end of file diff --git a/version2/backup/src/java/iotcloud/Entry.java b/version2/backup/src/java/iotcloud/Entry.java new file mode 100644 index 0000000..8395dec --- /dev/null +++ b/version2/backup/src/java/iotcloud/Entry.java @@ -0,0 +1,119 @@ +package iotcloud; +import java.nio.ByteBuffer; + +/** + * Generic class that wraps all the different types of information + * that can be stored in a Slot. + * @author Brian Demsky <bdemsky@uci.edu> + * @version 1.0 + */ + +abstract class Entry implements Liveness { + + + static final byte TypeCommit = 1; + static final byte TypeAbort = 2; + static final byte TypeTransaction = 3; + static final byte TypeNewKey = 4; + static final byte TypeLastMessage = 5; + static final byte TypeRejectedMessage = 6; + static final byte TypeTableStatus = 7; + + + + /* Records whether the information is still live or has been + superceded by a newer update. */ + + private boolean islive = true; + private Slot parentslot; + + public Entry(Slot _parentslot) { + parentslot = _parentslot; + } + + /** + * Static method for decoding byte array into Entry objects. First + * byte tells the type of entry. + */ + + static Entry decode(Slot slot, ByteBuffer bb) { + byte type = bb.get(); + switch (type) { + + case TypeCommit: + return Commit.decode(slot, bb); + + case TypeAbort: + return Abort.decode(slot, bb); + + case TypeTransaction: + return Transaction.decode(slot, bb); + + case TypeNewKey: + return NewKey.decode(slot, bb); + + case TypeLastMessage: + return LastMessage.decode(slot, bb); + + case TypeRejectedMessage: + return RejectedMessage.decode(slot, bb); + + case TypeTableStatus: + return TableStatus.decode(slot, bb); + + default: + throw new Error("Unrecognized Entry Type: " + type); + } + } + + /** + * Returns true if the Entry object is still live. + */ + + public boolean isLive() { + return islive; + } + + /** + * Flags the entry object as dead. Also decrements the live count + * of the parent slot. + */ + + public void setDead() { + + if (!islive ) { + return; // already dead + } + + islive = false; + + if (parentslot != null) { + parentslot.decrementLiveCount(); + } + } + + /** + * Serializes the Entry object into the byte buffer. + */ + + abstract void encode(ByteBuffer bb); + + /** + * Returns the size in bytes the entry object will take in the byte + * array. + */ + + abstract int getSize(); + + /** + * Returns a byte encoding the type of the entry object. + */ + + abstract byte getType(); + + /** + * Returns a copy of the Entry that can be added to a different slot. + */ + abstract Entry getCopy(Slot s); + +} diff --git a/version2/backup/src/java/iotcloud/Guard.java_backup b/version2/backup/src/java/iotcloud/Guard.java_backup new file mode 100644 index 0000000..f768b7f --- /dev/null +++ b/version2/backup/src/java/iotcloud/Guard.java_backup @@ -0,0 +1,163 @@ +package iotcloud; + +import java.util.Set; +import java.util.HashSet; +import java.util.Collection; +import java.util.List; +import java.util.ArrayList; + +import java.nio.ByteBuffer; + +import javax.script.ScriptEngine; +import javax.script.ScriptEngineManager; +import javax.script.ScriptException; +import java.lang.NullPointerException; + + +class Guard { + + static final byte Equal = 1; + static final byte NotEqual = 2; + private IoTString booleanExpression; + private List<KeyValue> keyValsNeeded = null; + + public Guard() { + booleanExpression = null; + } + + public Guard(IoTString _booleanExpression) { + booleanExpression = _booleanExpression; + } + + /** + * Create an equality expression for a key value. + * + */ + public static String createExpression(IoTString keyName, IoTString keyValue, byte op) { + if (op == Equal) { + return keyName.toString() + "=='" + keyValue.toString() + "'"; + } else if (op == NotEqual) { + return keyName.toString() + "!='" + keyValue.toString() + "'"; + } + + // Unrecognized op + return null; + } + + /** + * Add a boolean expression to the guard. + * + */ + public void setGuardExpression(String expr) { + booleanExpression = new IoTString(expr); + } + + /** + * Evaluate the guard expression for a given set of key value pairs. + * + */ + public boolean evaluate(Collection<KeyValue> kvSet) throws ScriptException, NullPointerException { + + // There are no conditions to evaluate + if (booleanExpression == null) { + return true; + } + + ScriptEngine engine = new ScriptEngineManager().getEngineByName("JavaScript"); + + if (keyValsNeeded == null) { + keyValsNeeded = new ArrayList<KeyValue>(); + + String booleanExprString = booleanExpression.toString(); + for (KeyValue kv : kvSet) { + if (booleanExprString.contains(kv.getKey().toString())) { + keyValsNeeded.add(kv); + } + } + } + + // All the current key value pairs that we need to evaluate the condition + // String[] variables = new String[kvSet.size()]; + + // Fill the variables array + // int i = 0; + // for (KeyValue kv : kvSet) { + // for (KeyValue kv : keyValsNeeded) { + // variables[i] = kv.getKey() + " ='" + kv.getValue() + "'"; + // i++; + // } + + String varEval = ""; + for (KeyValue kv : keyValsNeeded) { + varEval += "var " + kv.getKey() + " ='" + kv.getValue() + "'; \n"; + } + + varEval += booleanExpression.toString(); + + // Prep the evaluation engine (script engine) + + // for (String s : variables) { + // engine.eval(s); + // } + // engine.eval(varEval); + + + + // boolean engineEval = (Boolean)engine.eval(booleanExpression.toString()); + boolean engineEval = false; + + try { + engineEval = (Boolean)engine.eval(varEval); + } catch (Exception e) { + // If there was an error then the script evaluated to false + engineEval = false; + } + + // Evaluate the guard condition + // return 1 == (Integer)engine.eval(booleanExpression.toString()); + return engineEval; + } + + /** + * Get the size of the guard condition + * + */ + public int getSize() { + + if (booleanExpression == null) { + return Integer.BYTES; + } + + return Integer.BYTES + booleanExpression.length(); + } + + public void encode(ByteBuffer bb) { + if (booleanExpression == null) { + bb.putInt(0); + } else { + bb.putInt(booleanExpression.length()); + bb.put(booleanExpression.internalBytes()); + } + } + + static Guard decode(ByteBuffer bb) { + int exprLength = bb.getInt(); + + if (exprLength != 0) { + byte[] expr = new byte[exprLength]; + bb.get(expr); + return new Guard(IoTString.shallow(expr)); + } + return new Guard(null); + } + + public Guard getCopy() { + + if (booleanExpression == null) { + return new Guard(null); + } + + return new Guard(IoTString.shallow(booleanExpression.internalBytes())); + } + +} \ No newline at end of file diff --git a/version2/backup/src/java/iotcloud/IoTString.java b/version2/backup/src/java/iotcloud/IoTString.java new file mode 100644 index 0000000..83a3fa1 --- /dev/null +++ b/version2/backup/src/java/iotcloud/IoTString.java @@ -0,0 +1,105 @@ +package iotcloud; + +import java.util.Arrays; + +/** + * IoTString is wraps the underlying byte string. We don't use the + * standard String class as we have bytes and not chars. + * @author Brian Demsky <bdemsky@uci.edu> + * @version 1.0 + */ + + +final public class IoTString { + byte[] array; + int hashcode; + + private IoTString() { + } + + /** + * Builds an IoTString object around the byte array. This + * constructor makes a copy, so the caller is free to modify the byte array. + */ + + public IoTString(byte[] _array) { + array=(byte[]) _array.clone(); + hashcode=Arrays.hashCode(array); + } + + /** + * Converts the String object to a byte representation and stores it + * into the IoTString object. + */ + + public IoTString(String str) { + array=str.getBytes(); + hashcode=Arrays.hashCode(array); + } + + /** + * Internal methods to build an IoTString using the byte[] passed + * in. Caller is responsible for ensuring the byte[] is never + * modified. + */ + + static IoTString shallow(byte[] _array) { + IoTString i=new IoTString(); + i.array = _array; + i.hashcode = Arrays.hashCode(_array); + return i; + } + + /** + * Internal method to grab a reference to our byte array. Caller + * must not modify it. + */ + + byte[] internalBytes() { + return array; + } + + /** + * Returns the hashCode as computed by Arrays.hashcode(byte[]). + */ + + public int hashCode() { + return hashcode; + } + + /** + * Returns a String representation of the IoTString. + */ + + public String toString() { + return new String(array); + } + + /** + * Returns a copy of the underlying byte string. + */ + + public byte[] getBytes() { + return (byte[]) array.clone(); + } + + /** + * Returns true if two byte strings have the same content. + */ + + public boolean equals(Object o) { + if (o instanceof IoTString) { + IoTString i=(IoTString)o; + return Arrays.equals(array, i.array); + } + return false; + } + + /** + * Returns the length in bytes of the IoTString. + */ + + public int length() { + return array.length; + } +} diff --git a/version2/backup/src/java/iotcloud/KeyValue.java b/version2/backup/src/java/iotcloud/KeyValue.java new file mode 100644 index 0000000..cd4821a --- /dev/null +++ b/version2/backup/src/java/iotcloud/KeyValue.java @@ -0,0 +1,76 @@ +package iotcloud; +import java.nio.ByteBuffer; + +/** + * KeyValue entry for Slot. + * @author Brian Demsky <bdemsky@uci.edu> + * @version 1.0 + */ + +class KeyValue { /*extends Entry */ + private IoTString key; + private IoTString value; + + public KeyValue(IoTString _key, IoTString _value) { + key = _key; + value = _value; + } + + public IoTString getKey() { + return key; + } + + public IoTString getValue() { + return value; + } + + static KeyValue decode(ByteBuffer bb) { + int keylength = bb.getInt(); + int valuelength = bb.getInt(); + byte[] key = new byte[keylength]; + bb.get(key); + + if (valuelength != 0) { + byte[] value = new byte[valuelength]; + bb.get(value); + return new KeyValue(IoTString.shallow(key), IoTString.shallow(value)); + } + + return new KeyValue(IoTString.shallow(key), null); + } + + public void encode(ByteBuffer bb) { + bb.putInt(key.length()); + + if (value != null) { + bb.putInt(value.length()); + } else { + bb.putInt(0); + } + + bb.put(key.internalBytes()); + + if (value != null) { + bb.put(value.internalBytes()); + } + } + + public int getSize() { + if (value != null) { + return 2 * Integer.BYTES + key.length() + value.length(); + } + + return 2 * Integer.BYTES + key.length(); + } + + public String toString() { + if (value == null) { + return "null"; + } + return value.toString(); + } + + public KeyValue getCopy() { + return new KeyValue(key, value); + } +} diff --git a/version2/backup/src/java/iotcloud/LastMessage.java b/version2/backup/src/java/iotcloud/LastMessage.java new file mode 100644 index 0000000..738dff6 --- /dev/null +++ b/version2/backup/src/java/iotcloud/LastMessage.java @@ -0,0 +1,55 @@ +package iotcloud; + +import java.nio.ByteBuffer; + +/** + * This Entry records the last message sent by a given machine. + * @author Brian Demsky <bdemsky@uci.edu> + * @version 1.0 + */ + + +class LastMessage extends Entry { + private long machineid; + private long seqnum; + + public LastMessage(Slot slot, long _machineid, long _seqnum) { + super(slot); + machineid=_machineid; + seqnum=_seqnum; + } + + public long getMachineID() { + return machineid; + } + + public long getSequenceNumber() { + return seqnum; + } + + static Entry decode(Slot slot, ByteBuffer bb) { + long machineid=bb.getLong(); + long seqnum=bb.getLong(); + return new LastMessage(slot, machineid, seqnum); + } + + public void encode(ByteBuffer bb) { + bb.put(Entry.TypeLastMessage); + bb.putLong(machineid); + bb.putLong(seqnum); + } + + public int getSize() { + return 2*Long.BYTES+Byte.BYTES; + } + + public byte getType() { + return Entry.TypeLastMessage; + } + + public Entry getCopy(Slot s) { + return new LastMessage(s, machineid, seqnum); + } +} + + diff --git a/version2/backup/src/java/iotcloud/Liveness.java b/version2/backup/src/java/iotcloud/Liveness.java new file mode 100644 index 0000000..2c840e4 --- /dev/null +++ b/version2/backup/src/java/iotcloud/Liveness.java @@ -0,0 +1,11 @@ +package iotcloud; + +/** + * Interface common to both classes that record information about the + * last message sent by a machine. (Either a Slot or a LastMessage. + * @author Brian Demsky <bdemsky@uci.edu> + * @version 1.0 + */ + +interface Liveness { +} diff --git a/version2/backup/src/java/iotcloud/LocalComm.java b/version2/backup/src/java/iotcloud/LocalComm.java new file mode 100644 index 0000000..17e3c05 --- /dev/null +++ b/version2/backup/src/java/iotcloud/LocalComm.java @@ -0,0 +1,23 @@ +package iotcloud; + +class LocalComm { + private Table t1; + private Table t2; + + public LocalComm(Table _t1, Table _t2) { + t1 = _t1; + t2 = _t2; + } + + public byte[] sendDataToLocalDevice(Long deviceId, byte[] data) throws InterruptedException{ + System.out.println("Passing Locally"); + + if (deviceId == t1.getId()) { + return t1.localCommInput(data); + } else if (deviceId == t2.getId()) { + return t2.localCommInput(data); + } else { + throw new Error("Cannot send to " + deviceId + " using this local comm"); + } + } +} \ No newline at end of file diff --git a/version2/backup/src/java/iotcloud/Makefile b/version2/backup/src/java/iotcloud/Makefile new file mode 100644 index 0000000..2d45b63 --- /dev/null +++ b/version2/backup/src/java/iotcloud/Makefile @@ -0,0 +1,17 @@ +all: server + +JAVAC = javac +JAVADOC = javadoc +BIN_DIR = bin +DOCS_DIR = docs + +server: + $(JAVAC) -d $(BIN_DIR) *.java + +doc: server + $(JAVADOC) -private -d $(DOCS_DIR) *.java + +clean: + rm -r bin/* + rm -r docs/* + rm *~ diff --git a/version2/backup/src/java/iotcloud/NewKey.java b/version2/backup/src/java/iotcloud/NewKey.java new file mode 100644 index 0000000..0970016 --- /dev/null +++ b/version2/backup/src/java/iotcloud/NewKey.java @@ -0,0 +1,57 @@ +package iotcloud; + +import java.nio.ByteBuffer; + +/** + * This Entry records the abort sent by a given machine. + * @author Ali Younis <ayounis@uci.edu> + * @version 1.0 + */ + + +class NewKey extends Entry { + private IoTString key; + private long machineid; + + public NewKey(Slot slot, IoTString _key, long _machineid) { + super(slot); + key = _key; + machineid = _machineid; + } + + public long getMachineID() { + return machineid; + } + + public IoTString getKey() { + return key; + } + + static Entry decode(Slot slot, ByteBuffer bb) { + int keylength = bb.getInt(); + byte[] key = new byte[keylength]; + bb.get(key); + long machineid = bb.getLong(); + + return new NewKey(slot, IoTString.shallow(key), machineid); + } + + public void encode(ByteBuffer bb) { + bb.put(Entry.TypeNewKey); + bb.putInt(key.length()); + bb.put(key.internalBytes()); + bb.putLong(machineid); + } + + public int getSize() { + return Long.BYTES + Byte.BYTES + Integer.BYTES + key.length(); + } + + public byte getType() { + return Entry.TypeNewKey; + } + + public Entry getCopy(Slot s) { + return new NewKey(s, key, machineid); + } +} \ No newline at end of file diff --git a/version2/backup/src/java/iotcloud/Pair.java b/version2/backup/src/java/iotcloud/Pair.java new file mode 100644 index 0000000..73ed6bd --- /dev/null +++ b/version2/backup/src/java/iotcloud/Pair.java @@ -0,0 +1,23 @@ +package iotcloud; + +class Pair<A,B> { + private A a; + private B b; + + Pair(A a, B b) { + this.a=a; + this.b=b; + } + + A getFirst() { + return a; + } + + B getSecond() { + return b; + } + + public String toString() { + return "<"+a+","+b+">"; + } +} diff --git a/version2/backup/src/java/iotcloud/PendingTransaction.java b/version2/backup/src/java/iotcloud/PendingTransaction.java new file mode 100644 index 0000000..1a14674 --- /dev/null +++ b/version2/backup/src/java/iotcloud/PendingTransaction.java @@ -0,0 +1,138 @@ +package iotcloud; + +import java.util.Set; +import java.util.Map; +import java.util.HashSet; + +import javax.script.ScriptException; +import java.lang.NullPointerException; + + +class PendingTransaction { + + private Set<KeyValue> keyValueUpdateSet = null; + private Set<KeyValue> keyValueGuardSet = null; + private long arbitrator = -1; + private long machineLocalTransSeqNum = -1; + + public PendingTransaction() { + keyValueUpdateSet = new HashSet<KeyValue>(); + keyValueGuardSet = new HashSet<KeyValue>(); + } + + /** + * Add a new key value to the updates + * + */ + public void addKV(KeyValue newKV) { + + KeyValue rmKV = null; + + // Make sure there are no duplicates + for (KeyValue kv : keyValueUpdateSet) { + if (kv.getKey().equals(newKV.getKey())) { + + // Remove key if we are adding a newer version of the same key + rmKV = kv; + break; + } + } + + // Remove key if we are adding a newer version of the same key + if (rmKV != null) { + keyValueUpdateSet.remove(rmKV); + } + + // Add the key to the hash set + keyValueUpdateSet.add(newKV); + } + + + /** + * Add a new key value to the guard set + * + */ + public void addKVGuard(KeyValue newKV) { + // Add the key to the hash set + keyValueGuardSet.add(newKV); + } + + /** + * Checks if the arbitrator is the same + * + */ + public boolean checkArbitrator(long arb) { + if (arbitrator == -1) { + arbitrator = arb; + return true; + } + + return arb == arbitrator; + } + + /** + * Get the transaction arbitrator + * + */ + public long getArbitrator() { + return arbitrator; + } + + /** + * Get the key value update set + * + */ + public Set<KeyValue> getKVUpdates() { + return keyValueUpdateSet; + } + + + /** + * Get the key value update set + * + */ + public Set<KeyValue> getKVGuard() { + return keyValueGuardSet; + } + + public void setMachineLocalTransSeqNum(long _machineLocalTransSeqNum) { + machineLocalTransSeqNum = _machineLocalTransSeqNum; + } + + public long getMachineLocalTransSeqNum() { + return machineLocalTransSeqNum; + } + + public boolean evaluateGuard(Map<IoTString, KeyValue> keyValTableCommitted, Map<IoTString, KeyValue> keyValTableSpeculative, Map<IoTString, KeyValue> keyValTablePendingTransSpeculative) { + for (KeyValue kvGuard : keyValueGuardSet) { + + // First check if the key is in the speculative table, this is the value of the latest assumption + KeyValue kv = keyValTablePendingTransSpeculative.get(kvGuard.getKey()); + + + if (kv == null) { + // if it is not in the pending trans table then check the speculative table and use that + // value as our latest assumption + kv = keyValTableSpeculative.get(kvGuard.getKey()); + } + + + if (kv == null) { + // if it is not in the speculative table then check the committed table and use that + // value as our latest assumption + kv = keyValTableCommitted.get(kvGuard.getKey()); + } + + if (kvGuard.getValue() != null) { + if ((kv == null) || (!kvGuard.getValue().equals(kv.getValue()))) { + return false; + } + } else { + if (kv != null) { + return false; + } + } + } + return true; + } +} \ No newline at end of file diff --git a/version2/backup/src/java/iotcloud/RejectedMessage.java b/version2/backup/src/java/iotcloud/RejectedMessage.java new file mode 100644 index 0000000..9c84f18 --- /dev/null +++ b/version2/backup/src/java/iotcloud/RejectedMessage.java @@ -0,0 +1,88 @@ +package iotcloud; +import java.nio.ByteBuffer; +import java.util.HashSet; + +/** + * Entry for tracking messages that the server rejected. We have to + * make sure that all clients know that this message was rejected to + * prevent the server from reusing these messages in an attack. + * @author Brian Demsky + * @version 1.0 + */ + + +class RejectedMessage extends Entry { + /* Machine identifier */ + private long machineid; + /* Oldest sequence number in range */ + private long oldseqnum; + /* Newest sequence number in range */ + private long newseqnum; + /* Is the machine identifier of the relevant slots equal to (or not + * equal to) the specified machine identifier. */ + private boolean equalto; + /* Set of machines that have not received notification. */ + private HashSet<Long> watchset; + + RejectedMessage(Slot slot, long _machineid, long _oldseqnum, long _newseqnum, boolean _equalto) { + super(slot); + machineid=_machineid; + oldseqnum=_oldseqnum; + newseqnum=_newseqnum; + equalto=_equalto; + } + + long getOldSeqNum() { + return oldseqnum; + } + + long getNewSeqNum() { + return newseqnum; + } + + boolean getEqual() { + return equalto; + } + + long getMachineID() { + return machineid; + } + + static Entry decode(Slot slot, ByteBuffer bb) { + long machineid=bb.getLong(); + long oldseqnum=bb.getLong(); + long newseqnum=bb.getLong(); + byte equalto=bb.get(); + return new RejectedMessage(slot, machineid, oldseqnum, newseqnum, equalto==1); + } + + void setWatchSet(HashSet<Long> _watchset) { + watchset=_watchset; + } + + void removeWatcher(long machineid) { + if (watchset.remove(machineid)) + if (watchset.isEmpty()) + setDead(); + } + + void encode(ByteBuffer bb) { + bb.put(Entry.TypeRejectedMessage); + bb.putLong(machineid); + bb.putLong(oldseqnum); + bb.putLong(newseqnum); + bb.put(equalto?(byte)1:(byte)0); + } + + int getSize() { + return 3*Long.BYTES + 2*Byte.BYTES; + } + + byte getType() { + return Entry.TypeRejectedMessage; + } + + Entry getCopy(Slot s) { + return new RejectedMessage(s, machineid, oldseqnum, newseqnum, equalto); + } +} diff --git a/version2/backup/src/java/iotcloud/ServerException.java b/version2/backup/src/java/iotcloud/ServerException.java new file mode 100644 index 0000000..6d35c43 --- /dev/null +++ b/version2/backup/src/java/iotcloud/ServerException.java @@ -0,0 +1,8 @@ +package iotcloud; + +public class ServerException extends Exception { + + public ServerException(String message) { + super(message); + } +} diff --git a/version2/backup/src/java/iotcloud/Slot.java b/version2/backup/src/java/iotcloud/Slot.java new file mode 100644 index 0000000..ab04359 --- /dev/null +++ b/version2/backup/src/java/iotcloud/Slot.java @@ -0,0 +1,222 @@ +package iotcloud; +import java.util.Vector; +import java.nio.ByteBuffer; +import javax.crypto.Mac; +import java.util.Arrays; + +/** + * Data structuring for holding Slot information. + * @author Brian Demsky + * @version 1.0 + */ + +class Slot implements Liveness { + /** Sets the slot size. */ + static final int SLOT_SIZE = 2048; + /** Sets the size for the HMAC. */ + static final int HMAC_SIZE = 32; + + /** Sequence number of the slot. */ + private long seqnum; + /** HMAC of previous slot. */ + private byte[] prevhmac; + /** HMAC of this slot. */ + private byte[] hmac; + /** Machine that sent this slot. */ + private long machineid; + /** Vector of entries in this slot. */ + private Vector<Entry> entries; + /** Pieces of information that are live. */ + private int livecount; + /** Flag that indicates whether this slot is still live for + * recording the machine that sent it. */ + private boolean seqnumlive; + /** Number of bytes of free space. */ + private int freespace; + /** Reference to Table */ + private Table table; + + Slot(Table _table, long _seqnum, long _machineid, byte[] _prevhmac, byte[] _hmac) { + seqnum = _seqnum; + machineid = _machineid; + prevhmac = _prevhmac; + hmac = _hmac; + entries = new Vector<Entry>(); + livecount = 1; + seqnumlive = true; + freespace = SLOT_SIZE - getBaseSize(); + table = _table; + } + + Slot(Table _table, long _seqnum, long _machineid, byte[] _prevhmac) { + this(_table, _seqnum, _machineid, _prevhmac, null); + } + + Slot(Table _table, long _seqnum, long _machineid) { + this(_table, _seqnum, _machineid, new byte[HMAC_SIZE], null); + } + + byte[] getHMAC() { + return hmac; + } + + byte[] getPrevHMAC() { + return prevhmac; + } + + Entry addEntry(Entry e) { + e = e.getCopy(this); + entries.add(e); + livecount++; + freespace -= e.getSize(); + return e; + } + + void removeEntry(Entry e) { + entries.remove(e); + livecount--; + freespace += e.getSize(); + } + + private void addShallowEntry(Entry e) { + entries.add(e); + livecount++; + freespace -= e.getSize(); + } + + /** + * Returns true if the slot has free space to hold the entry without + * using its reserved space. */ + + boolean hasSpace(Entry e) { + int newfreespace = freespace - e.getSize(); + return newfreespace >= 0; + } + + Vector<Entry> getEntries() { + return entries; + } + + static Slot decode(Table table, byte[] array, Mac mac) { + mac.update(array, HMAC_SIZE, array.length - HMAC_SIZE); + byte[] realmac = mac.doFinal(); + + ByteBuffer bb = ByteBuffer.wrap(array); + byte[] hmac = new byte[HMAC_SIZE]; + byte[] prevhmac = new byte[HMAC_SIZE]; + bb.get(hmac); + bb.get(prevhmac); + if (!Arrays.equals(realmac, hmac)) + throw new Error("Server Error: Invalid HMAC! Potential Attack!"); + + long seqnum = bb.getLong(); + long machineid = bb.getLong(); + int numentries = bb.getInt(); + Slot slot = new Slot(table, seqnum, machineid, prevhmac, hmac); + + for (int i = 0; i < numentries; i++) { + slot.addShallowEntry(Entry.decode(slot, bb)); + } + + return slot; + } + + byte[] encode(Mac mac) { + byte[] array = new byte[SLOT_SIZE]; + ByteBuffer bb = ByteBuffer.wrap(array); + /* Leave space for the slot HMAC. */ + bb.position(HMAC_SIZE); + bb.put(prevhmac); + bb.putLong(seqnum); + bb.putLong(machineid); + bb.putInt(entries.size()); + for (Entry entry : entries) { + entry.encode(bb); + } + /* Compute our HMAC */ + mac.update(array, HMAC_SIZE, array.length - HMAC_SIZE); + byte[] realmac = mac.doFinal(); + hmac = realmac; + bb.position(0); + bb.put(realmac); + return array; + } + + /** + * Returns the empty size of a Slot. Includes 2 HMACs, the machine + * identifier, the sequence number, and the number of entries. + */ + int getBaseSize() { + return 2 * HMAC_SIZE + 2 * Long.BYTES + Integer.BYTES; + } + + /** + * Returns the live set of entries for this Slot. Generates a fake + * LastMessage entry to represent the information stored by the slot + * itself. + */ + + Vector<Entry> getLiveEntries(boolean resize) { + Vector<Entry> liveEntries = new Vector<Entry>(); + for (Entry entry : entries) { + if (entry.isLive()) { + if (!resize || entry.getType() != Entry.TypeTableStatus) + liveEntries.add(entry); + } + } + + if (seqnumlive && !resize) + liveEntries.add(new LastMessage(this, machineid, seqnum)); + + return liveEntries; + } + + /** + * Returns the sequence number of the slot. + */ + + long getSequenceNumber() { + return seqnum; + } + + /** + * Returns the machine that sent this slot. + */ + + long getMachineID() { + return machineid; + } + + /** + * Records that a newer slot records the fact that this slot was + * sent by the relevant machine. + */ + + void setDead() { + seqnumlive = false; + decrementLiveCount(); + } + + /** + * Update the count of live entries. + */ + + void decrementLiveCount() { + livecount--; + if (livecount == 0) { + table.decrementLiveCount(); + } + } + + /** + * Returns whether the slot stores any live information. + */ + + boolean isLive() { + return livecount > 0; + } + + public String toString() { + return "<" + getSequenceNumber() + ">"; + } +} diff --git a/version2/backup/src/java/iotcloud/SlotBuffer.java b/version2/backup/src/java/iotcloud/SlotBuffer.java new file mode 100644 index 0000000..716d0f2 --- /dev/null +++ b/version2/backup/src/java/iotcloud/SlotBuffer.java @@ -0,0 +1,122 @@ +package iotcloud; + +/** + * Circular buffer that holds the live set of slots. + * @author Brian Demsky + * @version 1.0 + */ + +class SlotBuffer { + static final int DEFAULT_SIZE = 128; + + private Slot[] array; + private int head; + private int tail; + public long oldestseqn; + + SlotBuffer() { + array = new Slot[DEFAULT_SIZE + 1]; + head = tail = 0; + oldestseqn = 0; + } + + int size() { + if (head >= tail) + return head - tail; + return (array.length + head) - tail; + } + + int capacity() { + return array.length - 1; + } + + void resize(int newsize) { + if (newsize == (array.length - 1)) + return; + + Slot[] newarray = new Slot[newsize + 1]; + int currsize = size(); + int index = tail; + for (int i = 0; i < currsize; i++) { + newarray[i] = array[index]; + if ((++index) == array.length) + index = 0; + } + array = newarray; + tail = 0; + head = currsize; + } + + private void incrementHead() { + head++; + if (head >= array.length) + head = 0; + } + + private void incrementTail() { + tail++; + if (tail >= array.length) + tail = 0; + } + + void putSlot(Slot s) { + + long checkNum = (getNewestSeqNum() + 1); + + if (checkNum != s.getSequenceNumber()) { + // We have a gap so expunge all our slots + oldestseqn = s.getSequenceNumber(); + tail = 0; + head = 1; + array[0] = s; + return; + } + + array[head] = s; + incrementHead(); + + if (oldestseqn == 0) { + oldestseqn = s.getSequenceNumber(); + } + + if (head == tail) { + incrementTail(); + oldestseqn++; + } + } + + Slot getSlot(long seqnum) { + int diff = (int) (seqnum - oldestseqn); + int index = diff + tail; + + if (index < 0) { + // Really old message so we dont have it anymore + return null; + } + + if (index >= array.length) { + if (head >= tail) { + return null; + } + index -= array.length; + } + + if (index >= array.length) { + + return null; + } + if (head >= tail && index >= head) { + return null; + } + + return array[index]; + } + + long getOldestSeqNum() { + return oldestseqn; + } + + long getNewestSeqNum() { + return oldestseqn + size() - 1; + } +} diff --git a/version2/backup/src/java/iotcloud/SlotIndexer.java b/version2/backup/src/java/iotcloud/SlotIndexer.java new file mode 100644 index 0000000..cecdf2d --- /dev/null +++ b/version2/backup/src/java/iotcloud/SlotIndexer.java @@ -0,0 +1,31 @@ +package iotcloud; + +/** + * Slot indexer allows slots in both the slot buffer and the new + * server response to looked up in a consistent fashion. + * @author Brian Demsky + * @version 1.0 + */ + +class SlotIndexer { + private Slot[] updates; + private SlotBuffer buffer; + private long firstslotseqnum; + + SlotIndexer(Slot[] _updates, SlotBuffer _buffer) { + buffer = _buffer; + updates = _updates; + firstslotseqnum = updates[0].getSequenceNumber(); + } + + Slot getSlot(long seqnum) { + if (seqnum >= firstslotseqnum) { + int offset = (int) (seqnum - firstslotseqnum); + if (offset >= updates.length) + throw new Error("Invalid Slot Sequence Number Reference"); + else + return updates[offset]; + } else + return buffer.getSlot(seqnum); + } +} diff --git a/version2/backup/src/java/iotcloud/Table.java b/version2/backup/src/java/iotcloud/Table.java new file mode 100644 index 0000000..f6d699b --- /dev/null +++ b/version2/backup/src/java/iotcloud/Table.java @@ -0,0 +1,1728 @@ +package iotcloud; +import java.util.HashMap; +import java.util.Map; +import java.util.Iterator; +import java.util.HashSet; +import java.util.Arrays; +import java.util.Vector; +import java.util.Random; +import java.util.Queue; +import java.util.LinkedList; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.Collection; +import java.util.Collections; +import java.nio.ByteBuffer; +import java.util.concurrent.Semaphore; + + +/** + * IoTTable data structure. Provides client inferface. + * @author Brian Demsky + * @version 1.0 + */ + +final public class Table { + private int numslots; //number of slots stored in buffer + + // machine id -> (sequence number, Slot or LastMessage); records last message by each client + private HashMap<Long, Pair<Long, Liveness> > lastmessagetable = new HashMap<Long, Pair<Long, Liveness> >(); + // machine id -> ... + private HashMap<Long, HashSet<RejectedMessage> > watchlist = new HashMap<Long, HashSet<RejectedMessage> >(); + private Vector<Long> rejectedmessagelist = new Vector<Long>(); + private SlotBuffer buffer; + private CloudComm cloud; + private long sequencenumber; //Largest sequence number a client has received + private long localmachineid; + private TableStatus lastTableStatus; + static final int FREE_SLOTS = 10; //number of slots that should be kept free + static final int SKIP_THRESHOLD = 10; + private long liveslotcount = 0; + private int chance; + static final double RESIZE_MULTIPLE = 1.2; + static final double RESIZE_THRESHOLD = 0.75; + static final int REJECTED_THRESHOLD = 5; + private int resizethreshold; + private long lastliveslotseqn; //smallest sequence number with a live entry + private Random random = new Random(); + private long lastUncommittedTransaction = 0; + + private int smallestTableStatusSeen = -1; + private int largestTableStatusSeen = -1; + private int lastSeenPendingTransactionSpeculateIndex = 0; + private int commitSequenceNumber = 0; + private long localTransactionSequenceNumber = 0; + + private PendingTransaction pendingTransBuild = null; // Pending Transaction used in building + private LinkedList<PendingTransaction> pendingTransQueue = null; // Queue of pending transactions + private Map<Long, Map<Long, Commit>> commitMap = null; // List of all the most recent live commits + private Map<Long, Abort> abortMap = null; // Set of the live aborts + private Map<IoTString, Commit> committedMapByKey = null; // Table of committed KV + private Map<IoTString, KeyValue> commitedTable = null; // Table of committed KV + private Map<IoTString, KeyValue> speculativeTable = null; // Table of speculative KV + private Map<Long, Transaction> uncommittedTransactionsMap = null; + private Map<IoTString, Long> arbitratorTable = null; // Table of arbitrators + private Map<IoTString, NewKey> newKeyTable = null; // Table of speculative KV + private Map<Long, Map<Long, Commit>> newCommitMap = null; // Map of all the new commits + private Map<Long, Long> lastCommitSeenSeqNumMap = null; // sequence number of the last commit that was seen grouped by arbitrator + private Map<Long, Long> lastCommitSeenTransSeqNumMap = null; // transaction sequence number of the last commit that was seen grouped by arbitrator + private Map<Long, Long> lastAbortSeenSeqNumMap = null; // sequence number of the last abort that was seen grouped by arbitrator + private Map<IoTString, KeyValue> pendingTransSpeculativeTable = null; + private List<Commit> pendingCommitsList = null; + private List<Commit> pendingCommitsToDelete = null; + private Map<Long, LocalComm> localCommunicationChannels; + private Map<Long, TransactionStatus> transactionStatusMap = null; + private Map<Long, TransactionStatus> transactionStatusNotSentMap = null; + + private Semaphore mutex = null; + + + + public Table(String hostname, String baseurl, String password, long _localmachineid) { + localmachineid = _localmachineid; + buffer = new SlotBuffer(); + numslots = buffer.capacity(); + setResizeThreshold(); + sequencenumber = 0; + cloud = new CloudComm(this, hostname, baseurl, password); + lastliveslotseqn = 1; + + setupDataStructs(); + } + + public Table(CloudComm _cloud, long _localmachineid) { + localmachineid = _localmachineid; + buffer = new SlotBuffer(); + numslots = buffer.capacity(); + setResizeThreshold(); + sequencenumber = 0; + cloud = _cloud; + + setupDataStructs(); + } + + private void setupDataStructs() { + pendingTransQueue = new LinkedList<PendingTransaction>(); + commitMap = new HashMap<Long, Map<Long, Commit>>(); + abortMap = new HashMap<Long, Abort>(); + committedMapByKey = new HashMap<IoTString, Commit>(); + commitedTable = new HashMap<IoTString, KeyValue>(); + speculativeTable = new HashMap<IoTString, KeyValue>(); + uncommittedTransactionsMap = new HashMap<Long, Transaction>(); + arbitratorTable = new HashMap<IoTString, Long>(); + newKeyTable = new HashMap<IoTString, NewKey>(); + newCommitMap = new HashMap<Long, Map<Long, Commit>>(); + lastCommitSeenSeqNumMap = new HashMap<Long, Long>(); + lastCommitSeenTransSeqNumMap = new HashMap<Long, Long>(); + lastAbortSeenSeqNumMap = new HashMap<Long, Long>(); + pendingTransSpeculativeTable = new HashMap<IoTString, KeyValue>(); + pendingCommitsList = new LinkedList<Commit>(); + pendingCommitsToDelete = new LinkedList<Commit>(); + localCommunicationChannels = new HashMap<Long, LocalComm>(); + transactionStatusMap = new HashMap<Long, TransactionStatus>(); + transactionStatusNotSentMap = new HashMap<Long, TransactionStatus>(); + mutex = new Semaphore(1); + } + + public void initTable() throws ServerException { + cloud.setSalt();//Set the salt + Slot s = new Slot(this, 1, localmachineid); + TableStatus status = new TableStatus(s, numslots); + s.addEntry(status); + Slot[] array = cloud.putSlot(s, numslots); + if (array == null) { + array = new Slot[] {s}; + /* update data structure */ + validateandupdate(array, true); + } else { + throw new Error("Error on initialization"); + } + } + + public void rebuild() throws ServerException, InterruptedException { + mutex.acquire(); + Slot[] newslots = cloud.getSlots(sequencenumber + 1); + validateandupdate(newslots, true); + mutex.release(); + } + + // TODO: delete method + public void printSlots() { + long o = buffer.getOldestSeqNum(); + long n = buffer.getNewestSeqNum(); + + int[] types = new int[10]; + + int num = 0; + + int livec = 0; + int deadc = 0; + for (long i = o; i < (n + 1); i++) { + Slot s = buffer.getSlot(i); + + Vector<Entry> entries = s.getEntries(); + + for (Entry e : entries) { + if (e.isLive()) { + int type = e.getType(); + types[type] = types[type] + 1; + num++; + livec++; + } else { + deadc++; + } + } + } + + for (int i = 0; i < 10; i++) { + System.out.println(i + " " + types[i]); + } + System.out.println("Live count: " + livec); + System.out.println("Dead count: " + deadc); + System.out.println("Old: " + o); + System.out.println("New: " + n); + System.out.println("Size: " + buffer.size()); + System.out.println("Commits Key Map: " + commitedTable.size()); + // System.out.println("Commits Live Map: " + commitMap.size()); + System.out.println("Pending: " + pendingTransQueue.size()); + + // List<IoTString> strList = new ArrayList<IoTString>(); + // for (int i = 0; i < 100; i++) { + // String keyA = "a" + i; + // String keyB = "b" + i; + // String keyC = "c" + i; + // String keyD = "d" + i; + + // IoTString iKeyA = new IoTString(keyA); + // IoTString iKeyB = new IoTString(keyB); + // IoTString iKeyC = new IoTString(keyC); + // IoTString iKeyD = new IoTString(keyD); + + // strList.add(iKeyA); + // strList.add(iKeyB); + // strList.add(iKeyC); + // strList.add(iKeyD); + // } + + + // for (Long l : commitMap.keySet()) { + // for (Long l2 : commitMap.get(l).keySet()) { + // for (KeyValue kv : commitMap.get(l).get(l2).getkeyValueUpdateSet()) { + // strList.remove(kv.getKey()); + // System.out.print(kv.getKey() + " "); + // } + // } + // } + + // System.out.println(); + // System.out.println(); + + // for (IoTString s : strList) { + // System.out.print(s + " "); + // } + // System.out.println(); + // System.out.println(strList.size()); + } + + public long getId() { + return localmachineid; + } + + public boolean hasConnection() { + return cloud.hasConnection(); + } + + public String toString() { + String retString = " Committed Table: \n"; + retString += "---------------------------\n"; + retString += commitedTable.toString(); + + retString += "\n\n"; + + retString += " Speculative Table: \n"; + retString += "---------------------------\n"; + retString += speculativeTable.toString(); + + return retString; + } + + public void addLocalComm(long machineId, LocalComm lc) { + localCommunicationChannels.put(machineId, lc); + } + public Long getArbitrator(IoTString key) throws InterruptedException { + + mutex.acquire(); + Long arb = arbitratorTable.get(key); + mutex.release(); + + return arb; + } + + public IoTString getCommitted(IoTString key) throws InterruptedException { + + mutex.acquire(); + KeyValue kv = commitedTable.get(key); + mutex.release(); + + + if (kv != null) { + return kv.getValue(); + } else { + return null; + } + } + + public IoTString getSpeculative(IoTString key) throws InterruptedException { + + mutex.acquire(); + + KeyValue kv = pendingTransSpeculativeTable.get(key); + + if (kv == null) { + kv = speculativeTable.get(key); + } + + if (kv == null) { + kv = commitedTable.get(key); + } + mutex.release(); + + + if (kv != null) { + return kv.getValue(); + } else { + return null; + } + } + + public IoTString getCommittedAtomic(IoTString key) throws InterruptedException { + + mutex.acquire(); + + KeyValue kv = commitedTable.get(key); + + if (arbitratorTable.get(key) == null) { + throw new Error("Key not Found."); + } + + // Make sure new key value pair matches the current arbitrator + if (!pendingTransBuild.checkArbitrator(arbitratorTable.get(key))) { + // TODO: Maybe not throw en error + throw new Error("Not all Key Values Match Arbitrator."); + } + + mutex.release(); + + if (kv != null) { + pendingTransBuild.addKVGuard(new KeyValue(key, kv.getValue())); + return kv.getValue(); + } else { + pendingTransBuild.addKVGuard(new KeyValue(key, null)); + return null; + } + } + + public IoTString getSpeculativeAtomic(IoTString key) throws InterruptedException { + + mutex.acquire(); + + if (arbitratorTable.get(key) == null) { + throw new Error("Key not Found."); + } + + // Make sure new key value pair matches the current arbitrator + if (!pendingTransBuild.checkArbitrator(arbitratorTable.get(key))) { + // TODO: Maybe not throw en error + throw new Error("Not all Key Values Match Arbitrator."); + } + + KeyValue kv = pendingTransSpeculativeTable.get(key); + + if (kv == null) { + kv = speculativeTable.get(key); + } + + if (kv == null) { + kv = commitedTable.get(key); + } + + mutex.release(); + + if (kv != null) { + pendingTransBuild.addKVGuard(new KeyValue(key, kv.getValue())); + return kv.getValue(); + } else { + pendingTransBuild.addKVGuard(new KeyValue(key, null)); + return null; + } + } + + public Pair<Boolean, Boolean> update() throws InterruptedException { + + mutex.acquire(); + + boolean gotLatestFromServer = false; + boolean didSendLocal = false; + + try { + Slot[] newslots = cloud.getSlots(sequencenumber + 1); + validateandupdate(newslots, false); + gotLatestFromServer = true; + + if (!pendingTransQueue.isEmpty()) { + + // We have a pending transaction so do full insertion + processPendingTrans(); + } else { + + // We dont have a pending transaction so do minimal effort + updateWithNotPendingTrans(); + } + + didSendLocal = true; + + + } catch (Exception e) { + // could not update so do nothing + } + + + mutex.release(); + return new Pair<Boolean, Boolean>(gotLatestFromServer, didSendLocal); + } + + public Boolean updateFromLocal(long arb) throws InterruptedException { + LocalComm lc = localCommunicationChannels.get(arb); + if (lc == null) { + // Cant talk directly to arbitrator so cant do anything + return false; + } + + byte[] array = new byte[Long.BYTES ]; + ByteBuffer bbEncode = ByteBuffer.wrap(array); + Long lastSeenCommit = lastCommitSeenSeqNumMap.get(arb); + if (lastSeenCommit != null) { + bbEncode.putLong(lastSeenCommit); + } else { + bbEncode.putLong(0); + } + + mutex.acquire(); + byte[] data = lc.sendDataToLocalDevice(arb, bbEncode.array()); + + // Decode the data + ByteBuffer bbDecode = ByteBuffer.wrap(data); + boolean didCommit = bbDecode.get() == 1; + int numberOfCommites = bbDecode.getInt(); + + List<Commit> newCommits = new LinkedList<Commit>(); + for (int i = 0; i < numberOfCommites; i++ ) { + bbDecode.get(); + Commit com = (Commit)Commit.decode(null, bbDecode); + newCommits.add(com); + } + + for (Commit commit : newCommits) { + // Prepare to process the commit + processEntry(commit); + } + + boolean didCommitOrSpeculate = proccessAllNewCommits(); + + // Go through all uncommitted transactions and kill the ones that are dead + deleteDeadUncommittedTransactions(); + + // Speculate on key value pairs + didCommitOrSpeculate |= createSpeculativeTable(); + createPendingTransactionSpeculativeTable(didCommitOrSpeculate); + + + mutex.release(); + + return true; + } + + public void startTransaction() throws InterruptedException { + // Create a new transaction, invalidates any old pending transactions. + pendingTransBuild = new PendingTransaction(); + } + + public void addKV(IoTString key, IoTString value) { + + if (arbitratorTable.get(key) == null) { + throw new Error("Key not Found."); + } + + // Make sure new key value pair matches the current arbitrator + if (!pendingTransBuild.checkArbitrator(arbitratorTable.get(key))) { + // TODO: Maybe not throw en error + throw new Error("Not all Key Values Match Arbitrator."); + } + + KeyValue kv = new KeyValue(key, value); + pendingTransBuild.addKV(kv); + } + + public TransactionStatus commitTransaction() throws InterruptedException { + + if (pendingTransBuild.getKVUpdates().size() == 0) { + + // transaction with no updates will have no effect on the system + return new TransactionStatus(TransactionStatus.StatusNoEffect, -1); + } + + mutex.acquire(); + + TransactionStatus transStatus = null; + + if (pendingTransBuild.getArbitrator() != localmachineid) { + + // set the local sequence number so we can recognize this transaction later + pendingTransBuild.setMachineLocalTransSeqNum(localTransactionSequenceNumber); + localTransactionSequenceNumber++; + + transStatus = new TransactionStatus(TransactionStatus.StatusPending, pendingTransBuild.getArbitrator()); + transactionStatusNotSentMap.put(pendingTransBuild.getMachineLocalTransSeqNum(), transStatus); + + // Add the pending transaction to the queue + pendingTransQueue.add(pendingTransBuild); + + + for (int i = lastSeenPendingTransactionSpeculateIndex; i < pendingTransQueue.size(); i++) { + PendingTransaction pt = pendingTransQueue.get(i); + + if (pt.evaluateGuard(commitedTable, speculativeTable, pendingTransSpeculativeTable)) { + + lastSeenPendingTransactionSpeculateIndex = i; + + for (KeyValue kv : pt.getKVUpdates()) { + pendingTransSpeculativeTable.put(kv.getKey(), kv); + } + + } + } + } else { + Transaction ut = new Transaction(null, + -1, + localmachineid, + pendingTransBuild.getArbitrator(), + pendingTransBuild.getKVUpdates(), + pendingTransBuild.getKVGuard()); + + Pair<Boolean, List<Commit>> retData = doLocalUpdateAndArbitrate(ut, lastCommitSeenSeqNumMap.get(localmachineid)); + + if (retData.getFirst()) { + transStatus = new TransactionStatus(TransactionStatus.StatusCommitted, pendingTransBuild.getArbitrator()); + } else { + transStatus = new TransactionStatus(TransactionStatus.StatusAborted, pendingTransBuild.getArbitrator()); + } + } + + // Try to insert transactions if possible + if (!pendingTransQueue.isEmpty()) { + // We have a pending transaction so do full insertion + processPendingTrans(); + } else { + try { + // We dont have a pending transaction so do minimal effort + updateWithNotPendingTrans(); + } catch (Exception e) { + // Do nothing + } + } + + // reset it so next time is fresh + pendingTransBuild = new PendingTransaction(); + + + mutex.release(); + return transStatus; + } + + public boolean createNewKey(IoTString keyName, long machineId) throws ServerException, InterruptedException { + try { + mutex.acquire(); + + while (true) { + if (arbitratorTable.get(keyName) != null) { + // There is already an arbitrator + mutex.release(); + return false; + } + + if (tryput(keyName, machineId, false)) { + // If successfully inserted + mutex.release(); + return true; + } + } + } catch (ServerException e) { + mutex.release(); + throw e; + } + } + + private void processPendingTrans() throws InterruptedException { + + boolean sentAllPending = false; + try { + while (!pendingTransQueue.isEmpty()) { + if (tryput( pendingTransQueue.peek(), false)) { + pendingTransQueue.poll(); + } + } + + // if got here then all pending transactions were sent + sentAllPending = true; + } catch (Exception e) { + // There was a connection error + sentAllPending = false; + } + + if (!sentAllPending) { + + for (Iterator<PendingTransaction> i = pendingTransQueue.iterator(); i.hasNext(); ) { + PendingTransaction pt = i.next(); + LocalComm lc = localCommunicationChannels.get(pt.getArbitrator()); + if (lc == null) { + // Cant talk directly to arbitrator so cant do anything + continue; + } + + + Transaction ut = new Transaction(null, + -1, + localmachineid, + pendingTransBuild.getArbitrator(), + pendingTransBuild.getKVUpdates(), + pendingTransBuild.getKVGuard()); + + + Pair<Boolean, List<Commit>> retData = sendTransactionToLocal(ut, lc); + + for (Commit commit : retData.getSecond()) { + // Prepare to process the commit + processEntry(commit); + } + + boolean didCommitOrSpeculate = proccessAllNewCommits(); + + // Go through all uncommitted transactions and kill the ones that are dead + deleteDeadUncommittedTransactions(); + + // Speculate on key value pairs + didCommitOrSpeculate |= createSpeculativeTable(); + createPendingTransactionSpeculativeTable(didCommitOrSpeculate); + + if (retData.getFirst()) { + TransactionStatus transStatus = transactionStatusNotSentMap.remove(pendingTransBuild.getMachineLocalTransSeqNum()); + if (transStatus != null) { + transStatus.setStatus(TransactionStatus.StatusCommitted); + } + + } else { + TransactionStatus transStatus = transactionStatusNotSentMap.remove(pendingTransBuild.getMachineLocalTransSeqNum()); + if (transStatus != null) { + transStatus.setStatus(TransactionStatus.StatusAborted); + } + } + i.remove(); + } + } + } + + private void updateWithNotPendingTrans() throws ServerException, InterruptedException { + boolean doEnd = false; + boolean needResize = false; + while (!doEnd && ((uncommittedTransactionsMap.keySet().size() > 0) || (pendingCommitsList.size() > 0)) ) { + boolean resize = needResize; + needResize = false; + + Slot s = new Slot(this, sequencenumber + 1, localmachineid, buffer.getSlot(sequencenumber).getHMAC()); + int newsize = 0; + if (liveslotcount > resizethreshold) { + resize = true; //Resize is forced + } + + if (resize) { + newsize = (int) (numslots * RESIZE_MULTIPLE); + TableStatus status = new TableStatus(s, newsize); + s.addEntry(status); + } + + doRejectedMessages(s); + + ThreeTuple<Boolean, Boolean, Long> retTup = doMandatoryResuce(s, resize); + + // Resize was needed so redo call + if (retTup.getFirst()) { + needResize = true; + continue; + } + + // Extract working variables + boolean seenliveslot = retTup.getSecond(); + long seqn = retTup.getThird(); + + // Did need to arbitrate + doEnd = !doArbitration(s); + + doOptionalRescue(s, seenliveslot, seqn, resize); + + int max = 0; + if (resize) { + max = newsize; + } + + Slot[] array = cloud.putSlot(s, max); + if (array == null) { + array = new Slot[] {s}; + rejectedmessagelist.clear(); + + // Delete pending commits that were sent to the cloud + deletePendingCommits(); + + } else { + if (array.length == 0) + throw new Error("Server Error: Did not send any slots"); + rejectedmessagelist.add(s.getSequenceNumber()); + doEnd = false; + } + + /* update data structure */ + validateandupdate(array, true); + } + } + + private Pair<Boolean, List<Commit>> sendTransactionToLocal(Transaction ut, LocalComm lc) throws InterruptedException { + + // encode the request + byte[] array = new byte[Long.BYTES + ut.getSize()]; + ByteBuffer bbEncode = ByteBuffer.wrap(array); + Long lastSeenCommit = lastCommitSeenSeqNumMap.get(ut.getArbitrator()); + if (lastSeenCommit != null) { + bbEncode.putLong(lastSeenCommit); + } else { + bbEncode.putLong(0); + } + ut.encode(bbEncode); + + byte[] data = lc.sendDataToLocalDevice(ut.getArbitrator(), bbEncode.array()); + + // Decode the data + ByteBuffer bbDecode = ByteBuffer.wrap(data); + boolean didCommit = bbDecode.get() == 1; + int numberOfCommites = bbDecode.getInt(); + + List<Commit> newCommits = new LinkedList<Commit>(); + for (int i = 0; i < numberOfCommites; i++ ) { + bbDecode.get(); + Commit com = (Commit)Commit.decode(null, bbDecode); + newCommits.add(com); + } + + return new Pair<Boolean, List<Commit>>(didCommit, newCommits); + } + + public byte[] localCommInput(byte[] data) throws InterruptedException { + + + + // Decode the data + ByteBuffer bbDecode = ByteBuffer.wrap(data); + long lastSeenCommit = bbDecode.getLong(); + + Transaction ut = null; + if (data.length != Long.BYTES) { + bbDecode.get(); + ut = (Transaction)Transaction.decode(null, bbDecode); + } + + mutex.acquire(); + // Do the local update and arbitrate + Pair<Boolean, List<Commit>> returnData = doLocalUpdateAndArbitrate(ut, lastSeenCommit); + mutex.release(); + + + // Calculate the size of the response + int size = Byte.BYTES + Integer.BYTES; + for (Commit com : returnData.getSecond()) { + size += com.getSize(); + } + + // encode the response + byte[] array = new byte[size]; + ByteBuffer bbEncode = ByteBuffer.wrap(array); + if (returnData.getFirst()) { + bbEncode.put((byte)1); + } else { + bbEncode.put((byte)0); + } + bbEncode.putInt(returnData.getSecond().size()); + + for (Commit com : returnData.getSecond()) { + com.encode(bbEncode); + } + + return bbEncode.array(); + } + + private Pair<Boolean, List<Commit>> doLocalUpdateAndArbitrate(Transaction ut, Long lastCommitSeen) { + + List<Commit> returnCommits = new ArrayList<Commit>(); + + if ((lastCommitSeenSeqNumMap.get(localmachineid) != null) && (lastCommitSeenSeqNumMap.get(localmachineid) > lastCommitSeen)) { + // There is a commit that the other client has not seen yet + + Map<Long, Commit> cm = commitMap.get(localmachineid); + if (cm != null) { + + List<Long> commitKeys = new ArrayList<Long>(cm.keySet()); + Collections.sort(commitKeys); + + + for (int i = (commitKeys.size() - 1); i >= 0; i--) { + Commit com = cm.get(commitKeys.get(i)); + + if (com.getSequenceNumber() <= lastCommitSeen) { + break; + } + returnCommits.add((Commit)com.getCopy(null)); + } + } + } + + + if ((ut == null) || (ut.getArbitrator() != localmachineid)) { + // We are not the arbitrator for that transaction so the other device is talking to the wrong arbitrator + // or there is no transaction to process + return new Pair<Boolean, List<Commit>>(false, returnCommits); + } + + if (!ut.evaluateGuard(commitedTable, null)) { + // Guard evaluated as false so return only the commits that the other device has not seen yet + return new Pair<Boolean, List<Commit>>(false, returnCommits); + } + + // create the commit + Commit commit = new Commit(null, + -1, + commitSequenceNumber, + ut.getArbitrator(), + ut.getkeyValueUpdateSet()); + commitSequenceNumber = commitSequenceNumber + 1; + + // Add to the pending commits list + pendingCommitsList.add(commit); + + // Add this commit so we can send it back + returnCommits.add(commit); + + // Prepare to process the commit + processEntry(commit); + + boolean didCommitOrSpeculate = proccessAllNewCommits(); + + // Go through all uncommitted transactions and kill the ones that are dead + deleteDeadUncommittedTransactions(); + + // Speculate on key value pairs + didCommitOrSpeculate |= createSpeculativeTable(); + createPendingTransactionSpeculativeTable(didCommitOrSpeculate); + + return new Pair<Boolean, List<Commit>>(true, returnCommits); + } + + public void decrementLiveCount() { + liveslotcount--; + } + + private void setResizeThreshold() { + int resize_lower = (int) (RESIZE_THRESHOLD * numslots); + resizethreshold = resize_lower - 1 + random.nextInt(numslots - resize_lower); + } + + private boolean tryput(PendingTransaction pendingTrans, boolean resize) throws ServerException { + Slot s = new Slot(this, sequencenumber + 1, localmachineid, buffer.getSlot(sequencenumber).getHMAC()); + + int newsize = 0; + if (liveslotcount > resizethreshold) { + resize = true; //Resize is forced + } + + if (resize) { + newsize = (int) (numslots * RESIZE_MULTIPLE); + TableStatus status = new TableStatus(s, newsize); + s.addEntry(status); + } + + doRejectedMessages(s); + + ThreeTuple<Boolean, Boolean, Long> retTup = doMandatoryResuce(s, resize); + + // Resize was needed so redo call + if (retTup.getFirst()) { + return tryput(pendingTrans, true); + } + + // Extract working variables + boolean seenliveslot = retTup.getSecond(); + long seqn = retTup.getThird(); + + doArbitration(s); + + Transaction trans = new Transaction(s, + s.getSequenceNumber(), + localmachineid, + pendingTrans.getArbitrator(), + pendingTrans.getKVUpdates(), + pendingTrans.getKVGuard()); + boolean insertedTrans = false; + if (s.hasSpace(trans)) { + s.addEntry(trans); + insertedTrans = true; + } + + doOptionalRescue(s, seenliveslot, seqn, resize); + Pair<Boolean, Slot[]> sendRetData = doSendSlots(s, insertedTrans, resize, newsize); + + if (sendRetData.getFirst()) { + // update the status and change what the sequence number is for the + TransactionStatus transStatus = transactionStatusNotSentMap.remove(pendingTrans.getMachineLocalTransSeqNum()); + transStatus.setStatus(TransactionStatus.StatusSent); + transStatus.setSentTransaction(); + transactionStatusMap.put(trans.getSequenceNumber(), transStatus); + } + + + if (sendRetData.getSecond().length != 0) { + // insert into the local block chain + validateandupdate(sendRetData.getSecond(), true); + } + + return sendRetData.getFirst(); + } + + private boolean tryput(IoTString keyName, long arbMachineid, boolean resize) throws ServerException { + Slot s = new Slot(this, sequencenumber + 1, localmachineid, buffer.getSlot(sequencenumber).getHMAC()); + int newsize = 0; + if (liveslotcount > resizethreshold) { + resize = true; //Resize is forced + } + + if (resize) { + newsize = (int) (numslots * RESIZE_MULTIPLE); + TableStatus status = new TableStatus(s, newsize); + s.addEntry(status); + } + + doRejectedMessages(s); + ThreeTuple<Boolean, Boolean, Long> retTup = doMandatoryResuce(s, resize); + + // Resize was needed so redo call + if (retTup.getFirst()) { + return tryput(keyName, arbMachineid, true); + } + + // Extract working variables + boolean seenliveslot = retTup.getSecond(); + long seqn = retTup.getThird(); + + doArbitration(s); + + NewKey newKey = new NewKey(s, keyName, arbMachineid); + + boolean insertedNewKey = false; + if (s.hasSpace(newKey)) { + s.addEntry(newKey); + insertedNewKey = true; + } + + doOptionalRescue(s, seenliveslot, seqn, resize); + Pair<Boolean, Slot[]> sendRetData = doSendSlots(s, insertedNewKey, resize, newsize); + + if (sendRetData.getSecond().length != 0) { + // insert into the local block chain + validateandupdate(sendRetData.getSecond(), true); + } + + return sendRetData.getFirst(); + } + + private void doRejectedMessages(Slot s) { + if (! rejectedmessagelist.isEmpty()) { + /* TODO: We should avoid generating a rejected message entry if + * there is already a sufficient entry in the queue (e.g., + * equalsto value of true and same sequence number). */ + + long old_seqn = rejectedmessagelist.firstElement(); + if (rejectedmessagelist.size() > REJECTED_THRESHOLD) { + long new_seqn = rejectedmessagelist.lastElement(); + RejectedMessage rm = new RejectedMessage(s, localmachineid, old_seqn, new_seqn, false); + s.addEntry(rm); + } else { + long prev_seqn = -1; + int i = 0; + /* Go through list of missing messages */ + for (; i < rejectedmessagelist.size(); i++) { + long curr_seqn = rejectedmessagelist.get(i); + Slot s_msg = buffer.getSlot(curr_seqn); + if (s_msg != null) + break; + prev_seqn = curr_seqn; + } + /* Generate rejected message entry for missing messages */ + if (prev_seqn != -1) { + RejectedMessage rm = new RejectedMessage(s, localmachineid, old_seqn, prev_seqn, false); + s.addEntry(rm); + } + /* Generate rejected message entries for present messages */ + for (; i < rejectedmessagelist.size(); i++) { + long curr_seqn = rejectedmessagelist.get(i); + Slot s_msg = buffer.getSlot(curr_seqn); + long machineid = s_msg.getMachineID(); + RejectedMessage rm = new RejectedMessage(s, machineid, curr_seqn, curr_seqn, true); + s.addEntry(rm); + } + } + } + } + + private ThreeTuple<Boolean, Boolean, Long> doMandatoryResuce(Slot s, boolean resize) { + long newestseqnum = buffer.getNewestSeqNum(); + long oldestseqnum = buffer.getOldestSeqNum(); + if (lastliveslotseqn < oldestseqnum) + lastliveslotseqn = oldestseqnum; + + long seqn = lastliveslotseqn; + boolean seenliveslot = false; + long firstiffull = newestseqnum + 1 - numslots; // smallest seq number in the buffer if it is full + long threshold = firstiffull + FREE_SLOTS; // we want the buffer to be clear of live entries up to this point + + + // Mandatory Rescue + for (; seqn < threshold; seqn++) { + Slot prevslot = buffer.getSlot(seqn); + // Push slot number forward + if (! seenliveslot) + lastliveslotseqn = seqn; + + if (! prevslot.isLive()) + continue; + seenliveslot = true; + Vector<Entry> liveentries = prevslot.getLiveEntries(resize); + for (Entry liveentry : liveentries) { + if (s.hasSpace(liveentry)) { + s.addEntry(liveentry); + } else if (seqn == firstiffull) { //if there's no space but the entry is about to fall off the queue + if (!resize) { + System.out.println("B"); //? + return new ThreeTuple<Boolean, Boolean, Long>(true, seenliveslot, seqn); + } + } + } + } + + // Did not resize + return new ThreeTuple<Boolean, Boolean, Long>(false, seenliveslot, seqn); + } + + private boolean doArbitration(Slot s) { + + // flag whether we have finished all arbitration + boolean stillHasArbitration = false; + + pendingCommitsToDelete.clear(); + + // First add queue commits + for (Commit commit : pendingCommitsList) { + if (s.hasSpace(commit)) { + s.addEntry(commit); + pendingCommitsToDelete.add(commit); + } else { + // Ran out of space so move on but still not done + stillHasArbitration = true; + return stillHasArbitration; + } + } + + // Arbitrate + Map<IoTString, KeyValue> speculativeTableTmp = new HashMap<IoTString, KeyValue>(); + List<Long> transSeqNums = new ArrayList<Long>(uncommittedTransactionsMap.keySet()); + + // Sort from oldest to newest + Collections.sort(transSeqNums); + + for (Long transNum : transSeqNums) { + Transaction ut = uncommittedTransactionsMap.get(transNum); + + // Check if this machine arbitrates for this transaction + if (ut.getArbitrator() != localmachineid ) { + continue; + } + + // we did have something to arbitrate on + stillHasArbitration = true; + + Entry newEntry = null; + + if (ut.evaluateGuard(commitedTable, speculativeTableTmp)) { + // Guard evaluated as true + + // update the local tmp current key set + for (KeyValue kv : ut.getkeyValueUpdateSet()) { + speculativeTableTmp.put(kv.getKey(), kv); + } + + // create the commit + newEntry = new Commit(s, + ut.getSequenceNumber(), + commitSequenceNumber, + ut.getArbitrator(), + ut.getkeyValueUpdateSet()); + commitSequenceNumber = commitSequenceNumber + 1; + } else { + // Guard was false + + // create the abort + newEntry = new Abort(s, + ut.getSequenceNumber(), + ut.getMachineID(), + ut.getArbitrator()); + } + + if ((newEntry != null) && s.hasSpace(newEntry)) { + s.addEntry(newEntry); + } else { + break; + } + } + + return stillHasArbitration; + } + + private void deletePendingCommits() { + for (Commit com : pendingCommitsToDelete) { + pendingCommitsList.remove(com); + } + pendingCommitsToDelete.clear(); + } + + private void doOptionalRescue(Slot s, boolean seenliveslot, long seqn, boolean resize) { + /* now go through live entries from least to greatest sequence number until + * either all live slots added, or the slot doesn't have enough room + * for SKIP_THRESHOLD consecutive entries*/ + int skipcount = 0; + long newestseqnum = buffer.getNewestSeqNum(); + search: + for (; seqn <= newestseqnum; seqn++) { + Slot prevslot = buffer.getSlot(seqn); + //Push slot number forward + if (!seenliveslot) + lastliveslotseqn = seqn; + + if (!prevslot.isLive()) + continue; + seenliveslot = true; + Vector<Entry> liveentries = prevslot.getLiveEntries(resize); + for (Entry liveentry : liveentries) { + if (s.hasSpace(liveentry)) + s.addEntry(liveentry); + else { + skipcount++; + if (skipcount > SKIP_THRESHOLD) + break search; + } + } + } + } + + private Pair<Boolean, Slot[]> doSendSlots(Slot s, boolean inserted, boolean resize, int newsize) throws ServerException { + int max = 0; + if (resize) + max = newsize; + + Slot[] array = cloud.putSlot(s, max); + if (array == null) { + array = new Slot[] {s}; + rejectedmessagelist.clear(); + + // Delete pending commits that were sent to the cloud + deletePendingCommits(); + } else { + // if (array.length == 0) + // throw new Error("Server Error: Did not send any slots"); + rejectedmessagelist.add(s.getSequenceNumber()); + inserted = false; + } + + return new Pair<Boolean, Slot[]>(inserted, array); + } + + private void validateandupdate(Slot[] newslots, boolean acceptupdatestolocal) { + /* The cloud communication layer has checked slot HMACs already + before decoding */ + if (newslots.length == 0) return; + + // Reset the table status declared sizes + smallestTableStatusSeen = -1; + largestTableStatusSeen = -1; + + long firstseqnum = newslots[0].getSequenceNumber(); + if (firstseqnum <= sequencenumber) { + throw new Error("Server Error: Sent older slots!"); + } + + SlotIndexer indexer = new SlotIndexer(newslots, buffer); + checkHMACChain(indexer, newslots); + + HashSet<Long> machineSet = new HashSet<Long>(lastmessagetable.keySet()); // + + // initExpectedSize(firstseqnum); + for (Slot slot : newslots) { + processSlot(indexer, slot, acceptupdatestolocal, machineSet); + // updateExpectedSize(); + } + + /* If there is a gap, check to see if the server sent us everything. */ + if (firstseqnum != (sequencenumber + 1)) { + + // TODO: Check size + checkNumSlots(newslots.length); + if (!machineSet.isEmpty()) { + throw new Error("Missing record for machines: " + machineSet); + } + } + + + commitNewMaxSize(); + + /* Commit new to slots. */ + for (Slot slot : newslots) { + buffer.putSlot(slot); + liveslotcount++; + } + sequencenumber = newslots[newslots.length - 1].getSequenceNumber(); + + // Process all on key value pairs + boolean didCommitOrSpeculate = proccessAllNewCommits(); + + // Go through all uncommitted transactions and kill the ones that are dead + deleteDeadUncommittedTransactions(); + + // Speculate on key value pairs + didCommitOrSpeculate |= createSpeculativeTable(); + + createPendingTransactionSpeculativeTable(didCommitOrSpeculate); + } + + private boolean proccessAllNewCommits() { + // Process only if there are commit + if (newCommitMap.keySet().size() == 0) { + return false; + } + boolean didProcessNewCommit = false; + + for (Long arb : newCommitMap.keySet()) { + + List<Long> commitSeqNums = new ArrayList<Long>(newCommitMap.get(arb).keySet()); + + // Sort from oldest to newest commit + Collections.sort(commitSeqNums); + + // Go through each new commit one by one + for (Long entrySeqNum : commitSeqNums) { + Commit entry = newCommitMap.get(arb).get(entrySeqNum); + + long lastCommitSeenSeqNum = -1; + if (lastCommitSeenSeqNumMap.get(entry.getTransArbitrator()) != null) { + lastCommitSeenSeqNum = lastCommitSeenSeqNumMap.get(entry.getTransArbitrator()); + } + + if (entry.getSequenceNumber() <= lastCommitSeenSeqNum) { + Map<Long, Commit> cm = commitMap.get(arb); + if (cm == null) { + cm = new HashMap<Long, Commit>(); + } + + Commit prevCommit = cm.put(entry.getSequenceNumber(), entry); + commitMap.put(arb, cm); + + if (prevCommit != null) { + prevCommit.setDead(); + + for (KeyValue kv : prevCommit.getkeyValueUpdateSet()) { + committedMapByKey.put(kv.getKey(), entry); + } + } + + continue; + } + + Set<Commit> commitsToEditSet = new HashSet<Commit>(); + + for (KeyValue kv : entry.getkeyValueUpdateSet()) { + commitsToEditSet.add(committedMapByKey.get(kv.getKey())); + } + + commitsToEditSet.remove(null); + + for (Commit prevCommit : commitsToEditSet) { + + Set<KeyValue> deletedKV = prevCommit.updateLiveKeys(entry.getkeyValueUpdateSet()); + + if (!prevCommit.isLive()) { + Map<Long, Commit> cm = commitMap.get(arb); + + // remove it from the map so that it can be set as dead + if (cm != null) { + cm.remove(prevCommit.getSequenceNumber()); + commitMap.put(arb, cm); + } + } + } + + // Add the new commit + Map<Long, Commit> cm = commitMap.get(arb); + if (cm == null) { + cm = new HashMap<Long, Commit>(); + } + cm.put(entry.getSequenceNumber(), entry); + commitMap.put(arb, cm); + + lastCommitSeenSeqNumMap.put(entry.getTransArbitrator(), entry.getSequenceNumber()); + + // set the trans sequence number if we are able to + if (entry.getTransSequenceNumber() != -1) { + lastCommitSeenTransSeqNumMap.put(entry.getTransArbitrator(), entry.getTransSequenceNumber()); + } + + didProcessNewCommit = true; + + // Update the committed table list + for (KeyValue kv : entry.getkeyValueUpdateSet()) { + IoTString key = kv.getKey(); + commitedTable.put(key, kv); + committedMapByKey.put(key, entry); + } + } + } + // Clear the new commits storage so we can use it later + newCommitMap.clear(); + + + // go through all saved transactions and update the status of those that can be updated + for (Iterator<Map.Entry<Long, TransactionStatus>> i = transactionStatusMap.entrySet().iterator(); i.hasNext();) { + Map.Entry<Long, TransactionStatus> entry = i.next(); + long seqnum = entry.getKey(); + TransactionStatus status = entry.getValue(); + + if (status.getSentTransaction()) { + + Long commitSeqNum = lastCommitSeenTransSeqNumMap.get(status.getArbitrator()); + Long abortSeqNum = lastAbortSeenSeqNumMap.get(status.getArbitrator()); + + if (((commitSeqNum != null) && (seqnum <= commitSeqNum)) || + ((abortSeqNum != null) && (seqnum <= abortSeqNum))) { + status.setStatus(TransactionStatus.StatusCommitted); + i.remove(); + } + } + } + + return didProcessNewCommit; + } + + private void deleteDeadUncommittedTransactions() { + // Make dead the transactions + for (Iterator<Map.Entry<Long, Transaction>> i = uncommittedTransactionsMap.entrySet().iterator(); i.hasNext();) { + Transaction prevtrans = i.next().getValue(); + long transArb = prevtrans.getArbitrator(); + + Long commitSeqNum = lastCommitSeenTransSeqNumMap.get(transArb); + Long abortSeqNum = lastAbortSeenSeqNumMap.get(transArb); + + if (((commitSeqNum != null) && (prevtrans.getSequenceNumber() <= commitSeqNum)) || + ((abortSeqNum != null) && (prevtrans.getSequenceNumber() <= abortSeqNum))) { + i.remove(); + prevtrans.setDead(); + } + } + } + + private boolean createSpeculativeTable() { + if (uncommittedTransactionsMap.keySet().size() == 0) { + return false; + } + + Map<IoTString, KeyValue> speculativeTableTmp = new HashMap<IoTString, KeyValue>(); + List<Long> utSeqNums = new ArrayList<Long>(uncommittedTransactionsMap.keySet()); + + // Sort from oldest to newest commit + Collections.sort(utSeqNums); + + if (utSeqNums.get(0) > (lastUncommittedTransaction)) { + + speculativeTable.clear(); + lastUncommittedTransaction = -1; + + for (Long key : utSeqNums) { + Transaction trans = uncommittedTransactionsMap.get(key); + + lastUncommittedTransaction = key; + + if (trans.evaluateGuard(commitedTable, speculativeTableTmp)) { + for (KeyValue kv : trans.getkeyValueUpdateSet()) { + speculativeTableTmp.put(kv.getKey(), kv); + } + } + + } + } else { + for (Long key : utSeqNums) { + + if (key <= lastUncommittedTransaction) { + continue; + } + + lastUncommittedTransaction = key; + + Transaction trans = uncommittedTransactionsMap.get(key); + + if (trans.evaluateGuard(speculativeTable, speculativeTableTmp)) { + for (KeyValue kv : trans.getkeyValueUpdateSet()) { + speculativeTableTmp.put(kv.getKey(), kv); + } + } + } + } + + for (IoTString key : speculativeTableTmp.keySet()) { + speculativeTable.put(key, speculativeTableTmp.get(key)); + } + + return true; + } + + private void createPendingTransactionSpeculativeTable(boolean didCommitOrSpeculate) { + + if (didCommitOrSpeculate) { + pendingTransSpeculativeTable.clear(); + lastSeenPendingTransactionSpeculateIndex = 0; + + int index = 0; + for (PendingTransaction pt : pendingTransQueue) { + if (pt.evaluateGuard(commitedTable, speculativeTable, pendingTransSpeculativeTable)) { + + lastSeenPendingTransactionSpeculateIndex = index; + index++; + + for (KeyValue kv : pt.getKVUpdates()) { + pendingTransSpeculativeTable.put(kv.getKey(), kv); + } + + } + } + } + } + + private int expectedsize, currmaxsize; + + private void checkNumSlots(int numslots) { + + + // We only have 1 size so we must have this many slots + if (largestTableStatusSeen == smallestTableStatusSeen) { + if (numslots != smallestTableStatusSeen) { + throw new Error("Server Error: Server did not send all slots. Expected: " + smallestTableStatusSeen + " Received:" + numslots); + } + } else { + // We have more than 1 + if (numslots < smallestTableStatusSeen) { + throw new Error("Server Error: Server did not send all slots. Expected at least: " + smallestTableStatusSeen + " Received:" + numslots); + } + } + + // if (numslots != expectedsize) { + // throw new Error("Server Error: Server did not send all slots. Expected: " + expectedsize + " Received:" + numslots); + // } + } + + private void initExpectedSize(long firstsequencenumber) { + long prevslots = firstsequencenumber; + expectedsize = (prevslots < ((long) numslots)) ? (int) prevslots : numslots; + currmaxsize = numslots; + } + + private void updateExpectedSize() { + expectedsize++; + if (expectedsize > currmaxsize) { + expectedsize = currmaxsize; + } + } + + private void updateCurrMaxSize(int newmaxsize) { + currmaxsize = newmaxsize; + } + + private void commitNewMaxSize() { + + if (largestTableStatusSeen == -1) { + currmaxsize = numslots; + } else { + currmaxsize = largestTableStatusSeen; + } + + if (numslots != currmaxsize) { + buffer.resize(currmaxsize); + } + + numslots = currmaxsize; + setResizeThreshold(); + } + + private void processEntry(LastMessage entry, HashSet<Long> machineSet) { + updateLastMessage(entry.getMachineID(), entry.getSequenceNumber(), entry, false, machineSet); + } + + private void processEntry(RejectedMessage entry, SlotIndexer indexer) { + long oldseqnum = entry.getOldSeqNum(); + long newseqnum = entry.getNewSeqNum(); + boolean isequal = entry.getEqual(); + long machineid = entry.getMachineID(); + for (long seqnum = oldseqnum; seqnum <= newseqnum; seqnum++) { + Slot slot = indexer.getSlot(seqnum); + if (slot != null) { + long slotmachineid = slot.getMachineID(); + if (isequal != (slotmachineid == machineid)) { + throw new Error("Server Error: Trying to insert rejected message for slot " + seqnum); + } + } + } + + HashSet<Long> watchset = new HashSet<Long>(); + for (Map.Entry<Long, Pair<Long, Liveness> > lastmsg_entry : lastmessagetable.entrySet()) { + long entry_mid = lastmsg_entry.getKey(); + /* We've seen it, don't need to continue to watch. Our next + * message will implicitly acknowledge it. */ + if (entry_mid == localmachineid) + continue; + Pair<Long, Liveness> v = lastmsg_entry.getValue(); + long entry_seqn = v.getFirst(); + if (entry_seqn < newseqnum) { + addWatchList(entry_mid, entry); + watchset.add(entry_mid); + } + } + if (watchset.isEmpty()) + entry.setDead(); + else + entry.setWatchSet(watchset); + } + + private void processEntry(NewKey entry) { + arbitratorTable.put(entry.getKey(), entry.getMachineID()); + + NewKey oldNewKey = newKeyTable.put(entry.getKey(), entry); + + if (oldNewKey != null) { + oldNewKey.setDead(); + } + } + + private void processEntry(Transaction entry) { + + long arb = entry.getArbitrator(); + Long comLast = lastCommitSeenTransSeqNumMap.get(arb); + Long abLast = lastAbortSeenSeqNumMap.get(arb); + + Transaction prevTrans = null; + + if ((comLast != null) && (comLast >= entry.getSequenceNumber())) { + prevTrans = uncommittedTransactionsMap.remove(entry.getSequenceNumber()); + } else if ((abLast != null) && (abLast >= entry.getSequenceNumber())) { + prevTrans = uncommittedTransactionsMap.remove(entry.getSequenceNumber()); + } else { + prevTrans = uncommittedTransactionsMap.put(entry.getSequenceNumber(), entry); + } + + // Duplicate so delete old copy + if (prevTrans != null) { + prevTrans.setDead(); + } + } + + private void processEntry(Abort entry) { + if (lastmessagetable.get(entry.getMachineID()).getFirst() < entry.getTransSequenceNumber()) { + // Abort has not been seen yet so we need to keep track of it + + Abort prevAbort = abortMap.put(entry.getTransSequenceNumber(), entry); + if (prevAbort != null) { + prevAbort.setDead(); // delete old version of the duplicate + } + + if ((lastAbortSeenSeqNumMap.get(entry.getTransArbitrator()) != null) && (entry.getTransSequenceNumber() > lastAbortSeenSeqNumMap.get(entry.getTransArbitrator()))) { + lastAbortSeenSeqNumMap.put(entry.getTransArbitrator(), entry.getTransSequenceNumber()); + } + } else { + // The machine already saw this so it is dead + entry.setDead(); + } + + // Update the status of the transaction and remove it since we are done with this transaction + TransactionStatus status = transactionStatusMap.remove(entry.getTransSequenceNumber()); + if (status != null) { + status.setStatus(TransactionStatus.StatusAborted); + } + } + + private void processEntry(Commit entry) { + Map<Long, Commit> arbMap = newCommitMap.get(entry.getTransArbitrator()); + + if (arbMap == null) { + arbMap = new HashMap<Long, Commit>(); + } + + Commit prevCommit = arbMap.put(entry.getSequenceNumber(), entry); + newCommitMap.put(entry.getTransArbitrator(), arbMap); + + if (prevCommit != null) { + prevCommit.setDead(); + } + } + + private void processEntry(TableStatus entry) { + int newnumslots = entry.getMaxSlots(); + // updateCurrMaxSize(newnumslots); + if (lastTableStatus != null) + lastTableStatus.setDead(); + lastTableStatus = entry; + + if ((smallestTableStatusSeen == -1) || (newnumslots < smallestTableStatusSeen)) { + smallestTableStatusSeen = newnumslots; + } + + if ((largestTableStatusSeen == -1) || (newnumslots > largestTableStatusSeen)) { + largestTableStatusSeen = newnumslots; + } + } + + private void addWatchList(long machineid, RejectedMessage entry) { + HashSet<RejectedMessage> entries = watchlist.get(machineid); + if (entries == null) + watchlist.put(machineid, entries = new HashSet<RejectedMessage>()); + entries.add(entry); + } + + private void updateLastMessage(long machineid, long seqnum, Liveness liveness, boolean acceptupdatestolocal, HashSet<Long> machineSet) { + machineSet.remove(machineid); + + HashSet<RejectedMessage> watchset = watchlist.get(machineid); + if (watchset != null) { + for (Iterator<RejectedMessage> rmit = watchset.iterator(); rmit.hasNext(); ) { + RejectedMessage rm = rmit.next(); + if (rm.getNewSeqNum() <= seqnum) { + /* Remove it from our watchlist */ + rmit.remove(); + /* Decrement machines that need to see this notification */ + rm.removeWatcher(machineid); + } + } + } + + if (machineid == localmachineid) { + /* Our own messages are immediately dead. */ + if (liveness instanceof LastMessage) { + ((LastMessage)liveness).setDead(); + } else if (liveness instanceof Slot) { + ((Slot)liveness).setDead(); + } else { + throw new Error("Unrecognized type"); + } + } + + // Set dead the abort + for (Iterator<Map.Entry<Long, Abort>> i = abortMap.entrySet().iterator(); i.hasNext();) { + Abort abort = i.next().getValue(); + + if ((abort.getMachineID() == machineid) && (abort.getTransSequenceNumber() <= seqnum)) { + abort.setDead(); + i.remove(); + } + } + + Pair<Long, Liveness> lastmsgentry = lastmessagetable.put(machineid, new Pair<Long, Liveness>(seqnum, liveness)); + if (lastmsgentry == null) + return; + + long lastmsgseqnum = lastmsgentry.getFirst(); + Liveness lastentry = lastmsgentry.getSecond(); + if (machineid != localmachineid) { + if (lastentry instanceof LastMessage) { + ((LastMessage)lastentry).setDead(); + } else if (lastentry instanceof Slot) { + ((Slot)lastentry).setDead(); + } else { + throw new Error("Unrecognized type"); + } + } + + if (machineid == localmachineid) { + if (lastmsgseqnum != seqnum && !acceptupdatestolocal) + throw new Error("Server Error: Mismatch on local machine sequence number, needed: " + seqnum + " got: " + lastmsgseqnum); + } else { + if (lastmsgseqnum > seqnum) + throw new Error("Server Error: Rollback on remote machine sequence number"); + } + } + + private void processSlot(SlotIndexer indexer, Slot slot, boolean acceptupdatestolocal, HashSet<Long> machineSet) { + updateLastMessage(slot.getMachineID(), slot.getSequenceNumber(), slot, acceptupdatestolocal, machineSet); + for (Entry entry : slot.getEntries()) { + switch (entry.getType()) { + + case Entry.TypeNewKey: + processEntry((NewKey)entry); + break; + + case Entry.TypeCommit: + processEntry((Commit)entry); + break; + + case Entry.TypeAbort: + processEntry((Abort)entry); + break; + + case Entry.TypeTransaction: + processEntry((Transaction)entry); + break; + + case Entry.TypeLastMessage: + processEntry((LastMessage)entry, machineSet); + break; + + case Entry.TypeRejectedMessage: + processEntry((RejectedMessage)entry, indexer); + break; + + case Entry.TypeTableStatus: + processEntry((TableStatus)entry); + break; + + default: + throw new Error("Unrecognized type: " + entry.getType()); + } + } + } + + private void checkHMACChain(SlotIndexer indexer, Slot[] newslots) { + for (int i = 0; i < newslots.length; i++) { + Slot currslot = newslots[i]; + Slot prevslot = indexer.getSlot(currslot.getSequenceNumber() - 1); + if (prevslot != null && + !Arrays.equals(prevslot.getHMAC(), currslot.getPrevHMAC())) + throw new Error("Server Error: Invalid HMAC Chain" + currslot + " " + prevslot); + } + } +} \ No newline at end of file diff --git a/version2/backup/src/java/iotcloud/TableStatus.java b/version2/backup/src/java/iotcloud/TableStatus.java new file mode 100644 index 0000000..62f3a6d --- /dev/null +++ b/version2/backup/src/java/iotcloud/TableStatus.java @@ -0,0 +1,45 @@ +package iotcloud; +import java.nio.ByteBuffer; + +/** + * TableStatus entries record the current size of the data structure + * in slots. Used to remember the size and to perform resizes. + * @author Brian Demsky + * @version 1.0 + */ + + +class TableStatus extends Entry { + private int maxslots; + + TableStatus(Slot slot, int _maxslots) { + super(slot); + maxslots=_maxslots; + } + + int getMaxSlots() { + return maxslots; + } + + static Entry decode(Slot slot, ByteBuffer bb) { + int maxslots=bb.getInt(); + return new TableStatus(slot, maxslots); + } + + void encode(ByteBuffer bb) { + bb.put(Entry.TypeTableStatus); + bb.putInt(maxslots); + } + + int getSize() { + return Integer.BYTES+Byte.BYTES; + } + + byte getType() { + return Entry.TypeTableStatus; + } + + Entry getCopy(Slot s) { + return new TableStatus(s, maxslots); + } +} diff --git a/version2/backup/src/java/iotcloud/Test.java b/version2/backup/src/java/iotcloud/Test.java new file mode 100644 index 0000000..0de2e5c --- /dev/null +++ b/version2/backup/src/java/iotcloud/Test.java @@ -0,0 +1,1878 @@ +package iotcloud; + +import java.util.List; +import java.util.ArrayList; + +/** + * Test cases. + * @author Brian Demsky + * @version 1.0 + */ + +public class Test { + + public static final int NUMBER_OF_TESTS = 100; + + public static void main(String[] args) throws ServerException, InterruptedException { + if (args[0].equals("2")) { + test2(); + } else if (args[0].equals("3")) { + test3(); + } else if (args[0].equals("4")) { + test4(); + } else if (args[0].equals("5")) { + test5(); + } else if (args[0].equals("6")) { + test6(); + } else if (args[0].equals("7")) { + test7(); + } else if (args[0].equals("8")) { + test8(); + } else if (args[0].equals("9")) { + test9(); + } else if (args[0].equals("10")) { + test10(); + } else if (args[0].equals("11")) { + test11(); + } + } + + + static void test11() throws InterruptedException { + + boolean foundError = false; + + // Setup the 2 clients + Table t1 = new Table("127.0.0.1", "http://127.0.0.1/test.iotcloud/", "reallysecret", 321); + while (true) { + try { + t1.initTable(); + break; + } catch (Exception e) {} + } + + Table t2 = new Table("127.0.0.1", "http://127.0.0.1/test.iotcloud/", "reallysecret", 351); + while (t2.update().getFirst() == false) {} + + LocalComm lc = new LocalComm(t1, t2); + t1.addLocalComm(t2.getId(), lc); + t2.addLocalComm(t1.getId(), lc); + + // Make the Keys + System.out.println("Setting up keys"); + for (int i = 0; i < 4; i++) { + String a = "a" + i; + String b = "b" + i; + String c = "c" + i; + String d = "d" + i; + IoTString ia = new IoTString(a); + IoTString ib = new IoTString(b); + IoTString ic = new IoTString(c); + IoTString id = new IoTString(d); + while (true) { + try { + t1.createNewKey(ia, 321); + break; + } catch (Exception e) {} + } + + while (true) { + try { + t1.createNewKey(ib, 351); + break; + } catch (Exception e) {} + } + + while (true) { + try { + t2.createNewKey(ic, 321); + break; + } catch (Exception e) {} + } + + while (true) { + try { + t2.createNewKey(id, 351); + break; + } catch (Exception e) {} + } + } + + // Do Updates for the keys + System.out.println("Setting Key-Values..."); + for (int t = 0; t < NUMBER_OF_TESTS; t++) { + for (int i = 0; i < 4; i++) { + String keyA = "a" + i; + String valueA = "a" + (i + t); + + IoTString iKeyA = new IoTString(keyA); + IoTString iValueA = new IoTString(valueA); + + t1.startTransaction(); + t1.addKV(iKeyA, iValueA); + t1.commitTransaction(); + } + } + + t2.updateFromLocal(321); + + System.out.println("Checking Key-Values..."); + for (int i = 0; i < 4; i++) { + + String keyA = "a" + i; + String valueA = "a" + (i + NUMBER_OF_TESTS - 1); + + IoTString iKeyA = new IoTString(keyA); + IoTString iValueA = new IoTString(valueA); + + IoTString testValA1 = t1.getCommitted(iKeyA); + IoTString testValA2 = t2.getCommitted(iKeyA); + + if ((testValA1 == null) || (testValA1.equals(iValueA) == false)) { + System.out.println("Key-Value t1 incorrect: " + keyA + " " + testValA1 + " " + iValueA); + foundError = true; + } + + if ((testValA2 == null) || (testValA2.equals(iValueA) == false)) { + System.out.println("Key-Value t2 incorrect: " + keyA + " " + testValA2 + " " + iValueA); + foundError = true; + } + } + + if (foundError) { + System.out.println("Found Errors..."); + } else { + System.out.println("No Errors Found..."); + } + } + + static void test10() throws InterruptedException { + + boolean foundError = false; + + // Setup the 2 clients + Table t1 = new Table("127.0.0.1", "http://127.0.0.1/test.iotcloud/", "reallysecret", 321); + while (true) { + try { + t1.rebuild(); + break; + } catch (Exception e) {} + } + + Table t2 = new Table("127.0.0.1", "http://127.0.0.1/test.iotcloud/", "reallysecret", 351); + while (true) { + try { + t2.rebuild(); + break; + } catch (Exception e) {} + } + + LocalComm lc = new LocalComm(t1, t2); + t1.addLocalComm(t2.getId(), lc); + t2.addLocalComm(t1.getId(), lc); + + + System.out.println("Checking Key-Values..."); + for (int i = 0; i < 4; i++) { + + String keyA = "a" + i; + String keyB = "b" + i; + String keyC = "c" + i; + String keyD = "d" + i; + String valueA = "a" + (i + NUMBER_OF_TESTS - 1); + String valueB = "b" + (i + NUMBER_OF_TESTS - 1); + String valueC = "c" + (i + NUMBER_OF_TESTS - 1); + String valueD = "d" + (i + NUMBER_OF_TESTS - 1); + + IoTString iKeyA = new IoTString(keyA); + IoTString iKeyB = new IoTString(keyB); + IoTString iKeyC = new IoTString(keyC); + IoTString iKeyD = new IoTString(keyD); + IoTString iValueA = new IoTString(valueA); + IoTString iValueB = new IoTString(valueB); + IoTString iValueC = new IoTString(valueC); + IoTString iValueD = new IoTString(valueD); + + + IoTString testValA1 = t1.getCommitted(iKeyA); + IoTString testValB1 = t1.getCommitted(iKeyB); + IoTString testValC1 = t1.getCommitted(iKeyC); + IoTString testValD1 = t1.getCommitted(iKeyD); + + IoTString testValA2 = t2.getCommitted(iKeyA); + IoTString testValB2 = t2.getCommitted(iKeyB); + IoTString testValC2 = t2.getCommitted(iKeyC); + IoTString testValD2 = t2.getCommitted(iKeyD); + + if ((testValA1 == null) || (testValA1.equals(iValueA) == false)) { + System.out.println("Key-Value t1 incorrect: " + keyA + " " + testValA1 + " " + iValueA); + foundError = true; + } + + if ((testValB1 == null) || (testValB1.equals(iValueB) == false)) { + System.out.println("Key-Value t1 incorrect: " + keyB + " " + testValB1 + " " + iValueB); + foundError = true; + } + + if ((testValC1 == null) || (testValC1.equals(iValueC) == false)) { + System.out.println("Key-Value t1 incorrect: " + keyC + " " + testValC1 + " " + iValueC); + foundError = true; + } + + if ((testValD1 == null) || (testValD1.equals(iValueD) == false)) { + System.out.println("Key-Value t1 incorrect: " + keyD + " " + testValD1 + " " + iValueD); + foundError = true; + } + + if ((testValA2 == null) || (testValA2.equals(iValueA) == false)) { + System.out.println("Key-Value t2 incorrect: " + keyA + " " + testValA2 + " " + iValueA); + foundError = true; + } + + if ((testValB2 == null) || (testValB2.equals(iValueB) == false)) { + System.out.println("Key-Value t2 incorrect: " + keyB + " " + testValB2 + " " + iValueB); + foundError = true; + } + + if ((testValC2 == null) || (testValC2.equals(iValueC) == false)) { + System.out.println("Key-Value t2 incorrect: " + keyC + " " + testValC2 + " " + iValueC); + foundError = true; + } + + if ((testValD2 == null) || (testValD2.equals(iValueD) == false)) { + System.out.println("Key-Value t2 incorrect: " + keyD + " " + testValD2 + " " + iValueD); + foundError = true; + } + } + + if (foundError) { + System.out.println("Found Errors..."); + } else { + System.out.println("No Errors Found..."); + } + } + + static void test9() throws InterruptedException { + + boolean foundError = false; + List<TransactionStatus> transStatusList = new ArrayList<TransactionStatus>(); + + // Setup the 2 clients + Table t1 = new Table("127.0.0.1", "http://127.0.0.1/test.iotcloud/", "reallysecret", 321); + while (true) { + try { + t1.initTable(); + break; + } catch (Exception e) {} + } + + Table t2 = new Table("127.0.0.1", "http://127.0.0.1/test.iotcloud/", "reallysecret", 351); + while (t2.update().getFirst() == false) {} + + // LocalComm lc = new LocalComm(t1, t2); + // t1.addLocalComm(t2.getId(), lc); + // t2.addLocalComm(t1.getId(), lc); + + + + // Make the Keys + System.out.println("Setting up keys"); + for (int i = 0; i < 4; i++) { + String a = "a" + i; + String b = "b" + i; + String c = "c" + i; + String d = "d" + i; + IoTString ia = new IoTString(a); + IoTString ib = new IoTString(b); + IoTString ic = new IoTString(c); + IoTString id = new IoTString(d); + while (true) { + try { + t1.createNewKey(ia, 321); + break; + } catch (Exception e) {} + } + + while (true) { + try { + t1.createNewKey(ib, 351); + break; + } catch (Exception e) {} + } + + while (true) { + try { + t2.createNewKey(ic, 321); + break; + } catch (Exception e) {} + } + + while (true) { + try { + t2.createNewKey(id, 351); + break; + } catch (Exception e) {} + } + } + + // Do Updates for the keys + System.out.println("Setting Key-Values..."); + + for (int t = 0; t < NUMBER_OF_TESTS; t++) { + for (int i = 0; i < 4; i++) { + String keyB = "b" + i; + String valueB = "b" + (i + t); + + IoTString iKeyB = new IoTString(keyB); + IoTString iValueB = new IoTString(valueB); + + + t1.startTransaction(); + t1.getSpeculativeAtomic(iKeyB); + t1.addKV(iKeyB, iValueB); + transStatusList.add(t1.commitTransaction()); + } + } + + while (t1.update().getFirst() == false) {} + + + for (int i = 0; i < 4; i++) { + + String keyB = "b" + i; + String valueB = "b" + (i + NUMBER_OF_TESTS - 1); + IoTString iKeyB = new IoTString(keyB); + IoTString iValueB = new IoTString(valueB); + + IoTString testValB1 = t1.getSpeculative(iKeyB); + IoTString testValB2 = t2.getSpeculative(iKeyB); + + if ((testValB1 == null) || (testValB1.equals(iValueB) == false)) { + System.out.println("Key-Value t1 incorrect: " + keyB + " " + testValB1); + foundError = true; + } + + if ((testValB2 != null)) { + System.out.println("Key-Value t2 incorrect: " + keyB + " " + testValB2); + foundError = true; + } + } + + for (int t = 0; t < NUMBER_OF_TESTS; t++) { + for (int i = 0; i < 4; i++) { + String keyC = "c" + i; + String valueC = "c" + (i + t); + + IoTString iKeyC = new IoTString(keyC); + IoTString iValueC = new IoTString(valueC); + + t2.startTransaction(); + t2.getSpeculativeAtomic(iKeyC); + t2.addKV(iKeyC, iValueC); + transStatusList.add(t2.commitTransaction()); + } + } + + while (t2.update().getFirst() == false) {} + + for (int i = 0; i < 4; i++) { + String keyC = "c" + i; + String valueC = "c" + (i + NUMBER_OF_TESTS - 1); + IoTString iKeyC = new IoTString(keyC); + IoTString iValueC = new IoTString(valueC); + + IoTString testValC1 = t1.getSpeculative(iKeyC); + IoTString testValC2 = t2.getSpeculative(iKeyC); + + if ((testValC1 != null)) { + System.out.println("Key-Value t1 incorrect: " + keyC + " " + testValC1); + foundError = true; + } + + if ((testValC2 == null) || (testValC2.equals(iValueC) == false)) { + System.out.println("Key-Value t2 incorrect: " + keyC + " " + testValC2); + foundError = true; + } + } + + for (int t = 0; t < NUMBER_OF_TESTS; t++) { + for (int i = 0; i < 4; i++) { + String keyA = "a" + i; + String keyD = "d" + i; + String valueA = "a" + (i + t); + String valueD = "d" + (i + t); + + IoTString iKeyA = new IoTString(keyA); + IoTString iKeyD = new IoTString(keyD); + IoTString iValueA = new IoTString(valueA); + IoTString iValueD = new IoTString(valueD); + + + t1.startTransaction(); + t1.addKV(iKeyA, iValueA); + transStatusList.add(t1.commitTransaction()); + + t2.startTransaction(); + t2.addKV(iKeyD, iValueD); + transStatusList.add(t2.commitTransaction()); + } + } + + System.out.println("Updating Clients..."); + while (t1.update().getFirst() == false) {} + while (t2.update().getFirst() == false) {} + + System.out.println("Checking Key-Values..."); + for (int i = 0; i < 4; i++) { + + String keyA = "a" + i; + String keyB = "b" + i; + String keyC = "c" + i; + String keyD = "d" + i; + String valueA = "a" + (i + NUMBER_OF_TESTS - 1); + String valueB = "b" + (i + NUMBER_OF_TESTS - 1); + String valueC = "c" + (i + NUMBER_OF_TESTS - 1); + String valueD = "d" + (i + NUMBER_OF_TESTS - 1); + + IoTString iKeyA = new IoTString(keyA); + IoTString iKeyB = new IoTString(keyB); + IoTString iKeyC = new IoTString(keyC); + IoTString iKeyD = new IoTString(keyD); + IoTString iValueA = new IoTString(valueA); + IoTString iValueB = new IoTString(valueB); + IoTString iValueC = new IoTString(valueC); + IoTString iValueD = new IoTString(valueD); + + + IoTString testValA1 = t1.getCommitted(iKeyA); + IoTString testValB1 = t1.getCommitted(iKeyB); + IoTString testValC1 = t1.getCommitted(iKeyC); + IoTString testValD1 = t1.getCommitted(iKeyD); + + IoTString testValA2 = t2.getCommitted(iKeyA); + IoTString testValB2 = t2.getCommitted(iKeyB); + IoTString testValC2 = t2.getCommitted(iKeyC); + IoTString testValD2 = t2.getCommitted(iKeyD); + + if ((testValA1 == null) || (testValA1.equals(iValueA) == false)) { + System.out.println("Key-Value t1 incorrect: " + keyA + " " + testValA1 + " " + iValueA); + foundError = true; + } + + if ((testValB1 == null) || (testValB1.equals(iValueB) == false)) { + System.out.println("Key-Value t1 incorrect: " + keyB + " " + testValB1 + " " + iValueB); + foundError = true; + } + + if ((testValC1 == null) || (testValC1.equals(iValueC) == false)) { + System.out.println("Key-Value t1 incorrect: " + keyC + " " + testValC1 + " " + iValueC); + foundError = true; + } + + if ((testValD1 == null) || (testValD1.equals(iValueD) == false)) { + System.out.println("Key-Value t1 incorrect: " + keyD + " " + testValD1 + " " + iValueD); + foundError = true; + } + + if ((testValA2 == null) || (testValA2.equals(iValueA) == false)) { + System.out.println("Key-Value t2 incorrect: " + keyA + " " + testValA2 + " " + iValueA); + foundError = true; + } + + if ((testValB2 == null) || (testValB2.equals(iValueB) == false)) { + System.out.println("Key-Value t2 incorrect: " + keyB + " " + testValB2 + " " + iValueB); + foundError = true; + } + + if ((testValC2 == null) || (testValC2.equals(iValueC) == false)) { + System.out.println("Key-Value t2 incorrect: " + keyC + " " + testValC2 + " " + iValueC); + foundError = true; + } + + if ((testValD2 == null) || (testValD2.equals(iValueD) == false)) { + System.out.println("Key-Value t2 incorrect: " + keyD + " " + testValD2 + " " + iValueD); + foundError = true; + } + } + + for (TransactionStatus status : transStatusList) { + if (status.getStatus() != TransactionStatus.StatusCommitted) { + foundError = true; + System.out.println(status.getStatus()); + } + } + + if (foundError) { + System.out.println("Found Errors..."); + } else { + System.out.println("No Errors Found..."); + } + } + + static void test8() throws InterruptedException { + + boolean foundError = false; + List<TransactionStatus> transStatusList = new ArrayList<TransactionStatus>(); + + // Setup the 2 clients + Table t1 = new Table("127.0.0.1", "http://127.0.0.1/test.iotcloud/", "reallysecret", 321); + while (true) { + try { + t1.initTable(); + break; + } catch (Exception e) {} + } + + Table t2 = new Table("127.0.0.1", "http://127.0.0.1/test.iotcloud/", "reallysecret", 351); + while (t2.update().getFirst() == false) {} + + LocalComm lc = new LocalComm(t1, t2); + t1.addLocalComm(t2.getId(), lc); + t2.addLocalComm(t1.getId(), lc); + + // Make the Keys + System.out.println("Setting up keys"); + for (int i = 0; i < NUMBER_OF_TESTS; i++) { + System.out.println(i); + + String a = "a" + i; + String b = "b" + i; + String c = "c" + i; + String d = "d" + i; + IoTString ia = new IoTString(a); + IoTString ib = new IoTString(b); + IoTString ic = new IoTString(c); + IoTString id = new IoTString(d); + + while (true) { + try { + t1.createNewKey(ia, 321); + break; + } catch (Exception e) {} + } + + while (true) { + try { + t1.createNewKey(ib, 351); + break; + } catch (Exception e) {} + } + + while (true) { + try { + t2.createNewKey(ic, 321); + break; + } catch (Exception e) {} + } + + while (true) { + try { + t2.createNewKey(id, 351); + break; + } catch (Exception e) {} + } + } + + // Do Updates for the keys + System.out.println("Setting Key-Values..."); + for (int i = 0; i < NUMBER_OF_TESTS; i++) { + System.out.println(i); + String keyA = "a" + i; + String keyB = "b" + i; + String keyC = "c" + i; + String keyD = "d" + i; + String valueA = "a" + i; + String valueB = "b" + i; + String valueC = "c" + i; + String valueD = "d" + i; + + IoTString iKeyA = new IoTString(keyA); + IoTString iKeyB = new IoTString(keyB); + IoTString iKeyC = new IoTString(keyC); + IoTString iKeyD = new IoTString(keyD); + IoTString iValueA = new IoTString(valueA); + IoTString iValueB = new IoTString(valueB); + IoTString iValueC = new IoTString(valueC); + IoTString iValueD = new IoTString(valueD); + + + String keyAPrev = "a" + (i - 1); + String keyBPrev = "b" + (i - 1); + String keyCPrev = "c" + (i - 1); + String keyDPrev = "d" + (i - 1); + String valueAPrev = "a" + (i - 1); + String valueBPrev = "b" + (i - 1); + String valueCPrev = "c" + (i - 1); + String valueDPrev = "d" + (i - 1); + + IoTString iKeyAPrev = new IoTString(keyAPrev); + IoTString iKeyBPrev = new IoTString(keyBPrev); + IoTString iKeyCPrev = new IoTString(keyCPrev); + IoTString iKeyDPrev = new IoTString(keyDPrev); + IoTString iValueAPrev = new IoTString(valueAPrev); + IoTString iValueBPrev = new IoTString(valueBPrev); + IoTString iValueCPrev = new IoTString(valueCPrev); + IoTString iValueDPrev = new IoTString(valueDPrev); + + t1.startTransaction(); + if (i != 0) { + IoTString tmp = t1.getSpeculative(iKeyAPrev); + if ((tmp == null) || !tmp.equals(iValueAPrev)) { + System.out.println("Key a Error: " + i); + foundError = true; + } + } + t1.addKV(iKeyA, iValueA); + transStatusList.add(t1.commitTransaction()); + + + + t1.startTransaction(); + if (i != 0) { + IoTString tmp = t1.getSpeculative(iKeyBPrev); + if ((tmp == null) || !tmp.equals(iValueBPrev)) { + System.out.println("Key b Error: " + i); + foundError = true; + } + } + t1.addKV(iKeyB, iValueB); + transStatusList.add(t1.commitTransaction()); + + + t2.startTransaction(); + if (i != 0) { + IoTString tmp = t2.getSpeculative(iKeyCPrev); + if ((tmp == null) || !tmp.equals(iValueCPrev)) { + System.out.println("Key c Error: " + i); + foundError = true; + } + } + t2.addKV(iKeyC, iValueC); + transStatusList.add(t2.commitTransaction()); + + t2.startTransaction(); + if (i != 0) { + IoTString tmp = t2.getSpeculative(iKeyDPrev); + if ((tmp == null) || !tmp.equals(iValueDPrev)) { + System.out.println("Key d Error: " + i); + foundError = true; + } + } + t2.addKV(iKeyD, iValueD); + transStatusList.add(t2.commitTransaction()); + } + + + System.out.println("Updating..."); + while (t1.update().getFirst() == false) {} + while (t2.update().getFirst() == false) {} + + + System.out.println("Checking Key-Values..."); + for (int i = 0; i < NUMBER_OF_TESTS; i++) { + + String keyA = "a" + i; + String keyB = "b" + i; + String keyC = "c" + i; + String keyD = "d" + i; + String valueA = "a" + i; + String valueB = "b" + i; + String valueC = "c" + i; + String valueD = "d" + i; + + IoTString iKeyA = new IoTString(keyA); + IoTString iKeyB = new IoTString(keyB); + IoTString iKeyC = new IoTString(keyC); + IoTString iKeyD = new IoTString(keyD); + IoTString iValueA = new IoTString(valueA); + IoTString iValueB = new IoTString(valueB); + IoTString iValueC = new IoTString(valueC); + IoTString iValueD = new IoTString(valueD); + + + IoTString testValA1 = t1.getCommitted(iKeyA); + IoTString testValB1 = t1.getCommitted(iKeyB); + IoTString testValC1 = t1.getCommitted(iKeyC); + IoTString testValD1 = t1.getCommitted(iKeyD); + + IoTString testValA2 = t2.getCommitted(iKeyA); + IoTString testValB2 = t2.getCommitted(iKeyB); + IoTString testValC2 = t2.getCommitted(iKeyC); + IoTString testValD2 = t2.getCommitted(iKeyD); + + if ((testValA1 == null) || (testValA1.equals(iValueA) == false)) { + System.out.println("Key-Value t1 incorrect: " + keyA); + foundError = true; + } + + if ((testValB1 == null) || (testValB1.equals(iValueB) == false)) { + System.out.println("Key-Value t1 incorrect: " + keyB); + foundError = true; + } + + if ((testValC1 == null) || (testValC1.equals(iValueC) == false)) { + System.out.println("Key-Value t1 incorrect: " + keyC); + foundError = true; + } + + if ((testValD1 == null) || (testValD1.equals(iValueD) == false)) { + System.out.println("Key-Value t1 incorrect: " + keyD); + foundError = true; + } + + + if ((testValA2 == null) || (testValA2.equals(iValueA) == false)) { + System.out.println("Key-Value t2 incorrect: " + keyA + " " + testValA2); + foundError = true; + } + + if ((testValB2 == null) || (testValB2.equals(iValueB) == false)) { + System.out.println("Key-Value t2 incorrect: " + keyB + " " + testValB2); + foundError = true; + } + + if ((testValC2 == null) || (testValC2.equals(iValueC) == false)) { + System.out.println("Key-Value t2 incorrect: " + keyC + " " + testValC2); + foundError = true; + } + + if ((testValD2 == null) || (testValD2.equals(iValueD) == false)) { + System.out.println("Key-Value t2 incorrect: " + keyD + " " + testValD2); + foundError = true; + } + } + + for (TransactionStatus status : transStatusList) { + if (status.getStatus() != TransactionStatus.StatusCommitted) { + foundError = true; + } + } + if (foundError) { + System.out.println("Found Errors..."); + } else { + System.out.println("No Errors Found..."); + } + } + + static void test7() throws ServerException, InterruptedException { + + boolean foundError = false; + List<TransactionStatus> transStatusList = new ArrayList<TransactionStatus>(); + + // Setup the 2 clients + Table t1 = new Table("127.0.0.1", "http://127.0.0.1/test.iotcloud/", "reallysecret", 321); + t1.initTable(); + Table t2 = new Table("127.0.0.1", "http://127.0.0.1/test.iotcloud/", "reallysecret", 351); + t2.update(); + + // Make the Keys + System.out.println("Setting up keys"); + for (int i = 0; i < 4; i++) { + String a = "a" + i; + String b = "b" + i; + String c = "c" + i; + String d = "d" + i; + IoTString ia = new IoTString(a); + IoTString ib = new IoTString(b); + IoTString ic = new IoTString(c); + IoTString id = new IoTString(d); + t1.createNewKey(ia, 321); + t1.createNewKey(ib, 351); + t2.createNewKey(ic, 321); + t2.createNewKey(id, 351); + } + + // Do Updates for the keys + System.out.println("Setting Key-Values..."); + + for (int t = 0; t < NUMBER_OF_TESTS; t++) { + for (int i = 0; i < 4; i++) { + String keyB = "b" + i; + String valueB = "b" + (i + t); + + IoTString iKeyB = new IoTString(keyB); + IoTString iValueB = new IoTString(valueB); + + t1.startTransaction(); + t1.getSpeculativeAtomic(iKeyB); + t1.addKV(iKeyB, iValueB); + transStatusList.add(t1.commitTransaction()); + } + } + + for (int i = 0; i < 4; i++) { + + String keyB = "b" + i; + String valueB = "b" + (i + NUMBER_OF_TESTS - 1); + IoTString iKeyB = new IoTString(keyB); + IoTString iValueB = new IoTString(valueB); + + IoTString testValB1 = t1.getSpeculative(iKeyB); + IoTString testValB2 = t2.getSpeculative(iKeyB); + + if ((testValB1 == null) || (testValB1.equals(iValueB) == false)) { + System.out.println("Key-Value t1 incorrect: " + keyB + " " + testValB1); + foundError = true; + } + + if ((testValB2 != null)) { + System.out.println("Key-Value t2 incorrect: " + keyB + " " + testValB2); + foundError = true; + } + } + + for (int t = 0; t < NUMBER_OF_TESTS; t++) { + for (int i = 0; i < 4; i++) { + String keyC = "c" + i; + String valueC = "c" + (i + t); + + IoTString iKeyC = new IoTString(keyC); + IoTString iValueC = new IoTString(valueC); + + t2.startTransaction(); + t2.getSpeculativeAtomic(iKeyC); + t2.addKV(iKeyC, iValueC); + transStatusList.add(t2.commitTransaction()); + } + } + + for (int i = 0; i < 4; i++) { + String keyC = "c" + i; + String valueC = "c" + (i + NUMBER_OF_TESTS - 1); + IoTString iKeyC = new IoTString(keyC); + IoTString iValueC = new IoTString(valueC); + + IoTString testValC1 = t1.getSpeculative(iKeyC); + IoTString testValC2 = t2.getSpeculative(iKeyC); + + if ((testValC1 != null)) { + System.out.println("Key-Value t1 incorrect: " + keyC + " " + testValC1); + foundError = true; + } + + if ((testValC2 == null) || (testValC2.equals(iValueC) == false)) { + System.out.println("Key-Value t2 incorrect: " + keyC + " " + testValC2); + foundError = true; + } + } + + for (int t = 0; t < NUMBER_OF_TESTS; t++) { + for (int i = 0; i < 4; i++) { + String keyA = "a" + i; + String keyD = "d" + i; + String valueA = "a" + (i + t); + String valueD = "d" + (i + t); + + IoTString iKeyA = new IoTString(keyA); + IoTString iKeyD = new IoTString(keyD); + IoTString iValueA = new IoTString(valueA); + IoTString iValueD = new IoTString(valueD); + + t1.startTransaction(); + t1.addKV(iKeyA, iValueA); + transStatusList.add(t1.commitTransaction()); + + t2.startTransaction(); + t2.addKV(iKeyD, iValueD); + transStatusList.add(t2.commitTransaction()); + } + } + + System.out.println("Updating Clients..."); + t1.update(); + t2.update(); + t1.update(); + t2.update(); + + System.out.println("Checking Key-Values..."); + for (int i = 0; i < 4; i++) { + + String keyA = "a" + i; + String keyB = "b" + i; + String keyC = "c" + i; + String keyD = "d" + i; + String valueA = "a" + (i + NUMBER_OF_TESTS - 1); + String valueB = "b" + (i + NUMBER_OF_TESTS - 1); + String valueC = "c" + (i + NUMBER_OF_TESTS - 1); + String valueD = "d" + (i + NUMBER_OF_TESTS - 1); + + IoTString iKeyA = new IoTString(keyA); + IoTString iKeyB = new IoTString(keyB); + IoTString iKeyC = new IoTString(keyC); + IoTString iKeyD = new IoTString(keyD); + IoTString iValueA = new IoTString(valueA); + IoTString iValueB = new IoTString(valueB); + IoTString iValueC = new IoTString(valueC); + IoTString iValueD = new IoTString(valueD); + + + IoTString testValA1 = t1.getCommitted(iKeyA); + IoTString testValB1 = t1.getCommitted(iKeyB); + IoTString testValC1 = t1.getCommitted(iKeyC); + IoTString testValD1 = t1.getCommitted(iKeyD); + + IoTString testValA2 = t2.getCommitted(iKeyA); + IoTString testValB2 = t2.getCommitted(iKeyB); + IoTString testValC2 = t2.getCommitted(iKeyC); + IoTString testValD2 = t2.getCommitted(iKeyD); + + if ((testValA1 == null) || (testValA1.equals(iValueA) == false)) { + System.out.println("Key-Value t1 incorrect: " + keyA + " " + testValA1); + foundError = true; + } + + if ((testValB1 == null) || (testValB1.equals(iValueB) == false)) { + System.out.println("Key-Value t1 incorrect: " + keyB + " " + testValB1); + foundError = true; + } + + if ((testValC1 == null) || (testValC1.equals(iValueC) == false)) { + System.out.println("Key-Value t1 incorrect: " + keyC + " " + testValC1); + foundError = true; + } + + if ((testValD1 == null) || (testValD1.equals(iValueD) == false)) { + System.out.println("Key-Value t1 incorrect: " + keyD + " " + testValD1); + foundError = true; + } + + if ((testValA2 == null) || (testValA2.equals(iValueA) == false)) { + System.out.println("Key-Value t2 incorrect: " + keyA + " " + testValA2); + foundError = true; + } + + if ((testValB2 == null) || (testValB2.equals(iValueB) == false)) { + System.out.println("Key-Value t2 incorrect: " + keyB + " " + testValB2); + foundError = true; + } + + if ((testValC2 == null) || (testValC2.equals(iValueC) == false)) { + System.out.println("Key-Value t2 incorrect: " + keyC + " " + testValC2); + foundError = true; + } + + if ((testValD2 == null) || (testValD2.equals(iValueD) == false)) { + System.out.println("Key-Value t2 incorrect: " + keyD + " " + testValD2); + foundError = true; + } + } + + for (TransactionStatus status : transStatusList) { + if (status.getStatus() != TransactionStatus.StatusCommitted) { + foundError = true; + } + } + + if (foundError) { + System.out.println("Found Errors..."); + } else { + System.out.println("No Errors Found..."); + } + } + + static void test6() throws ServerException, InterruptedException { + + boolean foundError = false; + List<TransactionStatus> transStatusList = new ArrayList<TransactionStatus>(); + + // Setup the 2 clients + Table t1 = new Table("127.0.0.1", "http://127.0.0.1/test.iotcloud/", "reallysecret", 321); + t1.initTable(); + Table t2 = new Table("127.0.0.1", "http://127.0.0.1/test.iotcloud/", "reallysecret", 351); + t2.update(); + + // Make the Keys + System.out.println("Setting up keys"); + for (int i = 0; i < NUMBER_OF_TESTS; i++) { + String a = "a" + i; + String b = "b" + i; + String c = "c" + i; + String d = "d" + i; + IoTString ia = new IoTString(a); + IoTString ib = new IoTString(b); + IoTString ic = new IoTString(c); + IoTString id = new IoTString(d); + t1.createNewKey(ia, 321); + t1.createNewKey(ib, 351); + t2.createNewKey(ic, 321); + t2.createNewKey(id, 351); + } + + // Do Updates for the keys + System.out.println("Setting Key-Values..."); + for (int i = 0; i < NUMBER_OF_TESTS; i++) { + String keyA = "a" + i; + String keyB = "b" + i; + String keyC = "c" + i; + String keyD = "d" + i; + String valueA = "a" + i; + String valueB = "b" + i; + String valueC = "c" + i; + String valueD = "d" + i; + + IoTString iKeyA = new IoTString(keyA); + IoTString iKeyB = new IoTString(keyB); + IoTString iKeyC = new IoTString(keyC); + IoTString iKeyD = new IoTString(keyD); + IoTString iValueA = new IoTString(valueA); + IoTString iValueB = new IoTString(valueB); + IoTString iValueC = new IoTString(valueC); + IoTString iValueD = new IoTString(valueD); + + t1.startTransaction(); + t1.getCommittedAtomic(iKeyA); + t1.addKV(iKeyA, iValueA); + transStatusList.add(t1.commitTransaction()); + + t1.startTransaction(); + t1.getCommittedAtomic(iKeyB); + t1.addKV(iKeyB, iValueB); + transStatusList.add(t1.commitTransaction()); + + t2.startTransaction(); + t2.getCommittedAtomic(iKeyC); + t2.addKV(iKeyC, iValueC); + transStatusList.add(t2.commitTransaction()); + + t2.startTransaction(); + t2.getCommittedAtomic(iKeyD); + t2.addKV(iKeyD, iValueD); + transStatusList.add(t2.commitTransaction()); + } + + System.out.println("Updating Clients..."); + t1.update(); + t2.update(); + + System.out.println("Checking Key-Values..."); + for (int i = 0; i < NUMBER_OF_TESTS; i++) { + + String keyA = "a" + i; + String keyB = "b" + i; + String keyC = "c" + i; + String keyD = "d" + i; + String valueA = "a" + i; + String valueB = "b" + i; + String valueC = "c" + i; + String valueD = "d" + i; + + IoTString iKeyA = new IoTString(keyA); + IoTString iKeyB = new IoTString(keyB); + IoTString iKeyC = new IoTString(keyC); + IoTString iKeyD = new IoTString(keyD); + IoTString iValueA = new IoTString(valueA); + IoTString iValueB = new IoTString(valueB); + IoTString iValueC = new IoTString(valueC); + IoTString iValueD = new IoTString(valueD); + + + IoTString testValA1 = t1.getCommitted(iKeyA); + IoTString testValB1 = t1.getCommitted(iKeyB); + IoTString testValC1 = t1.getCommitted(iKeyC); + IoTString testValD1 = t1.getCommitted(iKeyD); + + IoTString testValA2 = t2.getCommitted(iKeyA); + IoTString testValB2 = t2.getCommitted(iKeyB); + IoTString testValC2 = t2.getCommitted(iKeyC); + IoTString testValD2 = t2.getCommitted(iKeyD); + + if ((testValA1 == null) || (testValA1.equals(iValueA) == false)) { + System.out.println("Key-Value t1 incorrect: " + keyA); + foundError = true; + } + + if ((testValB1 == null) || (testValB1.equals(iValueB) == false)) { + System.out.println("Key-Value t1 incorrect: " + keyB); + foundError = true; + } + + if ((testValC1 == null) || (testValC1.equals(iValueC) == false)) { + System.out.println("Key-Value t1 incorrect: " + keyC); + foundError = true; + } + + if ((testValD1 == null) || (testValD1.equals(iValueD) == false)) { + System.out.println("Key-Value t1 incorrect: " + keyD); + foundError = true; + } + + + if ((testValA2 == null) || (testValA2.equals(iValueA) == false)) { + System.out.println("Key-Value t2 incorrect: " + keyA + " " + testValA2); + foundError = true; + } + + if ((testValB2 == null) || (testValB2.equals(iValueB) == false)) { + System.out.println("Key-Value t2 incorrect: " + keyB + " " + testValB2); + foundError = true; + } + + if ((testValC2 == null) || (testValC2.equals(iValueC) == false)) { + System.out.println("Key-Value t2 incorrect: " + keyC + " " + testValC2); + foundError = true; + } + + if ((testValD2 == null) || (testValD2.equals(iValueD) == false)) { + System.out.println("Key-Value t2 incorrect: " + keyD + " " + testValD2); + foundError = true; + } + } + + for (TransactionStatus status : transStatusList) { + if (status.getStatus() != TransactionStatus.StatusCommitted) { + foundError = true; + } + } + + if (foundError) { + System.out.println("Found Errors..."); + } else { + System.out.println("No Errors Found..."); + } + } + + static void test5() throws ServerException, InterruptedException { + + boolean foundError = false; + List<TransactionStatus> transStatusList = new ArrayList<TransactionStatus>(); + + // Setup the 2 clients + Table t1 = new Table("127.0.0.1", "http://127.0.0.1/test.iotcloud/", "reallysecret", 321); + t1.initTable(); + Table t2 = new Table("127.0.0.1", "http://127.0.0.1/test.iotcloud/", "reallysecret", 351); + t2.update(); + + + // Make the Keys + System.out.println("Setting up keys"); + for (int i = 0; i < 4; i++) { + String a = "a" + i; + String b = "b" + i; + String c = "c" + i; + String d = "d" + i; + IoTString ia = new IoTString(a); + IoTString ib = new IoTString(b); + IoTString ic = new IoTString(c); + IoTString id = new IoTString(d); + t1.createNewKey(ia, 321); + t1.createNewKey(ib, 351); + t2.createNewKey(ic, 321); + t2.createNewKey(id, 351); + } + + // Do Updates for the keys + System.out.println("Setting Key-Values..."); + + for (int t = 0; t < NUMBER_OF_TESTS; t++) { + for (int i = 0; i < 4; i++) { + String keyB = "b" + i; + String valueB = "b" + (i + t); + + IoTString iKeyB = new IoTString(keyB); + IoTString iValueB = new IoTString(valueB); + + t1.startTransaction(); + t1.addKV(iKeyB, iValueB); + transStatusList.add(t1.commitTransaction()); + } + } + + for (int i = 0; i < 4; i++) { + + String keyB = "b" + i; + String valueB = "b" + (i + NUMBER_OF_TESTS - 1); + IoTString iKeyB = new IoTString(keyB); + IoTString iValueB = new IoTString(valueB); + + IoTString testValB1 = t1.getSpeculative(iKeyB); + IoTString testValB2 = t2.getSpeculative(iKeyB); + + if ((testValB1 == null) || (testValB1.equals(iValueB) == false)) { + System.out.println("Key-Value t1 incorrect: " + keyB); + foundError = true; + } + + if ((testValB2 != null)) { + System.out.println("Key-Value t2 incorrect: " + keyB); + foundError = true; + } + } + + for (int t = 0; t < NUMBER_OF_TESTS; t++) { + for (int i = 0; i < 4; i++) { + String keyC = "c" + i; + String valueC = "c" + (i + t); + + IoTString iKeyC = new IoTString(keyC); + IoTString iValueC = new IoTString(valueC); + + t2.startTransaction(); + t2.addKV(iKeyC, iValueC); + transStatusList.add(t2.commitTransaction()); + } + } + + for (int i = 0; i < 4; i++) { + String keyC = "c" + i; + String valueC = "c" + (i + NUMBER_OF_TESTS - 1); + IoTString iKeyC = new IoTString(keyC); + IoTString iValueC = new IoTString(valueC); + + IoTString testValC1 = t1.getSpeculative(iKeyC); + IoTString testValC2 = t2.getSpeculative(iKeyC); + + if ((testValC1 != null)) { + System.out.println("Key-Value t1 incorrect: " + keyC + " " + testValC1); + foundError = true; + } + + if ((testValC2 == null) || (testValC2.equals(iValueC) == false)) { + System.out.println("Key-Value t2 incorrect: " + keyC + " " + testValC2); + foundError = true; + } + } + + for (int t = 0; t < NUMBER_OF_TESTS; t++) { + for (int i = 0; i < 4; i++) { + String keyA = "a" + i; + String keyD = "d" + i; + String valueA = "a" + (i + t); + String valueD = "d" + (i + t); + + IoTString iKeyA = new IoTString(keyA); + IoTString iKeyD = new IoTString(keyD); + IoTString iValueA = new IoTString(valueA); + IoTString iValueD = new IoTString(valueD); + + t1.startTransaction(); + t1.addKV(iKeyA, iValueA); + transStatusList.add(t1.commitTransaction()); + + t2.startTransaction(); + t2.addKV(iKeyD, iValueD); + transStatusList.add(t2.commitTransaction()); + } + } + + System.out.println("Updating Clients..."); + t1.update(); + t2.update(); + + + System.out.println("Checking Key-Values..."); + for (int i = 0; i < 4; i++) { + + String keyA = "a" + i; + String keyB = "b" + i; + String keyC = "c" + i; + String keyD = "d" + i; + String valueA = "a" + (i + NUMBER_OF_TESTS - 1); + String valueB = "b" + (i + NUMBER_OF_TESTS - 1); + String valueC = "c" + (i + NUMBER_OF_TESTS - 1); + String valueD = "d" + (i + NUMBER_OF_TESTS - 1); + + IoTString iKeyA = new IoTString(keyA); + IoTString iKeyB = new IoTString(keyB); + IoTString iKeyC = new IoTString(keyC); + IoTString iKeyD = new IoTString(keyD); + IoTString iValueA = new IoTString(valueA); + IoTString iValueB = new IoTString(valueB); + IoTString iValueC = new IoTString(valueC); + IoTString iValueD = new IoTString(valueD); + + + IoTString testValA1 = t1.getCommitted(iKeyA); + IoTString testValB1 = t1.getCommitted(iKeyB); + IoTString testValC1 = t1.getCommitted(iKeyC); + IoTString testValD1 = t1.getCommitted(iKeyD); + + IoTString testValA2 = t2.getCommitted(iKeyA); + IoTString testValB2 = t2.getCommitted(iKeyB); + IoTString testValC2 = t2.getCommitted(iKeyC); + IoTString testValD2 = t2.getCommitted(iKeyD); + + if ((testValA1 == null) || (testValA1.equals(iValueA) == false)) { + System.out.println("Key-Value t1 incorrect: " + keyA + " " + testValA1); + foundError = true; + } + + if ((testValB1 == null) || (testValB1.equals(iValueB) == false)) { + System.out.println("Key-Value t1 incorrect: " + keyB + " " + testValB1); + foundError = true; + } + + if ((testValC1 == null) || (testValC1.equals(iValueC) == false)) { + System.out.println("Key-Value t1 incorrect: " + keyC + " " + testValC1); + foundError = true; + } + + if ((testValD1 == null) || (testValD1.equals(iValueD) == false)) { + System.out.println("Key-Value t1 incorrect: " + keyD + " " + testValD1); + foundError = true; + } + + + if ((testValA2 == null) || (testValA2.equals(iValueA) == false)) { + System.out.println("Key-Value t2 incorrect: " + keyA + " " + testValA2); + foundError = true; + } + + if ((testValB2 == null) || (testValB2.equals(iValueB) == false)) { + System.out.println("Key-Value t2 incorrect: " + keyB + " " + testValB2); + foundError = true; + } + + if ((testValC2 == null) || (testValC2.equals(iValueC) == false)) { + System.out.println("Key-Value t2 incorrect: " + keyC + " " + testValC2); + foundError = true; + } + + if ((testValD2 == null) || (testValD2.equals(iValueD) == false)) { + System.out.println("Key-Value t2 incorrect: " + keyD + " " + testValD2); + foundError = true; + } + } + + for (TransactionStatus status : transStatusList) { + if (status.getStatus() != TransactionStatus.StatusCommitted) { + foundError = true; + } + } + + if (foundError) { + System.out.println("Found Errors..."); + } else { + System.out.println("No Errors Found..."); + } + } + + static void test4() throws ServerException, InterruptedException { + + boolean foundError = false; + long startTime = 0; + long endTime = 0; + List<TransactionStatus> transStatusList = new ArrayList<TransactionStatus>(); + + // Setup the 2 clients + Table t1 = new Table("127.0.0.1", "http://127.0.0.1/test.iotcloud/", "reallysecret", 321); + t1.initTable(); + Table t2 = new Table("127.0.0.1", "http://127.0.0.1/test.iotcloud/", "reallysecret", 351); + t2.update(); + + // Make the Keys + System.out.println("Setting up keys"); + startTime = System.currentTimeMillis(); + for (int i = 0; i < 4; i++) { + String a = "a" + i; + String b = "b" + i; + String c = "c" + i; + String d = "d" + i; + IoTString ia = new IoTString(a); + IoTString ib = new IoTString(b); + IoTString ic = new IoTString(c); + IoTString id = new IoTString(d); + t1.createNewKey(ia, 321); + t1.createNewKey(ib, 351); + t2.createNewKey(ic, 321); + t2.createNewKey(id, 351); + } + endTime = System.currentTimeMillis(); + System.out.println("Time Taken: " + (double) ((endTime - startTime) / 1000.0) ); + System.out.println("Time Taken Per Key: " + (double) (((endTime - startTime) / 1000.0) / (NUMBER_OF_TESTS * 4)) ); + System.out.println(); + + + // Do Updates for the keys + System.out.println("Setting Key-Values..."); + startTime = System.currentTimeMillis(); + for (int t = 0; t < NUMBER_OF_TESTS; t++) { + for (int i = 0; i < 4; i++) { + String keyA = "a" + i; + String keyB = "b" + i; + String keyC = "c" + i; + String keyD = "d" + i; + String valueA = "a" + i; + String valueB = "b" + i; + String valueC = "c" + i; + String valueD = "d" + i; + + IoTString iKeyA = new IoTString(keyA); + IoTString iKeyB = new IoTString(keyB); + IoTString iKeyC = new IoTString(keyC); + IoTString iKeyD = new IoTString(keyD); + IoTString iValueA = new IoTString(valueA); + IoTString iValueB = new IoTString(valueB); + IoTString iValueC = new IoTString(valueC); + IoTString iValueD = new IoTString(valueD); + + t1.startTransaction(); + t1.addKV(iKeyA, iValueA); + transStatusList.add(t1.commitTransaction()); + + t1.startTransaction(); + t1.addKV(iKeyB, iValueB); + transStatusList.add(t1.commitTransaction()); + + t2.startTransaction(); + t2.addKV(iKeyC, iValueC); + transStatusList.add(t2.commitTransaction()); + + t2.startTransaction(); + t2.addKV(iKeyD, iValueD); + transStatusList.add(t2.commitTransaction()); + } + } + endTime = System.currentTimeMillis(); + System.out.println("Time Taken: " + (double) ((endTime - startTime) / 1000.0) ); + System.out.println("Time Taken Per Update: " + (double) (((endTime - startTime) / 1000.0) / (NUMBER_OF_TESTS * 16)) ); + System.out.println(); + + + System.out.println("Updating Clients..."); + t1.update(); + t2.update(); + + + System.out.println("Checking Key-Values..."); + for (int i = 0; i < 4; i++) { + + String keyA = "a" + i; + String keyB = "b" + i; + String keyC = "c" + i; + String keyD = "d" + i; + String valueA = "a" + i; + String valueB = "b" + i; + String valueC = "c" + i; + String valueD = "d" + i; + + IoTString iKeyA = new IoTString(keyA); + IoTString iKeyB = new IoTString(keyB); + IoTString iKeyC = new IoTString(keyC); + IoTString iKeyD = new IoTString(keyD); + IoTString iValueA = new IoTString(valueA); + IoTString iValueB = new IoTString(valueB); + IoTString iValueC = new IoTString(valueC); + IoTString iValueD = new IoTString(valueD); + + + IoTString testValA1 = t1.getCommitted(iKeyA); + IoTString testValB1 = t1.getCommitted(iKeyB); + IoTString testValC1 = t1.getCommitted(iKeyC); + IoTString testValD1 = t1.getCommitted(iKeyD); + + IoTString testValA2 = t2.getCommitted(iKeyA); + IoTString testValB2 = t2.getCommitted(iKeyB); + IoTString testValC2 = t2.getCommitted(iKeyC); + IoTString testValD2 = t2.getCommitted(iKeyD); + + if ((testValA1 == null) || (testValA1.equals(iValueA) == false)) { + System.out.println("Key-Value t1 incorrect: " + keyA); + foundError = true; + } + + if ((testValB1 == null) || (testValB1.equals(iValueB) == false)) { + System.out.println("Key-Value t1 incorrect: " + keyB); + foundError = true; + } + + if ((testValC1 == null) || (testValC1.equals(iValueC) == false)) { + System.out.println("Key-Value t1 incorrect: " + keyC); + foundError = true; + } + + if ((testValD1 == null) || (testValD1.equals(iValueD) == false)) { + System.out.println("Key-Value t1 incorrect: " + keyD); + foundError = true; + } + + + if ((testValA2 == null) || (testValA2.equals(iValueA) == false)) { + System.out.println("Key-Value t2 incorrect: " + keyA + " " + testValA2); + foundError = true; + } + + if ((testValB2 == null) || (testValB2.equals(iValueB) == false)) { + System.out.println("Key-Value t2 incorrect: " + keyB + " " + testValB2); + foundError = true; + } + + if ((testValC2 == null) || (testValC2.equals(iValueC) == false)) { + System.out.println("Key-Value t2 incorrect: " + keyC + " " + testValC2); + foundError = true; + } + + if ((testValD2 == null) || (testValD2.equals(iValueD) == false)) { + System.out.println("Key-Value t2 incorrect: " + keyD + " " + testValD2); + foundError = true; + } + } + + for (TransactionStatus status : transStatusList) { + if (status.getStatus() != TransactionStatus.StatusCommitted) { + foundError = true; + } + } + + if (foundError) { + System.out.println("Found Errors..."); + } else { + System.out.println("No Errors Found..."); + } + } + + static void test3() throws ServerException, InterruptedException { + + long startTime = 0; + long endTime = 0; + boolean foundError = false; + + List<TransactionStatus> transStatusList = new ArrayList<TransactionStatus>(); + + // Setup the 2 clients + Table t1 = new Table("127.0.0.1", "http://127.0.0.1/test.iotcloud/", "reallysecret", 321); + t1.initTable(); + Table t2 = new Table("127.0.0.1", "http://127.0.0.1/test.iotcloud/", "reallysecret", 351); + t2.update(); + + + // Make the Keys + System.out.println("Setting up keys"); + startTime = System.currentTimeMillis(); + for (int i = 0; i < NUMBER_OF_TESTS; i++) { + String a = "a" + i; + String b = "b" + i; + String c = "c" + i; + String d = "d" + i; + IoTString ia = new IoTString(a); + IoTString ib = new IoTString(b); + IoTString ic = new IoTString(c); + IoTString id = new IoTString(d); + t1.createNewKey(ia, 321); + t1.createNewKey(ib, 351); + t2.createNewKey(ic, 321); + t2.createNewKey(id, 351); + } + endTime = System.currentTimeMillis(); + System.out.println("Time Taken: " + (double) ((endTime - startTime) / 1000.0) ); + System.out.println("Time Taken Per Key: " + (double) (((endTime - startTime) / 1000.0) / (NUMBER_OF_TESTS * 4)) ); + System.out.println(); + + + // Do Updates for the keys + System.out.println("Setting Key-Values..."); + startTime = System.currentTimeMillis(); + for (int i = 0; i < NUMBER_OF_TESTS; i++) { + String keyB = "b" + i; + String valueB = "b" + i; + + IoTString iKeyB = new IoTString(keyB); + IoTString iValueB = new IoTString(valueB); + + t1.startTransaction(); + t1.addKV(iKeyB, iValueB); + transStatusList.add(t1.commitTransaction()); + } + + for (int i = 0; i < NUMBER_OF_TESTS; i++) { + String keyC = "c" + i; + String valueC = "c" + i; + + IoTString iKeyC = new IoTString(keyC); + IoTString iValueC = new IoTString(valueC); + + t2.startTransaction(); + t2.addKV(iKeyC, iValueC); + transStatusList.add(t2.commitTransaction()); + } + + for (int i = 0; i < NUMBER_OF_TESTS; i++) { + String keyA = "a" + i; + String keyD = "d" + i; + String valueA = "a" + i; + String valueD = "d" + i; + + IoTString iKeyA = new IoTString(keyA); + IoTString iKeyD = new IoTString(keyD); + IoTString iValueA = new IoTString(valueA); + IoTString iValueD = new IoTString(valueD); + + t1.startTransaction(); + t1.addKV(iKeyA, iValueA); + transStatusList.add(t1.commitTransaction()); + + t2.startTransaction(); + t2.addKV(iKeyD, iValueD); + transStatusList.add(t2.commitTransaction()); + } + endTime = System.currentTimeMillis(); + System.out.println("Time Taken: " + (double) ((endTime - startTime) / 1000.0) ); + System.out.println("Time Taken Per Update: " + (double) (((endTime - startTime) / 1000.0) / (NUMBER_OF_TESTS * 4)) ); + System.out.println(); + + + System.out.println("Updating Clients..."); + t1.update(); + t2.update(); + + + System.out.println("Checking Key-Values..."); + for (int i = 0; i < NUMBER_OF_TESTS; i++) { + + String keyA = "a" + i; + String keyB = "b" + i; + String keyC = "c" + i; + String keyD = "d" + i; + String valueA = "a" + i; + String valueB = "b" + i; + String valueC = "c" + i; + String valueD = "d" + i; + + IoTString iKeyA = new IoTString(keyA); + IoTString iKeyB = new IoTString(keyB); + IoTString iKeyC = new IoTString(keyC); + IoTString iKeyD = new IoTString(keyD); + IoTString iValueA = new IoTString(valueA); + IoTString iValueB = new IoTString(valueB); + IoTString iValueC = new IoTString(valueC); + IoTString iValueD = new IoTString(valueD); + + + IoTString testValA1 = t1.getCommitted(iKeyA); + IoTString testValB1 = t1.getCommitted(iKeyB); + IoTString testValC1 = t1.getCommitted(iKeyC); + IoTString testValD1 = t1.getCommitted(iKeyD); + + IoTString testValA2 = t2.getCommitted(iKeyA); + IoTString testValB2 = t2.getCommitted(iKeyB); + IoTString testValC2 = t2.getCommitted(iKeyC); + IoTString testValD2 = t2.getCommitted(iKeyD); + + if ((testValA1 == null) || (testValA1.equals(iValueA) == false)) { + System.out.println("Key-Value t1 incorrect: " + keyA); + foundError = true; + } + + if ((testValB1 == null) || (testValB1.equals(iValueB) == false)) { + System.out.println("Key-Value t1 incorrect: " + keyB); + foundError = true; + } + + if ((testValC1 == null) || (testValC1.equals(iValueC) == false)) { + System.out.println("Key-Value t1 incorrect: " + keyC); + foundError = true; + } + + if ((testValD1 == null) || (testValD1.equals(iValueD) == false)) { + System.out.println("Key-Value t1 incorrect: " + keyD); + foundError = true; + } + + + if ((testValA2 == null) || (testValA2.equals(iValueA) == false)) { + System.out.println("Key-Value t2 incorrect: " + keyA + " " + testValA2); + foundError = true; + } + + if ((testValB2 == null) || (testValB2.equals(iValueB) == false)) { + System.out.println("Key-Value t2 incorrect: " + keyB + " " + testValB2); + foundError = true; + } + + if ((testValC2 == null) || (testValC2.equals(iValueC) == false)) { + System.out.println("Key-Value t2 incorrect: " + keyC + " " + testValC2); + foundError = true; + } + + if ((testValD2 == null) || (testValD2.equals(iValueD) == false)) { + System.out.println("Key-Value t2 incorrect: " + keyD + " " + testValD2); + foundError = true; + } + } + + for (TransactionStatus status : transStatusList) { + if (status.getStatus() != TransactionStatus.StatusCommitted) { + foundError = true; + } + } + + if (foundError) { + System.out.println("Found Errors..."); + } else { + System.out.println("No Errors Found..."); + } + } + + static void test2() throws ServerException, InterruptedException { + + boolean foundError = false; + long startTime = 0; + long endTime = 0; + List<TransactionStatus> transStatusList = new ArrayList<TransactionStatus>(); + + // Setup the 2 clients + Table t1 = new Table("127.0.0.1", "http://127.0.0.1/test.iotcloud/", "reallysecret", 321); + t1.initTable(); + System.out.println("T1 Ready"); + + Table t2 = new Table("127.0.0.1", "http://127.0.0.1/test.iotcloud/", "reallysecret", 351); + t2.update(); + System.out.println("T2 Ready"); + + // Make the Keys + System.out.println("Setting up keys"); + startTime = System.currentTimeMillis(); + for (int i = 0; i < NUMBER_OF_TESTS; i++) { + String a = "a" + i; + String b = "b" + i; + String c = "c" + i; + String d = "d" + i; + IoTString ia = new IoTString(a); + IoTString ib = new IoTString(b); + IoTString ic = new IoTString(c); + IoTString id = new IoTString(d); + t1.createNewKey(ia, 321); + t1.createNewKey(ib, 351); + t2.createNewKey(ic, 321); + t2.createNewKey(id, 351); + } + endTime = System.currentTimeMillis(); + System.out.println("Time Taken: " + (double) ((endTime - startTime) / 1000.0) ); + System.out.println("Time Taken Per Key: " + (double) (((endTime - startTime) / 1000.0) / (NUMBER_OF_TESTS * 4)) ); + System.out.println(); + + // Do Updates for the keys + System.out.println("Setting Key-Values..."); + startTime = System.currentTimeMillis(); + for (int i = 0; i < NUMBER_OF_TESTS; i++) { + String keyA = "a" + i; + String keyB = "b" + i; + String keyC = "c" + i; + String keyD = "d" + i; + String valueA = "a" + i; + String valueB = "b" + i; + String valueC = "c" + i; + String valueD = "d" + i; + + IoTString iKeyA = new IoTString(keyA); + IoTString iKeyB = new IoTString(keyB); + IoTString iKeyC = new IoTString(keyC); + IoTString iKeyD = new IoTString(keyD); + IoTString iValueA = new IoTString(valueA); + IoTString iValueB = new IoTString(valueB); + IoTString iValueC = new IoTString(valueC); + IoTString iValueD = new IoTString(valueD); + + t1.startTransaction(); + t1.addKV(iKeyA, iValueA); + transStatusList.add(t1.commitTransaction()); + + t1.startTransaction(); + t1.addKV(iKeyB, iValueB); + transStatusList.add(t1.commitTransaction()); + + t2.startTransaction(); + t2.addKV(iKeyC, iValueC); + transStatusList.add(t2.commitTransaction()); + + t2.startTransaction(); + t2.addKV(iKeyD, iValueD); + transStatusList.add(t2.commitTransaction()); + } + endTime = System.currentTimeMillis(); + System.out.println("Time Taken: " + (double) ((endTime - startTime) / 1000.0) ); + System.out.println("Time Taken Per Update: " + (double) (((endTime - startTime) / 1000.0) / (NUMBER_OF_TESTS * 4)) ); + System.out.println(); + + + System.out.println("Updating Clients..."); + t1.update(); + t2.update(); + + + System.out.println("Checking Key-Values..."); + for (int i = 0; i < NUMBER_OF_TESTS; i++) { + + String keyA = "a" + i; + String keyB = "b" + i; + String keyC = "c" + i; + String keyD = "d" + i; + String valueA = "a" + i; + String valueB = "b" + i; + String valueC = "c" + i; + String valueD = "d" + i; + + IoTString iKeyA = new IoTString(keyA); + IoTString iKeyB = new IoTString(keyB); + IoTString iKeyC = new IoTString(keyC); + IoTString iKeyD = new IoTString(keyD); + IoTString iValueA = new IoTString(valueA); + IoTString iValueB = new IoTString(valueB); + IoTString iValueC = new IoTString(valueC); + IoTString iValueD = new IoTString(valueD); + + + IoTString testValA1 = t1.getCommitted(iKeyA); + IoTString testValB1 = t1.getCommitted(iKeyB); + IoTString testValC1 = t1.getCommitted(iKeyC); + IoTString testValD1 = t1.getCommitted(iKeyD); + + IoTString testValA2 = t2.getCommitted(iKeyA); + IoTString testValB2 = t2.getCommitted(iKeyB); + IoTString testValC2 = t2.getCommitted(iKeyC); + IoTString testValD2 = t2.getCommitted(iKeyD); + + if ((testValA1 == null) || (testValA1.equals(iValueA) == false)) { + System.out.println("Key-Value t1 incorrect: " + keyA); + foundError = true; + } + + if ((testValB1 == null) || (testValB1.equals(iValueB) == false)) { + System.out.println("Key-Value t1 incorrect: " + keyB); + foundError = true; + } + + if ((testValC1 == null) || (testValC1.equals(iValueC) == false)) { + System.out.println("Key-Value t1 incorrect: " + keyC); + foundError = true; + } + + if ((testValD1 == null) || (testValD1.equals(iValueD) == false)) { + System.out.println("Key-Value t1 incorrect: " + keyD); + foundError = true; + } + + + if ((testValA2 == null) || (testValA2.equals(iValueA) == false)) { + System.out.println("Key-Value t2 incorrect: " + keyA + " " + testValA2); + foundError = true; + } + + if ((testValB2 == null) || (testValB2.equals(iValueB) == false)) { + System.out.println("Key-Value t2 incorrect: " + keyB + " " + testValB2); + foundError = true; + } + + if ((testValC2 == null) || (testValC2.equals(iValueC) == false)) { + System.out.println("Key-Value t2 incorrect: " + keyC + " " + testValC2); + foundError = true; + } + + if ((testValD2 == null) || (testValD2.equals(iValueD) == false)) { + System.out.println("Key-Value t2 incorrect: " + keyD + " " + testValD2); + foundError = true; + } + } + + for (TransactionStatus status : transStatusList) { + if (status.getStatus() != TransactionStatus.StatusCommitted) { + foundError = true; + } + } + + if (foundError) { + System.out.println("Found Errors..."); + } else { + System.out.println("No Errors Found..."); + } + } +} diff --git a/version2/backup/src/java/iotcloud/ThreeTuple.java b/version2/backup/src/java/iotcloud/ThreeTuple.java new file mode 100644 index 0000000..8a882a4 --- /dev/null +++ b/version2/backup/src/java/iotcloud/ThreeTuple.java @@ -0,0 +1,29 @@ +package iotcloud; + +class ThreeTuple<A, B, C> { + private A a; + private B b; + private C c; + + ThreeTuple(A a, B b, C c) { + this.a = a; + this.b = b; + this.c = c; + } + + A getFirst() { + return a; + } + + B getSecond() { + return b; + } + + C getThird() { + return c; + } + + public String toString() { + return "<" + a + "," + b + "," + c + ">"; + } +} diff --git a/version2/backup/src/java/iotcloud/Transaction.java b/version2/backup/src/java/iotcloud/Transaction.java new file mode 100644 index 0000000..90ef4d4 --- /dev/null +++ b/version2/backup/src/java/iotcloud/Transaction.java @@ -0,0 +1,154 @@ +package iotcloud; + +import java.nio.ByteBuffer; +import java.util.Set; +import java.util.HashSet; +import java.util.Map; + +class Transaction extends Entry { + + private long seqnum; + private long machineid; + private Set<KeyValue> keyValueUpdateSet = null; + private Set<KeyValue> keyValueGuardSet = null; + private Long arbitrator; + + public Transaction(Slot slot, long _seqnum, long _machineid, Long _arbitrator, Set<KeyValue> _keyValueUpdateSet, Set<KeyValue> _keyValueGuardSet) { + super(slot); + seqnum = _seqnum; + machineid = _machineid; + arbitrator = _arbitrator; + // keyValueUpdateSet = new HashSet<KeyValue>(); + // keyValueGuardSet = new HashSet<KeyValue>(); + + // for (KeyValue kv : _keyValueUpdateSet) { + // KeyValue kvCopy = kv.getCopy(); + // keyValueUpdateSet.add(kvCopy); + // } + + // for (KeyValue kv : _keyValueGuardSet) { + // KeyValue kvCopy = kv.getCopy(); + // keyValueGuardSet.add(kvCopy); + // } + + keyValueUpdateSet = _keyValueUpdateSet; + keyValueGuardSet = _keyValueGuardSet; + } + + public long getMachineID() { + return machineid; + } + + public long getArbitrator() { + return arbitrator; + } + + public long getSequenceNumber() { + return seqnum; + } + + + public Set<KeyValue> getkeyValueUpdateSet() { + return keyValueUpdateSet; + } + + public Set<KeyValue> getkeyValueGuardSet() { + return keyValueGuardSet; + } + + public boolean evaluateGuard(Map<IoTString, KeyValue> keyValTableCommitted, Map<IoTString, KeyValue> keyValTableSpeculative) { + for (KeyValue kvGuard : keyValueGuardSet) { + + // First check if the key is in the speculative table, this is the value of the latest assumption + KeyValue kv = null; + + // If we have a speculation table then use it first + if (keyValTableSpeculative != null) { + kv = keyValTableSpeculative.get(kvGuard.getKey()); + } + + if (kv == null) { + + // if it is not in the speculative table then check the committed table and use that + // value as our latest assumption + kv = keyValTableCommitted.get(kvGuard.getKey()); + } + + if (kvGuard.getValue() != null) { + if ((kv == null) || (!kvGuard.getValue().equals(kv.getValue()))) { + return false; + } + } else { + if (kv != null) { + return false; + } + } + } + return true; + } + + public byte getType() { + return Entry.TypeTransaction; + } + + public int getSize() { + int size = 3 * Long.BYTES + Byte.BYTES; // seq, machine id, entry type + size += Integer.BYTES; // number of KV's + size += Integer.BYTES; // number of Guard KV's + + // Size of each KV + for (KeyValue kv : keyValueUpdateSet) { + size += kv.getSize(); + } + + // Size of each Guard KV + for (KeyValue kv : keyValueGuardSet) { + size += kv.getSize(); + } + + return size; + } + + public void encode(ByteBuffer bb) { + bb.put(Entry.TypeTransaction); + bb.putLong(seqnum); + bb.putLong(machineid); + bb.putLong(arbitrator); + + bb.putInt(keyValueUpdateSet.size()); + for (KeyValue kv : keyValueUpdateSet) { + kv.encode(bb); + } + + bb.putInt(keyValueGuardSet.size()); + for (KeyValue kv : keyValueGuardSet) { + kv.encode(bb); + } + } + + static Entry decode(Slot slot, ByteBuffer bb) { + long seqnum = bb.getLong(); + long machineid = bb.getLong(); + long arbitrator = bb.getLong(); + int numberOfKeys = bb.getInt(); + + Set<KeyValue> kvSetUpdates = new HashSet<KeyValue>(); + for (int i = 0; i < numberOfKeys; i++) { + KeyValue kv = KeyValue.decode(bb); + kvSetUpdates.add(kv); + } + + int numberOfGuards = bb.getInt(); + Set<KeyValue> kvSetGuards = new HashSet<KeyValue>(); + for (int i = 0; i < numberOfGuards; i++) { + KeyValue kv = KeyValue.decode(bb); + kvSetGuards.add(kv); + } + + return new Transaction(slot, seqnum, machineid, arbitrator, kvSetUpdates, kvSetGuards); + } + + public Entry getCopy(Slot s) { + return new Transaction(s, seqnum, machineid, arbitrator, keyValueUpdateSet, keyValueGuardSet); + } +} \ No newline at end of file diff --git a/version2/backup/src/java/iotcloud/TransactionStatus.java b/version2/backup/src/java/iotcloud/TransactionStatus.java new file mode 100644 index 0000000..a42f570 --- /dev/null +++ b/version2/backup/src/java/iotcloud/TransactionStatus.java @@ -0,0 +1,53 @@ +package iotcloud; + +class TransactionStatus { + static final byte StatusAborted = 1; + static final byte StatusPending = 2; + static final byte StatusCommitted = 3; + // static final byte StatusRetrying = 4; + static final byte StatusSent = 5; + static final byte StatusNoEffect = 6; + + private byte status = 0; + private boolean applicationReleased = false; + private long arbitrator = 0; + private boolean wasSentInChain = false; + + public TransactionStatus(byte _status, long _arbitrator) { + status = _status; + arbitrator = _arbitrator; + } + + public byte getStatus() { + return status; + } + + public void setStatus(byte _status) { + status = _status; + } + + public void setSentTransaction() { + wasSentInChain = true; + } + + public boolean getSentTransaction() { + return wasSentInChain; + } + + + // public void setArbitrator(long _arbitrator) { + // arbitrator = _arbitrator; + // } + + public long getArbitrator() { + return arbitrator; + } + + public void release() { + applicationReleased = true; + } + + public boolean getReleased() { + return applicationReleased; + } +} \ No newline at end of file diff --git a/version2/src/java/iotcloud/issues.txt b/version2/backup/src/java/iotcloud/issues.txt similarity index 100% rename from version2/src/java/iotcloud/issues.txt rename to version2/backup/src/java/iotcloud/issues.txt diff --git a/version2/src/script/C.cfg b/version2/backup/src/script/C.cfg similarity index 100% rename from version2/src/script/C.cfg rename to version2/backup/src/script/C.cfg diff --git a/version2/src/script/java.cfg b/version2/backup/src/script/java.cfg similarity index 100% rename from version2/src/script/java.cfg rename to version2/backup/src/script/java.cfg diff --git a/version2/src/script/makefile b/version2/backup/src/script/makefile similarity index 100% rename from version2/src/script/makefile rename to version2/backup/src/script/makefile diff --git a/version2/backup/src/server/.dir-locals.el b/version2/backup/src/server/.dir-locals.el new file mode 100644 index 0000000..e166a2e --- /dev/null +++ b/version2/backup/src/server/.dir-locals.el @@ -0,0 +1,2 @@ +((nil . ((indent-tabs-mode . t)))) + diff --git a/version2/backup/src/server/Makefile b/version2/backup/src/server/Makefile new file mode 100644 index 0000000..8eee1fa --- /dev/null +++ b/version2/backup/src/server/Makefile @@ -0,0 +1,15 @@ +CPPFLAGS=-O0 -g -Wall + +all: iotcloud.fcgi + +iotcloud.fcgi: iotcloud.o iotquery.o + g++ $(CPPFLAGS) -o iotcloud.fcgi iotcloud.o iotquery.o -lfcgi -lfcgi++ + +iotcloud.o: iotcloud.cpp iotquery.h + g++ $(CPPFLAGS) -c -o iotcloud.o iotcloud.cpp + +iotquery.o: iotquery.cpp iotquery.h + g++ $(CPPFLAGS) -c -o iotquery.o iotquery.cpp + +clean: + rm *.o iotcloud.fcgi diff --git a/version2/backup/src/server/README.txt b/version2/backup/src/server/README.txt new file mode 100644 index 0000000..6eb138f --- /dev/null +++ b/version2/backup/src/server/README.txt @@ -0,0 +1,32 @@ +1) Requires apache2 +2) Requires fastcgi (libapache2-mod-fastcgi and libfcgi-dev) + +Setup on ubuntu +1) Install modules + +2) Add .htaccess file in /var/www/html +RewriteEngine on +RewriteBase / +SetHandler cgi-script +RewriteRule ^([a-zA-Z0-9._]*\.iotcloud/([a-zA-Z0-9._]*))$ /cgi-bin/iotcloud.fcgi/$1 + +3) Create account directory. For example, create the directory test.iotcloud in /var/www/html + -- To password protect, create the following .htaccess file in the account directory: +AuthType Basic +AuthName "Private" +AuthUserFile /var/www/html/foo.iotcloud/.htpasswd +Require valid-user + +4) In apache2.conf, add to the /var/www directory section: +AllowOverride FileInfo AuthConfig + +5) In the sites-enabled/000-default.conf file, add the line: +SetEnv IOTCLOUD_ROOT /iotcloud/ + +6) Create the /iotcloud directory. + +7) Create the account directory in the /iotcloud directory. For example, test.iotcloud and give it permissions that the apache daemon can write to. + +8) Compile cloud server by typing make + +9) Copy it to the cgi-bin directory. diff --git a/version2/backup/src/server/iotcloud.cpp b/version2/backup/src/server/iotcloud.cpp new file mode 100644 index 0000000..bb9eff8 --- /dev/null +++ b/version2/backup/src/server/iotcloud.cpp @@ -0,0 +1,40 @@ +#include <iostream> +#include "iotquery.h" + +using namespace std; + + +int main(void) { + // Backup the stdio streambufs + streambuf * cin_streambuf = cin.rdbuf(); + streambuf * cout_streambuf = cout.rdbuf(); + streambuf * cerr_streambuf = cerr.rdbuf(); + + FCGX_Request request; + + FCGX_Init(); + FCGX_InitRequest(&request, 0, 0); + + while (FCGX_Accept_r(&request) == 0) { + fcgi_streambuf cin_fcgi_streambuf(request.in); + fcgi_streambuf cout_fcgi_streambuf(request.out); + fcgi_streambuf cerr_fcgi_streambuf(request.err); + + cin.rdbuf(&cin_fcgi_streambuf); + cout.rdbuf(&cout_fcgi_streambuf); + cerr.rdbuf(&cerr_fcgi_streambuf); + + IoTQuery * iotquery=new IoTQuery(&request); + iotquery->processQuery(); + + delete iotquery; + } + + // restore stdio streambufs + cin.rdbuf(cin_streambuf); + cout.rdbuf(cout_streambuf); + cerr.rdbuf(cerr_streambuf); + + return 0; +} + diff --git a/version2/backup/src/server/iotquery.cpp b/version2/backup/src/server/iotquery.cpp new file mode 100644 index 0000000..0b1c4b3 --- /dev/null +++ b/version2/backup/src/server/iotquery.cpp @@ -0,0 +1,517 @@ +#include "iotquery.h" +#include <string.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/file.h> +#include <fcntl.h> +#include <unistd.h> +#include <stdlib.h> +#include <errno.h> +#include <netinet/in.h> + +using namespace std; + +const char * query_str="QUERY_STRING"; +const char * uri_str="REQUEST_URI"; +const char * method_str="REQUEST_METHOD"; +const char * iotcloudroot_str="IOTCLOUD_ROOT"; +const char * length_str="CONTENT_LENGTH"; + +IoTQuery::IoTQuery(FCGX_Request *request) : + request(request), + data(NULL), + directory(NULL), + uri(NULL), + query(NULL), + method(NULL), + iotcloudroot(NULL), + length(0), + oldestentry(0), + newestentry(0), + requestsequencenumber(0), + numqueueentries(DEFAULT_SIZE), + fd(-1), + reqGetSlot(false), + reqPutSlot(false), + reqSetSalt(false), + reqGetSalt(false) { +} + +IoTQuery::~IoTQuery() { + if (fd >= 0) + close(fd); + if (directory) + delete directory; + if (data) + delete data; +} + +/** + * Returns true if the account directory exists. + */ + +bool IoTQuery::checkDirectory() { + struct stat s; + int err=stat(directory, &s); + if (-1 == err) + return false; + return S_ISDIR(s.st_mode); +} + +/** + * Decodes query string from client. Extracts type of request, + * sequence number, and whether the request changes the number of + * slots. + */ + +void IoTQuery::decodeQuery() { + int len=strlen(query); + char * str=new char[len+1]; + memcpy(str, query, len+1); + char *tok_ptr=str; + + /* Parse commands */ + char *command=strsep(&tok_ptr, "&"); + if (strncmp(command, "req=putslot", 11) == 0) + reqPutSlot = true; + else if (strncmp(command, "req=getslot", 11) == 0) + reqGetSlot = true; + else if (strncmp(command, "req=setsalt", 11) == 0) + reqSetSalt = true; + else if (strncmp(command, "req=getsalt", 11) == 0) + reqGetSalt = true; + + /* Load Sequence Number for request */ + char *sequencenumber_str = strsep(&tok_ptr, "&"); + if (sequencenumber_str != NULL && + strncmp(sequencenumber_str, "seq=", 4) == 0) { + sequencenumber_str = strchr(sequencenumber_str, '='); + if (sequencenumber_str != NULL) { + requestsequencenumber = strtoll(sequencenumber_str + 1, NULL, 10); + } + } + + /* don't allow a really old sequence number */ + if (requestsequencenumber < oldestentry) + requestsequencenumber = oldestentry; + + /* Update size if we get request */ + char * numqueueentries_str = tok_ptr; + if (numqueueentries_str != NULL && + strncmp(numqueueentries_str, "max=", 4) == 0) { + numqueueentries_str = strchr(numqueueentries_str, '=') + 1; + numqueueentries = strtoll(numqueueentries_str, NULL, 10); + } + + delete str; +} + +/** + * Helper function to write data to file. + */ + +void doWrite(int fd, char *data, long long length) { + long long offset=0; + do { + long long byteswritten=write(fd, &data[offset], length); + if (byteswritten > 0) { + length -= byteswritten; + offset += byteswritten; + } else { + cerr << "Bytes not written" << endl; + if (byteswritten < 0) { + cerr << strerror(errno) << " error writing slot file" << endl; + } + return; + } + } while(length != 0); +} + +/** Helper function to read data from file. */ +bool doRead(int fd, void *buf, int numbytes) { + int offset=0; + char *ptr=(char *)buf; + do { + int bytesread=read(fd, ptr+offset, numbytes); + if (bytesread > 0) { + offset += bytesread; + numbytes -= bytesread; + } else + return false; + } while (numbytes!=0); + return true; +} + +/** + * Function that handles a getSlot request. + */ + +void IoTQuery::getSlot() { + int numrequeststosend = (int)((newestentry-requestsequencenumber)+1); + if (numrequeststosend < 0) + numrequeststosend = 0; + long long numbytes = 0; + int filesizes[numrequeststosend]; + int fdarray[numrequeststosend]; + int index=0; + for(long long seqn = requestsequencenumber; seqn <= newestentry; seqn++, index++) { + struct stat st; + char *filename=getSlotFileName(seqn); + if (stat(filename, &st) == 0) { + fdarray[index]=open(filename, O_RDONLY); + filesizes[index]=st.st_size; + numbytes+=filesizes[index]; + } else { + fdarray[index]=-1; + filesizes[index]=0; + } + delete filename; + } + const char header[]="getslot"; + + /* Size is the header + the payload + space for number of requests + plus sizes of each slot */ + + long long size=sizeof(header)-1+sizeof(numrequeststosend)+4*numrequeststosend+numbytes; + char * response = new char[size]; + long long offset=0; + memcpy(response, header, sizeof(header)-1); + offset+=sizeof(header)-1; + int numreq=htonl(numrequeststosend); + memcpy(response + offset, &numreq, sizeof(numreq)); + offset+=sizeof(numrequeststosend); + for(int i=0; i<numrequeststosend; i++) { + int filesize=htonl(filesizes[i]); + memcpy(response + offset, &filesize, sizeof(filesize)); + offset+=sizeof(int); + } + + /* Read the file data into the buffer */ + for(int i=0; i<numrequeststosend; i++) { + if (fdarray[i]>=0) { + doRead(fdarray[i], response+offset, filesizes[i]); + offset+=filesizes[i]; + } + } + + /* Send the response out to the webserver. */ + sendResponse(response, size); + + /* Delete the response buffer and close the files. */ + delete response; + for(int i=0; i<numrequeststosend; i++) { + if (fdarray[i] >= 0) + close(fdarray[i]); + } +} + +/** + * The method setSalt handles a setSalt request from the client. + */ + +void IoTQuery::setSalt() { + /* Write the slot data we received to a SLOT file */ + char *filename = getSaltFileName(); + int saltfd = open(filename, O_CREAT|O_WRONLY, S_IRUSR| S_IWUSR); + doWrite(saltfd, data, length); + char response[0]; + sendResponse(response, 0); + close(saltfd); + delete filename; +} + +/** + * The method getSalt handles a setSalt request from the client. + */ + +void IoTQuery::getSalt() { + /* Write the slot data we received to a SLOT file */ + char *filename = getSaltFileName(); + int filesize = 0; + struct stat st; + if (stat(filename, &st) == 0) { + filesize=st.st_size; + } else { + delete filename; + return; + } + int saltfd = open(filename, O_RDONLY); + int responsesize = filesize + sizeof(int); + char * response = new char[responsesize]; + doRead(saltfd, response+ sizeof(int), filesize); + int n_filesize=htonl(filesize); + *((int*) response) = n_filesize; + sendResponse(response, responsesize); + close(saltfd); + delete filename; + delete response; +} + +/** + * The method putSlot handles a putSlot request from the client + */ + +void IoTQuery::putSlot() { + /* Check if the request is stale and send update in that case. This + servers as an implicit failure of the request. */ + if (requestsequencenumber!=(newestentry+1)) { + getSlot(); + return; + } + + /* See if we have too many slots and if so, delete the old one */ + int numberofliveslots=(int) ((newestentry-oldestentry)+1); + if (numberofliveslots >= numqueueentries) { + removeOldestSlot(); + } + + /* Write the slot data we received to a SLOT file */ + char *filename = getSlotFileName(requestsequencenumber); + int slotfd = open(filename, O_CREAT|O_WRONLY, S_IRUSR| S_IWUSR); + doWrite(slotfd, data, length); + close(slotfd); + delete filename; + newestentry = requestsequencenumber; + + /* Update the seuqence numbers and other status file information. */ + updateStatusFile(); + + /* Send response acknowledging success */ + char command[]="putslot"; + sendResponse(command, sizeof(command)-1); +} + +/** + * Method sends response. It wraps in appropriate headers for web + * server. + */ + +void IoTQuery::sendResponse(char * bytes, int len) { + cout << "Accept-Ranges: bytes\r\n" + << "Content-Length: " << len << "\r\n" + << "\r\n"; + cout.write(bytes, len); +} + +/** + * Computes the name for a slot file for the given sequence number. + */ + +char * IoTQuery::getSlotFileName(long long seqnum) { + int directorylen=strlen(directory); + + /* Size is 19 digits for ASCII representation of a long + 4 + characters for SLOT string + 1 character for null termination + + directory size*/ + + char * filename=new char[25+directorylen]; + snprintf(filename, 25+directorylen, "%s/SLOT%lld", directory, seqnum); + return filename; +} + +/** + * Computes the name for a salt file + */ + +char * IoTQuery::getSaltFileName() { + int directorylen=strlen(directory); + + /* Size is 4 characters for SALT string + 1 character for null + termination + directory size*/ + + char * filename=new char[6+directorylen]; + snprintf(filename, 6+directorylen, "%s/SALT", directory); + return filename; +} + +/** + * Removes the oldest slot file + */ + +void IoTQuery::removeOldestSlot() { + if (oldestentry!=0) { + char * filename=getSlotFileName(oldestentry); + unlink(filename); + delete filename; + } + oldestentry++; +} + +/** + * Processes the query sent to the fastcgi handler. + */ + +void IoTQuery::processQuery() { + getQuery(); + getDirectory(); + readData(); + + /* Verify that we receive a post request. */ + if (strncmp(method, "POST", 4) != 0) { + cerr << "Not POST Request" << endl; + return; + } + + /* Make sure the directory is okay. */ + if (directory == NULL || + !checkDirectory()) { + cerr << "Directory " << directory << " does not exist" << endl; + return; + } + + /* Get queue state from the status file. If it doesn't exist, + create it. */ + if (!openStatusFile()) { + cerr << "Failed to open status file" << endl; + return; + } + + /* Lock status file to keep other requests out. */ + flock(fd, LOCK_EX); + + /* Decode query. */ + decodeQuery(); + + /* Handle request. */ + if (reqGetSlot) + getSlot(); + else if (reqPutSlot) + putSlot(); + else if (reqSetSalt) + setSalt(); + else if (reqGetSalt) + getSalt(); + else { + cerr << "No recognized request" << endl; + return; + } +} + +/** + * Reads in data for request. This is used for the slot to be + * inserted. + */ + +void IoTQuery::readData() { + if (length) { + data = new char[length+1]; + memset(data, 0, length+1); + cin.read(data, length); + } + do { + char dummy; + cin >> dummy; + } while (!cin.eof()); +} + + +/** + * Reads relevant environmental variables to find out the request. + */ + +void IoTQuery::getQuery() { + uri = FCGX_GetParam(uri_str, request->envp); + query = FCGX_GetParam(query_str, request->envp); + method = FCGX_GetParam(method_str, request->envp); + iotcloudroot = FCGX_GetParam(iotcloudroot_str, request->envp); + + /** We require the content-length header to be sent. */ + char * reqlength = FCGX_GetParam(length_str, request->envp); + if (reqlength) { + length=strtoll(reqlength, NULL, 10); + } else { + length=0; + } +} + +/** + * Initializes directory field from environmental variables. + */ + +void IoTQuery::getDirectory() { + char * split = strchr((char *)uri, '?'); + if (split == NULL) + return; + int split_len = (int) (split-uri); + int rootdir_len = strlen(iotcloudroot); + int directory_len = split_len + rootdir_len + 1; + directory = new char[directory_len]; + memcpy(directory, iotcloudroot, rootdir_len); + memcpy(directory + rootdir_len, uri, split_len); + directory[directory_len-1]=0; +} + +/** + * Helper function that is used to read the status file. + */ + +int doread(int fd, void *ptr, size_t count, off_t offset) { + do { + size_t bytesread=pread(fd, ptr, count, offset); + if (bytesread==count) { + return 1; + } else if (bytesread==0) { + return 0; + } + } while(1); +} + + +/** + * Writes the current state to the status file. + */ + +void IoTQuery::updateStatusFile() { + pwrite(fd, &numqueueentries, sizeof(numqueueentries), OFFSET_MAX); + pwrite(fd, &oldestentry, sizeof(oldestentry), OFFSET_OLD); + pwrite(fd, &newestentry, sizeof(newestentry), OFFSET_NEW); +} + +/** + * Reads in queue state from the status file. Returns true if + * successful. + */ + +bool IoTQuery::openStatusFile() { + char statusfile[]="queuestatus"; + int len=strlen(directory); + + char * filename=new char[len+sizeof(statusfile)+2]; + memcpy(filename, directory, len); + filename[len]='/'; + memcpy(filename+len+1, statusfile, sizeof(statusfile)); + filename[len+sizeof(statusfile)+1]=0; + fd=open(filename, O_CREAT| O_RDWR, S_IRUSR| S_IWUSR); + delete filename; + + if (fd < 0) { + cerr << strerror(errno) << " error opening statusfile" << endl; + return false; + } + + /* Read in queue size, oldest sequence number, and newest sequence number. */ + int size; + int needwrite=0; + if (doread(fd, &size, sizeof(size), OFFSET_MAX)) + numqueueentries=size; + else + needwrite=1; + + long long entry; + if (doread(fd, &entry, sizeof(entry), OFFSET_OLD)) + oldestentry=entry; + else + needwrite=1; + + if (doread(fd, &entry, sizeof(entry), OFFSET_NEW)) + newestentry=entry; + else + needwrite=1; + + if (needwrite) + updateStatusFile(); + + return true; +} + + diff --git a/version2/backup/src/server/iotquery.h b/version2/backup/src/server/iotquery.h new file mode 100644 index 0000000..ae39366 --- /dev/null +++ b/version2/backup/src/server/iotquery.h @@ -0,0 +1,68 @@ +#ifndef IOTQUERY_H +#define IOTQUERY_H +#include <iostream> +#include "fcgio.h" +#include "fcgi_stdio.h" + +#define DEFAULT_SIZE 128 +#define OFFSET_MAX 0 +#define OFFSET_OLD 4 +#define OFFSET_NEW 12 + +class IoTQuery { +public: + IoTQuery(FCGX_Request * request); + ~IoTQuery(); + void processQuery(); + +private: + void sendResponse(char *data, int length); + void getQuery(); + void getDirectory(); + void readData(); + bool checkDirectory(); + bool openStatusFile(); + void updateStatusFile(); + void decodeQuery(); + void getSlot(); + void putSlot(); + void setSalt(); + void getSalt(); + void removeOldestSlot(); + char * getSlotFileName(long long); + char * getSaltFileName(); + + FCGX_Request * request; + char *data; + /* Directory slot files are placed in. */ + char *directory; + /* Full URI from Apache */ + const char * uri; + /* Query portion of URI */ + const char * query; + /* Type of request: GET or PUT */ + const char * method; + /* Root directory for all accounts */ + const char * iotcloudroot; + /* Expected length of data from client */ + long long length; + /* Sequence number for oldest slot */ + long long oldestentry; + /* Sequence number for newest slot */ + long long newestentry; + /* Sequence number from request */ + long long requestsequencenumber; + /* Size of queue */ + int numqueueentries; + /* fd for queuestatus file */ + int fd; + /* Is the request to get a slot? */ + bool reqGetSlot; + /* Is the request to put a slot? */ + bool reqPutSlot; + /* Is the request to set the salt? */ + bool reqSetSalt; + /* Is the request to get the salt? */ + bool reqGetSalt; +}; +#endif diff --git a/version2/src/java/iotcloud/Abort.java b/version2/src/java/iotcloud/Abort.java index 327ce33..6231292 100644 --- a/version2/src/java/iotcloud/Abort.java +++ b/version2/src/java/iotcloud/Abort.java @@ -9,48 +9,88 @@ import java.nio.ByteBuffer; */ -class Abort extends Entry { - private long seqnumtrans; - private long machineid; - private long transarbitrator; +class Abort extends Entry{ + private long transactionClientLocalSequenceNumber = -1; + private long transactionSequenceNumber = -1; + private long sequenceNumber = -1; + private long transactionMachineId = -1; + private long transactionArbitrator = -1; + private Pair<Long, Long> abortId = null; - public Abort(Slot slot, long _seqnumtrans, long _machineid, long _transarbitrator) { + + public Abort(Slot slot, long _transactionClientLocalSequenceNumber, long _transactionSequenceNumber , long _transactionMachineId, long _transactionArbitrator) { + super(slot); + transactionClientLocalSequenceNumber = _transactionClientLocalSequenceNumber; + transactionSequenceNumber = _transactionSequenceNumber; + transactionMachineId = _transactionMachineId; + transactionArbitrator = _transactionArbitrator; + abortId = new Pair<Long, Long>(transactionMachineId, transactionClientLocalSequenceNumber); + } + + public Abort(Slot slot, long _transactionClientLocalSequenceNumber, long _transactionSequenceNumber, long _sequenceNumber , long _transactionMachineId, long _transactionArbitrator) { super(slot); - seqnumtrans = _seqnumtrans; - machineid = _machineid; - transarbitrator = _transarbitrator; + transactionClientLocalSequenceNumber = _transactionClientLocalSequenceNumber; + transactionSequenceNumber = _transactionSequenceNumber; + sequenceNumber = _sequenceNumber; + transactionMachineId = _transactionMachineId; + transactionArbitrator = _transactionArbitrator; + abortId = new Pair<Long, Long>(transactionMachineId, transactionClientLocalSequenceNumber); + } + + public Pair<Long, Long> getAbortId() { + return abortId; + } + + public long getTransactionMachineId() { + return transactionMachineId; + } + + public long getTransactionSequenceNumber() { + return transactionSequenceNumber; + } + + public long getTransactionClientLocalSequenceNumber() { + return transactionClientLocalSequenceNumber; + } + + public void setSlot(Slot s) { + parentslot = s; } - public long getMachineID() { - return machineid; + public long getSequenceNumber() { + return sequenceNumber; } - public long getTransSequenceNumber() { - return seqnumtrans; + public void setSequenceNumber(long _sequenceNumber) { + sequenceNumber = _sequenceNumber; } - public long getTransArbitrator() { - return transarbitrator; + public long getTransactionArbitrator() { + return transactionArbitrator; } static Entry decode(Slot slot, ByteBuffer bb) { - long seqnumtrans = bb.getLong(); - long machineid = bb.getLong(); - long transarbitrator = bb.getLong(); - return new Abort(slot, seqnumtrans, machineid, transarbitrator); + long transactionClientLocalSequenceNumber = bb.getLong(); + long transactionSequenceNumber = bb.getLong(); + long sequenceNumber = bb.getLong(); + long transactionMachineId = bb.getLong(); + long transactionArbitrator = bb.getLong(); + return new Abort(slot, transactionClientLocalSequenceNumber, transactionSequenceNumber, sequenceNumber, transactionMachineId, transactionArbitrator); } public void encode(ByteBuffer bb) { bb.put(Entry.TypeAbort); - bb.putLong(seqnumtrans); - bb.putLong(machineid); - bb.putLong(transarbitrator); + bb.putLong(transactionClientLocalSequenceNumber); + bb.putLong(transactionSequenceNumber); + bb.putLong(sequenceNumber); + bb.putLong(transactionMachineId); + bb.putLong(transactionArbitrator); } public int getSize() { - return (3 * Long.BYTES) + Byte.BYTES; + return (4 * Long.BYTES) + Byte.BYTES; } public byte getType() { @@ -58,6 +98,6 @@ class Abort extends Entry { } public Entry getCopy(Slot s) { - return new Abort(s, seqnumtrans, machineid, transarbitrator); + return new Abort(s, transactionClientLocalSequenceNumber, transactionSequenceNumber, sequenceNumber, transactionMachineId, transactionArbitrator); } } \ No newline at end of file diff --git a/version2/src/java/iotcloud/CloudComm.java b/version2/src/java/iotcloud/CloudComm.java index 6f548af..ac499e5 100644 --- a/version2/src/java/iotcloud/CloudComm.java +++ b/version2/src/java/iotcloud/CloudComm.java @@ -1,4 +1,5 @@ package iotcloud; + import java.io.*; import java.net.*; import java.util.Arrays; @@ -15,7 +16,6 @@ import java.security.SecureRandom; class CloudComm { - String hostname; String baseurl; Cipher encryptCipher; Cipher decryptCipher; @@ -36,9 +36,8 @@ class CloudComm { /** * Constructor for actual use. Takes in the url and password. */ - CloudComm(Table _table, String _hostname, String _baseurl, String _password) { + CloudComm(Table _table, String _baseurl, String _password) { this.table = _table; - this.hostname = _hostname; this.baseurl = _baseurl; this.password = _password; this.random = new SecureRandom(); @@ -111,24 +110,51 @@ class CloudComm { salt = saltTmp; } catch (Exception e) { - throw new ServerException("Failed setting salt"); + throw new ServerException("Failed setting salt", ServerException.TypeConnectTimeout); } initCrypt(); } - private void getSalt() throws Exception { - URL url = new URL(baseurl + "?req=getsalt"); - URLConnection con = url.openConnection(); - HttpURLConnection http = (HttpURLConnection) con; - http.setRequestMethod("POST"); - http.connect(); - - InputStream is = http.getInputStream(); - DataInputStream dis = new DataInputStream(is); - int salt_length = dis.readInt(); - byte [] tmp = new byte[salt_length]; - dis.readFully(tmp); - salt = tmp; + private void getSalt() throws ServerException { + URL url = null; + URLConnection con = null; + HttpURLConnection http = null; + + try { + url = new URL(baseurl + "?req=getsalt"); + } catch (Exception e) { + e.printStackTrace(); + throw new Error("getSlot failed"); + } + try { + + con = url.openConnection(); + http = (HttpURLConnection) con; + http.setRequestMethod("POST"); + http.setConnectTimeout(TIMEOUT_MILLIS); + http.setReadTimeout(TIMEOUT_MILLIS); + http.connect(); + } catch (SocketTimeoutException e) { + throw new ServerException("getSalt failed", ServerException.TypeConnectTimeout); + } catch (Exception e) { + e.printStackTrace(); + throw new Error("getSlot failed"); + } + + try { + InputStream is = http.getInputStream(); + DataInputStream dis = new DataInputStream(is); + int salt_length = dis.readInt(); + byte [] tmp = new byte[salt_length]; + dis.readFully(tmp); + salt = tmp; + } catch (SocketTimeoutException e) { + throw new ServerException("getSalt failed", ServerException.TypeInputTimeout); + } catch (Exception e) { + e.printStackTrace(); + throw new Error("getSlot failed"); + } + } /* @@ -136,8 +162,11 @@ class CloudComm { * On failure, the server will send slots with newer sequence * numbers. */ - Slot[] putSlot(Slot slot, int max) throws ServerException { + URL url = null; + URLConnection con = null; + HttpURLConnection http = null; + try { if (salt == null) { getSalt(); @@ -148,23 +177,30 @@ class CloudComm { byte[] bytes = slot.encode(mac); bytes = encryptCipher.doFinal(bytes); - - URL url = buildRequest(true, sequencenumber, max); - URLConnection con = url.openConnection(); - HttpURLConnection http = (HttpURLConnection) con; + url = buildRequest(true, sequencenumber, max); + con = url.openConnection(); + http = (HttpURLConnection) con; http.setRequestMethod("POST"); http.setFixedLengthStreamingMode(bytes.length); http.setDoOutput(true); http.setConnectTimeout(TIMEOUT_MILLIS); - // http.setReadTimeout(TIMEOUT_MILLIS); + http.setReadTimeout(TIMEOUT_MILLIS); http.connect(); OutputStream os = http.getOutputStream(); os.write(bytes); os.flush(); + } catch (SocketTimeoutException e) { + throw new ServerException("putSlot failed", ServerException.TypeConnectTimeout); + } catch (Exception e) { + e.printStackTrace(); + throw new Error("putSlot failed"); + } + + try { InputStream is = http.getInputStream(); DataInputStream dis = new DataInputStream(is); byte[] resptype = new byte[7]; @@ -177,8 +213,11 @@ class CloudComm { else throw new Error("Bad response to putslot"); + } catch (SocketTimeoutException e) { + throw new ServerException("putSlot failed", ServerException.TypeInputTimeout); } catch (Exception e) { - throw new ServerException("putSlot failed"); + e.printStackTrace(); + throw new Error("putSlot failed"); } } @@ -188,47 +227,46 @@ class CloudComm { * sequencenumber or newer. */ Slot[] getSlots(long sequencenumber) throws ServerException { + URL url = null; + URLConnection con = null; + HttpURLConnection http = null; + try { if (salt == null) { getSalt(); initCrypt(); } - URL url = buildRequest(false, sequencenumber, 0); - URLConnection con = url.openConnection(); - HttpURLConnection http = (HttpURLConnection) con; + url = buildRequest(false, sequencenumber, 0); + con = url.openConnection(); + http = (HttpURLConnection) con; http.setRequestMethod("POST"); http.setConnectTimeout(TIMEOUT_MILLIS); - // http.setReadTimeout(TIMEOUT_MILLIS); + http.setReadTimeout(TIMEOUT_MILLIS); + + http.connect(); + } catch (ServerException e) { + throw new ServerException("getSlots failed", ServerException.TypeConnectTimeout); + } catch (Exception e) { + e.printStackTrace(); + throw new Error("getSlots failed"); + } + + try { InputStream is = http.getInputStream(); DataInputStream dis = new DataInputStream(is); - - int responsecode = http.getResponseCode(); - if (responsecode != HttpURLConnection.HTTP_OK) { - // TODO: Remove this print - // System.out.println("Code: " + responsecode); - throw new ServerException("getSlots failed"); - } - byte[] resptype = new byte[7]; dis.readFully(resptype); if (!Arrays.equals(resptype, "getslot".getBytes())) throw new Error("Bad Response: " + new String(resptype)); else return processSlots(dis); + } catch (ServerException e) { + throw new ServerException("getSlots failed", ServerException.TypeInputTimeout); } catch (Exception e) { - // e.printStackTrace(); - throw new ServerException("getSlots failed"); - } - } - - public boolean hasConnection() { - try { - InetAddress address = InetAddress.getByName(hostname); - return address.isReachable(TIMEOUT_MILLIS); - } catch (Exception e) { - return false; + e.printStackTrace(); + throw new Error("getSlots failed"); } } @@ -254,8 +292,4 @@ class CloudComm { dis.close(); return slots; } - - - - } diff --git a/version2/src/java/iotcloud/Commit.java b/version2/src/java/iotcloud/Commit.java index fb52e67..c43fcfd 100644 --- a/version2/src/java/iotcloud/Commit.java +++ b/version2/src/java/iotcloud/Commit.java @@ -1,125 +1,246 @@ package iotcloud; -import java.nio.ByteBuffer; +import java.util.Map; +import java.util.HashMap; import java.util.Set; import java.util.HashSet; -import java.util.Iterator; +import java.nio.ByteBuffer; + +class Commit { + + private Map<Integer, CommitPart> parts = null; + private Set<Integer> missingParts = null; + private boolean isComplete = false; + private boolean hasLastPart = false; + private Set<KeyValue> keyValueUpdateSet = null; + private boolean isDead = false; + private long sequenceNumber = -1; + private long machineId = -1; + private long transactionSequenceNumber = -1; + + private Set<IoTString> liveKeys = null; + + public Commit() { + parts = new HashMap<Integer, CommitPart>(); + keyValueUpdateSet = new HashSet<KeyValue>(); + + liveKeys = new HashSet<IoTString>(); + } + + public Commit(long _sequenceNumber, long _machineId, long _transactionSequenceNumber) { + parts = new HashMap<Integer, CommitPart>(); + keyValueUpdateSet = new HashSet<KeyValue>(); + + liveKeys = new HashSet<IoTString>(); + + sequenceNumber = _sequenceNumber; + machineId = _machineId; + transactionSequenceNumber = _transactionSequenceNumber; + isComplete = true; + } + -/** - * This Entry records the commit of a transaction. - * @author Ali Younis <ayounis@uci.edu> - * @version 1.0 - */ + public void addPartDecode(CommitPart newPart) { + if (isDead) { + // If dead then just kill this part and move on + newPart.setDead(); + return; + } -class Commit extends Entry { - private long seqnumtrans; - private long seqnumcommit; - private long transarbitrator; - - private Set<KeyValue> keyValueUpdateSet = null; + CommitPart previoslySeenPart = parts.put(newPart.getPartNumber(), newPart); + if (previoslySeenPart != null) { + // Set dead the old one since the new one is a rescued version of this part + previoslySeenPart.setDead(); + } else if (newPart.isLastPart()) { + missingParts = new HashSet<Integer>(); + hasLastPart = true; - public Commit(Slot slot, long _seqnumtrans, long _seqnumcommit, long _transarbitrator, Set<KeyValue> _keyValueUpdateSet) { - super(slot); - seqnumtrans = _seqnumtrans; - seqnumcommit = _seqnumcommit; - transarbitrator = _transarbitrator; + for (int i = 0; i < newPart.getPartNumber(); i++) { + if (parts.get(i) == null) { + missingParts.add(i); + } + } + } - keyValueUpdateSet = new HashSet<KeyValue>(); + if (!isComplete && hasLastPart) { - for (KeyValue kv : _keyValueUpdateSet) { - KeyValue kvCopy = kv.getCopy(); - keyValueUpdateSet.add(kvCopy); - } - } + // We have seen this part so remove it from the set of missing parts + missingParts.remove(newPart.getPartNumber()); - public long getTransSequenceNumber() { - return seqnumtrans; - } - public long getSequenceNumber() { - return seqnumcommit; - } + // Check if all the parts have been seen + if (missingParts.size() == 0) { - public long getTransArbitrator() { - return transarbitrator; - } + // We have all the parts + isComplete = true; - public Set<KeyValue> getkeyValueUpdateSet() { - return keyValueUpdateSet; - } + // Decode all the parts and create the key value guard and update sets + decodeCommitData(); - public byte getType() { - return Entry.TypeCommit; - } + // Get the sequence number and arbitrator of this transaction + sequenceNumber = parts.get(0).getSequenceNumber(); + machineId = parts.get(0).getMachineId(); + transactionSequenceNumber = parts.get(0).getTransactionSequenceNumber(); + } + } + } - public int getSize() { - int size = 3 * Long.BYTES + Byte.BYTES; // seq id, entry type - size += Integer.BYTES; // number of KV's + public long getSequenceNumber() { + return sequenceNumber; + } - // Size of each KV - for (KeyValue kv : keyValueUpdateSet) { - size += kv.getSize(); - } + public long getTransactionSequenceNumber() { + return transactionSequenceNumber; + } - return size; - } - static Entry decode(Slot slot, ByteBuffer bb) { - long seqnumtrans = bb.getLong(); - long seqnumcommit = bb.getLong(); - long transarbitrator = bb.getLong(); - int numberOfKeys = bb.getInt(); + public Map<Integer, CommitPart> getParts() { + return parts; + } - Set<KeyValue> kvSet = new HashSet<KeyValue>(); - for (int i = 0; i < numberOfKeys; i++) { - KeyValue kv = KeyValue.decode(bb); - kvSet.add(kv); - } + public void addKV(KeyValue kv) { + keyValueUpdateSet.add(kv); + liveKeys.add(kv.getKey()); + } - return new Commit(slot, seqnumtrans, seqnumcommit, transarbitrator, kvSet); - } + public void invalidateKey(IoTString key) { + liveKeys.remove(key); - public void encode(ByteBuffer bb) { - bb.put(Entry.TypeCommit); - bb.putLong(seqnumtrans); - bb.putLong(seqnumcommit); - bb.putLong(transarbitrator); + if (liveKeys.size() == 0) { + setDead(); + } + } - bb.putInt(keyValueUpdateSet.size()); + public Set<KeyValue> getKeyValueUpdateSet() { + return keyValueUpdateSet; + } - for (KeyValue kv : keyValueUpdateSet) { - kv.encode(bb); - } - } + public int getNumberOfParts() { + return parts.size(); + } - public Entry getCopy(Slot s) { - return new Commit(s, seqnumtrans, seqnumcommit, transarbitrator, keyValueUpdateSet); - } + public long getMachineId() { + return machineId; + } - public Set<KeyValue> updateLiveKeys(Set<KeyValue> kvSet) { + public boolean isComplete() { + return isComplete; + } + + public boolean isLive() { + return !isDead; + } + + public void setDead() { + if (isDead) { + // Already dead + return; + } - if (!this.isLive()) - return new HashSet<KeyValue>(); + // Set dead + isDead = true; + + // Make all the parts of this transaction dead + for (Integer partNumber : parts.keySet()) { + CommitPart part = parts.get(partNumber); + part.setDead(); + } + } + + public CommitPart getPart(int index) { + return parts.get(index); + } + + public void createCommitParts() { + + parts.clear(); + + // Convert to bytes + byte[] byteData = convertDataToBytes(); + + + int commitPartCount = 0; + int currentPosition = 0; + int remaining = byteData.length; - Set<KeyValue> toDelete = new HashSet<KeyValue>(); + while (remaining > 0) { - for (KeyValue kv1 : kvSet) { - for (Iterator<KeyValue> i = keyValueUpdateSet.iterator(); i.hasNext();) { - KeyValue kv2 = i.next(); + Boolean isLastPart = false; + // determine how much to copy + int copySize = CommitPart.MAX_NON_HEADER_SIZE; + if (remaining <= CommitPart.MAX_NON_HEADER_SIZE) { + copySize = remaining; + isLastPart = true; // last bit of data so last part + } + + // Copy to a smaller version + byte[] partData = new byte[copySize]; + System.arraycopy(byteData, currentPosition, partData, 0, copySize); + + CommitPart part = new CommitPart(null, machineId, sequenceNumber, transactionSequenceNumber, commitPartCount, partData, isLastPart); + parts.put(part.getPartNumber(), part); + + // Update position, count and remaining + currentPosition += copySize; + commitPartCount++; + remaining -= copySize; + } + } + + private void decodeCommitData() { - if (kv1.getKey().equals(kv2.getKey())) { - toDelete.add(kv2); - i.remove(); - break; - } - } - } + // Calculate the size of the data section + int dataSize = 0; + for (int i = 0; i < parts.keySet().size(); i++) { + CommitPart tp = parts.get(i); + dataSize += tp.getDataSize(); + } + + byte[] combinedData = new byte[dataSize]; + int currentPosition = 0; + + // Stitch all the data sections together + for (int i = 0; i < parts.keySet().size(); i++) { + CommitPart tp = parts.get(i); + System.arraycopy(tp.getData(), 0, combinedData, currentPosition, tp.getDataSize()); + currentPosition += tp.getDataSize(); + } + + // Decoder Object + ByteBuffer bbDecode = ByteBuffer.wrap(combinedData); + + // Decode how many key value pairs need to be decoded + int numberOfKVUpdates = bbDecode.getInt(); + + // Decode all the updates key values + for (int i = 0; i < numberOfKVUpdates; i++) { + KeyValue kv = (KeyValue)KeyValue.decode(bbDecode); + keyValueUpdateSet.add(kv); + liveKeys.add(kv.getKey()); + } + } + + private byte[] convertDataToBytes() { + + // Calculate the size of the data + int sizeOfData = Integer.BYTES; // Number of Update KV's + for (KeyValue kv : keyValueUpdateSet) { + sizeOfData += kv.getSize(); + } + + // Data handlers and storage + byte[] dataArray = new byte[sizeOfData]; + ByteBuffer bbEncode = ByteBuffer.wrap(dataArray); + + // Encode the size of the updates and guard sets + bbEncode.putInt(keyValueUpdateSet.size()); + + // Encode all the updates + for (KeyValue kv : keyValueUpdateSet) { + kv.encode(bbEncode); + } - if (keyValueUpdateSet.size() == 0) { - this.setDead(); - } - - return toDelete; - } + return bbEncode.array(); + } } \ No newline at end of file diff --git a/version2/src/java/iotcloud/CommitPart.java b/version2/src/java/iotcloud/CommitPart.java new file mode 100644 index 0000000..cea9fbf --- /dev/null +++ b/version2/src/java/iotcloud/CommitPart.java @@ -0,0 +1,128 @@ + + +package iotcloud; + +import java.nio.ByteBuffer; + +class CommitPart extends Entry{ + + // Max size of the part excluding the fixed size header + public static final int MAX_NON_HEADER_SIZE = 512; + + + // Sequence number of the transaction this commit is for, -1 if not a cloud transaction + private long machineId = -1; // Machine Id of the device that made the commit + private long sequenceNumber = -1; // commit sequence number for this arbitrator + private long transactionSequenceNumber = -1; + private int partNumber = -1; // Parts position in the + private Boolean isLastPart = false; + private byte[] data = null; + + private Pair<Long, Integer> partId = null; + private Pair<Long, Long> commitId = null; + + + public CommitPart(Slot s, long _machineId, long _sequenceNumber, long _transactionSequenceNumber, int _partNumber, byte[] _data, Boolean _isLastPart) { + super(s); + machineId = _machineId; + sequenceNumber = _sequenceNumber; + transactionSequenceNumber = _transactionSequenceNumber; + partNumber = _partNumber; + isLastPart = _isLastPart; + data = _data; + + partId = new Pair<Long, Integer>(sequenceNumber, partNumber); + commitId = new Pair<Long, Long>(machineId, sequenceNumber); + } + + public int getSize() { + if (data == null) { + return (3 * Long.BYTES) + (2 * Integer.BYTES) + (2 * Byte.BYTES); + } + return (3 * Long.BYTES) + (2 * Integer.BYTES) + (2 * Byte.BYTES) + data.length; + } + + public void setSlot(Slot s) { + parentslot = s; + } + + public int getPartNumber() { + return partNumber; + } + + public int getDataSize() { + return data.length; + } + + public byte[] getData() { + return data; + } + + public Pair<Long, Integer> getPartId() { + return partId; + } + + public Pair<Long, Long> getCommitId() { + return commitId; + } + + public Boolean isLastPart() { + return isLastPart; + } + + public long getMachineId() { + return machineId; + } + + public long getTransactionSequenceNumber() { + return transactionSequenceNumber; + } + + public long getSequenceNumber() { + return sequenceNumber; + } + + public void setSequenceNumber(long _sequenceNumber) { + sequenceNumber = _sequenceNumber; + } + + static Entry decode(Slot s, ByteBuffer bb) { + long machineId = bb.getLong(); + long sequenceNumber = bb.getLong(); + long transactionSequenceNumber = bb.getLong(); + int partNumber = bb.getInt(); + int dataSize = bb.getInt(); + Boolean isLastPart = bb.get() == 1; + + // Get the data + byte[] data = new byte[dataSize]; + bb.get(data); + + return new CommitPart(s, machineId, sequenceNumber, transactionSequenceNumber, partNumber, data, isLastPart); + } + + public void encode(ByteBuffer bb) { + bb.put(Entry.TypeCommitPart); + bb.putLong(machineId); + bb.putLong(sequenceNumber); + bb.putLong(transactionSequenceNumber); + bb.putInt(partNumber); + bb.putInt(data.length); + + if (isLastPart) { + bb.put((byte)1); + } else { + bb.put((byte)0); + } + + bb.put(data); + } + + public byte getType() { + return Entry.TypeCommitPart; + } + + public Entry getCopy(Slot s) { + return new CommitPart(s, machineId, sequenceNumber, transactionSequenceNumber, partNumber, data, isLastPart); + } +} \ No newline at end of file diff --git a/version2/src/java/iotcloud/Entry.java b/version2/src/java/iotcloud/Entry.java index 8395dec..dd9e75b 100644 --- a/version2/src/java/iotcloud/Entry.java +++ b/version2/src/java/iotcloud/Entry.java @@ -10,10 +10,9 @@ import java.nio.ByteBuffer; abstract class Entry implements Liveness { - - static final byte TypeCommit = 1; + static final byte TypeCommitPart = 1; static final byte TypeAbort = 2; - static final byte TypeTransaction = 3; + static final byte TypeTransactionPart = 3; static final byte TypeNewKey = 4; static final byte TypeLastMessage = 5; static final byte TypeRejectedMessage = 6; @@ -25,7 +24,7 @@ abstract class Entry implements Liveness { superceded by a newer update. */ private boolean islive = true; - private Slot parentslot; + protected Slot parentslot; public Entry(Slot _parentslot) { parentslot = _parentslot; @@ -35,19 +34,18 @@ abstract class Entry implements Liveness { * Static method for decoding byte array into Entry objects. First * byte tells the type of entry. */ - static Entry decode(Slot slot, ByteBuffer bb) { byte type = bb.get(); switch (type) { - case TypeCommit: - return Commit.decode(slot, bb); + case TypeCommitPart: + return CommitPart.decode(slot, bb); case TypeAbort: return Abort.decode(slot, bb); - case TypeTransaction: - return Transaction.decode(slot, bb); + case TypeTransactionPart: + return TransactionPart.decode(slot, bb); case TypeNewKey: return NewKey.decode(slot, bb); @@ -69,16 +67,15 @@ abstract class Entry implements Liveness { /** * Returns true if the Entry object is still live. */ - public boolean isLive() { return islive; } + /** * Flags the entry object as dead. Also decrements the live count * of the parent slot. */ - public void setDead() { if (!islive ) { @@ -92,28 +89,28 @@ abstract class Entry implements Liveness { } } + /** * Serializes the Entry object into the byte buffer. */ - abstract void encode(ByteBuffer bb); + /** * Returns the size in bytes the entry object will take in the byte * array. */ - abstract int getSize(); + /** * Returns a byte encoding the type of the entry object. */ - abstract byte getType(); + /** * Returns a copy of the Entry that can be added to a different slot. */ abstract Entry getCopy(Slot s); - } diff --git a/version2/src/java/iotcloud/LocalComm.java b/version2/src/java/iotcloud/LocalComm.java index 17e3c05..c2eb11b 100644 --- a/version2/src/java/iotcloud/LocalComm.java +++ b/version2/src/java/iotcloud/LocalComm.java @@ -12,12 +12,14 @@ class LocalComm { public byte[] sendDataToLocalDevice(Long deviceId, byte[] data) throws InterruptedException{ System.out.println("Passing Locally"); - if (deviceId == t1.getId()) { - return t1.localCommInput(data); - } else if (deviceId == t2.getId()) { - return t2.localCommInput(data); + if (deviceId == t1.getMachineId()) { + // return t1.localCommInput(data); + } else if (deviceId == t2.getMachineId()) { + // return t2.localCommInput(data); } else { throw new Error("Cannot send to " + deviceId + " using this local comm"); } + + return new byte[0]; } } \ No newline at end of file diff --git a/version2/src/java/iotcloud/NewKey.java b/version2/src/java/iotcloud/NewKey.java index 0970016..3e53a1c 100644 --- a/version2/src/java/iotcloud/NewKey.java +++ b/version2/src/java/iotcloud/NewKey.java @@ -27,6 +27,10 @@ class NewKey extends Entry { return key; } + public void setSlot(Slot s) { + parentslot = s; + } + static Entry decode(Slot slot, ByteBuffer bb) { int keylength = bb.getInt(); byte[] key = new byte[keylength]; diff --git a/version2/src/java/iotcloud/Pair.java b/version2/src/java/iotcloud/Pair.java index 73ed6bd..6352fc1 100644 --- a/version2/src/java/iotcloud/Pair.java +++ b/version2/src/java/iotcloud/Pair.java @@ -1,12 +1,17 @@ package iotcloud; -class Pair<A,B> { +class Pair<A, B> { private A a; private B b; + int hashCode = -1; Pair(A a, B b) { - this.a=a; - this.b=b; + this.a = a; + this.b = b; + + hashCode = 23; + hashCode = hashCode * 31 + a.hashCode(); + hashCode = hashCode * 31 + b.hashCode(); } A getFirst() { @@ -17,7 +22,22 @@ class Pair<A,B> { return b; } + + public int hashCode() { + return hashCode; + } + + public boolean equals(Object o) { + if (o instanceof Pair) { + Pair i = (Pair)o; + if (a.equals(i.getFirst()) && b.equals(i.getSecond())) { + return true; + } + } + return false; + } + public String toString() { - return "<"+a+","+b+">"; + return "<" + a + "," + b + ">"; } } diff --git a/version2/src/java/iotcloud/PendingTransaction.java b/version2/src/java/iotcloud/PendingTransaction.java index 1a14674..ad752b5 100644 --- a/version2/src/java/iotcloud/PendingTransaction.java +++ b/version2/src/java/iotcloud/PendingTransaction.java @@ -6,6 +6,7 @@ import java.util.HashSet; import javax.script.ScriptException; import java.lang.NullPointerException; +import java.nio.ByteBuffer; class PendingTransaction { @@ -13,9 +14,13 @@ class PendingTransaction { private Set<KeyValue> keyValueUpdateSet = null; private Set<KeyValue> keyValueGuardSet = null; private long arbitrator = -1; - private long machineLocalTransSeqNum = -1; + private long clientLocalSequenceNumber = -1; + private long machineId = -1; - public PendingTransaction() { + private int currentDataSize = 0; + + public PendingTransaction(long _machineId) { + machineId = _machineId; keyValueUpdateSet = new HashSet<KeyValue>(); keyValueGuardSet = new HashSet<KeyValue>(); } @@ -41,13 +46,14 @@ class PendingTransaction { // Remove key if we are adding a newer version of the same key if (rmKV != null) { keyValueUpdateSet.remove(rmKV); + currentDataSize -= rmKV.getSize(); } // Add the key to the hash set keyValueUpdateSet.add(newKV); + currentDataSize += newKV.getSize(); } - /** * Add a new key value to the guard set * @@ -55,11 +61,11 @@ class PendingTransaction { public void addKVGuard(KeyValue newKV) { // Add the key to the hash set keyValueGuardSet.add(newKV); + currentDataSize += newKV.getSize(); } /** * Checks if the arbitrator is the same - * */ public boolean checkArbitrator(long arb) { if (arbitrator == -1) { @@ -72,7 +78,6 @@ class PendingTransaction { /** * Get the transaction arbitrator - * */ public long getArbitrator() { return arbitrator; @@ -80,27 +85,28 @@ class PendingTransaction { /** * Get the key value update set - * */ public Set<KeyValue> getKVUpdates() { return keyValueUpdateSet; } - /** - * Get the key value update set - * - */ + * Get the key value update set + */ public Set<KeyValue> getKVGuard() { return keyValueGuardSet; } - public void setMachineLocalTransSeqNum(long _machineLocalTransSeqNum) { - machineLocalTransSeqNum = _machineLocalTransSeqNum; + public void setClientLocalSequenceNumber(long _clientLocalSequenceNumber) { + clientLocalSequenceNumber = _clientLocalSequenceNumber; + } + + public long getClientLocalSequenceNumber() { + return clientLocalSequenceNumber; } - public long getMachineLocalTransSeqNum() { - return machineLocalTransSeqNum; + public long getMachineId() { + return machineId; } public boolean evaluateGuard(Map<IoTString, KeyValue> keyValTableCommitted, Map<IoTString, KeyValue> keyValTableSpeculative, Map<IoTString, KeyValue> keyValTablePendingTransSpeculative) { @@ -135,4 +141,78 @@ class PendingTransaction { } return true; } + + public Transaction createTransaction() { + + Transaction newTransaction = new Transaction(); + int transactionPartCount = 0; + + // Convert all the data into a byte array so we can start partitioning + byte[] byteData = convertDataToBytes(); + + int currentPosition = 0; + int remaining = byteData.length; + + while (remaining > 0) { + + Boolean isLastPart = false; + // determine how much to copy + int copySize = TransactionPart.MAX_NON_HEADER_SIZE; + if (remaining <= TransactionPart.MAX_NON_HEADER_SIZE) { + copySize = remaining; + isLastPart = true; // last bit of data so last part + } + + // Copy to a smaller version + byte[] partData = new byte[copySize]; + System.arraycopy(byteData, currentPosition, partData, 0, copySize); + + TransactionPart part = new TransactionPart(null, machineId, arbitrator, clientLocalSequenceNumber, transactionPartCount, partData, isLastPart); + newTransaction.addPartEncode(part); + + // Update position, count and remaining + currentPosition += copySize; + transactionPartCount++; + remaining -= copySize; + } + + // Add the Guard Conditions + for (KeyValue kv : keyValueGuardSet) { + newTransaction.addGuardKV(kv); + } + + // Add the updates + for (KeyValue kv : keyValueUpdateSet) { + newTransaction.addUpdateKV(kv); + } + + return newTransaction; + } + + private byte[] convertDataToBytes() { + + // Calculate the size of the data + int sizeOfData = 2 * Integer.BYTES; // Number of Update KV's and Guard KV's + sizeOfData += currentDataSize; + + // Data handlers and storage + byte[] dataArray = new byte[sizeOfData]; + ByteBuffer bbEncode = ByteBuffer.wrap(dataArray); + + // Encode the size of the updates and guard sets + bbEncode.putInt(keyValueGuardSet.size()); + bbEncode.putInt(keyValueUpdateSet.size()); + + // Encode all the guard conditions + for (KeyValue kv : keyValueGuardSet) { + kv.encode(bbEncode); + } + + // Encode all the updates + for (KeyValue kv : keyValueUpdateSet) { + kv.encode(bbEncode); + } + + return bbEncode.array(); + } } \ No newline at end of file diff --git a/version2/src/java/iotcloud/ServerException.java b/version2/src/java/iotcloud/ServerException.java index 6d35c43..b09096c 100644 --- a/version2/src/java/iotcloud/ServerException.java +++ b/version2/src/java/iotcloud/ServerException.java @@ -1,8 +1,18 @@ package iotcloud; public class ServerException extends Exception { - - public ServerException(String message) { + + public static final byte TypeConnectTimeout = 1; + public static final byte TypeInputTimeout = 2; + public static final byte TypeIncorrectResponseCode = 2; + private byte type = -1; + + public ServerException(String message, byte _type) { super(message); + type = _type; + } + + public byte getType() { + return type; } } diff --git a/version2/src/java/iotcloud/Table.java b/version2/src/java/iotcloud/Table.java index f6d699b..9764223 100644 --- a/version2/src/java/iotcloud/Table.java +++ b/version2/src/java/iotcloud/Table.java @@ -1,270 +1,193 @@ package iotcloud; -import java.util.HashMap; -import java.util.Map; + import java.util.Iterator; -import java.util.HashSet; +import java.util.Random; import java.util.Arrays; +import java.util.Map; +import java.util.Set; +import java.util.List; import java.util.Vector; -import java.util.Random; -import java.util.Queue; -import java.util.LinkedList; +import java.util.HashMap; +import java.util.HashSet; import java.util.ArrayList; -import java.util.List; -import java.util.Set; -import java.util.Collection; import java.util.Collections; -import java.nio.ByteBuffer; -import java.util.concurrent.Semaphore; - /** - * IoTTable data structure. Provides client inferface. + * IoTTable data structure. Provides client interface. * @author Brian Demsky * @version 1.0 */ final public class Table { - private int numslots; //number of slots stored in buffer - - // machine id -> (sequence number, Slot or LastMessage); records last message by each client - private HashMap<Long, Pair<Long, Liveness> > lastmessagetable = new HashMap<Long, Pair<Long, Liveness> >(); - // machine id -> ... - private HashMap<Long, HashSet<RejectedMessage> > watchlist = new HashMap<Long, HashSet<RejectedMessage> >(); - private Vector<Long> rejectedmessagelist = new Vector<Long>(); - private SlotBuffer buffer; - private CloudComm cloud; - private long sequencenumber; //Largest sequence number a client has received - private long localmachineid; - private TableStatus lastTableStatus; - static final int FREE_SLOTS = 10; //number of slots that should be kept free + + + /* Constants */ + static final int FREE_SLOTS = 10; // Number of slots that should be kept free static final int SKIP_THRESHOLD = 10; - private long liveslotcount = 0; - private int chance; static final double RESIZE_MULTIPLE = 1.2; static final double RESIZE_THRESHOLD = 0.75; static final int REJECTED_THRESHOLD = 5; - private int resizethreshold; - private long lastliveslotseqn; //smallest sequence number with a live entry - private Random random = new Random(); - private long lastUncommittedTransaction = 0; - - private int smallestTableStatusSeen = -1; - private int largestTableStatusSeen = -1; - private int lastSeenPendingTransactionSpeculateIndex = 0; - private int commitSequenceNumber = 0; - private long localTransactionSequenceNumber = 0; - - private PendingTransaction pendingTransBuild = null; // Pending Transaction used in building - private LinkedList<PendingTransaction> pendingTransQueue = null; // Queue of pending transactions - private Map<Long, Map<Long, Commit>> commitMap = null; // List of all the most recent live commits - private Map<Long, Abort> abortMap = null; // Set of the live aborts - private Map<IoTString, Commit> committedMapByKey = null; // Table of committed KV - private Map<IoTString, KeyValue> commitedTable = null; // Table of committed KV - private Map<IoTString, KeyValue> speculativeTable = null; // Table of speculative KV - private Map<Long, Transaction> uncommittedTransactionsMap = null; - private Map<IoTString, Long> arbitratorTable = null; // Table of arbitrators - private Map<IoTString, NewKey> newKeyTable = null; // Table of speculative KV - private Map<Long, Map<Long, Commit>> newCommitMap = null; // Map of all the new commits - private Map<Long, Long> lastCommitSeenSeqNumMap = null; // sequence number of the last commit that was seen grouped by arbitrator - private Map<Long, Long> lastCommitSeenTransSeqNumMap = null; // transaction sequence number of the last commit that was seen grouped by arbitrator - private Map<Long, Long> lastAbortSeenSeqNumMap = null; // sequence number of the last abort that was seen grouped by arbitrator - private Map<IoTString, KeyValue> pendingTransSpeculativeTable = null; - private List<Commit> pendingCommitsList = null; - private List<Commit> pendingCommitsToDelete = null; - private Map<Long, LocalComm> localCommunicationChannels; - private Map<Long, TransactionStatus> transactionStatusMap = null; - private Map<Long, TransactionStatus> transactionStatusNotSentMap = null; - - private Semaphore mutex = null; - - - - public Table(String hostname, String baseurl, String password, long _localmachineid) { - localmachineid = _localmachineid; - buffer = new SlotBuffer(); - numslots = buffer.capacity(); - setResizeThreshold(); - sequencenumber = 0; - cloud = new CloudComm(this, hostname, baseurl, password); - lastliveslotseqn = 1; - setupDataStructs(); + + /* Helper Objects */ + private SlotBuffer buffer = null; + private CloudComm cloud = null; + private Random random = null; + private TableStatus liveTableStatus = null; + private PendingTransaction pendingTransactionBuilder = null; // Pending Transaction used in building a Pending Transaction + private Transaction lastPendingTransactionSpeculatedOn = null; // Last transaction that was speculated on from the pending transaction + private Transaction firstPendingTransaction = null; // first transaction in the pending transaction list + + /* Variables */ + private int numberOfSlots = 0; // Number of slots stored in buffer + private int bufferResizeThreshold = 0; // Threshold on the number of live slots before a resize is needed + private long liveSlotCount = 0; // Number of currently live slots + private long oldestLiveSlotSequenceNumver = 0; // Smallest sequence number of the slot with a live entry + private long localMachineId = 0; // Machine ID of this client device + private long sequenceNumber = 0; // Largest sequence number a client has received + private int smallestTableStatusSeen = -1; // Smallest Table Status that was seen in the latest slots sent from the server + private int largestTableStatusSeen = -1; // Largest Table Status that was seen in the latest slots sent from the server + private long localTransactionSequenceNumber = 0; // Local sequence number counter for transactions + private long lastTransactionSequenceNumberSpeculatedOn = -1; // the last transaction that was speculated on + private long oldestTransactionSequenceNumberSpeculatedOn = -1; // the oldest transaction that was speculated on + private long localCommitSequenceNumber = 0; + + /* Data Structures */ + private Map<IoTString, KeyValue> committedKeyValueTable = null; // Table of committed key value pairs + private Map<IoTString, KeyValue> speculatedKeyValueTable = null; // Table of speculated key value pairs, if there is a speculative value + private Map<IoTString, KeyValue> pendingTransactionSpeculatedKeyValueTable = null; // Table of speculated key value pairs, if there is a speculative value from the pending transactions + + private Map<IoTString, NewKey> liveNewKeyTable = null; // Table of live new keys + private HashMap<Long, Pair<Long, Liveness>> lastMessageTable = null; // Last message sent by a client machine id -> (Seq Num, Slot or LastMessage); + private HashMap<Long, HashSet<RejectedMessage>> rejectedMessageWatchListTable = null; // Table of machine Ids and the set of rejected messages they have not seen yet + private Map<IoTString, Long> arbitratorTable = null; // Table of keys and their arbitrators + private Map<Pair<Long, Long>, Abort> liveAbortTable = null; // Table live abort messages + private Map<Long, Map<Pair<Long, Integer>, TransactionPart>> newTransactionParts = null; // transaction parts that are seen in this latest round of slots from the server + private Map<Long, Map<Pair<Long, Integer>, CommitPart>> newCommitParts = null; // commit parts that are seen in this latest round of slots from the server + private Map<Long, Long> lastArbitratedTransactionNumberByArbitratorTable = null; // Last transaction sequence number that an arbitrator arbitrated on + private Map<Long, Transaction> liveTransactionBySequenceNumberTable = null; // live transaction grouped by the sequence number + private Map<Pair<Long, Long>, Transaction> liveTransactionByTransactionIdTable = null; // live transaction grouped by the transaction ID + private Map<Long, Map<Long, Commit>> liveCommitsTable = null; + private Map<IoTString, Commit> liveCommitsByKeyTable = null; + private Map<Long, Long> lastCommitSeenSequenceNumberByArbitratorTable = null; + private Vector<Long> rejectedSlotList = null; // List of rejected slots that have yet to be sent to the server + + private List<Transaction> pendingTransactionQueue = null; + private List<Entry> pendingSendArbitrationEntries = null; + private List<Entry> pendingSendArbitrationEntriesToDelete = null; + private Map<Transaction, List<Integer>> transactionPartsSent = null; + private Map<Long, TransactionStatus> outstandingTransactionStatus = null; + + + + + + public Table(String baseurl, String password, long _localMachineId) { + localMachineId = _localMachineId; + cloud = new CloudComm(this, baseurl, password); + + init(); } - public Table(CloudComm _cloud, long _localmachineid) { - localmachineid = _localmachineid; - buffer = new SlotBuffer(); - numslots = buffer.capacity(); - setResizeThreshold(); - sequencenumber = 0; + public Table(CloudComm _cloud, long _localMachineId) { + localMachineId = _localMachineId; cloud = _cloud; - setupDataStructs(); + init(); } - private void setupDataStructs() { - pendingTransQueue = new LinkedList<PendingTransaction>(); - commitMap = new HashMap<Long, Map<Long, Commit>>(); - abortMap = new HashMap<Long, Abort>(); - committedMapByKey = new HashMap<IoTString, Commit>(); - commitedTable = new HashMap<IoTString, KeyValue>(); - speculativeTable = new HashMap<IoTString, KeyValue>(); - uncommittedTransactionsMap = new HashMap<Long, Transaction>(); + /** + * Init all the stuff needed for for table usage + */ + private void init() { + + // Init helper objects + random = new Random(); + buffer = new SlotBuffer(); + + // Set Variables + oldestLiveSlotSequenceNumver = 1; + + // init data structs + committedKeyValueTable = new HashMap<IoTString, KeyValue>(); + speculatedKeyValueTable = new HashMap<IoTString, KeyValue>(); + pendingTransactionSpeculatedKeyValueTable = new HashMap<IoTString, KeyValue>(); + liveNewKeyTable = new HashMap<IoTString, NewKey>(); + lastMessageTable = new HashMap<Long, Pair<Long, Liveness>>(); + rejectedMessageWatchListTable = new HashMap<Long, HashSet<RejectedMessage>>(); arbitratorTable = new HashMap<IoTString, Long>(); - newKeyTable = new HashMap<IoTString, NewKey>(); - newCommitMap = new HashMap<Long, Map<Long, Commit>>(); - lastCommitSeenSeqNumMap = new HashMap<Long, Long>(); - lastCommitSeenTransSeqNumMap = new HashMap<Long, Long>(); - lastAbortSeenSeqNumMap = new HashMap<Long, Long>(); - pendingTransSpeculativeTable = new HashMap<IoTString, KeyValue>(); - pendingCommitsList = new LinkedList<Commit>(); - pendingCommitsToDelete = new LinkedList<Commit>(); - localCommunicationChannels = new HashMap<Long, LocalComm>(); - transactionStatusMap = new HashMap<Long, TransactionStatus>(); - transactionStatusNotSentMap = new HashMap<Long, TransactionStatus>(); - mutex = new Semaphore(1); + liveAbortTable = new HashMap<Pair<Long, Long>, Abort>(); + newTransactionParts = new HashMap<Long, Map<Pair<Long, Integer>, TransactionPart>>(); + newCommitParts = new HashMap<Long, Map<Pair<Long, Integer>, CommitPart>>(); + lastArbitratedTransactionNumberByArbitratorTable = new HashMap<Long, Long>(); + liveTransactionBySequenceNumberTable = new HashMap<Long, Transaction>(); + liveTransactionByTransactionIdTable = new HashMap<Pair<Long, Long>, Transaction>(); + liveCommitsTable = new HashMap<Long, Map<Long, Commit>>(); + liveCommitsByKeyTable = new HashMap<IoTString, Commit>(); + lastCommitSeenSequenceNumberByArbitratorTable = new HashMap<Long, Long>(); + rejectedSlotList = new Vector<Long>(); + pendingTransactionQueue = new ArrayList<Transaction>(); + pendingSendArbitrationEntries = new ArrayList<Entry>(); + pendingSendArbitrationEntriesToDelete = new ArrayList<Entry>(); + transactionPartsSent = new HashMap<Transaction, List<Integer>>(); + outstandingTransactionStatus = new HashMap<Long, TransactionStatus>(); + + // Other init stuff + numberOfSlots = buffer.capacity(); + setResizeThreshold(); } - public void initTable() throws ServerException { - cloud.setSalt();//Set the salt - Slot s = new Slot(this, 1, localmachineid); - TableStatus status = new TableStatus(s, numslots); + /** + * Initialize the table by inserting a table status as the first entry into the table status + * also initialize the crypto stuff. + */ + public synchronized void initTable() throws ServerException { + cloud.setSalt(); //Set the salt + + // Create the first insertion into the block chain which is the table status + Slot s = new Slot(this, 1, localMachineId); + TableStatus status = new TableStatus(s, numberOfSlots); s.addEntry(status); - Slot[] array = cloud.putSlot(s, numslots); + Slot[] array = cloud.putSlot(s, numberOfSlots); + if (array == null) { array = new Slot[] {s}; - /* update data structure */ - validateandupdate(array, true); + // update local block chain + validateAndUpdate(array, true); } else { throw new Error("Error on initialization"); } } - public void rebuild() throws ServerException, InterruptedException { - mutex.acquire(); - Slot[] newslots = cloud.getSlots(sequencenumber + 1); - validateandupdate(newslots, true); - mutex.release(); - } - - // TODO: delete method - public void printSlots() { - long o = buffer.getOldestSeqNum(); - long n = buffer.getNewestSeqNum(); - - int[] types = new int[10]; - - int num = 0; - - int livec = 0; - int deadc = 0; - for (long i = o; i < (n + 1); i++) { - Slot s = buffer.getSlot(i); - - Vector<Entry> entries = s.getEntries(); - - for (Entry e : entries) { - if (e.isLive()) { - int type = e.getType(); - types[type] = types[type] + 1; - num++; - livec++; - } else { - deadc++; - } - } - } - - for (int i = 0; i < 10; i++) { - System.out.println(i + " " + types[i]); - } - System.out.println("Live count: " + livec); - System.out.println("Dead count: " + deadc); - System.out.println("Old: " + o); - System.out.println("New: " + n); - System.out.println("Size: " + buffer.size()); - System.out.println("Commits Key Map: " + commitedTable.size()); - // System.out.println("Commits Live Map: " + commitMap.size()); - System.out.println("Pending: " + pendingTransQueue.size()); - - // List<IoTString> strList = new ArrayList<IoTString>(); - // for (int i = 0; i < 100; i++) { - // String keyA = "a" + i; - // String keyB = "b" + i; - // String keyC = "c" + i; - // String keyD = "d" + i; - - // IoTString iKeyA = new IoTString(keyA); - // IoTString iKeyB = new IoTString(keyB); - // IoTString iKeyC = new IoTString(keyC); - // IoTString iKeyD = new IoTString(keyD); - - // strList.add(iKeyA); - // strList.add(iKeyB); - // strList.add(iKeyC); - // strList.add(iKeyD); - // } - - - // for (Long l : commitMap.keySet()) { - // for (Long l2 : commitMap.get(l).keySet()) { - // for (KeyValue kv : commitMap.get(l).get(l2).getkeyValueUpdateSet()) { - // strList.remove(kv.getKey()); - // System.out.print(kv.getKey() + " "); - // } - // } - // } - - // System.out.println(); - // System.out.println(); - - // for (IoTString s : strList) { - // System.out.print(s + " "); - // } - // System.out.println(); - // System.out.println(strList.size()); - } - - public long getId() { - return localmachineid; - } - - public boolean hasConnection() { - return cloud.hasConnection(); + /** + * Rebuild the table from scratch by pulling the latest block chain from the server. + */ + public synchronized void rebuild() throws ServerException { + // Just pull the latest slots from the server + Slot[] newslots = cloud.getSlots(sequenceNumber + 1); + validateAndUpdate(newslots, true); } - public String toString() { - String retString = " Committed Table: \n"; - retString += "---------------------------\n"; - retString += commitedTable.toString(); + // public String toString() { + // String retString = " Committed Table: \n"; + // retString += "---------------------------\n"; + // retString += commitedTable.toString(); - retString += "\n\n"; + // retString += "\n\n"; - retString += " Speculative Table: \n"; - retString += "---------------------------\n"; - retString += speculativeTable.toString(); - - return retString; - } - - public void addLocalComm(long machineId, LocalComm lc) { - localCommunicationChannels.put(machineId, lc); - } - public Long getArbitrator(IoTString key) throws InterruptedException { + // retString += " Speculative Table: \n"; + // retString += "---------------------------\n"; + // retString += speculativeTable.toString(); - mutex.acquire(); - Long arb = arbitratorTable.get(key); - mutex.release(); + // return retString; + // } - return arb; + public synchronized Long getArbitrator(IoTString key) { + return arbitratorTable.get(key); } - public IoTString getCommitted(IoTString key) throws InterruptedException { - - mutex.acquire(); - KeyValue kv = commitedTable.get(key); - mutex.release(); - + public synchronized IoTString getCommitted(IoTString key) { + KeyValue kv = committedKeyValueTable.get(key); if (kv != null) { return kv.getValue(); @@ -273,21 +196,16 @@ final public class Table { } } - public IoTString getSpeculative(IoTString key) throws InterruptedException { - - mutex.acquire(); - - KeyValue kv = pendingTransSpeculativeTable.get(key); + public synchronized IoTString getSpeculative(IoTString key) { + KeyValue kv = pendingTransactionSpeculatedKeyValueTable.get(key); if (kv == null) { - kv = speculativeTable.get(key); + kv = speculatedKeyValueTable.get(key); } if (kv == null) { - kv = commitedTable.get(key); + kv = committedKeyValueTable.get(key); } - mutex.release(); - if (kv != null) { return kv.getValue(); @@ -296,677 +214,419 @@ final public class Table { } } - public IoTString getCommittedAtomic(IoTString key) throws InterruptedException { - - mutex.acquire(); - - KeyValue kv = commitedTable.get(key); + public synchronized IoTString getCommittedAtomic(IoTString key) { + KeyValue kv = committedKeyValueTable.get(key); if (arbitratorTable.get(key) == null) { throw new Error("Key not Found."); } // Make sure new key value pair matches the current arbitrator - if (!pendingTransBuild.checkArbitrator(arbitratorTable.get(key))) { + if (!pendingTransactionBuilder.checkArbitrator(arbitratorTable.get(key))) { // TODO: Maybe not throw en error throw new Error("Not all Key Values Match Arbitrator."); } - mutex.release(); - if (kv != null) { - pendingTransBuild.addKVGuard(new KeyValue(key, kv.getValue())); + pendingTransactionBuilder.addKVGuard(new KeyValue(key, kv.getValue())); return kv.getValue(); } else { - pendingTransBuild.addKVGuard(new KeyValue(key, null)); + pendingTransactionBuilder.addKVGuard(new KeyValue(key, null)); return null; } } - public IoTString getSpeculativeAtomic(IoTString key) throws InterruptedException { - - mutex.acquire(); - + public synchronized IoTString getSpeculativeAtomic(IoTString key) { if (arbitratorTable.get(key) == null) { throw new Error("Key not Found."); } // Make sure new key value pair matches the current arbitrator - if (!pendingTransBuild.checkArbitrator(arbitratorTable.get(key))) { + if (!pendingTransactionBuilder.checkArbitrator(arbitratorTable.get(key))) { // TODO: Maybe not throw en error throw new Error("Not all Key Values Match Arbitrator."); } - KeyValue kv = pendingTransSpeculativeTable.get(key); + KeyValue kv = pendingTransactionSpeculatedKeyValueTable.get(key); if (kv == null) { - kv = speculativeTable.get(key); + kv = speculatedKeyValueTable.get(key); } if (kv == null) { - kv = commitedTable.get(key); + kv = committedKeyValueTable.get(key); } - mutex.release(); - if (kv != null) { - pendingTransBuild.addKVGuard(new KeyValue(key, kv.getValue())); + pendingTransactionBuilder.addKVGuard(new KeyValue(key, kv.getValue())); return kv.getValue(); } else { - pendingTransBuild.addKVGuard(new KeyValue(key, null)); + pendingTransactionBuilder.addKVGuard(new KeyValue(key, null)); return null; } } - public Pair<Boolean, Boolean> update() throws InterruptedException { - - mutex.acquire(); - - boolean gotLatestFromServer = false; - boolean didSendLocal = false; - + public synchronized void update() { try { - Slot[] newslots = cloud.getSlots(sequencenumber + 1); - validateandupdate(newslots, false); - gotLatestFromServer = true; - - if (!pendingTransQueue.isEmpty()) { - - // We have a pending transaction so do full insertion - processPendingTrans(); - } else { - - // We dont have a pending transaction so do minimal effort - updateWithNotPendingTrans(); - } - - didSendLocal = true; - - + Slot[] newSlots = cloud.getSlots(sequenceNumber + 1); + validateAndUpdate(newSlots, false); + sendToServer(null); } catch (Exception e) { - // could not update so do nothing + e.printStackTrace(); } - - - mutex.release(); - return new Pair<Boolean, Boolean>(gotLatestFromServer, didSendLocal); } - public Boolean updateFromLocal(long arb) throws InterruptedException { - LocalComm lc = localCommunicationChannels.get(arb); - if (lc == null) { - // Cant talk directly to arbitrator so cant do anything - return false; - } - - byte[] array = new byte[Long.BYTES ]; - ByteBuffer bbEncode = ByteBuffer.wrap(array); - Long lastSeenCommit = lastCommitSeenSeqNumMap.get(arb); - if (lastSeenCommit != null) { - bbEncode.putLong(lastSeenCommit); - } else { - bbEncode.putLong(0); - } - - mutex.acquire(); - byte[] data = lc.sendDataToLocalDevice(arb, bbEncode.array()); - - // Decode the data - ByteBuffer bbDecode = ByteBuffer.wrap(data); - boolean didCommit = bbDecode.get() == 1; - int numberOfCommites = bbDecode.getInt(); - - List<Commit> newCommits = new LinkedList<Commit>(); - for (int i = 0; i < numberOfCommites; i++ ) { - bbDecode.get(); - Commit com = (Commit)Commit.decode(null, bbDecode); - newCommits.add(com); - } + public synchronized boolean createNewKey(IoTString keyName, long machineId) { + while (true) { + if (arbitratorTable.get(keyName) != null) { + // There is already an arbitrator + return false; + } - for (Commit commit : newCommits) { - // Prepare to process the commit - processEntry(commit); + NewKey newKey = new NewKey(null, keyName, machineId); + if (sendToServer(newKey)) { + // If successfully inserted + return true; + } } - - boolean didCommitOrSpeculate = proccessAllNewCommits(); - - // Go through all uncommitted transactions and kill the ones that are dead - deleteDeadUncommittedTransactions(); - - // Speculate on key value pairs - didCommitOrSpeculate |= createSpeculativeTable(); - createPendingTransactionSpeculativeTable(didCommitOrSpeculate); - - - mutex.release(); - - return true; } - public void startTransaction() throws InterruptedException { + public void startTransaction() { // Create a new transaction, invalidates any old pending transactions. - pendingTransBuild = new PendingTransaction(); + pendingTransactionBuilder = new PendingTransaction(localMachineId); } - public void addKV(IoTString key, IoTString value) { + public synchronized void addKV(IoTString key, IoTString value) { + // Make sure it is a valid key if (arbitratorTable.get(key) == null) { throw new Error("Key not Found."); } // Make sure new key value pair matches the current arbitrator - if (!pendingTransBuild.checkArbitrator(arbitratorTable.get(key))) { + if (!pendingTransactionBuilder.checkArbitrator(arbitratorTable.get(key))) { // TODO: Maybe not throw en error throw new Error("Not all Key Values Match Arbitrator."); } + // Add the key value to this transaction KeyValue kv = new KeyValue(key, value); - pendingTransBuild.addKV(kv); + pendingTransactionBuilder.addKV(kv); } - public TransactionStatus commitTransaction() throws InterruptedException { - - if (pendingTransBuild.getKVUpdates().size() == 0) { + public synchronized TransactionStatus commitTransaction() { + if (pendingTransactionBuilder.getKVUpdates().size() == 0) { // transaction with no updates will have no effect on the system return new TransactionStatus(TransactionStatus.StatusNoEffect, -1); } - mutex.acquire(); - - TransactionStatus transStatus = null; - - if (pendingTransBuild.getArbitrator() != localmachineid) { - - // set the local sequence number so we can recognize this transaction later - pendingTransBuild.setMachineLocalTransSeqNum(localTransactionSequenceNumber); - localTransactionSequenceNumber++; - - transStatus = new TransactionStatus(TransactionStatus.StatusPending, pendingTransBuild.getArbitrator()); - transactionStatusNotSentMap.put(pendingTransBuild.getMachineLocalTransSeqNum(), transStatus); - - // Add the pending transaction to the queue - pendingTransQueue.add(pendingTransBuild); + // Set the local transaction sequence number and increment + pendingTransactionBuilder.setClientLocalSequenceNumber(localTransactionSequenceNumber); + localTransactionSequenceNumber++; + // Create the transaction status + TransactionStatus transactionStatus = new TransactionStatus(TransactionStatus.StatusPending, pendingTransactionBuilder.getArbitrator()); - for (int i = lastSeenPendingTransactionSpeculateIndex; i < pendingTransQueue.size(); i++) { - PendingTransaction pt = pendingTransQueue.get(i); + // Create the new transaction + Transaction newTransaction = pendingTransactionBuilder.createTransaction(); + newTransaction.setTransactionStatus(transactionStatus); - if (pt.evaluateGuard(commitedTable, speculativeTable, pendingTransSpeculativeTable)) { - - lastSeenPendingTransactionSpeculateIndex = i; - - for (KeyValue kv : pt.getKVUpdates()) { - pendingTransSpeculativeTable.put(kv.getKey(), kv); - } - - } - } - } else { - Transaction ut = new Transaction(null, - -1, - localmachineid, - pendingTransBuild.getArbitrator(), - pendingTransBuild.getKVUpdates(), - pendingTransBuild.getKVGuard()); - - Pair<Boolean, List<Commit>> retData = doLocalUpdateAndArbitrate(ut, lastCommitSeenSeqNumMap.get(localmachineid)); - - if (retData.getFirst()) { - transStatus = new TransactionStatus(TransactionStatus.StatusCommitted, pendingTransBuild.getArbitrator()); - } else { - transStatus = new TransactionStatus(TransactionStatus.StatusAborted, pendingTransBuild.getArbitrator()); - } - } - - // Try to insert transactions if possible - if (!pendingTransQueue.isEmpty()) { - // We have a pending transaction so do full insertion - processPendingTrans(); + if (pendingTransactionBuilder.getArbitrator() != localMachineId) { + // Add it to the queue and invalidate the builder for safety + pendingTransactionQueue.add(newTransaction); } else { - try { - // We dont have a pending transaction so do minimal effort - updateWithNotPendingTrans(); - } catch (Exception e) { - // Do nothing - } + arbitrateOnLocalTransaction(newTransaction); + updateLiveStateFromLocal(); } - // reset it so next time is fresh - pendingTransBuild = new PendingTransaction(); + pendingTransactionBuilder = new PendingTransaction(localMachineId); + sendToServer(null); - mutex.release(); - return transStatus; + return transactionStatus; } - public boolean createNewKey(IoTString keyName, long machineId) throws ServerException, InterruptedException { - try { - mutex.acquire(); + /** + * Get the machine ID for this client + */ + public long getMachineId() { + return localMachineId; + } - while (true) { - if (arbitratorTable.get(keyName) != null) { - // There is already an arbitrator - mutex.release(); - return false; - } + /** + * Decrement the number of live slots that we currently have + */ + public void decrementLiveCount() { + liveSlotCount--; + } - if (tryput(keyName, machineId, false)) { - // If successfully inserted - mutex.release(); - return true; - } - } - } catch (ServerException e) { - mutex.release(); - throw e; - } + /** + * Recalculate the new resize threshold + */ + private void setResizeThreshold() { + int resizeLower = (int) (RESIZE_THRESHOLD * numberOfSlots); + bufferResizeThreshold = resizeLower - 1 + random.nextInt(numberOfSlots - resizeLower); } - private void processPendingTrans() throws InterruptedException { + private boolean sendToServer(NewKey newKey) { - boolean sentAllPending = false; try { - while (!pendingTransQueue.isEmpty()) { - if (tryput( pendingTransQueue.peek(), false)) { - pendingTransQueue.poll(); - } - } + // While we have stuff that needs inserting into the block chain + while ((pendingTransactionQueue.size() > 0) || (pendingSendArbitrationEntries.size() > 0) || (newKey != null)) { - // if got here then all pending transactions were sent - sentAllPending = true; - } catch (Exception e) { - // There was a connection error - sentAllPending = false; - } + // try { + // Thread.sleep(300); + // } catch (Exception e) { - if (!sentAllPending) { + // } - for (Iterator<PendingTransaction> i = pendingTransQueue.iterator(); i.hasNext(); ) { - PendingTransaction pt = i.next(); - LocalComm lc = localCommunicationChannels.get(pt.getArbitrator()); - if (lc == null) { - // Cant talk directly to arbitrator so cant do anything - continue; - } + // Create the slot + Slot slot = new Slot(this, sequenceNumber + 1, localMachineId, buffer.getSlot(sequenceNumber).getHMAC()); + // Try to fill the slot with data + ThreeTuple<Boolean, Integer, Boolean> fillSlotsReturn = fillSlot(slot, false, newKey); + boolean needsResize = fillSlotsReturn.getFirst(); + int newSize = fillSlotsReturn.getSecond(); + Boolean insertedNewKey = fillSlotsReturn.getThird(); - Transaction ut = new Transaction(null, - -1, - localmachineid, - pendingTransBuild.getArbitrator(), - pendingTransBuild.getKVUpdates(), - pendingTransBuild.getKVGuard()); + if (needsResize) { + // Reset which transaction to send + for (Transaction transaction : transactionPartsSent.keySet()) { + transaction.resetNextPartToSend(); + // Set the transaction sequence number back to nothing + if (!transaction.didSendAPartToServer()) { + transaction.setSequenceNumber(-1); + } + } - Pair<Boolean, List<Commit>> retData = sendTransactionToLocal(ut, lc); + // Clear the sent data since we are trying again + pendingSendArbitrationEntriesToDelete.clear(); + transactionPartsSent.clear(); - for (Commit commit : retData.getSecond()) { - // Prepare to process the commit - processEntry(commit); + // We needed a resize so try again + fillSlot(slot, true, newKey); } - boolean didCommitOrSpeculate = proccessAllNewCommits(); - - // Go through all uncommitted transactions and kill the ones that are dead - deleteDeadUncommittedTransactions(); + // Try to send to the server + Pair<Boolean, Slot[]> sendSlotsReturn = sendSlotsToServer(slot, newSize); - // Speculate on key value pairs - didCommitOrSpeculate |= createSpeculativeTable(); - createPendingTransactionSpeculativeTable(didCommitOrSpeculate); - - if (retData.getFirst()) { - TransactionStatus transStatus = transactionStatusNotSentMap.remove(pendingTransBuild.getMachineLocalTransSeqNum()); - if (transStatus != null) { - transStatus.setStatus(TransactionStatus.StatusCommitted); - } + if (sendSlotsReturn.getFirst()) { + // Did insert into the block chain - } else { - TransactionStatus transStatus = transactionStatusNotSentMap.remove(pendingTransBuild.getMachineLocalTransSeqNum()); - if (transStatus != null) { - transStatus.setStatus(TransactionStatus.StatusAborted); - } - } - i.remove(); - } - } - } + // New Key was successfully inserted into the block chain so dont want to insert it again + newKey = null; - private void updateWithNotPendingTrans() throws ServerException, InterruptedException { - boolean doEnd = false; - boolean needResize = false; - while (!doEnd && ((uncommittedTransactionsMap.keySet().size() > 0) || (pendingCommitsList.size() > 0)) ) { - boolean resize = needResize; - needResize = false; - - Slot s = new Slot(this, sequencenumber + 1, localmachineid, buffer.getSlot(sequencenumber).getHMAC()); - int newsize = 0; - if (liveslotcount > resizethreshold) { - resize = true; //Resize is forced - } + // Remove the aborts and commit parts that were sent from the pending to send queue + pendingSendArbitrationEntries.removeAll(pendingSendArbitrationEntriesToDelete); - if (resize) { - newsize = (int) (numslots * RESIZE_MULTIPLE); - TableStatus status = new TableStatus(s, newsize); - s.addEntry(status); - } + for (Transaction transaction : transactionPartsSent.keySet()) { - doRejectedMessages(s); + // Update which transactions parts still need to be sent + transaction.removeSentParts(transactionPartsSent.get(transaction)); - ThreeTuple<Boolean, Boolean, Long> retTup = doMandatoryResuce(s, resize); + // Add the transaction status to the outstanding list + outstandingTransactionStatus.put(transaction.getSequenceNumber(), transaction.getTransactionStatus()); - // Resize was needed so redo call - if (retTup.getFirst()) { - needResize = true; - continue; - } + // Update the transaction status + transaction.getTransactionStatus().setStatus(TransactionStatus.StatusSentPartial); - // Extract working variables - boolean seenliveslot = retTup.getSecond(); - long seqn = retTup.getThird(); + // Check if all the transaction parts were successfully sent and if so then remove it from pending + if (transaction.didSendAllParts()) { + transaction.getTransactionStatus().setStatus(TransactionStatus.StatusSentFully); + pendingTransactionQueue.remove(transaction); + } + } + } else { + // Reset which transaction to send + for (Transaction transaction : transactionPartsSent.keySet()) { + transaction.resetNextPartToSend(); - // Did need to arbitrate - doEnd = !doArbitration(s); + // Set the transaction sequence number back to nothing + if (!transaction.didSendAPartToServer()) { + transaction.setSequenceNumber(-1); + } + } + } - doOptionalRescue(s, seenliveslot, seqn, resize); + // Clear the sent data in preparation for next send + pendingSendArbitrationEntriesToDelete.clear(); + transactionPartsSent.clear(); - int max = 0; - if (resize) { - max = newsize; + if (sendSlotsReturn.getSecond().length != 0) { + // insert into the local block chain + validateAndUpdate(sendSlotsReturn.getSecond(), true); + } } + } catch (ServerException e) { - Slot[] array = cloud.putSlot(s, max); - if (array == null) { - array = new Slot[] {s}; - rejectedmessagelist.clear(); + if (e.getType() != ServerException.TypeInputTimeout) { + e.printStackTrace(); - // Delete pending commits that were sent to the cloud - deletePendingCommits(); + // Nothing was able to be sent to the server so just clear these data structures + for (Transaction transaction : transactionPartsSent.keySet()) { + // Set the transaction sequence number back to nothing + if (!transaction.didSendAPartToServer()) { + transaction.setSequenceNumber(-1); + } + } - } else { - if (array.length == 0) - throw new Error("Server Error: Did not send any slots"); - rejectedmessagelist.add(s.getSequenceNumber()); - doEnd = false; + pendingSendArbitrationEntriesToDelete.clear(); + transactionPartsSent.clear(); + } else { + // There was a partial send to the server } - - /* update data structure */ - validateandupdate(array, true); - } - } - - private Pair<Boolean, List<Commit>> sendTransactionToLocal(Transaction ut, LocalComm lc) throws InterruptedException { - - // encode the request - byte[] array = new byte[Long.BYTES + ut.getSize()]; - ByteBuffer bbEncode = ByteBuffer.wrap(array); - Long lastSeenCommit = lastCommitSeenSeqNumMap.get(ut.getArbitrator()); - if (lastSeenCommit != null) { - bbEncode.putLong(lastSeenCommit); - } else { - bbEncode.putLong(0); } - ut.encode(bbEncode); - byte[] data = lc.sendDataToLocalDevice(ut.getArbitrator(), bbEncode.array()); - - // Decode the data - ByteBuffer bbDecode = ByteBuffer.wrap(data); - boolean didCommit = bbDecode.get() == 1; - int numberOfCommites = bbDecode.getInt(); - - List<Commit> newCommits = new LinkedList<Commit>(); - for (int i = 0; i < numberOfCommites; i++ ) { - bbDecode.get(); - Commit com = (Commit)Commit.decode(null, bbDecode); - newCommits.add(com); - } - - return new Pair<Boolean, List<Commit>>(didCommit, newCommits); - } - - public byte[] localCommInput(byte[] data) throws InterruptedException { - - - - // Decode the data - ByteBuffer bbDecode = ByteBuffer.wrap(data); - long lastSeenCommit = bbDecode.getLong(); - - Transaction ut = null; - if (data.length != Long.BYTES) { - bbDecode.get(); - ut = (Transaction)Transaction.decode(null, bbDecode); - } - - mutex.acquire(); - // Do the local update and arbitrate - Pair<Boolean, List<Commit>> returnData = doLocalUpdateAndArbitrate(ut, lastSeenCommit); - mutex.release(); - - - // Calculate the size of the response - int size = Byte.BYTES + Integer.BYTES; - for (Commit com : returnData.getSecond()) { - size += com.getSize(); - } - - // encode the response - byte[] array = new byte[size]; - ByteBuffer bbEncode = ByteBuffer.wrap(array); - if (returnData.getFirst()) { - bbEncode.put((byte)1); - } else { - bbEncode.put((byte)0); - } - bbEncode.putInt(returnData.getSecond().size()); - - for (Commit com : returnData.getSecond()) { - com.encode(bbEncode); - } - - return bbEncode.array(); + return newKey == null; } - private Pair<Boolean, List<Commit>> doLocalUpdateAndArbitrate(Transaction ut, Long lastCommitSeen) { - - List<Commit> returnCommits = new ArrayList<Commit>(); - - if ((lastCommitSeenSeqNumMap.get(localmachineid) != null) && (lastCommitSeenSeqNumMap.get(localmachineid) > lastCommitSeen)) { - // There is a commit that the other client has not seen yet - - Map<Long, Commit> cm = commitMap.get(localmachineid); - if (cm != null) { + private Pair<Boolean, Slot[]> sendSlotsToServer(Slot slot, int newSize) throws ServerException { - List<Long> commitKeys = new ArrayList<Long>(cm.keySet()); - Collections.sort(commitKeys); + boolean inserted = true; - - for (int i = (commitKeys.size() - 1); i >= 0; i--) { - Commit com = cm.get(commitKeys.get(i)); - - if (com.getSequenceNumber() <= lastCommitSeen) { - break; - } - returnCommits.add((Commit)com.getCopy(null)); - } + Slot[] array = cloud.putSlot(slot, newSize); + if (array == null) { + array = new Slot[] {slot}; + rejectedSlotList.clear(); + } else { + if (array.length == 0) { + throw new Error("Server Error: Did not send any slots"); } + rejectedSlotList.add(slot.getSequenceNumber()); + inserted = false; } - - if ((ut == null) || (ut.getArbitrator() != localmachineid)) { - // We are not the arbitrator for that transaction so the other device is talking to the wrong arbitrator - // or there is no transaction to process - return new Pair<Boolean, List<Commit>>(false, returnCommits); - } - - if (!ut.evaluateGuard(commitedTable, null)) { - // Guard evaluated as false so return only the commits that the other device has not seen yet - return new Pair<Boolean, List<Commit>>(false, returnCommits); - } - - // create the commit - Commit commit = new Commit(null, - -1, - commitSequenceNumber, - ut.getArbitrator(), - ut.getkeyValueUpdateSet()); - commitSequenceNumber = commitSequenceNumber + 1; - - // Add to the pending commits list - pendingCommitsList.add(commit); - - // Add this commit so we can send it back - returnCommits.add(commit); - - // Prepare to process the commit - processEntry(commit); - - boolean didCommitOrSpeculate = proccessAllNewCommits(); - - // Go through all uncommitted transactions and kill the ones that are dead - deleteDeadUncommittedTransactions(); - - // Speculate on key value pairs - didCommitOrSpeculate |= createSpeculativeTable(); - createPendingTransactionSpeculativeTable(didCommitOrSpeculate); - - return new Pair<Boolean, List<Commit>>(true, returnCommits); - } - - public void decrementLiveCount() { - liveslotcount--; - } - - private void setResizeThreshold() { - int resize_lower = (int) (RESIZE_THRESHOLD * numslots); - resizethreshold = resize_lower - 1 + random.nextInt(numslots - resize_lower); + return new Pair<Boolean, Slot[]>(inserted, array); } - private boolean tryput(PendingTransaction pendingTrans, boolean resize) throws ServerException { - Slot s = new Slot(this, sequencenumber + 1, localmachineid, buffer.getSlot(sequencenumber).getHMAC()); - - int newsize = 0; - if (liveslotcount > resizethreshold) { + /** + * Returns false if a resize was needed + */ + private ThreeTuple<Boolean, Integer, Boolean> fillSlot(Slot slot, boolean resize, NewKey newKeyEntry) { + int newSize = 0; + if (liveSlotCount > bufferResizeThreshold) { resize = true; //Resize is forced } if (resize) { - newsize = (int) (numslots * RESIZE_MULTIPLE); - TableStatus status = new TableStatus(s, newsize); - s.addEntry(status); + newSize = (int) (numberOfSlots * RESIZE_MULTIPLE); + TableStatus status = new TableStatus(slot, newSize); + slot.addEntry(status); } - doRejectedMessages(s); + // Fill with rejected slots first before doing anything else + doRejectedMessages(slot); - ThreeTuple<Boolean, Boolean, Long> retTup = doMandatoryResuce(s, resize); - - // Resize was needed so redo call - if (retTup.getFirst()) { - return tryput(pendingTrans, true); - } + // Do mandatory rescue of entries + ThreeTuple<Boolean, Boolean, Long> mandatoryRescueReturn = doMandatoryResuce(slot, resize); // Extract working variables - boolean seenliveslot = retTup.getSecond(); - long seqn = retTup.getThird(); + boolean needsResize = mandatoryRescueReturn.getFirst(); + boolean seenLiveSlot = mandatoryRescueReturn.getSecond(); + long currentRescueSequenceNumber = mandatoryRescueReturn.getThird(); - doArbitration(s); - - Transaction trans = new Transaction(s, - s.getSequenceNumber(), - localmachineid, - pendingTrans.getArbitrator(), - pendingTrans.getKVUpdates(), - pendingTrans.getKVGuard()); - boolean insertedTrans = false; - if (s.hasSpace(trans)) { - s.addEntry(trans); - insertedTrans = true; + if (needsResize && !resize) { + // We need to resize but we are not resizing so return false + return new ThreeTuple<Boolean, Integer, Boolean>(true, null, null); } - doOptionalRescue(s, seenliveslot, seqn, resize); - Pair<Boolean, Slot[]> sendRetData = doSendSlots(s, insertedTrans, resize, newsize); - - if (sendRetData.getFirst()) { - // update the status and change what the sequence number is for the - TransactionStatus transStatus = transactionStatusNotSentMap.remove(pendingTrans.getMachineLocalTransSeqNum()); - transStatus.setStatus(TransactionStatus.StatusSent); - transStatus.setSentTransaction(); - transactionStatusMap.put(trans.getSequenceNumber(), transStatus); + boolean inserted = false; + if (newKeyEntry != null) { + newKeyEntry.setSlot(slot); + if (slot.hasSpace(newKeyEntry)) { + slot.addEntry(newKeyEntry); + inserted = true; + } } + // Clear the transactions, aborts and commits that were sent previously + transactionPartsSent.clear(); + pendingSendArbitrationEntriesToDelete.clear(); - if (sendRetData.getSecond().length != 0) { - // insert into the local block chain - validateandupdate(sendRetData.getSecond(), true); - } + // Insert pending arbitration data + for (Entry arbitrationData : pendingSendArbitrationEntries) { - return sendRetData.getFirst(); - } + // If it is an abort then we need to set some information + if (arbitrationData instanceof Abort) { + ((Abort)arbitrationData).setSequenceNumber(slot.getSequenceNumber()); + } - private boolean tryput(IoTString keyName, long arbMachineid, boolean resize) throws ServerException { - Slot s = new Slot(this, sequencenumber + 1, localmachineid, buffer.getSlot(sequencenumber).getHMAC()); - int newsize = 0; - if (liveslotcount > resizethreshold) { - resize = true; //Resize is forced - } + if (!slot.hasSpace(arbitrationData)) { + // No space so cant do anything else with these data entries + break; + } - if (resize) { - newsize = (int) (numslots * RESIZE_MULTIPLE); - TableStatus status = new TableStatus(s, newsize); - s.addEntry(status); + // Add to this current slot and add it to entries to delete + slot.addEntry(arbitrationData); + pendingSendArbitrationEntriesToDelete.add(arbitrationData); } - doRejectedMessages(s); - ThreeTuple<Boolean, Boolean, Long> retTup = doMandatoryResuce(s, resize); + // Insert as many transactions as possible while keeping order + for (Transaction transaction : pendingTransactionQueue) { - // Resize was needed so redo call - if (retTup.getFirst()) { - return tryput(keyName, arbMachineid, true); - } + // Set the transaction sequence number if it has yet to be inserted into the block chain + if (!transaction.didSendAPartToServer()) { + transaction.setSequenceNumber(slot.getSequenceNumber()); + } - // Extract working variables - boolean seenliveslot = retTup.getSecond(); - long seqn = retTup.getThird(); + boolean ranOutOfSpace = false; - doArbitration(s); + while (true) { + TransactionPart part = transaction.getNextPartToSend(); + + if (part == null) { + // Ran out of parts to send for this transaction so move on + break; + } - NewKey newKey = new NewKey(s, keyName, arbMachineid); + if (slot.hasSpace(part)) { + slot.addEntry(part); - boolean insertedNewKey = false; - if (s.hasSpace(newKey)) { - s.addEntry(newKey); - insertedNewKey = true; - } + List<Integer> partsSent = transactionPartsSent.get(transaction); + if (partsSent == null) { + partsSent = new ArrayList<Integer>(); + transactionPartsSent.put(transaction, partsSent); + } + + partsSent.add(part.getPartNumber()); + transactionPartsSent.put(transaction, partsSent); - doOptionalRescue(s, seenliveslot, seqn, resize); - Pair<Boolean, Slot[]> sendRetData = doSendSlots(s, insertedNewKey, resize, newsize); + } else { + ranOutOfSpace = true; + break; + } + } - if (sendRetData.getSecond().length != 0) { - // insert into the local block chain - validateandupdate(sendRetData.getSecond(), true); + if (ranOutOfSpace) { + break; + } } - return sendRetData.getFirst(); + // Fill the remainder of the slot with rescue data + doOptionalRescue(slot, seenLiveSlot, currentRescueSequenceNumber, resize); + + return new ThreeTuple<Boolean, Integer, Boolean>(false, newSize, inserted); } private void doRejectedMessages(Slot s) { - if (! rejectedmessagelist.isEmpty()) { + if (! rejectedSlotList.isEmpty()) { /* TODO: We should avoid generating a rejected message entry if * there is already a sufficient entry in the queue (e.g., * equalsto value of true and same sequence number). */ - long old_seqn = rejectedmessagelist.firstElement(); - if (rejectedmessagelist.size() > REJECTED_THRESHOLD) { - long new_seqn = rejectedmessagelist.lastElement(); - RejectedMessage rm = new RejectedMessage(s, localmachineid, old_seqn, new_seqn, false); + long old_seqn = rejectedSlotList.firstElement(); + if (rejectedSlotList.size() > REJECTED_THRESHOLD) { + long new_seqn = rejectedSlotList.lastElement(); + RejectedMessage rm = new RejectedMessage(s, localMachineId, old_seqn, new_seqn, false); s.addEntry(rm); } else { long prev_seqn = -1; int i = 0; /* Go through list of missing messages */ - for (; i < rejectedmessagelist.size(); i++) { - long curr_seqn = rejectedmessagelist.get(i); + for (; i < rejectedSlotList.size(); i++) { + long curr_seqn = rejectedSlotList.get(i); Slot s_msg = buffer.getSlot(curr_seqn); if (s_msg != null) break; @@ -974,12 +634,12 @@ final public class Table { } /* Generate rejected message entry for missing messages */ if (prev_seqn != -1) { - RejectedMessage rm = new RejectedMessage(s, localmachineid, old_seqn, prev_seqn, false); + RejectedMessage rm = new RejectedMessage(s, localMachineId, old_seqn, prev_seqn, false); s.addEntry(rm); } /* Generate rejected message entries for present messages */ - for (; i < rejectedmessagelist.size(); i++) { - long curr_seqn = rejectedmessagelist.get(i); + for (; i < rejectedSlotList.size(); i++) { + long curr_seqn = rejectedSlotList.get(i); Slot s_msg = buffer.getSlot(curr_seqn); long machineid = s_msg.getMachineID(); RejectedMessage rm = new RejectedMessage(s, machineid, curr_seqn, curr_seqn, true); @@ -989,124 +649,54 @@ final public class Table { } } - private ThreeTuple<Boolean, Boolean, Long> doMandatoryResuce(Slot s, boolean resize) { - long newestseqnum = buffer.getNewestSeqNum(); - long oldestseqnum = buffer.getOldestSeqNum(); - if (lastliveslotseqn < oldestseqnum) - lastliveslotseqn = oldestseqnum; + private ThreeTuple<Boolean, Boolean, Long> doMandatoryResuce(Slot slot, boolean resize) { + long newestSequenceNumber = buffer.getNewestSeqNum(); + long oldestSequenceNumber = buffer.getOldestSeqNum(); + if (oldestLiveSlotSequenceNumver < oldestSequenceNumber) { + oldestLiveSlotSequenceNumver = oldestSequenceNumber; + } - long seqn = lastliveslotseqn; - boolean seenliveslot = false; - long firstiffull = newestseqnum + 1 - numslots; // smallest seq number in the buffer if it is full - long threshold = firstiffull + FREE_SLOTS; // we want the buffer to be clear of live entries up to this point + long currentSequenceNumber = oldestLiveSlotSequenceNumver; + boolean seenLiveSlot = false; + long firstIfFull = newestSequenceNumber + 1 - numberOfSlots; // smallest seq number in the buffer if it is full + long threshold = firstIfFull + FREE_SLOTS; // we want the buffer to be clear of live entries up to this point // Mandatory Rescue - for (; seqn < threshold; seqn++) { - Slot prevslot = buffer.getSlot(seqn); + for (; currentSequenceNumber < threshold; currentSequenceNumber++) { + Slot previousSlot = buffer.getSlot(currentSequenceNumber); // Push slot number forward - if (! seenliveslot) - lastliveslotseqn = seqn; + if (! seenLiveSlot) { + oldestLiveSlotSequenceNumver = currentSequenceNumber; + } - if (! prevslot.isLive()) + if (!previousSlot.isLive()) { continue; - seenliveslot = true; - Vector<Entry> liveentries = prevslot.getLiveEntries(resize); - for (Entry liveentry : liveentries) { - if (s.hasSpace(liveentry)) { - s.addEntry(liveentry); - } else if (seqn == firstiffull) { //if there's no space but the entry is about to fall off the queue - if (!resize) { - System.out.println("B"); //? - return new ThreeTuple<Boolean, Boolean, Long>(true, seenliveslot, seqn); - } - } } - } - // Did not resize - return new ThreeTuple<Boolean, Boolean, Long>(false, seenliveslot, seqn); - } + // We have seen a live slot + seenLiveSlot = true; - private boolean doArbitration(Slot s) { + // Get all the live entries for a slot + Vector<Entry> liveEntries = previousSlot.getLiveEntries(resize); - // flag whether we have finished all arbitration - boolean stillHasArbitration = false; + // Iterate over all the live entries and try to rescue them + for (Entry liveEntry : liveEntries) { + if (slot.hasSpace(liveEntry)) { - pendingCommitsToDelete.clear(); + // Enough space to rescue the entry + slot.addEntry(liveEntry); + } else if (currentSequenceNumber == firstIfFull) { + //if there's no space but the entry is about to fall off the queue + System.out.println("B"); //? + return new ThreeTuple<Boolean, Boolean, Long>(true, seenLiveSlot, currentSequenceNumber); - // First add queue commits - for (Commit commit : pendingCommitsList) { - if (s.hasSpace(commit)) { - s.addEntry(commit); - pendingCommitsToDelete.add(commit); - } else { - // Ran out of space so move on but still not done - stillHasArbitration = true; - return stillHasArbitration; + } } } - // Arbitrate - Map<IoTString, KeyValue> speculativeTableTmp = new HashMap<IoTString, KeyValue>(); - List<Long> transSeqNums = new ArrayList<Long>(uncommittedTransactionsMap.keySet()); - - // Sort from oldest to newest - Collections.sort(transSeqNums); - - for (Long transNum : transSeqNums) { - Transaction ut = uncommittedTransactionsMap.get(transNum); - - // Check if this machine arbitrates for this transaction - if (ut.getArbitrator() != localmachineid ) { - continue; - } - - // we did have something to arbitrate on - stillHasArbitration = true; - - Entry newEntry = null; - - if (ut.evaluateGuard(commitedTable, speculativeTableTmp)) { - // Guard evaluated as true - - // update the local tmp current key set - for (KeyValue kv : ut.getkeyValueUpdateSet()) { - speculativeTableTmp.put(kv.getKey(), kv); - } - - // create the commit - newEntry = new Commit(s, - ut.getSequenceNumber(), - commitSequenceNumber, - ut.getArbitrator(), - ut.getkeyValueUpdateSet()); - commitSequenceNumber = commitSequenceNumber + 1; - } else { - // Guard was false - - // create the abort - newEntry = new Abort(s, - ut.getSequenceNumber(), - ut.getMachineID(), - ut.getArbitrator()); - } - - if ((newEntry != null) && s.hasSpace(newEntry)) { - s.addEntry(newEntry); - } else { - break; - } - } - - return stillHasArbitration; - } - - private void deletePendingCommits() { - for (Commit com : pendingCommitsToDelete) { - pendingCommitsList.remove(com); - } - pendingCommitsToDelete.clear(); + // Did not resize + return new ThreeTuple<Boolean, Boolean, Long>(false, seenLiveSlot, currentSequenceNumber); } private void doOptionalRescue(Slot s, boolean seenliveslot, long seqn, boolean resize) { @@ -1120,7 +710,7 @@ final public class Table { Slot prevslot = buffer.getSlot(seqn); //Push slot number forward if (!seenliveslot) - lastliveslotseqn = seqn; + oldestLiveSlotSequenceNumver = seqn; if (!prevslot.isLive()) continue; @@ -1138,501 +728,943 @@ final public class Table { } } - private Pair<Boolean, Slot[]> doSendSlots(Slot s, boolean inserted, boolean resize, int newsize) throws ServerException { - int max = 0; - if (resize) - max = newsize; - - Slot[] array = cloud.putSlot(s, max); - if (array == null) { - array = new Slot[] {s}; - rejectedmessagelist.clear(); + /** + * Checks for malicious activity and updates the local copy of the block chain. + */ + private void validateAndUpdate(Slot[] newSlots, boolean acceptUpdatesToLocal) { - // Delete pending commits that were sent to the cloud - deletePendingCommits(); - } else { - // if (array.length == 0) - // throw new Error("Server Error: Did not send any slots"); - rejectedmessagelist.add(s.getSequenceNumber()); - inserted = false; + // The cloud communication layer has checked slot HMACs already before decoding + if (newSlots.length == 0) { + return; } - return new Pair<Boolean, Slot[]>(inserted, array); - } - - private void validateandupdate(Slot[] newslots, boolean acceptupdatestolocal) { - /* The cloud communication layer has checked slot HMACs already - before decoding */ - if (newslots.length == 0) return; - // Reset the table status declared sizes smallestTableStatusSeen = -1; largestTableStatusSeen = -1; - long firstseqnum = newslots[0].getSequenceNumber(); - if (firstseqnum <= sequencenumber) { + + // Make sure all slots are newer than the last largest slot this client has seen + long firstSeqNum = newSlots[0].getSequenceNumber(); + if (firstSeqNum <= sequenceNumber) { throw new Error("Server Error: Sent older slots!"); } - SlotIndexer indexer = new SlotIndexer(newslots, buffer); - checkHMACChain(indexer, newslots); + // Create an object that can access both new slots and slots in our local chain + // without committing slots to our local chain + SlotIndexer indexer = new SlotIndexer(newSlots, buffer); + + // Check that the HMAC chain is not broken + checkHMACChain(indexer, newSlots); - HashSet<Long> machineSet = new HashSet<Long>(lastmessagetable.keySet()); // + // Set to keep track of messages from clients + HashSet<Long> machineSet = new HashSet<Long>(lastMessageTable.keySet()); - // initExpectedSize(firstseqnum); - for (Slot slot : newslots) { - processSlot(indexer, slot, acceptupdatestolocal, machineSet); - // updateExpectedSize(); + // Process each slots data + for (Slot slot : newSlots) { + processSlot(indexer, slot, acceptUpdatesToLocal, machineSet); } - /* If there is a gap, check to see if the server sent us everything. */ - if (firstseqnum != (sequencenumber + 1)) { + // If there is a gap, check to see if the server sent us everything. + if (firstSeqNum != (sequenceNumber + 1)) { - // TODO: Check size - checkNumSlots(newslots.length); + // Check the size of the slots that were sent down by the server. + // Can only check the size if there was a gap + checkNumSlots(newSlots.length); + + // Since there was a gap every machine must have pushed a slot or must have + // a last message message. If not then the server is hiding slots if (!machineSet.isEmpty()) { throw new Error("Missing record for machines: " + machineSet); } } - + // Update the size of our local block chain. commitNewMaxSize(); - /* Commit new to slots. */ - for (Slot slot : newslots) { + // Commit new to slots to the local block chain. + for (Slot slot : newSlots) { + + // Insert this slot into our local block chain copy. buffer.putSlot(slot); - liveslotcount++; + + // Keep track of how many slots are currently live (have live data in them). + liveSlotCount++; } - sequencenumber = newslots[newslots.length - 1].getSequenceNumber(); - // Process all on key value pairs - boolean didCommitOrSpeculate = proccessAllNewCommits(); + // Get the sequence number of the latest slot in the system + sequenceNumber = newSlots[newSlots.length - 1].getSequenceNumber(); + + updateLiveStateFromServer(); + } + + private void updateLiveStateFromServer() { + // Process the new transaction parts + processNewTransactionParts(); + + // Do arbitration on new transactions that were received + arbitrateFromServer(); - // Go through all uncommitted transactions and kill the ones that are dead - deleteDeadUncommittedTransactions(); + // Update all the committed keys + boolean didCommitOrSpeculate = updateCommittedTable(); - // Speculate on key value pairs - didCommitOrSpeculate |= createSpeculativeTable(); + // Delete the transactions that are now dead + updateLiveTransactionsAndStatus(); - createPendingTransactionSpeculativeTable(didCommitOrSpeculate); + // Do speculations + didCommitOrSpeculate |= updateSpeculativeTable(didCommitOrSpeculate); + updatePendingTransactionSpeculativeTable(didCommitOrSpeculate); } - private boolean proccessAllNewCommits() { - // Process only if there are commit - if (newCommitMap.keySet().size() == 0) { - return false; + private void updateLiveStateFromLocal() { + // Update all the committed keys + boolean didCommitOrSpeculate = updateCommittedTable(); + + // Delete the transactions that are now dead + updateLiveTransactionsAndStatus(); + + // Do speculations + didCommitOrSpeculate |= updateSpeculativeTable(didCommitOrSpeculate); + updatePendingTransactionSpeculativeTable(didCommitOrSpeculate); + } + + /** + * Check the size of the block chain to make sure there are enough slots sent back by the server. + * This is only called when we have a gap between the slots that we have locally and the slots + * sent by the server therefore in the slots sent by the server there will be at least 1 Table + * status message + */ + private void checkNumSlots(int numberOfSlots) { + + // We only have 1 size so we must have this many slots + if (largestTableStatusSeen == smallestTableStatusSeen) { + if (numberOfSlots != smallestTableStatusSeen) { + throw new Error("Server Error: Server did not send all slots. Expected: " + smallestTableStatusSeen + " Received:" + numberOfSlots); + } + } else { + // We have more than 1 + if (numberOfSlots < smallestTableStatusSeen) { + throw new Error("Server Error: Server did not send all slots. Expected at least: " + smallestTableStatusSeen + " Received:" + numberOfSlots); + } } - boolean didProcessNewCommit = false; + } - for (Long arb : newCommitMap.keySet()) { + /** + * Update the size of of the local buffer if it is needed. + */ + private void commitNewMaxSize() { - List<Long> commitSeqNums = new ArrayList<Long>(newCommitMap.get(arb).keySet()); + int currMaxSize = 0; - // Sort from oldest to newest commit - Collections.sort(commitSeqNums); + if (largestTableStatusSeen == -1) { + // No table status seen so the current max size does not change + currMaxSize = numberOfSlots; + } else { + currMaxSize = largestTableStatusSeen; + } - // Go through each new commit one by one - for (Long entrySeqNum : commitSeqNums) { - Commit entry = newCommitMap.get(arb).get(entrySeqNum); + // Resize the local slot buffer + if (numberOfSlots != currMaxSize) { + buffer.resize(currMaxSize); + } - long lastCommitSeenSeqNum = -1; - if (lastCommitSeenSeqNumMap.get(entry.getTransArbitrator()) != null) { - lastCommitSeenSeqNum = lastCommitSeenSeqNumMap.get(entry.getTransArbitrator()); - } + // Change the number of local slots to the new size + numberOfSlots = currMaxSize; - if (entry.getSequenceNumber() <= lastCommitSeenSeqNum) { - Map<Long, Commit> cm = commitMap.get(arb); - if (cm == null) { - cm = new HashMap<Long, Commit>(); - } + // Recalculate the resize threshold since the size of the local buffer has changed + setResizeThreshold(); + } - Commit prevCommit = cm.put(entry.getSequenceNumber(), entry); - commitMap.put(arb, cm); + /** + * Process the new transaction parts from this latest round of slots received from the server + */ + private void processNewTransactionParts() { - if (prevCommit != null) { - prevCommit.setDead(); + if (newTransactionParts.size() == 0) { + // Nothing new to process + return; + } - for (KeyValue kv : prevCommit.getkeyValueUpdateSet()) { - committedMapByKey.put(kv.getKey(), entry); - } - } + // Iterate through all the machine Ids that we received new parts for + for (Long machineId : newTransactionParts.keySet()) { + Map<Pair<Long, Integer>, TransactionPart> parts = newTransactionParts.get(machineId); + + // Iterate through all the parts for that machine Id + for (Pair<Long, Integer> partId : parts.keySet()) { + TransactionPart part = parts.get(partId); + Long lastTransactionNumber = lastArbitratedTransactionNumberByArbitratorTable.get(part.getArbitratorId()); + if ((lastTransactionNumber != null) && (lastTransactionNumber >= part.getSequenceNumber())) { + // Set dead the transaction part + part.setDead(); continue; } - Set<Commit> commitsToEditSet = new HashSet<Commit>(); + // Get the transaction object for that sequence number + Transaction transaction = liveTransactionBySequenceNumberTable.get(part.getSequenceNumber()); + + if (transaction == null) { + // This is a new transaction that we dont have so make a new one + transaction = new Transaction(); - for (KeyValue kv : entry.getkeyValueUpdateSet()) { - commitsToEditSet.add(committedMapByKey.get(kv.getKey())); + // Insert this new transaction into the live tables + liveTransactionBySequenceNumberTable.put(part.getSequenceNumber(), transaction); + liveTransactionByTransactionIdTable.put(part.getTransactionId(), transaction); } - commitsToEditSet.remove(null); + // Add that part to the transaction + transaction.addPartDecode(part); + } + } - for (Commit prevCommit : commitsToEditSet) { + // Clear all the new transaction parts in preparation for the next time the server sends slots + newTransactionParts.clear(); + } - Set<KeyValue> deletedKV = prevCommit.updateLiveKeys(entry.getkeyValueUpdateSet()); + public void arbitrateFromServer() { - if (!prevCommit.isLive()) { - Map<Long, Commit> cm = commitMap.get(arb); + if (liveTransactionBySequenceNumberTable.size() == 0) { + // Nothing to arbitrate on so move on + return; + } - // remove it from the map so that it can be set as dead - if (cm != null) { - cm.remove(prevCommit.getSequenceNumber()); - commitMap.put(arb, cm); - } - } - } + // Get the transaction sequence numbers and sort from oldest to newest + List<Long> transactionSequenceNumbers = new ArrayList<Long>(liveTransactionBySequenceNumberTable.keySet()); + Collections.sort(transactionSequenceNumbers); - // Add the new commit - Map<Long, Commit> cm = commitMap.get(arb); - if (cm == null) { - cm = new HashMap<Long, Commit>(); - } - cm.put(entry.getSequenceNumber(), entry); - commitMap.put(arb, cm); + // Collection of key value pairs that are + Map<IoTString, KeyValue> speculativeTableTmp = new HashMap<IoTString, KeyValue>(); - lastCommitSeenSeqNumMap.put(entry.getTransArbitrator(), entry.getSequenceNumber()); + // The last transaction arbitrated on + long lastTransactionCommitted = -1; - // set the trans sequence number if we are able to - if (entry.getTransSequenceNumber() != -1) { - lastCommitSeenTransSeqNumMap.put(entry.getTransArbitrator(), entry.getTransSequenceNumber()); - } + for (Long transactionSequenceNumber : transactionSequenceNumbers) { + Transaction transaction = liveTransactionBySequenceNumberTable.get(transactionSequenceNumber); - didProcessNewCommit = true; + // Check if this machine arbitrates for this transaction if not then we cant arbitrate this transaction + if (transaction.getArbitrator() != localMachineId) { + continue; + } + + + if (!transaction.isComplete()) { + // Will arbitrate in incorrect order if we continue so just break + // Most likely this + break; + } - // Update the committed table list - for (KeyValue kv : entry.getkeyValueUpdateSet()) { - IoTString key = kv.getKey(); - commitedTable.put(key, kv); - committedMapByKey.put(key, entry); + + if (transaction.evaluateGuard(committedKeyValueTable, speculativeTableTmp, null)) { + // Guard evaluated as true + + // Update the local changes so we can make the commit + for (KeyValue kv : transaction.getKeyValueUpdateSet()) { + speculativeTableTmp.put(kv.getKey(), kv); } + + // Update what the last transaction committed was for use in batch commit + lastTransactionCommitted = transaction.getSequenceNumber(); + + } else { + // Guard evaluated was false so create abort + + // Create the abort + Abort newAbort = new Abort(null, + transaction.getClientLocalSequenceNumber(), + transaction.getSequenceNumber(), + transaction.getMachineId(), + transaction.getArbitrator()); + + // Add the abort to the queue of aborts to send out + pendingSendArbitrationEntries.add(newAbort); + + // Insert the abort so we can process + processEntry(newAbort); } } - // Clear the new commits storage so we can use it later - newCommitMap.clear(); + // If there is something to commit + if (speculativeTableTmp.size() != 0) { - // go through all saved transactions and update the status of those that can be updated - for (Iterator<Map.Entry<Long, TransactionStatus>> i = transactionStatusMap.entrySet().iterator(); i.hasNext();) { - Map.Entry<Long, TransactionStatus> entry = i.next(); - long seqnum = entry.getKey(); - TransactionStatus status = entry.getValue(); + // Create the commit and increment the commit sequence number + Commit newCommit = new Commit(localCommitSequenceNumber, localMachineId, lastTransactionCommitted); + localCommitSequenceNumber++; - if (status.getSentTransaction()) { + // Add all the new keys to the commit + for (KeyValue kv : speculativeTableTmp.values()) { + newCommit.addKV(kv); + } - Long commitSeqNum = lastCommitSeenTransSeqNumMap.get(status.getArbitrator()); - Long abortSeqNum = lastAbortSeenSeqNumMap.get(status.getArbitrator()); + // create the commit parts + newCommit.createCommitParts(); - if (((commitSeqNum != null) && (seqnum <= commitSeqNum)) || - ((abortSeqNum != null) && (seqnum <= abortSeqNum))) { - status.setStatus(TransactionStatus.StatusCommitted); - i.remove(); - } + // Append all the commit parts to the end of the pending queue waiting for sending to the server + pendingSendArbitrationEntries.addAll(newCommit.getParts().values()); + + // Insert the commit so we can process it + for (CommitPart commitPart : newCommit.getParts().values()) { + processEntry(commitPart); } } - - return didProcessNewCommit; } - private void deleteDeadUncommittedTransactions() { - // Make dead the transactions - for (Iterator<Map.Entry<Long, Transaction>> i = uncommittedTransactionsMap.entrySet().iterator(); i.hasNext();) { - Transaction prevtrans = i.next().getValue(); - long transArb = prevtrans.getArbitrator(); + public void arbitrateOnLocalTransaction(Transaction transaction) { - Long commitSeqNum = lastCommitSeenTransSeqNumMap.get(transArb); - Long abortSeqNum = lastAbortSeenSeqNumMap.get(transArb); + // Check if this machine arbitrates for this transaction if not then we cant arbitrate this transaction + if (transaction.getArbitrator() != localMachineId) { + return; + } - if (((commitSeqNum != null) && (prevtrans.getSequenceNumber() <= commitSeqNum)) || - ((abortSeqNum != null) && (prevtrans.getSequenceNumber() <= abortSeqNum))) { - i.remove(); - prevtrans.setDead(); + if (!transaction.isComplete()) { + // Will arbitrate in incorrect order if we continue so just break + // Most likely this + return; + } + + if (transaction.evaluateGuard(committedKeyValueTable, null, null)) { + // Guard evaluated as true + + // Create the commit and increment the commit sequence number + Commit newCommit = new Commit(localCommitSequenceNumber, localMachineId, -1); + localCommitSequenceNumber++; + + // Update the local changes so we can make the commit + for (KeyValue kv : transaction.getKeyValueUpdateSet()) { + newCommit.addKV(kv); + } + + // create the commit parts + newCommit.createCommitParts(); + + // Append all the commit parts to the end of the pending queue waiting for sending to the server + pendingSendArbitrationEntries.addAll(newCommit.getParts().values()); + + // Insert the commit so we can process it + for (CommitPart commitPart : newCommit.getParts().values()) { + processEntry(commitPart); } + + TransactionStatus status = transaction.getTransactionStatus(); + status.setStatus(TransactionStatus.StatusCommitted); + + } else { + // Guard evaluated was false so create abort + TransactionStatus status = transaction.getTransactionStatus(); + status.setStatus(TransactionStatus.StatusAborted); } } - private boolean createSpeculativeTable() { - if (uncommittedTransactionsMap.keySet().size() == 0) { + /** + * Update all the commits and the committed tables, sets dead the dead transactions + */ + private boolean updateCommittedTable() { + + if (newCommitParts.size() == 0) { + // Nothing new to process return false; } - Map<IoTString, KeyValue> speculativeTableTmp = new HashMap<IoTString, KeyValue>(); - List<Long> utSeqNums = new ArrayList<Long>(uncommittedTransactionsMap.keySet()); + // Iterate through all the machine Ids that we received new parts for + for (Long machineId : newCommitParts.keySet()) { + Map<Pair<Long, Integer>, CommitPart> parts = newCommitParts.get(machineId); - // Sort from oldest to newest commit - Collections.sort(utSeqNums); + // Iterate through all the parts for that machine Id + for (Pair<Long, Integer> partId : parts.keySet()) { + CommitPart part = parts.get(partId); - if (utSeqNums.get(0) > (lastUncommittedTransaction)) { + // Get the transaction object for that sequence number + Map<Long, Commit> commitForClientTable = liveCommitsTable.get(part.getMachineId()); - speculativeTable.clear(); - lastUncommittedTransaction = -1; + if (commitForClientTable == null) { + // This is the first commit from this device + commitForClientTable = new HashMap<Long, Commit>(); + liveCommitsTable.put(part.getMachineId(), commitForClientTable); + } - for (Long key : utSeqNums) { - Transaction trans = uncommittedTransactionsMap.get(key); + Commit commit = commitForClientTable.get(part.getSequenceNumber()); - lastUncommittedTransaction = key; + if (commit == null) { + // This is a new commit that we dont have so make a new one + commit = new Commit(); - if (trans.evaluateGuard(commitedTable, speculativeTableTmp)) { - for (KeyValue kv : trans.getkeyValueUpdateSet()) { - speculativeTableTmp.put(kv.getKey(), kv); - } + // Insert this new commit into the live tables + commitForClientTable.put(part.getSequenceNumber(), commit); } + // Add that part to the commit + commit.addPartDecode(part); } - } else { - for (Long key : utSeqNums) { + } + + // Clear all the new commits parts in preparation for the next time the server sends slots + newCommitParts.clear(); + + // If we process a new commit keep track of it for future use + boolean didProcessANewCommit = false; + + // Process the commits one by one + for (Long arbitratorId : liveCommitsTable.keySet()) { + + // Get all the commits for a specific arbitrator + Map<Long, Commit> commitForClientTable = liveCommitsTable.get(arbitratorId); + + // Sort the commits in order + List<Long> commitSequenceNumbers = new ArrayList<Long>(commitForClientTable.keySet()); + Collections.sort(commitSequenceNumbers); + + // Go through each new commit one by one + for (int i = 0; i < commitSequenceNumbers.size(); i++) { + Long commitSequenceNumber = commitSequenceNumbers.get(i); + Commit commit = commitForClientTable.get(commitSequenceNumber); + + // Special processing if a commit is not complete + if (!commit.isComplete()) { + if (i == (commitSequenceNumbers.size() - 1)) { + // If there is an incomplete commit and this commit is the latest one seen then this commit cannot be processed and there are no other commits + break; + } else { + // This is a commit that was already dead but parts of it are still in the block chain (not flushed out yet). + // Delete it and move on + commit.setDead(); + commitForClientTable.remove(commit.getSequenceNumber()); + continue; + } + } + + // Get the last commit seen from this arbitrator + long lastCommitSeenSequenceNumber = -1; + if (lastCommitSeenSequenceNumberByArbitratorTable.get(commit.getMachineId()) != null) { + lastCommitSeenSequenceNumber = lastCommitSeenSequenceNumberByArbitratorTable.get(commit.getMachineId()); + } + + + + + + + // We have already seen this commit before so need to do the full processing on this commit + if (commit.getSequenceNumber() <= lastCommitSeenSequenceNumber) { + + // Update the last transaction that was updated if we can + if (commit.getTransactionSequenceNumber() != -1) { + Long lastTransactionNumber = lastArbitratedTransactionNumberByArbitratorTable.get(commit.getMachineId()); + + // Update the last transaction sequence number that the arbitrator arbitrated on + if ((lastTransactionNumber == null) || (lastTransactionNumber < commit.getTransactionSequenceNumber())) { + lastArbitratedTransactionNumberByArbitratorTable.put(commit.getMachineId(), commit.getTransactionSequenceNumber()); + } + } - if (key <= lastUncommittedTransaction) { continue; } - lastUncommittedTransaction = key; - Transaction trans = uncommittedTransactionsMap.get(key); + // If we got here then this is a brand new commit and needs full processing - if (trans.evaluateGuard(speculativeTable, speculativeTableTmp)) { - for (KeyValue kv : trans.getkeyValueUpdateSet()) { - speculativeTableTmp.put(kv.getKey(), kv); + // Get what commits should be edited, these are the commits that have live values for their keys + Set<Commit> commitsToEdit = new HashSet<Commit>(); + for (KeyValue kv : commit.getKeyValueUpdateSet()) { + commitsToEdit.add(liveCommitsByKeyTable.get(kv.getKey())); + } + commitsToEdit.remove(null); // remove null since it could be in this set + + // Update each previous commit that needs to be updated + for (Commit previousCommit : commitsToEdit) { + + // Only bother with live commits (TODO: Maybe remove this check) + if (previousCommit.isLive()) { + + // Update which keys in the old commits are still live + for (KeyValue kv : commit.getKeyValueUpdateSet()) { + previousCommit.invalidateKey(kv.getKey()); + } + + // if the commit is now dead then remove it + if (!previousCommit.isLive()) { + commitForClientTable.remove(previousCommit); + } } } + + // Update the last seen sequence number from this arbitrator + lastCommitSeenSequenceNumberByArbitratorTable.put(commit.getMachineId(), commit.getSequenceNumber()); + + // Update the last transaction that was updated if we can + if (commit.getTransactionSequenceNumber() != -1) { + Long lastTransactionNumber = lastArbitratedTransactionNumberByArbitratorTable.get(commit.getMachineId()); + + // Update the last transaction sequence number that the arbitrator arbitrated on + if ((lastTransactionNumber == null) || (lastTransactionNumber < commit.getTransactionSequenceNumber())) { + lastArbitratedTransactionNumberByArbitratorTable.put(commit.getMachineId(), commit.getTransactionSequenceNumber()); + } + } + + // We processed a new commit that we havent seen before + didProcessANewCommit = true; + + // Update the committed table of keys and which commit is using which key + for (KeyValue kv : commit.getKeyValueUpdateSet()) { + committedKeyValueTable.put(kv.getKey(), kv); + liveCommitsByKeyTable.put(kv.getKey(), commit); + } + } + } + + return didProcessANewCommit; + } + + /** + * Create the speculative table from transactions that are still live and have come from the cloud + */ + private boolean updateSpeculativeTable(boolean didProcessNewCommits) { + if (liveTransactionBySequenceNumberTable.keySet().size() == 0) { + // There is nothing to speculate on + return false; + } + + // Create a list of the transaction sequence numbers and sort them from oldest to newest + List<Long> transactionSequenceNumbersSorted = new ArrayList<Long>(liveTransactionBySequenceNumberTable.keySet()); + Collections.sort(transactionSequenceNumbersSorted); + + boolean hasGapInTransactionSequenceNumbers = transactionSequenceNumbersSorted.get(0) != oldestTransactionSequenceNumberSpeculatedOn; + + + if (hasGapInTransactionSequenceNumbers || didProcessNewCommits) { + // If there is a gap in the transaction sequence numbers then there was a commit or an abort of a transaction + // OR there was a new commit (Could be from offline commit) so a redo the speculation from scratch + + // Start from scratch + speculatedKeyValueTable.clear(); + lastTransactionSequenceNumberSpeculatedOn = -1; + oldestTransactionSequenceNumberSpeculatedOn = -1; + + } + + // Remember the front of the transaction list + oldestTransactionSequenceNumberSpeculatedOn = transactionSequenceNumbersSorted.get(0); + + // Find where to start arbitration from + int startIndex = transactionSequenceNumbersSorted.indexOf(lastTransactionSequenceNumberSpeculatedOn) + 1; + + if (startIndex >= transactionSequenceNumbersSorted.size()) { + // Make sure we are not out of bounds + return false; // did not speculate + } + + Set<Long> incompleteTransactionArbitrator = new HashSet<Long>(); + boolean didSkip = true; + + for (int i = startIndex; i < transactionSequenceNumbersSorted.size(); i++) { + long transactionSequenceNumber = transactionSequenceNumbersSorted.get(i); + Transaction transaction = liveTransactionBySequenceNumberTable.get(transactionSequenceNumber); + + if (!transaction.isComplete()) { + // If there is an incomplete transaction then there is nothing we can do + // add this transactions arbitrator to the list of arbitrators we should ignore + incompleteTransactionArbitrator.add(transaction.getArbitrator()); + didSkip = true; + continue; + } + + if (incompleteTransactionArbitrator.contains(transaction.getArbitrator())) { + continue; + } + + lastTransactionSequenceNumberSpeculatedOn = transactionSequenceNumber; + + if (transaction.evaluateGuard(committedKeyValueTable, speculatedKeyValueTable, null)) { + // Guard evaluated to true so update the speculative table + for (KeyValue kv : transaction.getKeyValueUpdateSet()) { + speculatedKeyValueTable.put(kv.getKey(), kv); + } } } - for (IoTString key : speculativeTableTmp.keySet()) { - speculativeTable.put(key, speculativeTableTmp.get(key)); + if (didSkip) { + // Since there was a skip we need to redo the speculation next time around + lastTransactionSequenceNumberSpeculatedOn = -1; + oldestTransactionSequenceNumberSpeculatedOn = -1; } + // We did some speculation return true; } - private void createPendingTransactionSpeculativeTable(boolean didCommitOrSpeculate) { + /** + * Create the pending transaction speculative table from transactions that are still in the pending transaction buffer + */ + private void updatePendingTransactionSpeculativeTable(boolean didProcessNewCommitsOrSpeculate) { + if (pendingTransactionQueue.size() == 0) { + // There is nothing to speculate on + return; + } - if (didCommitOrSpeculate) { - pendingTransSpeculativeTable.clear(); - lastSeenPendingTransactionSpeculateIndex = 0; - int index = 0; - for (PendingTransaction pt : pendingTransQueue) { - if (pt.evaluateGuard(commitedTable, speculativeTable, pendingTransSpeculativeTable)) { + if (didProcessNewCommitsOrSpeculate || (firstPendingTransaction != pendingTransactionQueue.get(0))) { + // need to reset on the pending speculation + lastPendingTransactionSpeculatedOn = null; + firstPendingTransaction = pendingTransactionQueue.get(0); + pendingTransactionSpeculatedKeyValueTable.clear(); + } - lastSeenPendingTransactionSpeculateIndex = index; - index++; + // Find where to start arbitration from + int startIndex = pendingTransactionQueue.indexOf(firstPendingTransaction) + 1; - for (KeyValue kv : pt.getKVUpdates()) { - pendingTransSpeculativeTable.put(kv.getKey(), kv); - } + if (startIndex >= pendingTransactionQueue.size()) { + // Make sure we are not out of bounds + return; + } + + for (int i = startIndex; i < pendingTransactionQueue.size(); i++) { + Transaction transaction = pendingTransactionQueue.get(i); + lastPendingTransactionSpeculatedOn = transaction; + + if (transaction.evaluateGuard(committedKeyValueTable, speculatedKeyValueTable, pendingTransactionSpeculatedKeyValueTable)) { + // Guard evaluated to true so update the speculative table + for (KeyValue kv : transaction.getKeyValueUpdateSet()) { + pendingTransactionSpeculatedKeyValueTable.put(kv.getKey(), kv); } } } } - private int expectedsize, currmaxsize; + /** + * Set dead and remove from the live transaction tables the transactions that are dead + */ + private void updateLiveTransactionsAndStatus() { - private void checkNumSlots(int numslots) { + // Go through each of the transactions + for (Iterator<Map.Entry<Long, Transaction>> iter = liveTransactionBySequenceNumberTable.entrySet().iterator(); iter.hasNext();) { + Transaction transaction = iter.next().getValue(); + // Check if the transaction is dead + Long lastTransactionNumber = lastArbitratedTransactionNumberByArbitratorTable.get(transaction.getArbitrator()); + if ((lastTransactionNumber != null) && (lastTransactionNumber >= transaction.getSequenceNumber())) { - // We only have 1 size so we must have this many slots - if (largestTableStatusSeen == smallestTableStatusSeen) { - if (numslots != smallestTableStatusSeen) { - throw new Error("Server Error: Server did not send all slots. Expected: " + smallestTableStatusSeen + " Received:" + numslots); - } - } else { - // We have more than 1 - if (numslots < smallestTableStatusSeen) { - throw new Error("Server Error: Server did not send all slots. Expected at least: " + smallestTableStatusSeen + " Received:" + numslots); + // Set dead the transaction + transaction.setDead(); + + // Remove the transaction from the live table + iter.remove(); + liveTransactionByTransactionIdTable.remove(transaction.getId()); } } - // if (numslots != expectedsize) { - // throw new Error("Server Error: Server did not send all slots. Expected: " + expectedsize + " Received:" + numslots); - // } - } + // Go through each of the transactions + for (Iterator<Map.Entry<Long, TransactionStatus>> iter = outstandingTransactionStatus.entrySet().iterator(); iter.hasNext();) { + TransactionStatus status = iter.next().getValue(); - private void initExpectedSize(long firstsequencenumber) { - long prevslots = firstsequencenumber; - expectedsize = (prevslots < ((long) numslots)) ? (int) prevslots : numslots; - currmaxsize = numslots; + // Check if the transaction is dead + Long lastTransactionNumber = lastArbitratedTransactionNumberByArbitratorTable.get(status.getTransactionArbitrator()); + if ((lastTransactionNumber != null) && (lastTransactionNumber >= status.getTransactionSequenceNumber())) { + + // Set committed + status.setStatus(TransactionStatus.StatusCommitted); + + // Remove + iter.remove(); + } + } } - private void updateExpectedSize() { - expectedsize++; - if (expectedsize > currmaxsize) { - expectedsize = currmaxsize; + + /** + * Process this slot, entry by entry. Also update the latest message sent by slot + */ + private void processSlot(SlotIndexer indexer, Slot slot, boolean acceptUpdatesToLocal, HashSet<Long> machineSet) { + + // Update the last message seen + updateLastMessage(slot.getMachineID(), slot.getSequenceNumber(), slot, acceptUpdatesToLocal, machineSet); + + // Process each entry in the slot + for (Entry entry : slot.getEntries()) { + switch (entry.getType()) { + + case Entry.TypeCommitPart: + processEntry((CommitPart)entry); + break; + + case Entry.TypeAbort: + processEntry((Abort)entry); + break; + + case Entry.TypeTransactionPart: + processEntry((TransactionPart)entry); + break; + + case Entry.TypeNewKey: + processEntry((NewKey)entry); + break; + + case Entry.TypeLastMessage: + processEntry((LastMessage)entry, machineSet); + break; + + case Entry.TypeRejectedMessage: + processEntry((RejectedMessage)entry, indexer); + break; + + case Entry.TypeTableStatus: + processEntry((TableStatus)entry); + break; + + default: + throw new Error("Unrecognized type: " + entry.getType()); + } } } - private void updateCurrMaxSize(int newmaxsize) { - currmaxsize = newmaxsize; + /** + * Update the last message that was sent for a machine Id + */ + private void processEntry(LastMessage entry, HashSet<Long> machineSet) { + // Update what the last message received by a machine was + updateLastMessage(entry.getMachineID(), entry.getSequenceNumber(), entry, false, machineSet); } - private void commitNewMaxSize() { + /** + * Add the new key to the arbitrators table and update the set of live new keys (in case of a rescued new key message) + */ + private void processEntry(NewKey entry) { - if (largestTableStatusSeen == -1) { - currmaxsize = numslots; - } else { - currmaxsize = largestTableStatusSeen; + // Update the arbitrator table with the new key information + arbitratorTable.put(entry.getKey(), entry.getMachineID()); + + // Update what the latest live new key is + NewKey oldNewKey = liveNewKeyTable.put(entry.getKey(), entry); + if (oldNewKey != null) { + // Delete the old new key messages + oldNewKey.setDead(); } + } + + /** + * Process new table status entries and set dead the old ones as new ones come in. + * keeps track of the largest and smallest table status seen in this current round + * of updating the local copy of the block chain + */ + private void processEntry(TableStatus entry) { + int newNumSlots = entry.getMaxSlots(); - if (numslots != currmaxsize) { - buffer.resize(currmaxsize); + if (liveTableStatus != null) { + // We have a larger table status so the old table status is no longer alive + liveTableStatus.setDead(); } - numslots = currmaxsize; - setResizeThreshold(); - } + // Make this new table status the latest alive table status + liveTableStatus = entry; - private void processEntry(LastMessage entry, HashSet<Long> machineSet) { - updateLastMessage(entry.getMachineID(), entry.getSequenceNumber(), entry, false, machineSet); + if ((smallestTableStatusSeen == -1) || (newNumSlots < smallestTableStatusSeen)) { + smallestTableStatusSeen = newNumSlots; + } + + if ((largestTableStatusSeen == -1) || (newNumSlots > largestTableStatusSeen)) { + largestTableStatusSeen = newNumSlots; + } } + /** + * Check old messages to see if there is a block chain violation. Also + */ private void processEntry(RejectedMessage entry, SlotIndexer indexer) { - long oldseqnum = entry.getOldSeqNum(); - long newseqnum = entry.getNewSeqNum(); + long oldSeqNum = entry.getOldSeqNum(); + long newSeqNum = entry.getNewSeqNum(); boolean isequal = entry.getEqual(); - long machineid = entry.getMachineID(); - for (long seqnum = oldseqnum; seqnum <= newseqnum; seqnum++) { - Slot slot = indexer.getSlot(seqnum); + long machineId = entry.getMachineID(); + + + // Check if we have messages that were supposed to be rejected in our local block chain + for (long seqNum = oldSeqNum; seqNum <= newSeqNum; seqNum++) { + + // Get the slot + Slot slot = indexer.getSlot(seqNum); + if (slot != null) { - long slotmachineid = slot.getMachineID(); - if (isequal != (slotmachineid == machineid)) { - throw new Error("Server Error: Trying to insert rejected message for slot " + seqnum); + // If we have this slot make sure that it was not supposed to be a rejected slot + + long slotMachineId = slot.getMachineID(); + if (isequal != (slotMachineId == machineId)) { + throw new Error("Server Error: Trying to insert rejected message for slot " + seqNum); } } } - HashSet<Long> watchset = new HashSet<Long>(); - for (Map.Entry<Long, Pair<Long, Liveness> > lastmsg_entry : lastmessagetable.entrySet()) { - long entry_mid = lastmsg_entry.getKey(); - /* We've seen it, don't need to continue to watch. Our next - * message will implicitly acknowledge it. */ - if (entry_mid == localmachineid) - continue; - Pair<Long, Liveness> v = lastmsg_entry.getValue(); - long entry_seqn = v.getFirst(); - if (entry_seqn < newseqnum) { - addWatchList(entry_mid, entry); - watchset.add(entry_mid); - } - } - if (watchset.isEmpty()) - entry.setDead(); - else - entry.setWatchSet(watchset); - } - private void processEntry(NewKey entry) { - arbitratorTable.put(entry.getKey(), entry.getMachineID()); + // Create a list of clients to watch until they see this rejected message entry. + HashSet<Long> deviceWatchSet = new HashSet<Long>(); + for (Map.Entry<Long, Pair<Long, Liveness>> lastMessageEntry : lastMessageTable.entrySet()) { - NewKey oldNewKey = newKeyTable.put(entry.getKey(), entry); + // Machine ID for the last message entry + long lastMessageEntryMachineId = lastMessageEntry.getKey(); - if (oldNewKey != null) { - oldNewKey.setDead(); - } - } + // We've seen it, don't need to continue to watch. Our next + // message will implicitly acknowledge it. + if (lastMessageEntryMachineId == localMachineId) { + continue; + } - private void processEntry(Transaction entry) { + Pair<Long, Liveness> lastMessageValue = lastMessageEntry.getValue(); + long entrySequenceNumber = lastMessageValue.getFirst(); - long arb = entry.getArbitrator(); - Long comLast = lastCommitSeenTransSeqNumMap.get(arb); - Long abLast = lastAbortSeenSeqNumMap.get(arb); + if (entrySequenceNumber < newSeqNum) { - Transaction prevTrans = null; + // Add this rejected message to the set of messages that this machine ID did not see yet + addWatchList(lastMessageEntryMachineId, entry); - if ((comLast != null) && (comLast >= entry.getSequenceNumber())) { - prevTrans = uncommittedTransactionsMap.remove(entry.getSequenceNumber()); - } else if ((abLast != null) && (abLast >= entry.getSequenceNumber())) { - prevTrans = uncommittedTransactionsMap.remove(entry.getSequenceNumber()); - } else { - prevTrans = uncommittedTransactionsMap.put(entry.getSequenceNumber(), entry); + // This client did not see this rejected message yet so add it to the watch set to monitor + deviceWatchSet.add(lastMessageEntryMachineId); + } } - // Duplicate so delete old copy - if (prevTrans != null) { - prevTrans.setDead(); + if (deviceWatchSet.isEmpty()) { + // This rejected message has been seen by all the clients so + entry.setDead(); + } else { + // We need to watch this rejected message + entry.setWatchSet(deviceWatchSet); } } + /** + * Check if this abort is live, if not then save it so we can kill it later. + * update the last transaction number that was arbitrated on. + */ private void processEntry(Abort entry) { - if (lastmessagetable.get(entry.getMachineID()).getFirst() < entry.getTransSequenceNumber()) { - // Abort has not been seen yet so we need to keep track of it - Abort prevAbort = abortMap.put(entry.getTransSequenceNumber(), entry); - if (prevAbort != null) { - prevAbort.setDead(); // delete old version of the duplicate - } + // Abort has not been seen by the client it is for yet so we need to keep track of it + Abort previouslySeenAbort = liveAbortTable.put(entry.getAbortId(), entry); + if (previouslySeenAbort != null) { + previouslySeenAbort.setDead(); // Delete old version of the abort since we got a rescued newer version + } + + if ((entry.getSequenceNumber() != -1) && (lastMessageTable.get(entry.getTransactionMachineId()).getFirst() >= entry.getSequenceNumber())) { - if ((lastAbortSeenSeqNumMap.get(entry.getTransArbitrator()) != null) && (entry.getTransSequenceNumber() > lastAbortSeenSeqNumMap.get(entry.getTransArbitrator()))) { - lastAbortSeenSeqNumMap.put(entry.getTransArbitrator(), entry.getTransSequenceNumber()); - } - } else { // The machine already saw this so it is dead entry.setDead(); + liveAbortTable.remove(entry); + return; } - // Update the status of the transaction and remove it since we are done with this transaction - TransactionStatus status = transactionStatusMap.remove(entry.getTransSequenceNumber()); + + // update the transaction status + TransactionStatus status = outstandingTransactionStatus.remove(entry.getTransactionSequenceNumber()); if (status != null) { status.setStatus(TransactionStatus.StatusAborted); } - } - private void processEntry(Commit entry) { - Map<Long, Commit> arbMap = newCommitMap.get(entry.getTransArbitrator()); - if (arbMap == null) { - arbMap = new HashMap<Long, Commit>(); + // Set dead a transaction if we can + Transaction transactionToSetDead = liveTransactionByTransactionIdTable.remove(new Pair<Long, Long>(entry.getTransactionMachineId(), entry.getTransactionClientLocalSequenceNumber())); + if (transactionToSetDead != null) { + liveTransactionBySequenceNumberTable.remove(transactionToSetDead.getSequenceNumber()); } - Commit prevCommit = arbMap.put(entry.getSequenceNumber(), entry); - newCommitMap.put(entry.getTransArbitrator(), arbMap); + // Update the last transaction sequence number that the arbitrator arbitrated on + Long lastTransactionNumber = lastArbitratedTransactionNumberByArbitratorTable.get(entry.getTransactionArbitrator()); + if ((lastTransactionNumber == null) || (lastTransactionNumber < entry.getTransactionSequenceNumber())) { - if (prevCommit != null) { - prevCommit.setDead(); + // Is a valid one + if (entry.getTransactionSequenceNumber() != -1) { + lastArbitratedTransactionNumberByArbitratorTable.put(entry.getTransactionArbitrator(), entry.getTransactionSequenceNumber()); + } } } - private void processEntry(TableStatus entry) { - int newnumslots = entry.getMaxSlots(); - // updateCurrMaxSize(newnumslots); - if (lastTableStatus != null) - lastTableStatus.setDead(); - lastTableStatus = entry; + /** + * Set dead the transaction part if that transaction is dead and keep track of all new parts + */ + private void processEntry(TransactionPart entry) { + // Check if we have already seen this transaction and set it dead OR if it is not alive + Long lastTransactionNumber = lastArbitratedTransactionNumberByArbitratorTable.get(entry.getArbitratorId()); + if ((lastTransactionNumber != null) && (lastTransactionNumber >= entry.getSequenceNumber())) { + // This transaction is dead, it was already committed or aborted + entry.setDead(); + return; + } - if ((smallestTableStatusSeen == -1) || (newnumslots < smallestTableStatusSeen)) { - smallestTableStatusSeen = newnumslots; + // This part is still alive + Map<Pair<Long, Integer>, TransactionPart> transactionPart = newTransactionParts.get(entry.getMachineId()); + + if (transactionPart == null) { + // Dont have a table for this machine Id yet so make one + transactionPart = new HashMap<Pair<Long, Integer>, TransactionPart>(); + newTransactionParts.put(entry.getMachineId(), transactionPart); } - if ((largestTableStatusSeen == -1) || (newnumslots > largestTableStatusSeen)) { - largestTableStatusSeen = newnumslots; + // Update the part and set dead ones we have already seen (got a rescued version) + TransactionPart previouslySeenPart = transactionPart.put(entry.getPartId(), entry); + if (previouslySeenPart != null) { + previouslySeenPart.setDead(); } } - private void addWatchList(long machineid, RejectedMessage entry) { - HashSet<RejectedMessage> entries = watchlist.get(machineid); - if (entries == null) - watchlist.put(machineid, entries = new HashSet<RejectedMessage>()); - entries.add(entry); + /** + * Process new commit entries and save them for future use. Delete duplicates + */ + private void processEntry(CommitPart entry) { + Map<Pair<Long, Integer>, CommitPart> commitPart = newCommitParts.get(entry.getMachineId()); + + if (commitPart == null) { + // Dont have a table for this machine Id yet so make one + commitPart = new HashMap<Pair<Long, Integer>, CommitPart>(); + newCommitParts.put(entry.getMachineId(), commitPart); + } + + // Update the part and set dead ones we have already seen (got a rescued version) + CommitPart previouslySeenPart = commitPart.put(entry.getPartId(), entry); + if (previouslySeenPart != null) { + previouslySeenPart.setDead(); + } } - private void updateLastMessage(long machineid, long seqnum, Liveness liveness, boolean acceptupdatestolocal, HashSet<Long> machineSet) { - machineSet.remove(machineid); + /** + * Update the last message seen table. Update and set dead the appropriate RejectedMessages as clients see them. + * Updates the live aborts, removes those that are dead and sets them dead. + * Check that the last message seen is correct and that there is no mismatch of our own last message or that + * other clients have not had a rollback on the last message. + */ + private void updateLastMessage(long machineId, long seqNum, Liveness liveness, boolean acceptUpdatesToLocal, HashSet<Long> machineSet) { - HashSet<RejectedMessage> watchset = watchlist.get(machineid); + // We have seen this machine ID + machineSet.remove(machineId); + + // Get the set of rejected messages that this machine Id is has not seen yet + HashSet<RejectedMessage> watchset = rejectedMessageWatchListTable.get(machineId); + + // If there is a rejected message that this machine Id has not seen yet if (watchset != null) { + + // Go through each rejected message that this machine Id has not seen yet for (Iterator<RejectedMessage> rmit = watchset.iterator(); rmit.hasNext(); ) { + RejectedMessage rm = rmit.next(); - if (rm.getNewSeqNum() <= seqnum) { - /* Remove it from our watchlist */ + + // If this machine Id has seen this rejected message... + if (rm.getNewSeqNum() <= seqNum) { + + // Remove it from our watchlist rmit.remove(); - /* Decrement machines that need to see this notification */ - rm.removeWatcher(machineid); + + // Decrement machines that need to see this notification + rm.removeWatcher(machineId); } } } - if (machineid == localmachineid) { - /* Our own messages are immediately dead. */ + // Set dead the abort + for (Iterator<Map.Entry<Pair<Long, Long>, Abort>> i = liveAbortTable.entrySet().iterator(); i.hasNext();) { + Abort abort = i.next().getValue(); + + if ((abort.getTransactionMachineId() == machineId) && (abort.getSequenceNumber() <= seqNum)) { + abort.setDead(); + i.remove(); + } + } + + + + if (machineId == localMachineId) { + // Our own messages are immediately dead. if (liveness instanceof LastMessage) { ((LastMessage)liveness).setDead(); } else if (liveness instanceof Slot) { @@ -1642,87 +1674,65 @@ final public class Table { } } - // Set dead the abort - for (Iterator<Map.Entry<Long, Abort>> i = abortMap.entrySet().iterator(); i.hasNext();) { - Abort abort = i.next().getValue(); - - if ((abort.getMachineID() == machineid) && (abort.getTransSequenceNumber() <= seqnum)) { - abort.setDead(); - i.remove(); - } + // Get the old last message for this device + Pair<Long, Liveness> lastMessageEntry = lastMessageTable.put(machineId, new Pair<Long, Liveness>(seqNum, liveness)); + if (lastMessageEntry == null) { + // If no last message then there is nothing else to process + return; } - Pair<Long, Liveness> lastmsgentry = lastmessagetable.put(machineid, new Pair<Long, Liveness>(seqnum, liveness)); - if (lastmsgentry == null) - return; + long lastMessageSeqNum = lastMessageEntry.getFirst(); + Liveness lastEntry = lastMessageEntry.getSecond(); - long lastmsgseqnum = lastmsgentry.getFirst(); - Liveness lastentry = lastmsgentry.getSecond(); - if (machineid != localmachineid) { - if (lastentry instanceof LastMessage) { - ((LastMessage)lastentry).setDead(); - } else if (lastentry instanceof Slot) { - ((Slot)lastentry).setDead(); + // If it is not our machine Id since we already set ours to dead + if (machineId != localMachineId) { + if (lastEntry instanceof LastMessage) { + ((LastMessage)lastEntry).setDead(); + } else if (lastEntry instanceof Slot) { + ((Slot)lastEntry).setDead(); } else { throw new Error("Unrecognized type"); } } - if (machineid == localmachineid) { - if (lastmsgseqnum != seqnum && !acceptupdatestolocal) - throw new Error("Server Error: Mismatch on local machine sequence number, needed: " + seqnum + " got: " + lastmsgseqnum); + // Make sure the server is not playing any games + if (machineId == localMachineId) { + + // We were not making any updates and we had a machine mismatch + if (lastMessageSeqNum != seqNum && !acceptUpdatesToLocal) { + throw new Error("Server Error: Mismatch on local machine sequence number, needed: " + seqNum + " got: " + lastMessageSeqNum); + } } else { - if (lastmsgseqnum > seqnum) + if (lastMessageSeqNum > seqNum) { throw new Error("Server Error: Rollback on remote machine sequence number"); + } } } - private void processSlot(SlotIndexer indexer, Slot slot, boolean acceptupdatestolocal, HashSet<Long> machineSet) { - updateLastMessage(slot.getMachineID(), slot.getSequenceNumber(), slot, acceptupdatestolocal, machineSet); - for (Entry entry : slot.getEntries()) { - switch (entry.getType()) { - - case Entry.TypeNewKey: - processEntry((NewKey)entry); - break; - - case Entry.TypeCommit: - processEntry((Commit)entry); - break; - - case Entry.TypeAbort: - processEntry((Abort)entry); - break; - - case Entry.TypeTransaction: - processEntry((Transaction)entry); - break; - - case Entry.TypeLastMessage: - processEntry((LastMessage)entry, machineSet); - break; - - case Entry.TypeRejectedMessage: - processEntry((RejectedMessage)entry, indexer); - break; - - case Entry.TypeTableStatus: - processEntry((TableStatus)entry); - break; - - default: - throw new Error("Unrecognized type: " + entry.getType()); - } + /** + * Add a rejected message entry to the watch set to keep track of which clients have seen that + * rejected message entry and which have not. + */ + private void addWatchList(long machineId, RejectedMessage entry) { + HashSet<RejectedMessage> entries = rejectedMessageWatchListTable.get(machineId); + if (entries == null) { + // There is no set for this machine ID yet so create one + entries = new HashSet<RejectedMessage>(); + rejectedMessageWatchListTable.put(machineId, entries); } + entries.add(entry); } - private void checkHMACChain(SlotIndexer indexer, Slot[] newslots) { - for (int i = 0; i < newslots.length; i++) { - Slot currslot = newslots[i]; - Slot prevslot = indexer.getSlot(currslot.getSequenceNumber() - 1); - if (prevslot != null && - !Arrays.equals(prevslot.getHMAC(), currslot.getPrevHMAC())) - throw new Error("Server Error: Invalid HMAC Chain" + currslot + " " + prevslot); + /** + * Check if the HMAC chain is not violated + */ + private void checkHMACChain(SlotIndexer indexer, Slot[] newSlots) { + for (int i = 0; i < newSlots.length; i++) { + Slot currSlot = newSlots[i]; + Slot prevSlot = indexer.getSlot(currSlot.getSequenceNumber() - 1); + if (prevSlot != null && + !Arrays.equals(prevSlot.getHMAC(), currSlot.getPrevHMAC())) + throw new Error("Server Error: Invalid HMAC Chain" + currSlot + " " + prevSlot); } } } \ No newline at end of file diff --git a/version2/src/java/iotcloud/Test.java b/version2/src/java/iotcloud/Test.java index 0de2e5c..64b9a90 100644 --- a/version2/src/java/iotcloud/Test.java +++ b/version2/src/java/iotcloud/Test.java @@ -11,1868 +11,1151 @@ import java.util.ArrayList; public class Test { - public static final int NUMBER_OF_TESTS = 100; - - public static void main(String[] args) throws ServerException, InterruptedException { - if (args[0].equals("2")) { - test2(); - } else if (args[0].equals("3")) { - test3(); - } else if (args[0].equals("4")) { - test4(); - } else if (args[0].equals("5")) { - test5(); - } else if (args[0].equals("6")) { - test6(); - } else if (args[0].equals("7")) { - test7(); - } else if (args[0].equals("8")) { - test8(); - } else if (args[0].equals("9")) { - test9(); - } else if (args[0].equals("10")) { - test10(); - } else if (args[0].equals("11")) { - test11(); - } - } - - - static void test11() throws InterruptedException { - - boolean foundError = false; - - // Setup the 2 clients - Table t1 = new Table("127.0.0.1", "http://127.0.0.1/test.iotcloud/", "reallysecret", 321); - while (true) { - try { - t1.initTable(); - break; - } catch (Exception e) {} - } - - Table t2 = new Table("127.0.0.1", "http://127.0.0.1/test.iotcloud/", "reallysecret", 351); - while (t2.update().getFirst() == false) {} - - LocalComm lc = new LocalComm(t1, t2); - t1.addLocalComm(t2.getId(), lc); - t2.addLocalComm(t1.getId(), lc); - - // Make the Keys - System.out.println("Setting up keys"); - for (int i = 0; i < 4; i++) { - String a = "a" + i; - String b = "b" + i; - String c = "c" + i; - String d = "d" + i; - IoTString ia = new IoTString(a); - IoTString ib = new IoTString(b); - IoTString ic = new IoTString(c); - IoTString id = new IoTString(d); - while (true) { - try { - t1.createNewKey(ia, 321); - break; - } catch (Exception e) {} - } - - while (true) { - try { - t1.createNewKey(ib, 351); - break; - } catch (Exception e) {} - } - - while (true) { - try { - t2.createNewKey(ic, 321); - break; - } catch (Exception e) {} - } - - while (true) { - try { - t2.createNewKey(id, 351); - break; - } catch (Exception e) {} - } - } - - // Do Updates for the keys - System.out.println("Setting Key-Values..."); - for (int t = 0; t < NUMBER_OF_TESTS; t++) { - for (int i = 0; i < 4; i++) { - String keyA = "a" + i; - String valueA = "a" + (i + t); - - IoTString iKeyA = new IoTString(keyA); - IoTString iValueA = new IoTString(valueA); - - t1.startTransaction(); - t1.addKV(iKeyA, iValueA); - t1.commitTransaction(); - } - } - - t2.updateFromLocal(321); - - System.out.println("Checking Key-Values..."); - for (int i = 0; i < 4; i++) { - - String keyA = "a" + i; - String valueA = "a" + (i + NUMBER_OF_TESTS - 1); - - IoTString iKeyA = new IoTString(keyA); - IoTString iValueA = new IoTString(valueA); - - IoTString testValA1 = t1.getCommitted(iKeyA); - IoTString testValA2 = t2.getCommitted(iKeyA); - - if ((testValA1 == null) || (testValA1.equals(iValueA) == false)) { - System.out.println("Key-Value t1 incorrect: " + keyA + " " + testValA1 + " " + iValueA); - foundError = true; - } - - if ((testValA2 == null) || (testValA2.equals(iValueA) == false)) { - System.out.println("Key-Value t2 incorrect: " + keyA + " " + testValA2 + " " + iValueA); - foundError = true; - } - } - - if (foundError) { - System.out.println("Found Errors..."); - } else { - System.out.println("No Errors Found..."); - } - } - - static void test10() throws InterruptedException { - - boolean foundError = false; - - // Setup the 2 clients - Table t1 = new Table("127.0.0.1", "http://127.0.0.1/test.iotcloud/", "reallysecret", 321); - while (true) { - try { - t1.rebuild(); - break; - } catch (Exception e) {} - } - - Table t2 = new Table("127.0.0.1", "http://127.0.0.1/test.iotcloud/", "reallysecret", 351); - while (true) { - try { - t2.rebuild(); - break; - } catch (Exception e) {} - } - - LocalComm lc = new LocalComm(t1, t2); - t1.addLocalComm(t2.getId(), lc); - t2.addLocalComm(t1.getId(), lc); - - - System.out.println("Checking Key-Values..."); - for (int i = 0; i < 4; i++) { - - String keyA = "a" + i; - String keyB = "b" + i; - String keyC = "c" + i; - String keyD = "d" + i; - String valueA = "a" + (i + NUMBER_OF_TESTS - 1); - String valueB = "b" + (i + NUMBER_OF_TESTS - 1); - String valueC = "c" + (i + NUMBER_OF_TESTS - 1); - String valueD = "d" + (i + NUMBER_OF_TESTS - 1); - - IoTString iKeyA = new IoTString(keyA); - IoTString iKeyB = new IoTString(keyB); - IoTString iKeyC = new IoTString(keyC); - IoTString iKeyD = new IoTString(keyD); - IoTString iValueA = new IoTString(valueA); - IoTString iValueB = new IoTString(valueB); - IoTString iValueC = new IoTString(valueC); - IoTString iValueD = new IoTString(valueD); - - - IoTString testValA1 = t1.getCommitted(iKeyA); - IoTString testValB1 = t1.getCommitted(iKeyB); - IoTString testValC1 = t1.getCommitted(iKeyC); - IoTString testValD1 = t1.getCommitted(iKeyD); - - IoTString testValA2 = t2.getCommitted(iKeyA); - IoTString testValB2 = t2.getCommitted(iKeyB); - IoTString testValC2 = t2.getCommitted(iKeyC); - IoTString testValD2 = t2.getCommitted(iKeyD); - - if ((testValA1 == null) || (testValA1.equals(iValueA) == false)) { - System.out.println("Key-Value t1 incorrect: " + keyA + " " + testValA1 + " " + iValueA); - foundError = true; - } - - if ((testValB1 == null) || (testValB1.equals(iValueB) == false)) { - System.out.println("Key-Value t1 incorrect: " + keyB + " " + testValB1 + " " + iValueB); - foundError = true; - } - - if ((testValC1 == null) || (testValC1.equals(iValueC) == false)) { - System.out.println("Key-Value t1 incorrect: " + keyC + " " + testValC1 + " " + iValueC); - foundError = true; - } - - if ((testValD1 == null) || (testValD1.equals(iValueD) == false)) { - System.out.println("Key-Value t1 incorrect: " + keyD + " " + testValD1 + " " + iValueD); - foundError = true; - } - - if ((testValA2 == null) || (testValA2.equals(iValueA) == false)) { - System.out.println("Key-Value t2 incorrect: " + keyA + " " + testValA2 + " " + iValueA); - foundError = true; - } - - if ((testValB2 == null) || (testValB2.equals(iValueB) == false)) { - System.out.println("Key-Value t2 incorrect: " + keyB + " " + testValB2 + " " + iValueB); - foundError = true; - } - - if ((testValC2 == null) || (testValC2.equals(iValueC) == false)) { - System.out.println("Key-Value t2 incorrect: " + keyC + " " + testValC2 + " " + iValueC); - foundError = true; - } - - if ((testValD2 == null) || (testValD2.equals(iValueD) == false)) { - System.out.println("Key-Value t2 incorrect: " + keyD + " " + testValD2 + " " + iValueD); - foundError = true; - } - } - - if (foundError) { - System.out.println("Found Errors..."); - } else { - System.out.println("No Errors Found..."); - } - } - - static void test9() throws InterruptedException { - - boolean foundError = false; - List<TransactionStatus> transStatusList = new ArrayList<TransactionStatus>(); - - // Setup the 2 clients - Table t1 = new Table("127.0.0.1", "http://127.0.0.1/test.iotcloud/", "reallysecret", 321); - while (true) { - try { - t1.initTable(); - break; - } catch (Exception e) {} - } - - Table t2 = new Table("127.0.0.1", "http://127.0.0.1/test.iotcloud/", "reallysecret", 351); - while (t2.update().getFirst() == false) {} - - // LocalComm lc = new LocalComm(t1, t2); - // t1.addLocalComm(t2.getId(), lc); - // t2.addLocalComm(t1.getId(), lc); - - - - // Make the Keys - System.out.println("Setting up keys"); - for (int i = 0; i < 4; i++) { - String a = "a" + i; - String b = "b" + i; - String c = "c" + i; - String d = "d" + i; - IoTString ia = new IoTString(a); - IoTString ib = new IoTString(b); - IoTString ic = new IoTString(c); - IoTString id = new IoTString(d); - while (true) { - try { - t1.createNewKey(ia, 321); - break; - } catch (Exception e) {} - } - - while (true) { - try { - t1.createNewKey(ib, 351); - break; - } catch (Exception e) {} - } - - while (true) { - try { - t2.createNewKey(ic, 321); - break; - } catch (Exception e) {} - } - - while (true) { - try { - t2.createNewKey(id, 351); - break; - } catch (Exception e) {} - } - } - - // Do Updates for the keys - System.out.println("Setting Key-Values..."); - - for (int t = 0; t < NUMBER_OF_TESTS; t++) { - for (int i = 0; i < 4; i++) { - String keyB = "b" + i; - String valueB = "b" + (i + t); - - IoTString iKeyB = new IoTString(keyB); - IoTString iValueB = new IoTString(valueB); - - - t1.startTransaction(); - t1.getSpeculativeAtomic(iKeyB); - t1.addKV(iKeyB, iValueB); - transStatusList.add(t1.commitTransaction()); - } - } - - while (t1.update().getFirst() == false) {} - - - for (int i = 0; i < 4; i++) { - - String keyB = "b" + i; - String valueB = "b" + (i + NUMBER_OF_TESTS - 1); - IoTString iKeyB = new IoTString(keyB); - IoTString iValueB = new IoTString(valueB); - - IoTString testValB1 = t1.getSpeculative(iKeyB); - IoTString testValB2 = t2.getSpeculative(iKeyB); - - if ((testValB1 == null) || (testValB1.equals(iValueB) == false)) { - System.out.println("Key-Value t1 incorrect: " + keyB + " " + testValB1); - foundError = true; - } - - if ((testValB2 != null)) { - System.out.println("Key-Value t2 incorrect: " + keyB + " " + testValB2); - foundError = true; - } - } - - for (int t = 0; t < NUMBER_OF_TESTS; t++) { - for (int i = 0; i < 4; i++) { - String keyC = "c" + i; - String valueC = "c" + (i + t); - - IoTString iKeyC = new IoTString(keyC); - IoTString iValueC = new IoTString(valueC); - - t2.startTransaction(); - t2.getSpeculativeAtomic(iKeyC); - t2.addKV(iKeyC, iValueC); - transStatusList.add(t2.commitTransaction()); - } - } - - while (t2.update().getFirst() == false) {} - - for (int i = 0; i < 4; i++) { - String keyC = "c" + i; - String valueC = "c" + (i + NUMBER_OF_TESTS - 1); - IoTString iKeyC = new IoTString(keyC); - IoTString iValueC = new IoTString(valueC); - - IoTString testValC1 = t1.getSpeculative(iKeyC); - IoTString testValC2 = t2.getSpeculative(iKeyC); - - if ((testValC1 != null)) { - System.out.println("Key-Value t1 incorrect: " + keyC + " " + testValC1); - foundError = true; - } - - if ((testValC2 == null) || (testValC2.equals(iValueC) == false)) { - System.out.println("Key-Value t2 incorrect: " + keyC + " " + testValC2); - foundError = true; - } - } - - for (int t = 0; t < NUMBER_OF_TESTS; t++) { - for (int i = 0; i < 4; i++) { - String keyA = "a" + i; - String keyD = "d" + i; - String valueA = "a" + (i + t); - String valueD = "d" + (i + t); - - IoTString iKeyA = new IoTString(keyA); - IoTString iKeyD = new IoTString(keyD); - IoTString iValueA = new IoTString(valueA); - IoTString iValueD = new IoTString(valueD); - - - t1.startTransaction(); - t1.addKV(iKeyA, iValueA); - transStatusList.add(t1.commitTransaction()); - - t2.startTransaction(); - t2.addKV(iKeyD, iValueD); - transStatusList.add(t2.commitTransaction()); - } - } - - System.out.println("Updating Clients..."); - while (t1.update().getFirst() == false) {} - while (t2.update().getFirst() == false) {} - - System.out.println("Checking Key-Values..."); - for (int i = 0; i < 4; i++) { - - String keyA = "a" + i; - String keyB = "b" + i; - String keyC = "c" + i; - String keyD = "d" + i; - String valueA = "a" + (i + NUMBER_OF_TESTS - 1); - String valueB = "b" + (i + NUMBER_OF_TESTS - 1); - String valueC = "c" + (i + NUMBER_OF_TESTS - 1); - String valueD = "d" + (i + NUMBER_OF_TESTS - 1); - - IoTString iKeyA = new IoTString(keyA); - IoTString iKeyB = new IoTString(keyB); - IoTString iKeyC = new IoTString(keyC); - IoTString iKeyD = new IoTString(keyD); - IoTString iValueA = new IoTString(valueA); - IoTString iValueB = new IoTString(valueB); - IoTString iValueC = new IoTString(valueC); - IoTString iValueD = new IoTString(valueD); - - - IoTString testValA1 = t1.getCommitted(iKeyA); - IoTString testValB1 = t1.getCommitted(iKeyB); - IoTString testValC1 = t1.getCommitted(iKeyC); - IoTString testValD1 = t1.getCommitted(iKeyD); - - IoTString testValA2 = t2.getCommitted(iKeyA); - IoTString testValB2 = t2.getCommitted(iKeyB); - IoTString testValC2 = t2.getCommitted(iKeyC); - IoTString testValD2 = t2.getCommitted(iKeyD); - - if ((testValA1 == null) || (testValA1.equals(iValueA) == false)) { - System.out.println("Key-Value t1 incorrect: " + keyA + " " + testValA1 + " " + iValueA); - foundError = true; - } - - if ((testValB1 == null) || (testValB1.equals(iValueB) == false)) { - System.out.println("Key-Value t1 incorrect: " + keyB + " " + testValB1 + " " + iValueB); - foundError = true; - } - - if ((testValC1 == null) || (testValC1.equals(iValueC) == false)) { - System.out.println("Key-Value t1 incorrect: " + keyC + " " + testValC1 + " " + iValueC); - foundError = true; - } - - if ((testValD1 == null) || (testValD1.equals(iValueD) == false)) { - System.out.println("Key-Value t1 incorrect: " + keyD + " " + testValD1 + " " + iValueD); - foundError = true; - } - - if ((testValA2 == null) || (testValA2.equals(iValueA) == false)) { - System.out.println("Key-Value t2 incorrect: " + keyA + " " + testValA2 + " " + iValueA); - foundError = true; - } - - if ((testValB2 == null) || (testValB2.equals(iValueB) == false)) { - System.out.println("Key-Value t2 incorrect: " + keyB + " " + testValB2 + " " + iValueB); - foundError = true; - } - - if ((testValC2 == null) || (testValC2.equals(iValueC) == false)) { - System.out.println("Key-Value t2 incorrect: " + keyC + " " + testValC2 + " " + iValueC); - foundError = true; - } - - if ((testValD2 == null) || (testValD2.equals(iValueD) == false)) { - System.out.println("Key-Value t2 incorrect: " + keyD + " " + testValD2 + " " + iValueD); - foundError = true; - } - } - - for (TransactionStatus status : transStatusList) { - if (status.getStatus() != TransactionStatus.StatusCommitted) { - foundError = true; - System.out.println(status.getStatus()); - } - } - - if (foundError) { - System.out.println("Found Errors..."); - } else { - System.out.println("No Errors Found..."); - } - } - - static void test8() throws InterruptedException { - - boolean foundError = false; - List<TransactionStatus> transStatusList = new ArrayList<TransactionStatus>(); - - // Setup the 2 clients - Table t1 = new Table("127.0.0.1", "http://127.0.0.1/test.iotcloud/", "reallysecret", 321); - while (true) { - try { - t1.initTable(); - break; - } catch (Exception e) {} - } - - Table t2 = new Table("127.0.0.1", "http://127.0.0.1/test.iotcloud/", "reallysecret", 351); - while (t2.update().getFirst() == false) {} - - LocalComm lc = new LocalComm(t1, t2); - t1.addLocalComm(t2.getId(), lc); - t2.addLocalComm(t1.getId(), lc); - - // Make the Keys - System.out.println("Setting up keys"); - for (int i = 0; i < NUMBER_OF_TESTS; i++) { - System.out.println(i); - - String a = "a" + i; - String b = "b" + i; - String c = "c" + i; - String d = "d" + i; - IoTString ia = new IoTString(a); - IoTString ib = new IoTString(b); - IoTString ic = new IoTString(c); - IoTString id = new IoTString(d); - - while (true) { - try { - t1.createNewKey(ia, 321); - break; - } catch (Exception e) {} - } - - while (true) { - try { - t1.createNewKey(ib, 351); - break; - } catch (Exception e) {} - } - - while (true) { - try { - t2.createNewKey(ic, 321); - break; - } catch (Exception e) {} - } - - while (true) { - try { - t2.createNewKey(id, 351); - break; - } catch (Exception e) {} - } - } - - // Do Updates for the keys - System.out.println("Setting Key-Values..."); - for (int i = 0; i < NUMBER_OF_TESTS; i++) { - System.out.println(i); - String keyA = "a" + i; - String keyB = "b" + i; - String keyC = "c" + i; - String keyD = "d" + i; - String valueA = "a" + i; - String valueB = "b" + i; - String valueC = "c" + i; - String valueD = "d" + i; - - IoTString iKeyA = new IoTString(keyA); - IoTString iKeyB = new IoTString(keyB); - IoTString iKeyC = new IoTString(keyC); - IoTString iKeyD = new IoTString(keyD); - IoTString iValueA = new IoTString(valueA); - IoTString iValueB = new IoTString(valueB); - IoTString iValueC = new IoTString(valueC); - IoTString iValueD = new IoTString(valueD); - - - String keyAPrev = "a" + (i - 1); - String keyBPrev = "b" + (i - 1); - String keyCPrev = "c" + (i - 1); - String keyDPrev = "d" + (i - 1); - String valueAPrev = "a" + (i - 1); - String valueBPrev = "b" + (i - 1); - String valueCPrev = "c" + (i - 1); - String valueDPrev = "d" + (i - 1); - - IoTString iKeyAPrev = new IoTString(keyAPrev); - IoTString iKeyBPrev = new IoTString(keyBPrev); - IoTString iKeyCPrev = new IoTString(keyCPrev); - IoTString iKeyDPrev = new IoTString(keyDPrev); - IoTString iValueAPrev = new IoTString(valueAPrev); - IoTString iValueBPrev = new IoTString(valueBPrev); - IoTString iValueCPrev = new IoTString(valueCPrev); - IoTString iValueDPrev = new IoTString(valueDPrev); - - t1.startTransaction(); - if (i != 0) { - IoTString tmp = t1.getSpeculative(iKeyAPrev); - if ((tmp == null) || !tmp.equals(iValueAPrev)) { - System.out.println("Key a Error: " + i); - foundError = true; - } - } - t1.addKV(iKeyA, iValueA); - transStatusList.add(t1.commitTransaction()); - - - - t1.startTransaction(); - if (i != 0) { - IoTString tmp = t1.getSpeculative(iKeyBPrev); - if ((tmp == null) || !tmp.equals(iValueBPrev)) { - System.out.println("Key b Error: " + i); - foundError = true; - } - } - t1.addKV(iKeyB, iValueB); - transStatusList.add(t1.commitTransaction()); - - - t2.startTransaction(); - if (i != 0) { - IoTString tmp = t2.getSpeculative(iKeyCPrev); - if ((tmp == null) || !tmp.equals(iValueCPrev)) { - System.out.println("Key c Error: " + i); - foundError = true; - } - } - t2.addKV(iKeyC, iValueC); - transStatusList.add(t2.commitTransaction()); - - t2.startTransaction(); - if (i != 0) { - IoTString tmp = t2.getSpeculative(iKeyDPrev); - if ((tmp == null) || !tmp.equals(iValueDPrev)) { - System.out.println("Key d Error: " + i); - foundError = true; - } - } - t2.addKV(iKeyD, iValueD); - transStatusList.add(t2.commitTransaction()); - } - - - System.out.println("Updating..."); - while (t1.update().getFirst() == false) {} - while (t2.update().getFirst() == false) {} - - - System.out.println("Checking Key-Values..."); - for (int i = 0; i < NUMBER_OF_TESTS; i++) { - - String keyA = "a" + i; - String keyB = "b" + i; - String keyC = "c" + i; - String keyD = "d" + i; - String valueA = "a" + i; - String valueB = "b" + i; - String valueC = "c" + i; - String valueD = "d" + i; - - IoTString iKeyA = new IoTString(keyA); - IoTString iKeyB = new IoTString(keyB); - IoTString iKeyC = new IoTString(keyC); - IoTString iKeyD = new IoTString(keyD); - IoTString iValueA = new IoTString(valueA); - IoTString iValueB = new IoTString(valueB); - IoTString iValueC = new IoTString(valueC); - IoTString iValueD = new IoTString(valueD); - - - IoTString testValA1 = t1.getCommitted(iKeyA); - IoTString testValB1 = t1.getCommitted(iKeyB); - IoTString testValC1 = t1.getCommitted(iKeyC); - IoTString testValD1 = t1.getCommitted(iKeyD); - - IoTString testValA2 = t2.getCommitted(iKeyA); - IoTString testValB2 = t2.getCommitted(iKeyB); - IoTString testValC2 = t2.getCommitted(iKeyC); - IoTString testValD2 = t2.getCommitted(iKeyD); - - if ((testValA1 == null) || (testValA1.equals(iValueA) == false)) { - System.out.println("Key-Value t1 incorrect: " + keyA); - foundError = true; - } - - if ((testValB1 == null) || (testValB1.equals(iValueB) == false)) { - System.out.println("Key-Value t1 incorrect: " + keyB); - foundError = true; - } - - if ((testValC1 == null) || (testValC1.equals(iValueC) == false)) { - System.out.println("Key-Value t1 incorrect: " + keyC); - foundError = true; - } - - if ((testValD1 == null) || (testValD1.equals(iValueD) == false)) { - System.out.println("Key-Value t1 incorrect: " + keyD); - foundError = true; - } - - - if ((testValA2 == null) || (testValA2.equals(iValueA) == false)) { - System.out.println("Key-Value t2 incorrect: " + keyA + " " + testValA2); - foundError = true; - } - - if ((testValB2 == null) || (testValB2.equals(iValueB) == false)) { - System.out.println("Key-Value t2 incorrect: " + keyB + " " + testValB2); - foundError = true; - } - - if ((testValC2 == null) || (testValC2.equals(iValueC) == false)) { - System.out.println("Key-Value t2 incorrect: " + keyC + " " + testValC2); - foundError = true; - } - - if ((testValD2 == null) || (testValD2.equals(iValueD) == false)) { - System.out.println("Key-Value t2 incorrect: " + keyD + " " + testValD2); - foundError = true; - } - } - - for (TransactionStatus status : transStatusList) { - if (status.getStatus() != TransactionStatus.StatusCommitted) { - foundError = true; - } - } - if (foundError) { - System.out.println("Found Errors..."); - } else { - System.out.println("No Errors Found..."); - } - } - - static void test7() throws ServerException, InterruptedException { - - boolean foundError = false; - List<TransactionStatus> transStatusList = new ArrayList<TransactionStatus>(); - - // Setup the 2 clients - Table t1 = new Table("127.0.0.1", "http://127.0.0.1/test.iotcloud/", "reallysecret", 321); - t1.initTable(); - Table t2 = new Table("127.0.0.1", "http://127.0.0.1/test.iotcloud/", "reallysecret", 351); - t2.update(); - - // Make the Keys - System.out.println("Setting up keys"); - for (int i = 0; i < 4; i++) { - String a = "a" + i; - String b = "b" + i; - String c = "c" + i; - String d = "d" + i; - IoTString ia = new IoTString(a); - IoTString ib = new IoTString(b); - IoTString ic = new IoTString(c); - IoTString id = new IoTString(d); - t1.createNewKey(ia, 321); - t1.createNewKey(ib, 351); - t2.createNewKey(ic, 321); - t2.createNewKey(id, 351); - } - - // Do Updates for the keys - System.out.println("Setting Key-Values..."); - - for (int t = 0; t < NUMBER_OF_TESTS; t++) { - for (int i = 0; i < 4; i++) { - String keyB = "b" + i; - String valueB = "b" + (i + t); - - IoTString iKeyB = new IoTString(keyB); - IoTString iValueB = new IoTString(valueB); - - t1.startTransaction(); - t1.getSpeculativeAtomic(iKeyB); - t1.addKV(iKeyB, iValueB); - transStatusList.add(t1.commitTransaction()); - } - } - - for (int i = 0; i < 4; i++) { - - String keyB = "b" + i; - String valueB = "b" + (i + NUMBER_OF_TESTS - 1); - IoTString iKeyB = new IoTString(keyB); - IoTString iValueB = new IoTString(valueB); - - IoTString testValB1 = t1.getSpeculative(iKeyB); - IoTString testValB2 = t2.getSpeculative(iKeyB); - - if ((testValB1 == null) || (testValB1.equals(iValueB) == false)) { - System.out.println("Key-Value t1 incorrect: " + keyB + " " + testValB1); - foundError = true; - } - - if ((testValB2 != null)) { - System.out.println("Key-Value t2 incorrect: " + keyB + " " + testValB2); - foundError = true; - } - } - - for (int t = 0; t < NUMBER_OF_TESTS; t++) { - for (int i = 0; i < 4; i++) { - String keyC = "c" + i; - String valueC = "c" + (i + t); - - IoTString iKeyC = new IoTString(keyC); - IoTString iValueC = new IoTString(valueC); - - t2.startTransaction(); - t2.getSpeculativeAtomic(iKeyC); - t2.addKV(iKeyC, iValueC); - transStatusList.add(t2.commitTransaction()); - } - } - - for (int i = 0; i < 4; i++) { - String keyC = "c" + i; - String valueC = "c" + (i + NUMBER_OF_TESTS - 1); - IoTString iKeyC = new IoTString(keyC); - IoTString iValueC = new IoTString(valueC); - - IoTString testValC1 = t1.getSpeculative(iKeyC); - IoTString testValC2 = t2.getSpeculative(iKeyC); - - if ((testValC1 != null)) { - System.out.println("Key-Value t1 incorrect: " + keyC + " " + testValC1); - foundError = true; - } - - if ((testValC2 == null) || (testValC2.equals(iValueC) == false)) { - System.out.println("Key-Value t2 incorrect: " + keyC + " " + testValC2); - foundError = true; - } - } - - for (int t = 0; t < NUMBER_OF_TESTS; t++) { - for (int i = 0; i < 4; i++) { - String keyA = "a" + i; - String keyD = "d" + i; - String valueA = "a" + (i + t); - String valueD = "d" + (i + t); - - IoTString iKeyA = new IoTString(keyA); - IoTString iKeyD = new IoTString(keyD); - IoTString iValueA = new IoTString(valueA); - IoTString iValueD = new IoTString(valueD); - - t1.startTransaction(); - t1.addKV(iKeyA, iValueA); - transStatusList.add(t1.commitTransaction()); - - t2.startTransaction(); - t2.addKV(iKeyD, iValueD); - transStatusList.add(t2.commitTransaction()); - } - } - - System.out.println("Updating Clients..."); - t1.update(); - t2.update(); - t1.update(); - t2.update(); - - System.out.println("Checking Key-Values..."); - for (int i = 0; i < 4; i++) { - - String keyA = "a" + i; - String keyB = "b" + i; - String keyC = "c" + i; - String keyD = "d" + i; - String valueA = "a" + (i + NUMBER_OF_TESTS - 1); - String valueB = "b" + (i + NUMBER_OF_TESTS - 1); - String valueC = "c" + (i + NUMBER_OF_TESTS - 1); - String valueD = "d" + (i + NUMBER_OF_TESTS - 1); - - IoTString iKeyA = new IoTString(keyA); - IoTString iKeyB = new IoTString(keyB); - IoTString iKeyC = new IoTString(keyC); - IoTString iKeyD = new IoTString(keyD); - IoTString iValueA = new IoTString(valueA); - IoTString iValueB = new IoTString(valueB); - IoTString iValueC = new IoTString(valueC); - IoTString iValueD = new IoTString(valueD); - - - IoTString testValA1 = t1.getCommitted(iKeyA); - IoTString testValB1 = t1.getCommitted(iKeyB); - IoTString testValC1 = t1.getCommitted(iKeyC); - IoTString testValD1 = t1.getCommitted(iKeyD); - - IoTString testValA2 = t2.getCommitted(iKeyA); - IoTString testValB2 = t2.getCommitted(iKeyB); - IoTString testValC2 = t2.getCommitted(iKeyC); - IoTString testValD2 = t2.getCommitted(iKeyD); - - if ((testValA1 == null) || (testValA1.equals(iValueA) == false)) { - System.out.println("Key-Value t1 incorrect: " + keyA + " " + testValA1); - foundError = true; - } - - if ((testValB1 == null) || (testValB1.equals(iValueB) == false)) { - System.out.println("Key-Value t1 incorrect: " + keyB + " " + testValB1); - foundError = true; - } - - if ((testValC1 == null) || (testValC1.equals(iValueC) == false)) { - System.out.println("Key-Value t1 incorrect: " + keyC + " " + testValC1); - foundError = true; - } - - if ((testValD1 == null) || (testValD1.equals(iValueD) == false)) { - System.out.println("Key-Value t1 incorrect: " + keyD + " " + testValD1); - foundError = true; - } - - if ((testValA2 == null) || (testValA2.equals(iValueA) == false)) { - System.out.println("Key-Value t2 incorrect: " + keyA + " " + testValA2); - foundError = true; - } - - if ((testValB2 == null) || (testValB2.equals(iValueB) == false)) { - System.out.println("Key-Value t2 incorrect: " + keyB + " " + testValB2); - foundError = true; - } - - if ((testValC2 == null) || (testValC2.equals(iValueC) == false)) { - System.out.println("Key-Value t2 incorrect: " + keyC + " " + testValC2); - foundError = true; - } - - if ((testValD2 == null) || (testValD2.equals(iValueD) == false)) { - System.out.println("Key-Value t2 incorrect: " + keyD + " " + testValD2); - foundError = true; - } - } - - for (TransactionStatus status : transStatusList) { - if (status.getStatus() != TransactionStatus.StatusCommitted) { - foundError = true; - } - } - - if (foundError) { - System.out.println("Found Errors..."); - } else { - System.out.println("No Errors Found..."); - } - } - - static void test6() throws ServerException, InterruptedException { - - boolean foundError = false; - List<TransactionStatus> transStatusList = new ArrayList<TransactionStatus>(); - - // Setup the 2 clients - Table t1 = new Table("127.0.0.1", "http://127.0.0.1/test.iotcloud/", "reallysecret", 321); - t1.initTable(); - Table t2 = new Table("127.0.0.1", "http://127.0.0.1/test.iotcloud/", "reallysecret", 351); - t2.update(); - - // Make the Keys - System.out.println("Setting up keys"); - for (int i = 0; i < NUMBER_OF_TESTS; i++) { - String a = "a" + i; - String b = "b" + i; - String c = "c" + i; - String d = "d" + i; - IoTString ia = new IoTString(a); - IoTString ib = new IoTString(b); - IoTString ic = new IoTString(c); - IoTString id = new IoTString(d); - t1.createNewKey(ia, 321); - t1.createNewKey(ib, 351); - t2.createNewKey(ic, 321); - t2.createNewKey(id, 351); - } - - // Do Updates for the keys - System.out.println("Setting Key-Values..."); - for (int i = 0; i < NUMBER_OF_TESTS; i++) { - String keyA = "a" + i; - String keyB = "b" + i; - String keyC = "c" + i; - String keyD = "d" + i; - String valueA = "a" + i; - String valueB = "b" + i; - String valueC = "c" + i; - String valueD = "d" + i; - - IoTString iKeyA = new IoTString(keyA); - IoTString iKeyB = new IoTString(keyB); - IoTString iKeyC = new IoTString(keyC); - IoTString iKeyD = new IoTString(keyD); - IoTString iValueA = new IoTString(valueA); - IoTString iValueB = new IoTString(valueB); - IoTString iValueC = new IoTString(valueC); - IoTString iValueD = new IoTString(valueD); - - t1.startTransaction(); - t1.getCommittedAtomic(iKeyA); - t1.addKV(iKeyA, iValueA); - transStatusList.add(t1.commitTransaction()); - - t1.startTransaction(); - t1.getCommittedAtomic(iKeyB); - t1.addKV(iKeyB, iValueB); - transStatusList.add(t1.commitTransaction()); - - t2.startTransaction(); - t2.getCommittedAtomic(iKeyC); - t2.addKV(iKeyC, iValueC); - transStatusList.add(t2.commitTransaction()); - - t2.startTransaction(); - t2.getCommittedAtomic(iKeyD); - t2.addKV(iKeyD, iValueD); - transStatusList.add(t2.commitTransaction()); - } - - System.out.println("Updating Clients..."); - t1.update(); - t2.update(); - - System.out.println("Checking Key-Values..."); - for (int i = 0; i < NUMBER_OF_TESTS; i++) { - - String keyA = "a" + i; - String keyB = "b" + i; - String keyC = "c" + i; - String keyD = "d" + i; - String valueA = "a" + i; - String valueB = "b" + i; - String valueC = "c" + i; - String valueD = "d" + i; - - IoTString iKeyA = new IoTString(keyA); - IoTString iKeyB = new IoTString(keyB); - IoTString iKeyC = new IoTString(keyC); - IoTString iKeyD = new IoTString(keyD); - IoTString iValueA = new IoTString(valueA); - IoTString iValueB = new IoTString(valueB); - IoTString iValueC = new IoTString(valueC); - IoTString iValueD = new IoTString(valueD); - - - IoTString testValA1 = t1.getCommitted(iKeyA); - IoTString testValB1 = t1.getCommitted(iKeyB); - IoTString testValC1 = t1.getCommitted(iKeyC); - IoTString testValD1 = t1.getCommitted(iKeyD); - - IoTString testValA2 = t2.getCommitted(iKeyA); - IoTString testValB2 = t2.getCommitted(iKeyB); - IoTString testValC2 = t2.getCommitted(iKeyC); - IoTString testValD2 = t2.getCommitted(iKeyD); - - if ((testValA1 == null) || (testValA1.equals(iValueA) == false)) { - System.out.println("Key-Value t1 incorrect: " + keyA); - foundError = true; - } - - if ((testValB1 == null) || (testValB1.equals(iValueB) == false)) { - System.out.println("Key-Value t1 incorrect: " + keyB); - foundError = true; - } - - if ((testValC1 == null) || (testValC1.equals(iValueC) == false)) { - System.out.println("Key-Value t1 incorrect: " + keyC); - foundError = true; - } - - if ((testValD1 == null) || (testValD1.equals(iValueD) == false)) { - System.out.println("Key-Value t1 incorrect: " + keyD); - foundError = true; - } - - - if ((testValA2 == null) || (testValA2.equals(iValueA) == false)) { - System.out.println("Key-Value t2 incorrect: " + keyA + " " + testValA2); - foundError = true; - } - - if ((testValB2 == null) || (testValB2.equals(iValueB) == false)) { - System.out.println("Key-Value t2 incorrect: " + keyB + " " + testValB2); - foundError = true; - } - - if ((testValC2 == null) || (testValC2.equals(iValueC) == false)) { - System.out.println("Key-Value t2 incorrect: " + keyC + " " + testValC2); - foundError = true; - } - - if ((testValD2 == null) || (testValD2.equals(iValueD) == false)) { - System.out.println("Key-Value t2 incorrect: " + keyD + " " + testValD2); - foundError = true; - } - } - - for (TransactionStatus status : transStatusList) { - if (status.getStatus() != TransactionStatus.StatusCommitted) { - foundError = true; - } - } - - if (foundError) { - System.out.println("Found Errors..."); - } else { - System.out.println("No Errors Found..."); - } - } - - static void test5() throws ServerException, InterruptedException { - - boolean foundError = false; - List<TransactionStatus> transStatusList = new ArrayList<TransactionStatus>(); - - // Setup the 2 clients - Table t1 = new Table("127.0.0.1", "http://127.0.0.1/test.iotcloud/", "reallysecret", 321); - t1.initTable(); - Table t2 = new Table("127.0.0.1", "http://127.0.0.1/test.iotcloud/", "reallysecret", 351); - t2.update(); - - - // Make the Keys - System.out.println("Setting up keys"); - for (int i = 0; i < 4; i++) { - String a = "a" + i; - String b = "b" + i; - String c = "c" + i; - String d = "d" + i; - IoTString ia = new IoTString(a); - IoTString ib = new IoTString(b); - IoTString ic = new IoTString(c); - IoTString id = new IoTString(d); - t1.createNewKey(ia, 321); - t1.createNewKey(ib, 351); - t2.createNewKey(ic, 321); - t2.createNewKey(id, 351); - } - - // Do Updates for the keys - System.out.println("Setting Key-Values..."); - - for (int t = 0; t < NUMBER_OF_TESTS; t++) { - for (int i = 0; i < 4; i++) { - String keyB = "b" + i; - String valueB = "b" + (i + t); - - IoTString iKeyB = new IoTString(keyB); - IoTString iValueB = new IoTString(valueB); - - t1.startTransaction(); - t1.addKV(iKeyB, iValueB); - transStatusList.add(t1.commitTransaction()); - } - } - - for (int i = 0; i < 4; i++) { - - String keyB = "b" + i; - String valueB = "b" + (i + NUMBER_OF_TESTS - 1); - IoTString iKeyB = new IoTString(keyB); - IoTString iValueB = new IoTString(valueB); - - IoTString testValB1 = t1.getSpeculative(iKeyB); - IoTString testValB2 = t2.getSpeculative(iKeyB); - - if ((testValB1 == null) || (testValB1.equals(iValueB) == false)) { - System.out.println("Key-Value t1 incorrect: " + keyB); - foundError = true; - } - - if ((testValB2 != null)) { - System.out.println("Key-Value t2 incorrect: " + keyB); - foundError = true; - } - } - - for (int t = 0; t < NUMBER_OF_TESTS; t++) { - for (int i = 0; i < 4; i++) { - String keyC = "c" + i; - String valueC = "c" + (i + t); - - IoTString iKeyC = new IoTString(keyC); - IoTString iValueC = new IoTString(valueC); - - t2.startTransaction(); - t2.addKV(iKeyC, iValueC); - transStatusList.add(t2.commitTransaction()); - } - } - - for (int i = 0; i < 4; i++) { - String keyC = "c" + i; - String valueC = "c" + (i + NUMBER_OF_TESTS - 1); - IoTString iKeyC = new IoTString(keyC); - IoTString iValueC = new IoTString(valueC); - - IoTString testValC1 = t1.getSpeculative(iKeyC); - IoTString testValC2 = t2.getSpeculative(iKeyC); - - if ((testValC1 != null)) { - System.out.println("Key-Value t1 incorrect: " + keyC + " " + testValC1); - foundError = true; - } - - if ((testValC2 == null) || (testValC2.equals(iValueC) == false)) { - System.out.println("Key-Value t2 incorrect: " + keyC + " " + testValC2); - foundError = true; - } - } - - for (int t = 0; t < NUMBER_OF_TESTS; t++) { - for (int i = 0; i < 4; i++) { - String keyA = "a" + i; - String keyD = "d" + i; - String valueA = "a" + (i + t); - String valueD = "d" + (i + t); - - IoTString iKeyA = new IoTString(keyA); - IoTString iKeyD = new IoTString(keyD); - IoTString iValueA = new IoTString(valueA); - IoTString iValueD = new IoTString(valueD); - - t1.startTransaction(); - t1.addKV(iKeyA, iValueA); - transStatusList.add(t1.commitTransaction()); - - t2.startTransaction(); - t2.addKV(iKeyD, iValueD); - transStatusList.add(t2.commitTransaction()); - } - } - - System.out.println("Updating Clients..."); - t1.update(); - t2.update(); - - - System.out.println("Checking Key-Values..."); - for (int i = 0; i < 4; i++) { - - String keyA = "a" + i; - String keyB = "b" + i; - String keyC = "c" + i; - String keyD = "d" + i; - String valueA = "a" + (i + NUMBER_OF_TESTS - 1); - String valueB = "b" + (i + NUMBER_OF_TESTS - 1); - String valueC = "c" + (i + NUMBER_OF_TESTS - 1); - String valueD = "d" + (i + NUMBER_OF_TESTS - 1); - - IoTString iKeyA = new IoTString(keyA); - IoTString iKeyB = new IoTString(keyB); - IoTString iKeyC = new IoTString(keyC); - IoTString iKeyD = new IoTString(keyD); - IoTString iValueA = new IoTString(valueA); - IoTString iValueB = new IoTString(valueB); - IoTString iValueC = new IoTString(valueC); - IoTString iValueD = new IoTString(valueD); - - - IoTString testValA1 = t1.getCommitted(iKeyA); - IoTString testValB1 = t1.getCommitted(iKeyB); - IoTString testValC1 = t1.getCommitted(iKeyC); - IoTString testValD1 = t1.getCommitted(iKeyD); - - IoTString testValA2 = t2.getCommitted(iKeyA); - IoTString testValB2 = t2.getCommitted(iKeyB); - IoTString testValC2 = t2.getCommitted(iKeyC); - IoTString testValD2 = t2.getCommitted(iKeyD); - - if ((testValA1 == null) || (testValA1.equals(iValueA) == false)) { - System.out.println("Key-Value t1 incorrect: " + keyA + " " + testValA1); - foundError = true; - } - - if ((testValB1 == null) || (testValB1.equals(iValueB) == false)) { - System.out.println("Key-Value t1 incorrect: " + keyB + " " + testValB1); - foundError = true; - } - - if ((testValC1 == null) || (testValC1.equals(iValueC) == false)) { - System.out.println("Key-Value t1 incorrect: " + keyC + " " + testValC1); - foundError = true; - } - - if ((testValD1 == null) || (testValD1.equals(iValueD) == false)) { - System.out.println("Key-Value t1 incorrect: " + keyD + " " + testValD1); - foundError = true; - } - - - if ((testValA2 == null) || (testValA2.equals(iValueA) == false)) { - System.out.println("Key-Value t2 incorrect: " + keyA + " " + testValA2); - foundError = true; - } - - if ((testValB2 == null) || (testValB2.equals(iValueB) == false)) { - System.out.println("Key-Value t2 incorrect: " + keyB + " " + testValB2); - foundError = true; - } - - if ((testValC2 == null) || (testValC2.equals(iValueC) == false)) { - System.out.println("Key-Value t2 incorrect: " + keyC + " " + testValC2); - foundError = true; - } - - if ((testValD2 == null) || (testValD2.equals(iValueD) == false)) { - System.out.println("Key-Value t2 incorrect: " + keyD + " " + testValD2); - foundError = true; - } - } - - for (TransactionStatus status : transStatusList) { - if (status.getStatus() != TransactionStatus.StatusCommitted) { - foundError = true; - } - } - - if (foundError) { - System.out.println("Found Errors..."); - } else { - System.out.println("No Errors Found..."); - } - } - - static void test4() throws ServerException, InterruptedException { - - boolean foundError = false; - long startTime = 0; - long endTime = 0; - List<TransactionStatus> transStatusList = new ArrayList<TransactionStatus>(); - - // Setup the 2 clients - Table t1 = new Table("127.0.0.1", "http://127.0.0.1/test.iotcloud/", "reallysecret", 321); - t1.initTable(); - Table t2 = new Table("127.0.0.1", "http://127.0.0.1/test.iotcloud/", "reallysecret", 351); - t2.update(); - - // Make the Keys - System.out.println("Setting up keys"); - startTime = System.currentTimeMillis(); - for (int i = 0; i < 4; i++) { - String a = "a" + i; - String b = "b" + i; - String c = "c" + i; - String d = "d" + i; - IoTString ia = new IoTString(a); - IoTString ib = new IoTString(b); - IoTString ic = new IoTString(c); - IoTString id = new IoTString(d); - t1.createNewKey(ia, 321); - t1.createNewKey(ib, 351); - t2.createNewKey(ic, 321); - t2.createNewKey(id, 351); - } - endTime = System.currentTimeMillis(); - System.out.println("Time Taken: " + (double) ((endTime - startTime) / 1000.0) ); - System.out.println("Time Taken Per Key: " + (double) (((endTime - startTime) / 1000.0) / (NUMBER_OF_TESTS * 4)) ); - System.out.println(); - - - // Do Updates for the keys - System.out.println("Setting Key-Values..."); - startTime = System.currentTimeMillis(); - for (int t = 0; t < NUMBER_OF_TESTS; t++) { - for (int i = 0; i < 4; i++) { - String keyA = "a" + i; - String keyB = "b" + i; - String keyC = "c" + i; - String keyD = "d" + i; - String valueA = "a" + i; - String valueB = "b" + i; - String valueC = "c" + i; - String valueD = "d" + i; - - IoTString iKeyA = new IoTString(keyA); - IoTString iKeyB = new IoTString(keyB); - IoTString iKeyC = new IoTString(keyC); - IoTString iKeyD = new IoTString(keyD); - IoTString iValueA = new IoTString(valueA); - IoTString iValueB = new IoTString(valueB); - IoTString iValueC = new IoTString(valueC); - IoTString iValueD = new IoTString(valueD); - - t1.startTransaction(); - t1.addKV(iKeyA, iValueA); - transStatusList.add(t1.commitTransaction()); - - t1.startTransaction(); - t1.addKV(iKeyB, iValueB); - transStatusList.add(t1.commitTransaction()); - - t2.startTransaction(); - t2.addKV(iKeyC, iValueC); - transStatusList.add(t2.commitTransaction()); - - t2.startTransaction(); - t2.addKV(iKeyD, iValueD); - transStatusList.add(t2.commitTransaction()); - } - } - endTime = System.currentTimeMillis(); - System.out.println("Time Taken: " + (double) ((endTime - startTime) / 1000.0) ); - System.out.println("Time Taken Per Update: " + (double) (((endTime - startTime) / 1000.0) / (NUMBER_OF_TESTS * 16)) ); - System.out.println(); - - - System.out.println("Updating Clients..."); - t1.update(); - t2.update(); - - - System.out.println("Checking Key-Values..."); - for (int i = 0; i < 4; i++) { - - String keyA = "a" + i; - String keyB = "b" + i; - String keyC = "c" + i; - String keyD = "d" + i; - String valueA = "a" + i; - String valueB = "b" + i; - String valueC = "c" + i; - String valueD = "d" + i; - - IoTString iKeyA = new IoTString(keyA); - IoTString iKeyB = new IoTString(keyB); - IoTString iKeyC = new IoTString(keyC); - IoTString iKeyD = new IoTString(keyD); - IoTString iValueA = new IoTString(valueA); - IoTString iValueB = new IoTString(valueB); - IoTString iValueC = new IoTString(valueC); - IoTString iValueD = new IoTString(valueD); - - - IoTString testValA1 = t1.getCommitted(iKeyA); - IoTString testValB1 = t1.getCommitted(iKeyB); - IoTString testValC1 = t1.getCommitted(iKeyC); - IoTString testValD1 = t1.getCommitted(iKeyD); - - IoTString testValA2 = t2.getCommitted(iKeyA); - IoTString testValB2 = t2.getCommitted(iKeyB); - IoTString testValC2 = t2.getCommitted(iKeyC); - IoTString testValD2 = t2.getCommitted(iKeyD); - - if ((testValA1 == null) || (testValA1.equals(iValueA) == false)) { - System.out.println("Key-Value t1 incorrect: " + keyA); - foundError = true; - } - - if ((testValB1 == null) || (testValB1.equals(iValueB) == false)) { - System.out.println("Key-Value t1 incorrect: " + keyB); - foundError = true; - } - - if ((testValC1 == null) || (testValC1.equals(iValueC) == false)) { - System.out.println("Key-Value t1 incorrect: " + keyC); - foundError = true; - } - - if ((testValD1 == null) || (testValD1.equals(iValueD) == false)) { - System.out.println("Key-Value t1 incorrect: " + keyD); - foundError = true; - } - - - if ((testValA2 == null) || (testValA2.equals(iValueA) == false)) { - System.out.println("Key-Value t2 incorrect: " + keyA + " " + testValA2); - foundError = true; - } - - if ((testValB2 == null) || (testValB2.equals(iValueB) == false)) { - System.out.println("Key-Value t2 incorrect: " + keyB + " " + testValB2); - foundError = true; - } - - if ((testValC2 == null) || (testValC2.equals(iValueC) == false)) { - System.out.println("Key-Value t2 incorrect: " + keyC + " " + testValC2); - foundError = true; - } - - if ((testValD2 == null) || (testValD2.equals(iValueD) == false)) { - System.out.println("Key-Value t2 incorrect: " + keyD + " " + testValD2); - foundError = true; - } - } - - for (TransactionStatus status : transStatusList) { - if (status.getStatus() != TransactionStatus.StatusCommitted) { - foundError = true; - } - } - - if (foundError) { - System.out.println("Found Errors..."); - } else { - System.out.println("No Errors Found..."); - } - } - - static void test3() throws ServerException, InterruptedException { - - long startTime = 0; - long endTime = 0; - boolean foundError = false; - - List<TransactionStatus> transStatusList = new ArrayList<TransactionStatus>(); - - // Setup the 2 clients - Table t1 = new Table("127.0.0.1", "http://127.0.0.1/test.iotcloud/", "reallysecret", 321); - t1.initTable(); - Table t2 = new Table("127.0.0.1", "http://127.0.0.1/test.iotcloud/", "reallysecret", 351); - t2.update(); - - - // Make the Keys - System.out.println("Setting up keys"); - startTime = System.currentTimeMillis(); - for (int i = 0; i < NUMBER_OF_TESTS; i++) { - String a = "a" + i; - String b = "b" + i; - String c = "c" + i; - String d = "d" + i; - IoTString ia = new IoTString(a); - IoTString ib = new IoTString(b); - IoTString ic = new IoTString(c); - IoTString id = new IoTString(d); - t1.createNewKey(ia, 321); - t1.createNewKey(ib, 351); - t2.createNewKey(ic, 321); - t2.createNewKey(id, 351); - } - endTime = System.currentTimeMillis(); - System.out.println("Time Taken: " + (double) ((endTime - startTime) / 1000.0) ); - System.out.println("Time Taken Per Key: " + (double) (((endTime - startTime) / 1000.0) / (NUMBER_OF_TESTS * 4)) ); - System.out.println(); - - - // Do Updates for the keys - System.out.println("Setting Key-Values..."); - startTime = System.currentTimeMillis(); - for (int i = 0; i < NUMBER_OF_TESTS; i++) { - String keyB = "b" + i; - String valueB = "b" + i; - - IoTString iKeyB = new IoTString(keyB); - IoTString iValueB = new IoTString(valueB); - - t1.startTransaction(); - t1.addKV(iKeyB, iValueB); - transStatusList.add(t1.commitTransaction()); - } - - for (int i = 0; i < NUMBER_OF_TESTS; i++) { - String keyC = "c" + i; - String valueC = "c" + i; - - IoTString iKeyC = new IoTString(keyC); - IoTString iValueC = new IoTString(valueC); - - t2.startTransaction(); - t2.addKV(iKeyC, iValueC); - transStatusList.add(t2.commitTransaction()); - } - - for (int i = 0; i < NUMBER_OF_TESTS; i++) { - String keyA = "a" + i; - String keyD = "d" + i; - String valueA = "a" + i; - String valueD = "d" + i; - - IoTString iKeyA = new IoTString(keyA); - IoTString iKeyD = new IoTString(keyD); - IoTString iValueA = new IoTString(valueA); - IoTString iValueD = new IoTString(valueD); - - t1.startTransaction(); - t1.addKV(iKeyA, iValueA); - transStatusList.add(t1.commitTransaction()); - - t2.startTransaction(); - t2.addKV(iKeyD, iValueD); - transStatusList.add(t2.commitTransaction()); - } - endTime = System.currentTimeMillis(); - System.out.println("Time Taken: " + (double) ((endTime - startTime) / 1000.0) ); - System.out.println("Time Taken Per Update: " + (double) (((endTime - startTime) / 1000.0) / (NUMBER_OF_TESTS * 4)) ); - System.out.println(); - - - System.out.println("Updating Clients..."); - t1.update(); - t2.update(); - - - System.out.println("Checking Key-Values..."); - for (int i = 0; i < NUMBER_OF_TESTS; i++) { - - String keyA = "a" + i; - String keyB = "b" + i; - String keyC = "c" + i; - String keyD = "d" + i; - String valueA = "a" + i; - String valueB = "b" + i; - String valueC = "c" + i; - String valueD = "d" + i; - - IoTString iKeyA = new IoTString(keyA); - IoTString iKeyB = new IoTString(keyB); - IoTString iKeyC = new IoTString(keyC); - IoTString iKeyD = new IoTString(keyD); - IoTString iValueA = new IoTString(valueA); - IoTString iValueB = new IoTString(valueB); - IoTString iValueC = new IoTString(valueC); - IoTString iValueD = new IoTString(valueD); - - - IoTString testValA1 = t1.getCommitted(iKeyA); - IoTString testValB1 = t1.getCommitted(iKeyB); - IoTString testValC1 = t1.getCommitted(iKeyC); - IoTString testValD1 = t1.getCommitted(iKeyD); - - IoTString testValA2 = t2.getCommitted(iKeyA); - IoTString testValB2 = t2.getCommitted(iKeyB); - IoTString testValC2 = t2.getCommitted(iKeyC); - IoTString testValD2 = t2.getCommitted(iKeyD); - - if ((testValA1 == null) || (testValA1.equals(iValueA) == false)) { - System.out.println("Key-Value t1 incorrect: " + keyA); - foundError = true; - } - - if ((testValB1 == null) || (testValB1.equals(iValueB) == false)) { - System.out.println("Key-Value t1 incorrect: " + keyB); - foundError = true; - } - - if ((testValC1 == null) || (testValC1.equals(iValueC) == false)) { - System.out.println("Key-Value t1 incorrect: " + keyC); - foundError = true; - } - - if ((testValD1 == null) || (testValD1.equals(iValueD) == false)) { - System.out.println("Key-Value t1 incorrect: " + keyD); - foundError = true; - } - - - if ((testValA2 == null) || (testValA2.equals(iValueA) == false)) { - System.out.println("Key-Value t2 incorrect: " + keyA + " " + testValA2); - foundError = true; - } - - if ((testValB2 == null) || (testValB2.equals(iValueB) == false)) { - System.out.println("Key-Value t2 incorrect: " + keyB + " " + testValB2); - foundError = true; - } - - if ((testValC2 == null) || (testValC2.equals(iValueC) == false)) { - System.out.println("Key-Value t2 incorrect: " + keyC + " " + testValC2); - foundError = true; - } - - if ((testValD2 == null) || (testValD2.equals(iValueD) == false)) { - System.out.println("Key-Value t2 incorrect: " + keyD + " " + testValD2); - foundError = true; - } - } - - for (TransactionStatus status : transStatusList) { - if (status.getStatus() != TransactionStatus.StatusCommitted) { - foundError = true; - } - } - - if (foundError) { - System.out.println("Found Errors..."); - } else { - System.out.println("No Errors Found..."); - } - } - - static void test2() throws ServerException, InterruptedException { - - boolean foundError = false; - long startTime = 0; - long endTime = 0; - List<TransactionStatus> transStatusList = new ArrayList<TransactionStatus>(); - - // Setup the 2 clients - Table t1 = new Table("127.0.0.1", "http://127.0.0.1/test.iotcloud/", "reallysecret", 321); - t1.initTable(); - System.out.println("T1 Ready"); - - Table t2 = new Table("127.0.0.1", "http://127.0.0.1/test.iotcloud/", "reallysecret", 351); - t2.update(); - System.out.println("T2 Ready"); - - // Make the Keys - System.out.println("Setting up keys"); - startTime = System.currentTimeMillis(); - for (int i = 0; i < NUMBER_OF_TESTS; i++) { - String a = "a" + i; - String b = "b" + i; - String c = "c" + i; - String d = "d" + i; - IoTString ia = new IoTString(a); - IoTString ib = new IoTString(b); - IoTString ic = new IoTString(c); - IoTString id = new IoTString(d); - t1.createNewKey(ia, 321); - t1.createNewKey(ib, 351); - t2.createNewKey(ic, 321); - t2.createNewKey(id, 351); - } - endTime = System.currentTimeMillis(); - System.out.println("Time Taken: " + (double) ((endTime - startTime) / 1000.0) ); - System.out.println("Time Taken Per Key: " + (double) (((endTime - startTime) / 1000.0) / (NUMBER_OF_TESTS * 4)) ); - System.out.println(); - - // Do Updates for the keys - System.out.println("Setting Key-Values..."); - startTime = System.currentTimeMillis(); - for (int i = 0; i < NUMBER_OF_TESTS; i++) { - String keyA = "a" + i; - String keyB = "b" + i; - String keyC = "c" + i; - String keyD = "d" + i; - String valueA = "a" + i; - String valueB = "b" + i; - String valueC = "c" + i; - String valueD = "d" + i; - - IoTString iKeyA = new IoTString(keyA); - IoTString iKeyB = new IoTString(keyB); - IoTString iKeyC = new IoTString(keyC); - IoTString iKeyD = new IoTString(keyD); - IoTString iValueA = new IoTString(valueA); - IoTString iValueB = new IoTString(valueB); - IoTString iValueC = new IoTString(valueC); - IoTString iValueD = new IoTString(valueD); - - t1.startTransaction(); - t1.addKV(iKeyA, iValueA); - transStatusList.add(t1.commitTransaction()); - - t1.startTransaction(); - t1.addKV(iKeyB, iValueB); - transStatusList.add(t1.commitTransaction()); - - t2.startTransaction(); - t2.addKV(iKeyC, iValueC); - transStatusList.add(t2.commitTransaction()); - - t2.startTransaction(); - t2.addKV(iKeyD, iValueD); - transStatusList.add(t2.commitTransaction()); - } - endTime = System.currentTimeMillis(); - System.out.println("Time Taken: " + (double) ((endTime - startTime) / 1000.0) ); - System.out.println("Time Taken Per Update: " + (double) (((endTime - startTime) / 1000.0) / (NUMBER_OF_TESTS * 4)) ); - System.out.println(); - - - System.out.println("Updating Clients..."); - t1.update(); - t2.update(); - - - System.out.println("Checking Key-Values..."); - for (int i = 0; i < NUMBER_OF_TESTS; i++) { - - String keyA = "a" + i; - String keyB = "b" + i; - String keyC = "c" + i; - String keyD = "d" + i; - String valueA = "a" + i; - String valueB = "b" + i; - String valueC = "c" + i; - String valueD = "d" + i; - - IoTString iKeyA = new IoTString(keyA); - IoTString iKeyB = new IoTString(keyB); - IoTString iKeyC = new IoTString(keyC); - IoTString iKeyD = new IoTString(keyD); - IoTString iValueA = new IoTString(valueA); - IoTString iValueB = new IoTString(valueB); - IoTString iValueC = new IoTString(valueC); - IoTString iValueD = new IoTString(valueD); - - - IoTString testValA1 = t1.getCommitted(iKeyA); - IoTString testValB1 = t1.getCommitted(iKeyB); - IoTString testValC1 = t1.getCommitted(iKeyC); - IoTString testValD1 = t1.getCommitted(iKeyD); - - IoTString testValA2 = t2.getCommitted(iKeyA); - IoTString testValB2 = t2.getCommitted(iKeyB); - IoTString testValC2 = t2.getCommitted(iKeyC); - IoTString testValD2 = t2.getCommitted(iKeyD); - - if ((testValA1 == null) || (testValA1.equals(iValueA) == false)) { - System.out.println("Key-Value t1 incorrect: " + keyA); - foundError = true; - } - - if ((testValB1 == null) || (testValB1.equals(iValueB) == false)) { - System.out.println("Key-Value t1 incorrect: " + keyB); - foundError = true; - } - - if ((testValC1 == null) || (testValC1.equals(iValueC) == false)) { - System.out.println("Key-Value t1 incorrect: " + keyC); - foundError = true; - } - - if ((testValD1 == null) || (testValD1.equals(iValueD) == false)) { - System.out.println("Key-Value t1 incorrect: " + keyD); - foundError = true; - } - - - if ((testValA2 == null) || (testValA2.equals(iValueA) == false)) { - System.out.println("Key-Value t2 incorrect: " + keyA + " " + testValA2); - foundError = true; - } - - if ((testValB2 == null) || (testValB2.equals(iValueB) == false)) { - System.out.println("Key-Value t2 incorrect: " + keyB + " " + testValB2); - foundError = true; - } - - if ((testValC2 == null) || (testValC2.equals(iValueC) == false)) { - System.out.println("Key-Value t2 incorrect: " + keyC + " " + testValC2); - foundError = true; - } - - if ((testValD2 == null) || (testValD2.equals(iValueD) == false)) { - System.out.println("Key-Value t2 incorrect: " + keyD + " " + testValD2); - foundError = true; - } - } - - for (TransactionStatus status : transStatusList) { - if (status.getStatus() != TransactionStatus.StatusCommitted) { - foundError = true; - } - } - - if (foundError) { - System.out.println("Found Errors..."); - } else { - System.out.println("No Errors Found..."); - } - } + public static final int NUMBER_OF_TESTS = 100; + + public static void main(String[] args) throws ServerException { + if (args[0].equals("2")) { + test2(); + } else if (args[0].equals("3")) { + test3(); + } else if (args[0].equals("4")) { + test4(); + } else if (args[0].equals("5")) { + test5(); + } else if (args[0].equals("6")) { + test6(); + } else if (args[0].equals("7")) { + test7(); + } + } + + static void test7() throws ServerException { + + boolean foundError = false; + List<TransactionStatus> transStatusList = new ArrayList<TransactionStatus>(); + + // Setup the 2 clients + Table t1 = new Table("http://127.0.0.1/test.iotcloud/", "reallysecret", 321); + t1.initTable(); + Table t2 = new Table("http://127.0.0.1/test.iotcloud/", "reallysecret", 351); + t2.update(); + + // Make the Keys + System.out.println("Setting up keys"); + for (int i = 0; i < 4; i++) { + String a = "a" + i; + String b = "b" + i; + String c = "c" + i; + String d = "d" + i; + IoTString ia = new IoTString(a); + IoTString ib = new IoTString(b); + IoTString ic = new IoTString(c); + IoTString id = new IoTString(d); + t1.createNewKey(ia, 321); + t1.createNewKey(ib, 351); + t2.createNewKey(ic, 321); + t2.createNewKey(id, 351); + } + + // Do Updates for the keys + System.out.println("Setting Key-Values..."); + + for (int t = 0; t < NUMBER_OF_TESTS; t++) { + for (int i = 0; i < 4; i++) { + String keyB = "b" + i; + String valueB = "b" + (i + t); + + IoTString iKeyB = new IoTString(keyB); + IoTString iValueB = new IoTString(valueB); + + t1.startTransaction(); + t1.getSpeculativeAtomic(iKeyB); + t1.addKV(iKeyB, iValueB); + transStatusList.add(t1.commitTransaction()); + } + } + + for (int i = 0; i < 4; i++) { + + String keyB = "b" + i; + String valueB = "b" + (i + NUMBER_OF_TESTS - 1); + IoTString iKeyB = new IoTString(keyB); + IoTString iValueB = new IoTString(valueB); + + IoTString testValB1 = t1.getSpeculative(iKeyB); + IoTString testValB2 = t2.getSpeculative(iKeyB); + + if ((testValB1 == null) || (testValB1.equals(iValueB) == false)) { + System.out.println("Key-Value t1 incorrect: " + keyB + " " + testValB1); + foundError = true; + } + + if ((testValB2 != null)) { + System.out.println("Key-Value t2 incorrect: " + keyB + " " + testValB2); + foundError = true; + } + } + + for (int t = 0; t < NUMBER_OF_TESTS; t++) { + for (int i = 0; i < 4; i++) { + String keyC = "c" + i; + String valueC = "c" + (i + t); + + IoTString iKeyC = new IoTString(keyC); + IoTString iValueC = new IoTString(valueC); + + t2.startTransaction(); + t2.getSpeculativeAtomic(iKeyC); + t2.addKV(iKeyC, iValueC); + transStatusList.add(t2.commitTransaction()); + } + } + + for (int i = 0; i < 4; i++) { + String keyC = "c" + i; + String valueC = "c" + (i + NUMBER_OF_TESTS - 1); + IoTString iKeyC = new IoTString(keyC); + IoTString iValueC = new IoTString(valueC); + + IoTString testValC1 = t1.getSpeculative(iKeyC); + IoTString testValC2 = t2.getSpeculative(iKeyC); + + if ((testValC1 != null)) { + System.out.println("Key-Value t1 incorrect: " + keyC + " " + testValC1); + foundError = true; + } + + if ((testValC2 == null) || (testValC2.equals(iValueC) == false)) { + System.out.println("Key-Value t2 incorrect: " + keyC + " " + testValC2); + foundError = true; + } + } + + for (int t = 0; t < NUMBER_OF_TESTS; t++) { + for (int i = 0; i < 4; i++) { + String keyA = "a" + i; + String keyD = "d" + i; + String valueA = "a" + (i + t); + String valueD = "d" + (i + t); + + IoTString iKeyA = new IoTString(keyA); + IoTString iKeyD = new IoTString(keyD); + IoTString iValueA = new IoTString(valueA); + IoTString iValueD = new IoTString(valueD); + + t1.startTransaction(); + t1.addKV(iKeyA, iValueA); + transStatusList.add(t1.commitTransaction()); + + t2.startTransaction(); + t2.addKV(iKeyD, iValueD); + transStatusList.add(t2.commitTransaction()); + } + } + + System.out.println("Updating Clients..."); + t1.update(); + t2.update(); + t1.update(); + t2.update(); + + System.out.println("Checking Key-Values..."); + for (int i = 0; i < 4; i++) { + + String keyA = "a" + i; + String keyB = "b" + i; + String keyC = "c" + i; + String keyD = "d" + i; + String valueA = "a" + (i + NUMBER_OF_TESTS - 1); + String valueB = "b" + (i + NUMBER_OF_TESTS - 1); + String valueC = "c" + (i + NUMBER_OF_TESTS - 1); + String valueD = "d" + (i + NUMBER_OF_TESTS - 1); + + IoTString iKeyA = new IoTString(keyA); + IoTString iKeyB = new IoTString(keyB); + IoTString iKeyC = new IoTString(keyC); + IoTString iKeyD = new IoTString(keyD); + IoTString iValueA = new IoTString(valueA); + IoTString iValueB = new IoTString(valueB); + IoTString iValueC = new IoTString(valueC); + IoTString iValueD = new IoTString(valueD); + + + IoTString testValA1 = t1.getCommitted(iKeyA); + IoTString testValB1 = t1.getCommitted(iKeyB); + IoTString testValC1 = t1.getCommitted(iKeyC); + IoTString testValD1 = t1.getCommitted(iKeyD); + + IoTString testValA2 = t2.getCommitted(iKeyA); + IoTString testValB2 = t2.getCommitted(iKeyB); + IoTString testValC2 = t2.getCommitted(iKeyC); + IoTString testValD2 = t2.getCommitted(iKeyD); + + if ((testValA1 == null) || (testValA1.equals(iValueA) == false)) { + System.out.println("Key-Value t1 incorrect: " + keyA + " " + testValA1); + foundError = true; + } + + if ((testValB1 == null) || (testValB1.equals(iValueB) == false)) { + System.out.println("Key-Value t1 incorrect: " + keyB + " " + testValB1); + foundError = true; + } + + if ((testValC1 == null) || (testValC1.equals(iValueC) == false)) { + System.out.println("Key-Value t1 incorrect: " + keyC + " " + testValC1); + foundError = true; + } + + if ((testValD1 == null) || (testValD1.equals(iValueD) == false)) { + System.out.println("Key-Value t1 incorrect: " + keyD + " " + testValD1); + foundError = true; + } + + if ((testValA2 == null) || (testValA2.equals(iValueA) == false)) { + System.out.println("Key-Value t2 incorrect: " + keyA + " " + testValA2); + foundError = true; + } + + if ((testValB2 == null) || (testValB2.equals(iValueB) == false)) { + System.out.println("Key-Value t2 incorrect: " + keyB + " " + testValB2); + foundError = true; + } + + if ((testValC2 == null) || (testValC2.equals(iValueC) == false)) { + System.out.println("Key-Value t2 incorrect: " + keyC + " " + testValC2); + foundError = true; + } + + if ((testValD2 == null) || (testValD2.equals(iValueD) == false)) { + System.out.println("Key-Value t2 incorrect: " + keyD + " " + testValD2); + foundError = true; + } + } + + for (TransactionStatus status : transStatusList) { + if (status.getStatus() != TransactionStatus.StatusCommitted) { + foundError = true; + } + } + + if (foundError) { + System.out.println("Found Errors..."); + } else { + System.out.println("No Errors Found..."); + } + } + + static void test6() throws ServerException { + + boolean foundError = false; + List<TransactionStatus> transStatusList = new ArrayList<TransactionStatus>(); + + // Setup the 2 clients + Table t1 = new Table("http://127.0.0.1/test.iotcloud/", "reallysecret", 321); + t1.initTable(); + Table t2 = new Table("http://127.0.0.1/test.iotcloud/", "reallysecret", 351); + t2.update(); + + // Make the Keys + System.out.println("Setting up keys"); + for (int i = 0; i < NUMBER_OF_TESTS; i++) { + String a = "a" + i; + String b = "b" + i; + String c = "c" + i; + String d = "d" + i; + IoTString ia = new IoTString(a); + IoTString ib = new IoTString(b); + IoTString ic = new IoTString(c); + IoTString id = new IoTString(d); + t1.createNewKey(ia, 321); + t1.createNewKey(ib, 351); + t2.createNewKey(ic, 321); + t2.createNewKey(id, 351); + } + + // Do Updates for the keys + System.out.println("Setting Key-Values..."); + for (int i = 0; i < NUMBER_OF_TESTS; i++) { + String keyA = "a" + i; + String keyB = "b" + i; + String keyC = "c" + i; + String keyD = "d" + i; + String valueA = "a" + i; + String valueB = "b" + i; + String valueC = "c" + i; + String valueD = "d" + i; + + IoTString iKeyA = new IoTString(keyA); + IoTString iKeyB = new IoTString(keyB); + IoTString iKeyC = new IoTString(keyC); + IoTString iKeyD = new IoTString(keyD); + IoTString iValueA = new IoTString(valueA); + IoTString iValueB = new IoTString(valueB); + IoTString iValueC = new IoTString(valueC); + IoTString iValueD = new IoTString(valueD); + + t1.startTransaction(); + t1.getCommittedAtomic(iKeyA); + t1.addKV(iKeyA, iValueA); + transStatusList.add(t1.commitTransaction()); + + t1.startTransaction(); + t1.getCommittedAtomic(iKeyB); + t1.addKV(iKeyB, iValueB); + transStatusList.add(t1.commitTransaction()); + + t2.startTransaction(); + t2.getCommittedAtomic(iKeyC); + t2.addKV(iKeyC, iValueC); + transStatusList.add(t2.commitTransaction()); + + t2.startTransaction(); + t2.getCommittedAtomic(iKeyD); + t2.addKV(iKeyD, iValueD); + transStatusList.add(t2.commitTransaction()); + } + + System.out.println("Updating Clients..."); + t1.update(); + t2.update(); + + System.out.println("Checking Key-Values..."); + for (int i = 0; i < NUMBER_OF_TESTS; i++) { + + String keyA = "a" + i; + String keyB = "b" + i; + String keyC = "c" + i; + String keyD = "d" + i; + String valueA = "a" + i; + String valueB = "b" + i; + String valueC = "c" + i; + String valueD = "d" + i; + + IoTString iKeyA = new IoTString(keyA); + IoTString iKeyB = new IoTString(keyB); + IoTString iKeyC = new IoTString(keyC); + IoTString iKeyD = new IoTString(keyD); + IoTString iValueA = new IoTString(valueA); + IoTString iValueB = new IoTString(valueB); + IoTString iValueC = new IoTString(valueC); + IoTString iValueD = new IoTString(valueD); + + + IoTString testValA1 = t1.getCommitted(iKeyA); + IoTString testValB1 = t1.getCommitted(iKeyB); + IoTString testValC1 = t1.getCommitted(iKeyC); + IoTString testValD1 = t1.getCommitted(iKeyD); + + IoTString testValA2 = t2.getCommitted(iKeyA); + IoTString testValB2 = t2.getCommitted(iKeyB); + IoTString testValC2 = t2.getCommitted(iKeyC); + IoTString testValD2 = t2.getCommitted(iKeyD); + + if ((testValA1 == null) || (testValA1.equals(iValueA) == false)) { + System.out.println("Key-Value t1 incorrect: " + keyA); + foundError = true; + } + + if ((testValB1 == null) || (testValB1.equals(iValueB) == false)) { + System.out.println("Key-Value t1 incorrect: " + keyB); + foundError = true; + } + + if ((testValC1 == null) || (testValC1.equals(iValueC) == false)) { + System.out.println("Key-Value t1 incorrect: " + keyC); + foundError = true; + } + + if ((testValD1 == null) || (testValD1.equals(iValueD) == false)) { + System.out.println("Key-Value t1 incorrect: " + keyD); + foundError = true; + } + + + if ((testValA2 == null) || (testValA2.equals(iValueA) == false)) { + System.out.println("Key-Value t2 incorrect: " + keyA + " " + testValA2); + foundError = true; + } + + if ((testValB2 == null) || (testValB2.equals(iValueB) == false)) { + System.out.println("Key-Value t2 incorrect: " + keyB + " " + testValB2); + foundError = true; + } + + if ((testValC2 == null) || (testValC2.equals(iValueC) == false)) { + System.out.println("Key-Value t2 incorrect: " + keyC + " " + testValC2); + foundError = true; + } + + if ((testValD2 == null) || (testValD2.equals(iValueD) == false)) { + System.out.println("Key-Value t2 incorrect: " + keyD + " " + testValD2); + foundError = true; + } + } + + for (TransactionStatus status : transStatusList) { + if (status.getStatus() != TransactionStatus.StatusCommitted) { + foundError = true; + } + } + + if (foundError) { + System.out.println("Found Errors..."); + } else { + System.out.println("No Errors Found..."); + } + } + + static void test5() throws ServerException { + + boolean foundError = false; + List<TransactionStatus> transStatusList = new ArrayList<TransactionStatus>(); + + // Setup the 2 clients + Table t1 = new Table("http://127.0.0.1/test.iotcloud/", "reallysecret", 321); + t1.initTable(); + Table t2 = new Table("http://127.0.0.1/test.iotcloud/", "reallysecret", 351); + t2.update(); + + + // Make the Keys + System.out.println("Setting up keys"); + for (int i = 0; i < 4; i++) { + String a = "a" + i; + String b = "b" + i; + String c = "c" + i; + String d = "d" + i; + IoTString ia = new IoTString(a); + IoTString ib = new IoTString(b); + IoTString ic = new IoTString(c); + IoTString id = new IoTString(d); + t1.createNewKey(ia, 321); + t1.createNewKey(ib, 351); + t2.createNewKey(ic, 321); + t2.createNewKey(id, 351); + } + + // Do Updates for the keys + System.out.println("Setting Key-Values..."); + + for (int t = 0; t < NUMBER_OF_TESTS; t++) { + for (int i = 0; i < 4; i++) { + String keyB = "b" + i; + String valueB = "b" + (i + t); + + IoTString iKeyB = new IoTString(keyB); + IoTString iValueB = new IoTString(valueB); + + t1.startTransaction(); + t1.addKV(iKeyB, iValueB); + transStatusList.add(t1.commitTransaction()); + } + } + + for (int i = 0; i < 4; i++) { + + String keyB = "b" + i; + String valueB = "b" + (i + NUMBER_OF_TESTS - 1); + IoTString iKeyB = new IoTString(keyB); + IoTString iValueB = new IoTString(valueB); + + IoTString testValB1 = t1.getSpeculative(iKeyB); + IoTString testValB2 = t2.getSpeculative(iKeyB); + + if ((testValB1 == null) || (testValB1.equals(iValueB) == false)) { + System.out.println("Key-Value t1 incorrect: " + keyB); + foundError = true; + } + + if ((testValB2 != null)) { + System.out.println("Key-Value t2 incorrect: " + keyB); + foundError = true; + } + } + + for (int t = 0; t < NUMBER_OF_TESTS; t++) { + for (int i = 0; i < 4; i++) { + String keyC = "c" + i; + String valueC = "c" + (i + t); + + IoTString iKeyC = new IoTString(keyC); + IoTString iValueC = new IoTString(valueC); + + t2.startTransaction(); + t2.addKV(iKeyC, iValueC); + transStatusList.add(t2.commitTransaction()); + } + } + + for (int i = 0; i < 4; i++) { + String keyC = "c" + i; + String valueC = "c" + (i + NUMBER_OF_TESTS - 1); + IoTString iKeyC = new IoTString(keyC); + IoTString iValueC = new IoTString(valueC); + + IoTString testValC1 = t1.getSpeculative(iKeyC); + IoTString testValC2 = t2.getSpeculative(iKeyC); + + if ((testValC1 != null)) { + System.out.println("Key-Value t1 incorrect: " + keyC + " " + testValC1); + foundError = true; + } + + if ((testValC2 == null) || (testValC2.equals(iValueC) == false)) { + System.out.println("Key-Value t2 incorrect: " + keyC + " " + testValC2); + foundError = true; + } + } + + for (int t = 0; t < NUMBER_OF_TESTS; t++) { + for (int i = 0; i < 4; i++) { + String keyA = "a" + i; + String keyD = "d" + i; + String valueA = "a" + (i + t); + String valueD = "d" + (i + t); + + IoTString iKeyA = new IoTString(keyA); + IoTString iKeyD = new IoTString(keyD); + IoTString iValueA = new IoTString(valueA); + IoTString iValueD = new IoTString(valueD); + + t1.startTransaction(); + t1.addKV(iKeyA, iValueA); + transStatusList.add(t1.commitTransaction()); + + t2.startTransaction(); + t2.addKV(iKeyD, iValueD); + transStatusList.add(t2.commitTransaction()); + } + } + + System.out.println("Updating Clients..."); + t1.update(); + t2.update(); + + + System.out.println("Checking Key-Values..."); + for (int i = 0; i < 4; i++) { + + String keyA = "a" + i; + String keyB = "b" + i; + String keyC = "c" + i; + String keyD = "d" + i; + String valueA = "a" + (i + NUMBER_OF_TESTS - 1); + String valueB = "b" + (i + NUMBER_OF_TESTS - 1); + String valueC = "c" + (i + NUMBER_OF_TESTS - 1); + String valueD = "d" + (i + NUMBER_OF_TESTS - 1); + + IoTString iKeyA = new IoTString(keyA); + IoTString iKeyB = new IoTString(keyB); + IoTString iKeyC = new IoTString(keyC); + IoTString iKeyD = new IoTString(keyD); + IoTString iValueA = new IoTString(valueA); + IoTString iValueB = new IoTString(valueB); + IoTString iValueC = new IoTString(valueC); + IoTString iValueD = new IoTString(valueD); + + + IoTString testValA1 = t1.getCommitted(iKeyA); + IoTString testValB1 = t1.getCommitted(iKeyB); + IoTString testValC1 = t1.getCommitted(iKeyC); + IoTString testValD1 = t1.getCommitted(iKeyD); + + IoTString testValA2 = t2.getCommitted(iKeyA); + IoTString testValB2 = t2.getCommitted(iKeyB); + IoTString testValC2 = t2.getCommitted(iKeyC); + IoTString testValD2 = t2.getCommitted(iKeyD); + + if ((testValA1 == null) || (testValA1.equals(iValueA) == false)) { + System.out.println("Key-Value t1 incorrect: " + keyA + " " + testValA1); + foundError = true; + } + + if ((testValB1 == null) || (testValB1.equals(iValueB) == false)) { + System.out.println("Key-Value t1 incorrect: " + keyB + " " + testValB1); + foundError = true; + } + + if ((testValC1 == null) || (testValC1.equals(iValueC) == false)) { + System.out.println("Key-Value t1 incorrect: " + keyC + " " + testValC1); + foundError = true; + } + + if ((testValD1 == null) || (testValD1.equals(iValueD) == false)) { + System.out.println("Key-Value t1 incorrect: " + keyD + " " + testValD1); + foundError = true; + } + + + if ((testValA2 == null) || (testValA2.equals(iValueA) == false)) { + System.out.println("Key-Value t2 incorrect: " + keyA + " " + testValA2); + foundError = true; + } + + if ((testValB2 == null) || (testValB2.equals(iValueB) == false)) { + System.out.println("Key-Value t2 incorrect: " + keyB + " " + testValB2); + foundError = true; + } + + if ((testValC2 == null) || (testValC2.equals(iValueC) == false)) { + System.out.println("Key-Value t2 incorrect: " + keyC + " " + testValC2); + foundError = true; + } + + if ((testValD2 == null) || (testValD2.equals(iValueD) == false)) { + System.out.println("Key-Value t2 incorrect: " + keyD + " " + testValD2); + foundError = true; + } + } + + for (TransactionStatus status : transStatusList) { + if (status.getStatus() != TransactionStatus.StatusCommitted) { + foundError = true; + } + } + + if (foundError) { + System.out.println("Found Errors..."); + } else { + System.out.println("No Errors Found..."); + } + } + + static void test4() throws ServerException { + + boolean foundError = false; + long startTime = 0; + long endTime = 0; + List<TransactionStatus> transStatusList = new ArrayList<TransactionStatus>(); + + // Setup the 2 clients + Table t1 = new Table("http://127.0.0.1/test.iotcloud/", "reallysecret", 321); + t1.initTable(); + Table t2 = new Table("http://127.0.0.1/test.iotcloud/", "reallysecret", 351); + t2.update(); + + // Make the Keys + System.out.println("Setting up keys"); + startTime = System.currentTimeMillis(); + for (int i = 0; i < 4; i++) { + String a = "a" + i; + String b = "b" + i; + String c = "c" + i; + String d = "d" + i; + IoTString ia = new IoTString(a); + IoTString ib = new IoTString(b); + IoTString ic = new IoTString(c); + IoTString id = new IoTString(d); + t1.createNewKey(ia, 321); + t1.createNewKey(ib, 351); + t2.createNewKey(ic, 321); + t2.createNewKey(id, 351); + } + endTime = System.currentTimeMillis(); + System.out.println("Time Taken: " + (double) ((endTime - startTime) / 1000.0) ); + System.out.println("Time Taken Per Key: " + (double) (((endTime - startTime) / 1000.0) / (NUMBER_OF_TESTS * 4)) ); + System.out.println(); + + + // Do Updates for the keys + System.out.println("Setting Key-Values..."); + startTime = System.currentTimeMillis(); + for (int t = 0; t < NUMBER_OF_TESTS; t++) { + for (int i = 0; i < 4; i++) { + String keyA = "a" + i; + String keyB = "b" + i; + String keyC = "c" + i; + String keyD = "d" + i; + String valueA = "a" + i; + String valueB = "b" + i; + String valueC = "c" + i; + String valueD = "d" + i; + + IoTString iKeyA = new IoTString(keyA); + IoTString iKeyB = new IoTString(keyB); + IoTString iKeyC = new IoTString(keyC); + IoTString iKeyD = new IoTString(keyD); + IoTString iValueA = new IoTString(valueA); + IoTString iValueB = new IoTString(valueB); + IoTString iValueC = new IoTString(valueC); + IoTString iValueD = new IoTString(valueD); + + t1.startTransaction(); + t1.addKV(iKeyA, iValueA); + transStatusList.add(t1.commitTransaction()); + + t1.startTransaction(); + t1.addKV(iKeyB, iValueB); + transStatusList.add(t1.commitTransaction()); + + t2.startTransaction(); + t2.addKV(iKeyC, iValueC); + transStatusList.add(t2.commitTransaction()); + + t2.startTransaction(); + t2.addKV(iKeyD, iValueD); + transStatusList.add(t2.commitTransaction()); + } + } + endTime = System.currentTimeMillis(); + System.out.println("Time Taken: " + (double) ((endTime - startTime) / 1000.0) ); + System.out.println("Time Taken Per Update: " + (double) (((endTime - startTime) / 1000.0) / (NUMBER_OF_TESTS * 16)) ); + System.out.println(); + + + System.out.println("Updating Clients..."); + t1.update(); + t2.update(); + + + System.out.println("Checking Key-Values..."); + for (int i = 0; i < 4; i++) { + + String keyA = "a" + i; + String keyB = "b" + i; + String keyC = "c" + i; + String keyD = "d" + i; + String valueA = "a" + i; + String valueB = "b" + i; + String valueC = "c" + i; + String valueD = "d" + i; + + IoTString iKeyA = new IoTString(keyA); + IoTString iKeyB = new IoTString(keyB); + IoTString iKeyC = new IoTString(keyC); + IoTString iKeyD = new IoTString(keyD); + IoTString iValueA = new IoTString(valueA); + IoTString iValueB = new IoTString(valueB); + IoTString iValueC = new IoTString(valueC); + IoTString iValueD = new IoTString(valueD); + + + IoTString testValA1 = t1.getCommitted(iKeyA); + IoTString testValB1 = t1.getCommitted(iKeyB); + IoTString testValC1 = t1.getCommitted(iKeyC); + IoTString testValD1 = t1.getCommitted(iKeyD); + + IoTString testValA2 = t2.getCommitted(iKeyA); + IoTString testValB2 = t2.getCommitted(iKeyB); + IoTString testValC2 = t2.getCommitted(iKeyC); + IoTString testValD2 = t2.getCommitted(iKeyD); + + if ((testValA1 == null) || (testValA1.equals(iValueA) == false)) { + System.out.println("Key-Value t1 incorrect: " + keyA); + foundError = true; + } + + if ((testValB1 == null) || (testValB1.equals(iValueB) == false)) { + System.out.println("Key-Value t1 incorrect: " + keyB); + foundError = true; + } + + if ((testValC1 == null) || (testValC1.equals(iValueC) == false)) { + System.out.println("Key-Value t1 incorrect: " + keyC); + foundError = true; + } + + if ((testValD1 == null) || (testValD1.equals(iValueD) == false)) { + System.out.println("Key-Value t1 incorrect: " + keyD); + foundError = true; + } + + + if ((testValA2 == null) || (testValA2.equals(iValueA) == false)) { + System.out.println("Key-Value t2 incorrect: " + keyA + " " + testValA2); + foundError = true; + } + + if ((testValB2 == null) || (testValB2.equals(iValueB) == false)) { + System.out.println("Key-Value t2 incorrect: " + keyB + " " + testValB2); + foundError = true; + } + + if ((testValC2 == null) || (testValC2.equals(iValueC) == false)) { + System.out.println("Key-Value t2 incorrect: " + keyC + " " + testValC2); + foundError = true; + } + + if ((testValD2 == null) || (testValD2.equals(iValueD) == false)) { + System.out.println("Key-Value t2 incorrect: " + keyD + " " + testValD2); + foundError = true; + } + } + + for (TransactionStatus status : transStatusList) { + if (status.getStatus() != TransactionStatus.StatusCommitted) { + foundError = true; + } + } + + if (foundError) { + System.out.println("Found Errors..."); + } else { + System.out.println("No Errors Found..."); + } + } + + static void test3() throws ServerException { + + long startTime = 0; + long endTime = 0; + boolean foundError = false; + + List<TransactionStatus> transStatusList = new ArrayList<TransactionStatus>(); + + // Setup the 2 clients + Table t1 = new Table("http://127.0.0.1/test.iotcloud/", "reallysecret", 321); + t1.initTable(); + Table t2 = new Table("http://127.0.0.1/test.iotcloud/", "reallysecret", 351); + t2.update(); + + + // Make the Keys + System.out.println("Setting up keys"); + startTime = System.currentTimeMillis(); + for (int i = 0; i < NUMBER_OF_TESTS; i++) { + String a = "a" + i; + String b = "b" + i; + String c = "c" + i; + String d = "d" + i; + IoTString ia = new IoTString(a); + IoTString ib = new IoTString(b); + IoTString ic = new IoTString(c); + IoTString id = new IoTString(d); + t1.createNewKey(ia, 321); + t1.createNewKey(ib, 351); + t2.createNewKey(ic, 321); + t2.createNewKey(id, 351); + } + endTime = System.currentTimeMillis(); + System.out.println("Time Taken: " + (double) ((endTime - startTime) / 1000.0) ); + System.out.println("Time Taken Per Key: " + (double) (((endTime - startTime) / 1000.0) / (NUMBER_OF_TESTS * 4)) ); + System.out.println(); + + + // Do Updates for the keys + System.out.println("Setting Key-Values..."); + startTime = System.currentTimeMillis(); + + System.out.println("B..."); + for (int i = 0; i < NUMBER_OF_TESTS; i++) { + String keyB = "b" + i; + String valueB = "b" + i; + + IoTString iKeyB = new IoTString(keyB); + IoTString iValueB = new IoTString(valueB); + + t1.startTransaction(); + t1.addKV(iKeyB, iValueB); + transStatusList.add(t1.commitTransaction()); + } + + System.out.println("C..."); + for (int i = 0; i < NUMBER_OF_TESTS; i++) { + String keyC = "c" + i; + String valueC = "c" + i; + + IoTString iKeyC = new IoTString(keyC); + IoTString iValueC = new IoTString(valueC); + + t2.startTransaction(); + t2.addKV(iKeyC, iValueC); + transStatusList.add(t2.commitTransaction()); + } + + System.out.println("A, D..."); + for (int i = 0; i < NUMBER_OF_TESTS; i++) { + String keyA = "a" + i; + String keyD = "d" + i; + String valueA = "a" + i; + String valueD = "d" + i; + + IoTString iKeyA = new IoTString(keyA); + IoTString iKeyD = new IoTString(keyD); + IoTString iValueA = new IoTString(valueA); + IoTString iValueD = new IoTString(valueD); + + t1.startTransaction(); + t1.addKV(iKeyA, iValueA); + transStatusList.add(t1.commitTransaction()); + + t2.startTransaction(); + t2.addKV(iKeyD, iValueD); + transStatusList.add(t2.commitTransaction()); + } + endTime = System.currentTimeMillis(); + System.out.println("Time Taken: " + (double) ((endTime - startTime) / 1000.0) ); + System.out.println("Time Taken Per Update: " + (double) (((endTime - startTime) / 1000.0) / (NUMBER_OF_TESTS * 4)) ); + System.out.println(); + + + System.out.println("Updating Clients..."); + t1.update(); + t2.update(); + + + System.out.println("Checking Key-Values..."); + for (int i = 0; i < NUMBER_OF_TESTS; i++) { + + String keyA = "a" + i; + String keyB = "b" + i; + String keyC = "c" + i; + String keyD = "d" + i; + String valueA = "a" + i; + String valueB = "b" + i; + String valueC = "c" + i; + String valueD = "d" + i; + + IoTString iKeyA = new IoTString(keyA); + IoTString iKeyB = new IoTString(keyB); + IoTString iKeyC = new IoTString(keyC); + IoTString iKeyD = new IoTString(keyD); + IoTString iValueA = new IoTString(valueA); + IoTString iValueB = new IoTString(valueB); + IoTString iValueC = new IoTString(valueC); + IoTString iValueD = new IoTString(valueD); + + + IoTString testValA1 = t1.getCommitted(iKeyA); + IoTString testValB1 = t1.getCommitted(iKeyB); + IoTString testValC1 = t1.getCommitted(iKeyC); + IoTString testValD1 = t1.getCommitted(iKeyD); + + IoTString testValA2 = t2.getCommitted(iKeyA); + IoTString testValB2 = t2.getCommitted(iKeyB); + IoTString testValC2 = t2.getCommitted(iKeyC); + IoTString testValD2 = t2.getCommitted(iKeyD); + + if ((testValA1 == null) || (testValA1.equals(iValueA) == false)) { + System.out.println("Key-Value t1 incorrect: " + keyA); + foundError = true; + } + + if ((testValB1 == null) || (testValB1.equals(iValueB) == false)) { + System.out.println("Key-Value t1 incorrect: " + keyB); + foundError = true; + } + + if ((testValC1 == null) || (testValC1.equals(iValueC) == false)) { + System.out.println("Key-Value t1 incorrect: " + keyC); + foundError = true; + } + + if ((testValD1 == null) || (testValD1.equals(iValueD) == false)) { + System.out.println("Key-Value t1 incorrect: " + keyD); + foundError = true; + } + + + if ((testValA2 == null) || (testValA2.equals(iValueA) == false)) { + System.out.println("Key-Value t2 incorrect: " + keyA + " " + testValA2); + foundError = true; + } + + if ((testValB2 == null) || (testValB2.equals(iValueB) == false)) { + System.out.println("Key-Value t2 incorrect: " + keyB + " " + testValB2); + foundError = true; + } + + if ((testValC2 == null) || (testValC2.equals(iValueC) == false)) { + System.out.println("Key-Value t2 incorrect: " + keyC + " " + testValC2); + foundError = true; + } + + if ((testValD2 == null) || (testValD2.equals(iValueD) == false)) { + System.out.println("Key-Value t2 incorrect: " + keyD + " " + testValD2); + foundError = true; + } + } + + for (TransactionStatus status : transStatusList) { + if (status.getStatus() != TransactionStatus.StatusCommitted) { + foundError = true; + } + } + + if (foundError) { + System.out.println("Found Errors..."); + } else { + System.out.println("No Errors Found..."); + } + } + + static void test2() throws ServerException { + + boolean foundError = false; + long startTime = 0; + long endTime = 0; + List<TransactionStatus> transStatusList = new ArrayList<TransactionStatus>(); + + // Setup the 2 clients + Table t1 = new Table("http://127.0.0.1/test.iotcloud/", "reallysecret", 321); + t1.initTable(); + System.out.println("T1 Ready"); + + Table t2 = new Table("http://127.0.0.1/test.iotcloud/", "reallysecret", 351); + t2.update(); + System.out.println("T2 Ready"); + + // Make the Keys + System.out.println("Setting up keys"); + startTime = System.currentTimeMillis(); + for (int i = 0; i < NUMBER_OF_TESTS; i++) { + String a = "a" + i; + String b = "b" + i; + String c = "c" + i; + String d = "d" + i; + IoTString ia = new IoTString(a); + IoTString ib = new IoTString(b); + IoTString ic = new IoTString(c); + IoTString id = new IoTString(d); + t1.createNewKey(ia, 321); + t1.createNewKey(ib, 351); + t2.createNewKey(ic, 321); + t2.createNewKey(id, 351); + } + endTime = System.currentTimeMillis(); + System.out.println("Time Taken: " + (double) ((endTime - startTime) / 1000.0) ); + System.out.println("Time Taken Per Key: " + (double) (((endTime - startTime) / 1000.0) / (NUMBER_OF_TESTS * 4)) ); + System.out.println(); + + // Do Updates for the keys + System.out.println("Setting Key-Values..."); + startTime = System.currentTimeMillis(); + for (int i = 0; i < NUMBER_OF_TESTS; i++) { + String keyA = "a" + i; + String keyB = "b" + i; + String keyC = "c" + i; + String keyD = "d" + i; + String valueA = "a" + i; + String valueB = "b" + i; + String valueC = "c" + i; + String valueD = "d" + i; + + IoTString iKeyA = new IoTString(keyA); + IoTString iKeyB = new IoTString(keyB); + IoTString iKeyC = new IoTString(keyC); + IoTString iKeyD = new IoTString(keyD); + IoTString iValueA = new IoTString(valueA); + IoTString iValueB = new IoTString(valueB); + IoTString iValueC = new IoTString(valueC); + IoTString iValueD = new IoTString(valueD); + + t1.startTransaction(); + t1.addKV(iKeyA, iValueA); + transStatusList.add(t1.commitTransaction()); + + + t1.startTransaction(); + t1.addKV(iKeyB, iValueB); + transStatusList.add(t1.commitTransaction()); + + + t2.startTransaction(); + t2.addKV(iKeyC, iValueC); + transStatusList.add(t2.commitTransaction()); + + + t2.startTransaction(); + t2.addKV(iKeyD, iValueD); + transStatusList.add(t2.commitTransaction()); + } + endTime = System.currentTimeMillis(); + System.out.println("Time Taken: " + (double) ((endTime - startTime) / 1000.0) ); + System.out.println("Time Taken Per Update: " + (double) (((endTime - startTime) / 1000.0) / (NUMBER_OF_TESTS * 4)) ); + System.out.println(); + + + System.out.println("Updating Clients..."); + t1.update(); + t2.update(); + + + System.out.println("Checking Key-Values..."); + for (int i = 0; i < NUMBER_OF_TESTS; i++) { + + String keyA = "a" + i; + String keyB = "b" + i; + String keyC = "c" + i; + String keyD = "d" + i; + String valueA = "a" + i; + String valueB = "b" + i; + String valueC = "c" + i; + String valueD = "d" + i; + + IoTString iKeyA = new IoTString(keyA); + IoTString iKeyB = new IoTString(keyB); + IoTString iKeyC = new IoTString(keyC); + IoTString iKeyD = new IoTString(keyD); + IoTString iValueA = new IoTString(valueA); + IoTString iValueB = new IoTString(valueB); + IoTString iValueC = new IoTString(valueC); + IoTString iValueD = new IoTString(valueD); + + + IoTString testValA1 = t1.getCommitted(iKeyA); + IoTString testValB1 = t1.getCommitted(iKeyB); + IoTString testValC1 = t1.getCommitted(iKeyC); + IoTString testValD1 = t1.getCommitted(iKeyD); + + IoTString testValA2 = t2.getCommitted(iKeyA); + IoTString testValB2 = t2.getCommitted(iKeyB); + IoTString testValC2 = t2.getCommitted(iKeyC); + IoTString testValD2 = t2.getCommitted(iKeyD); + + if ((testValA1 == null) || (testValA1.equals(iValueA) == false)) { + System.out.println("Key-Value t1 incorrect: " + keyA); + foundError = true; + } + + if ((testValB1 == null) || (testValB1.equals(iValueB) == false)) { + System.out.println("Key-Value t1 incorrect: " + keyB); + foundError = true; + } + + if ((testValC1 == null) || (testValC1.equals(iValueC) == false)) { + System.out.println("Key-Value t1 incorrect: " + keyC); + foundError = true; + } + + if ((testValD1 == null) || (testValD1.equals(iValueD) == false)) { + System.out.println("Key-Value t1 incorrect: " + keyD); + foundError = true; + } + + + if ((testValA2 == null) || (testValA2.equals(iValueA) == false)) { + System.out.println("Key-Value t2 incorrect: " + keyA + " " + testValA2); + foundError = true; + } + + if ((testValB2 == null) || (testValB2.equals(iValueB) == false)) { + System.out.println("Key-Value t2 incorrect: " + keyB + " " + testValB2); + foundError = true; + } + + if ((testValC2 == null) || (testValC2.equals(iValueC) == false)) { + System.out.println("Key-Value t2 incorrect: " + keyC + " " + testValC2); + foundError = true; + } + + if ((testValD2 == null) || (testValD2.equals(iValueD) == false)) { + System.out.println("Key-Value t2 incorrect: " + keyD + " " + testValD2); + foundError = true; + } + } + + for (TransactionStatus status : transStatusList) { + if (status.getStatus() != TransactionStatus.StatusCommitted) { + foundError = true; + System.out.println(status.getStatus()); + } + } + + if (foundError) { + System.out.println("Found Errors..."); + } else { + System.out.println("No Errors Found..."); + } + } } diff --git a/version2/src/java/iotcloud/Transaction.java b/version2/src/java/iotcloud/Transaction.java index 90ef4d4..82280c0 100644 --- a/version2/src/java/iotcloud/Transaction.java +++ b/version2/src/java/iotcloud/Transaction.java @@ -1,154 +1,283 @@ package iotcloud; -import java.nio.ByteBuffer; +import java.util.Map; import java.util.Set; +import java.util.List; +import java.util.ArrayList; +import java.util.HashMap; import java.util.HashSet; -import java.util.Map; +import java.nio.ByteBuffer; -class Transaction extends Entry { +class Transaction { - private long seqnum; - private long machineid; - private Set<KeyValue> keyValueUpdateSet = null; + private Map<Integer, TransactionPart> parts = null; + private Set<Integer> missingParts = null; + private List<Integer> partsPendingSend = null; + private boolean isComplete = false; private Set<KeyValue> keyValueGuardSet = null; - private Long arbitrator; + private Set<KeyValue> keyValueUpdateSet = null; + private boolean isDead = false; + private long sequenceNumber = -1; + private long clientLocalSequenceNumber = -1; + private long arbitratorId = -1; + private long machineId = -1; + private Pair<Long, Long> transactionId = null; + + private int nextPartToSend = 0; + private boolean didSendAPartToServer = false; + + private TransactionStatus transactionStatus = null; + + public Transaction() { + parts = new HashMap<Integer, TransactionPart>(); + keyValueGuardSet = new HashSet<KeyValue>(); + keyValueUpdateSet = new HashSet<KeyValue>(); + partsPendingSend = new ArrayList<Integer>(); + } + + public void addPartEncode(TransactionPart newPart) { + parts.put(newPart.getPartNumber(), newPart); + partsPendingSend.add(newPart.getPartNumber()); + + // Get the sequence number and other important information + sequenceNumber = newPart.getSequenceNumber(); + arbitratorId = newPart.getArbitratorId(); + transactionId = newPart.getTransactionId(); + clientLocalSequenceNumber = newPart.getClientLocalSequenceNumber(); + machineId = newPart.getMachineId(); + + isComplete = true; + } + + public void addPartDecode(TransactionPart newPart) { + + if (isDead) { + // If dead then just kill this part and move on + newPart.setDead(); + return; + } + + // Get the sequence number and other important information + sequenceNumber = newPart.getSequenceNumber(); + arbitratorId = newPart.getArbitratorId(); + transactionId = newPart.getTransactionId(); + clientLocalSequenceNumber = newPart.getClientLocalSequenceNumber(); + machineId = newPart.getMachineId(); + + TransactionPart previoslySeenPart = parts.put(newPart.getPartNumber(), newPart); + + if (previoslySeenPart != null) { + // Set dead the old one since the new one is a rescued version of this part + previoslySeenPart.setDead(); + } else if (newPart.isLastPart()) { + missingParts = new HashSet<Integer>(); - public Transaction(Slot slot, long _seqnum, long _machineid, Long _arbitrator, Set<KeyValue> _keyValueUpdateSet, Set<KeyValue> _keyValueGuardSet) { - super(slot); - seqnum = _seqnum; - machineid = _machineid; - arbitrator = _arbitrator; - // keyValueUpdateSet = new HashSet<KeyValue>(); - // keyValueGuardSet = new HashSet<KeyValue>(); + for (int i = 0; i < newPart.getPartNumber(); i++) { + if (parts.get(i) == null) { + missingParts.add(i); + } + } + } - // for (KeyValue kv : _keyValueUpdateSet) { - // KeyValue kvCopy = kv.getCopy(); - // keyValueUpdateSet.add(kvCopy); - // } + if (!isComplete) { - // for (KeyValue kv : _keyValueGuardSet) { - // KeyValue kvCopy = kv.getCopy(); - // keyValueGuardSet.add(kvCopy); - // } + // We have seen this part so remove it from the set of missing parts + missingParts.remove(newPart.getPartNumber()); - keyValueUpdateSet = _keyValueUpdateSet; - keyValueGuardSet = _keyValueGuardSet; + // Check if all the parts have been seen + if (missingParts.size() == 0) { + + // We have all the parts + isComplete = true; + + // Decode all the parts and create the key value guard and update sets + decodeTransactionData(); + } + } } - public long getMachineID() { - return machineid; + public void addUpdateKV(KeyValue kv) { + keyValueUpdateSet.add(kv); } - public long getArbitrator() { - return arbitrator; + public void addGuardKV(KeyValue kv) { + keyValueGuardSet.add(kv); } + public long getSequenceNumber() { - return seqnum; + return sequenceNumber; } + public void setSequenceNumber(long _sequenceNumber) { + sequenceNumber = _sequenceNumber; - public Set<KeyValue> getkeyValueUpdateSet() { - return keyValueUpdateSet; + for (Integer i : parts.keySet()) { + parts.get(i).setSequenceNumber(sequenceNumber); + } } - public Set<KeyValue> getkeyValueGuardSet() { - return keyValueGuardSet; + public long getClientLocalSequenceNumber() { + return clientLocalSequenceNumber; } - public boolean evaluateGuard(Map<IoTString, KeyValue> keyValTableCommitted, Map<IoTString, KeyValue> keyValTableSpeculative) { - for (KeyValue kvGuard : keyValueGuardSet) { + public Map<Integer, TransactionPart> getParts() { + return parts; + } - // First check if the key is in the speculative table, this is the value of the latest assumption - KeyValue kv = null; - // If we have a speculation table then use it first - if (keyValTableSpeculative != null) { - kv = keyValTableSpeculative.get(kvGuard.getKey()); - } - - if (kv == null) { + public boolean didSendAPartToServer() { + return didSendAPartToServer; + } - // if it is not in the speculative table then check the committed table and use that - // value as our latest assumption - kv = keyValTableCommitted.get(kvGuard.getKey()); - } + public void resetNextPartToSend() { + nextPartToSend = 0; + } - if (kvGuard.getValue() != null) { - if ((kv == null) || (!kvGuard.getValue().equals(kv.getValue()))) { - return false; - } - } else { - if (kv != null) { - return false; - } - } + public TransactionPart getNextPartToSend() { + if ((partsPendingSend.size() == 0) || (partsPendingSend.size() == nextPartToSend)) { + return null; } - return true; + TransactionPart part = parts.get(partsPendingSend.get(nextPartToSend)); + nextPartToSend++; + return part; + } + + public void setTransactionStatus(TransactionStatus _transactionStatus) { + transactionStatus = _transactionStatus; } - public byte getType() { - return Entry.TypeTransaction; + public TransactionStatus getTransactionStatus() { + return transactionStatus; } - public int getSize() { - int size = 3 * Long.BYTES + Byte.BYTES; // seq, machine id, entry type - size += Integer.BYTES; // number of KV's - size += Integer.BYTES; // number of Guard KV's + public void removeSentParts(List<Integer> sentParts) { + nextPartToSend = 0; + partsPendingSend.removeAll(sentParts); + didSendAPartToServer = true; + transactionStatus.setTransactionSequenceNumber(sequenceNumber); + } - // Size of each KV - for (KeyValue kv : keyValueUpdateSet) { - size += kv.getSize(); - } + public boolean didSendAllParts() { + return partsPendingSend.isEmpty(); + } - // Size of each Guard KV - for (KeyValue kv : keyValueGuardSet) { - size += kv.getSize(); - } - return size; + public Set<KeyValue> getKeyValueUpdateSet() { + return keyValueUpdateSet; + } + + public int getNumberOfParts() { + return parts.size(); + } + + public long getMachineId() { + return machineId; + } + + public long getArbitrator() { + return arbitratorId; + } + + public boolean isComplete() { + return isComplete; } - public void encode(ByteBuffer bb) { - bb.put(Entry.TypeTransaction); - bb.putLong(seqnum); - bb.putLong(machineid); - bb.putLong(arbitrator); + public Pair<Long, Long> getId() { + return transactionId; + } - bb.putInt(keyValueUpdateSet.size()); - for (KeyValue kv : keyValueUpdateSet) { - kv.encode(bb); + public void setDead() { + if (isDead) { + // Already dead + return; } - bb.putInt(keyValueGuardSet.size()); - for (KeyValue kv : keyValueGuardSet) { - kv.encode(bb); + // Set dead + isDead = true; + + // Make all the parts of this transaction dead + for (Integer partNumber : parts.keySet()) { + TransactionPart part = parts.get(partNumber); + part.setDead(); } } - static Entry decode(Slot slot, ByteBuffer bb) { - long seqnum = bb.getLong(); - long machineid = bb.getLong(); - long arbitrator = bb.getLong(); - int numberOfKeys = bb.getInt(); + public TransactionPart getPart(int index) { + return parts.get(index); + } + + private void decodeTransactionData() { - Set<KeyValue> kvSetUpdates = new HashSet<KeyValue>(); - for (int i = 0; i < numberOfKeys; i++) { - KeyValue kv = KeyValue.decode(bb); - kvSetUpdates.add(kv); + // Calculate the size of the data section + int dataSize = 0; + for (int i = 0; i < parts.keySet().size(); i++) { + TransactionPart tp = parts.get(i); + dataSize += tp.getDataSize(); } - int numberOfGuards = bb.getInt(); - Set<KeyValue> kvSetGuards = new HashSet<KeyValue>(); - for (int i = 0; i < numberOfGuards; i++) { - KeyValue kv = KeyValue.decode(bb); - kvSetGuards.add(kv); + byte[] combinedData = new byte[dataSize]; + int currentPosition = 0; + + // Stitch all the data sections together + for (int i = 0; i < parts.keySet().size(); i++) { + TransactionPart tp = parts.get(i); + System.arraycopy(tp.getData(), 0, combinedData, currentPosition, tp.getDataSize()); + currentPosition += tp.getDataSize(); } - return new Transaction(slot, seqnum, machineid, arbitrator, kvSetUpdates, kvSetGuards); + // Decoder Object + ByteBuffer bbDecode = ByteBuffer.wrap(combinedData); + + // Decode how many key value pairs need to be decoded + int numberOfKVGuards = bbDecode.getInt(); + int numberOfKVUpdates = bbDecode.getInt(); + + // Decode all the guard key values + for (int i = 0; i < numberOfKVGuards; i++) { + KeyValue kv = (KeyValue)KeyValue.decode(bbDecode); + keyValueGuardSet.add(kv); + } + + // Decode all the updates key values + for (int i = 0; i < numberOfKVUpdates; i++) { + KeyValue kv = (KeyValue)KeyValue.decode(bbDecode); + keyValueUpdateSet.add(kv); + } } - public Entry getCopy(Slot s) { - return new Transaction(s, seqnum, machineid, arbitrator, keyValueUpdateSet, keyValueGuardSet); + public boolean evaluateGuard(Map<IoTString, KeyValue> committedKeyValueTable, Map<IoTString, KeyValue> speculatedKeyValueTable, Map<IoTString, KeyValue> pendingTransactionSpeculatedKeyValueTable) { + for (KeyValue kvGuard : keyValueGuardSet) { + + // First check if the key is in the speculative table, this is the value of the latest assumption + KeyValue kv = null; + + // If we have a speculation table then use it first + if (pendingTransactionSpeculatedKeyValueTable != null) { + kv = pendingTransactionSpeculatedKeyValueTable.get(kvGuard.getKey()); + } + + // If we have a speculation table then use it first + if ((kv == null) && (speculatedKeyValueTable != null)) { + kv = speculatedKeyValueTable.get(kvGuard.getKey()); + } + + if (kv == null) { + // if it is not in the speculative table then check the committed table and use that + // value as our latest assumption + kv = committedKeyValueTable.get(kvGuard.getKey()); + } + + if (kvGuard.getValue() != null) { + if ((kv == null) || (!kvGuard.getValue().equals(kv.getValue()))) { + return false; + } + } else { + if (kv != null) { + return false; + } + } + } + return true; } } \ No newline at end of file diff --git a/version2/src/java/iotcloud/TransactionPart.java b/version2/src/java/iotcloud/TransactionPart.java new file mode 100644 index 0000000..17d8e44 --- /dev/null +++ b/version2/src/java/iotcloud/TransactionPart.java @@ -0,0 +1,140 @@ +package iotcloud; + +import java.nio.ByteBuffer; + +class TransactionPart extends Entry { + + // Max size of the part excluding the fixed size header + public static final int MAX_NON_HEADER_SIZE = 512; + + private long sequenceNumber = -1; + private long machineId = -1; + private long arbitratorId = -1; + private long clientLocalSequenceNumber = -1; // Sequence number of the transaction that this is a part of + private int partNumber = -1; // Parts position in the + private Boolean isLastPart = false; + + private Pair<Long, Long> transactionId = null; + private Pair<Long, Integer> partId = null; + + private byte[] data = null; + + public TransactionPart(Slot s, long _machineId, long _arbitratorId, long _clientLocalSequenceNumber, int _partNumber, byte[] _data, Boolean _isLastPart) { + super(s); + machineId = _machineId; + arbitratorId = _arbitratorId; + clientLocalSequenceNumber = _clientLocalSequenceNumber; + partNumber = _partNumber; + data = _data; + isLastPart = _isLastPart; + + transactionId = new Pair<Long, Long>(machineId, clientLocalSequenceNumber); + partId = new Pair<Long, Integer>(clientLocalSequenceNumber, partNumber); + + } + + public int getSize() { + if (data == null) { + return (4 * Long.BYTES) + (2 * Integer.BYTES) + (2 * Byte.BYTES); + } + return (4 * Long.BYTES) + (2 * Integer.BYTES) + (2 * Byte.BYTES) + data.length; + } + + public void setSlot(Slot s) { + parentslot = s; + } + + public Pair<Long, Long> getTransactionId() { + return transactionId; + } + + public long getArbitratorId() { + return arbitratorId; + } + + public Pair<Long, Integer> getPartId() { + return partId; + } + + public int getPartNumber() { + return partNumber; + } + + public int getDataSize() { + return data.length; + } + + public byte[] getData() { + return data; + } + + public Boolean isLastPart() { + return isLastPart; + } + + public long getMachineId() { + return machineId; + } + + public long getClientLocalSequenceNumber() { + return clientLocalSequenceNumber; + } + + + public long getSequenceNumber() { + return sequenceNumber; + } + + public void setSequenceNumber(long _sequenceNumber) { + sequenceNumber = _sequenceNumber; + } + + static Entry decode(Slot s, ByteBuffer bb) { + long sequenceNumber = bb.getLong(); + long machineId = bb.getLong(); + long arbitratorId = bb.getLong(); + long clientLocalSequenceNumber = bb.getLong(); + int partNumber = bb.getInt(); + int dataSize = bb.getInt(); + Boolean isLastPart = bb.get() == 1; + + // Get the data + byte[] data = new byte[dataSize]; + bb.get(data); + + TransactionPart returnTransactionPart = new TransactionPart(s, machineId, arbitratorId, clientLocalSequenceNumber, partNumber, data, isLastPart); + returnTransactionPart.setSequenceNumber(sequenceNumber); + + return returnTransactionPart; + } + + public void encode(ByteBuffer bb) { + bb.put(Entry.TypeTransactionPart); + bb.putLong(sequenceNumber); + bb.putLong(machineId); + bb.putLong(arbitratorId); + bb.putLong(clientLocalSequenceNumber); + bb.putInt(partNumber); + bb.putInt(data.length); + + if (isLastPart) { + bb.put((byte)1); + } else { + bb.put((byte)0); + } + + bb.put(data); + } + + public byte getType() { + return Entry.TypeTransactionPart; + } + + public Entry getCopy(Slot s) { + + TransactionPart copyTransaction = new TransactionPart(s, machineId, arbitratorId, clientLocalSequenceNumber, partNumber, data, isLastPart); + copyTransaction.setSequenceNumber(sequenceNumber); + + return copyTransaction; + } +} \ No newline at end of file diff --git a/version2/src/java/iotcloud/TransactionStatus.java b/version2/src/java/iotcloud/TransactionStatus.java index a42f570..e397d6c 100644 --- a/version2/src/java/iotcloud/TransactionStatus.java +++ b/version2/src/java/iotcloud/TransactionStatus.java @@ -5,13 +5,16 @@ class TransactionStatus { static final byte StatusPending = 2; static final byte StatusCommitted = 3; // static final byte StatusRetrying = 4; - static final byte StatusSent = 5; - static final byte StatusNoEffect = 6; + static final byte StatusSentPartial = 5; + static final byte StatusSentFully = 6; + static final byte StatusNoEffect = 10; private byte status = 0; private boolean applicationReleased = false; - private long arbitrator = 0; private boolean wasSentInChain = false; + private long transactionSequenceNumber = 0; + private long arbitrator = -1; + public TransactionStatus(byte _status, long _arbitrator) { status = _status; @@ -26,20 +29,15 @@ class TransactionStatus { status = _status; } - public void setSentTransaction() { - wasSentInChain = true; + public long getTransactionSequenceNumber() { + return transactionSequenceNumber; } - public boolean getSentTransaction() { - return wasSentInChain; + public void setTransactionSequenceNumber(long _transactionSequenceNumber) { + transactionSequenceNumber = _transactionSequenceNumber; } - - // public void setArbitrator(long _arbitrator) { - // arbitrator = _arbitrator; - // } - - public long getArbitrator() { + public long getTransactionArbitrator() { return arbitrator; } -- 2.34.1