From 8f2cd2d576d466dd791db72a6c54348d69af8541 Mon Sep 17 00:00:00 2001 From: Ali Younis 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 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 + +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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

(N9t)xe=-L4ew0Mk$QhCuXQ6b{gcuouAS z{}CfQm5Rn8`ixaA5o9r8iUL_!!OxE=VFL0r%vwVh3o1QH*S5T36bzJzs+(Dk$~m=_ zt>lK_K|zX0zT5G`Ae4zqJM<$Ub9Hs|{b}+4i<+{iCi#CqwVU|XmO?$~D+=ZBtyobq z>4amoGHxx4n*kC1Kd~6p$me5`N%v{5S^Tp!f9vfwxkZH|Y zWO1=XjhkteF@3Xes=tV27-e^JLKZA~_ljh|JT0r86BS7eCn0ChdGmlP#p;q#Ui~$# zH`}57C)7NBWd*gS6~xUOH$jg@F_8&0VWhX(bRLm)_}@4Y`Rin z@_&P*|8{4?`saUcFlOb#^L=ytk;@U7bO;=rrgDIa z+!<%K_U?cxrfFbIcd{g}Q6X#tRt?))MgcDeQ)f$>Z8{|*iNEPMR&i^EG1uR}@7F>1 zX}gmS^z{j#fo^1(W07c57D-uJMkQ|-Z%?0|+&!tNhZH|n-5Ob4IW^rmU7T)9tB+gI zv`izNj8vn367lsw^M%MsD05r0+-aDUck*qncW=+0UQL~_ngklns#GJ5=ov}aM-ojY z3deF)>!gk(Ig%!B4!xSyZcS3_V(ba!>W`KxVkx{eD{YfZHg2R-O!jotAk0SPL#Abl zr~-!d%T$@v!8ocAGif3?(EP6N7AcqSbSfGKy#6Zxy3&0l%qub3P*|FmRiIPY8U#i^ z^EITo>{AQ_zp9pO+9cdzsu@gPZdc(I_%f}~ym)f@>QWj65@uFXOfM3lumF+W8&2$P z8q*6nlXGbtiH|K*F!mhM;)4bD7Y~;zZQnkzsMb5BYS~Af* z$t6TSHc690WEWXuTGk`je|cJL@`3}^^<2qy{G!r~7Q4QI;4pcXb-E4_?8n!|&;xf0 zmRZ7vTI$@W8=|;HAD2P%CUr}fWt>60k6?o3U9&By7-edtqkj2BH~v~VcHIJ(B2Yjh zgl#%ujV>+TqQSd$n^vPJ){a7{^6faxXWY15qbLO1!g6Pc2$N|n&_dyx1C>rdI#e*i z9|N+DyVzMr)|()3S%{p69^Ug0b!XL1&NLMD19D~rn8Mde!#wtktj4UEFI2B#agzXF z@LrO*By4-~=;|FX0;45Fy+BaP`Vv%UV0?a+#ZhuflAE1cqlxu~@p8(;M&DyB+GMZG z{Q!(<^L%8jBMA=6tRv@7`Xu6vRxY{~%mqJCuJ-;W9Gr)VClKW56S3d@723D07+-P; zCrRR>4u1L**bu3P9KlDPZ$NUH4LE35Fc4I1jEa{7gF3q|b!tdn6&hJxye zACGu8_^xk;3MFqW0DLo|EeQnexk$w@tnAIPDRZkcw@-VgTNYVE^gx#ZOo|o9)ZGnf zZ_qI1eNGJW6w@v3tCj;CINmBXpoLgD0adPOS#J&d+EQYB2Dkh_qM+ads^W76DJQnD z%2AC9@S=joUyaE@L)P#LV&wQNs#{CkE-2pSv8nQ>>Vr~XXaQUqYMwqCpAdtF@auKR z9ryHb$J_hEmK9R3oi>rnXcF19IW&b;K_4Z_{(94?UEZv{#E@?j`_5wj$DYE9oVrW3 zgg<--Z1i%sdDFv&Z3Suh+Z>tU;LO3 z4=XM-C+vjKqMn;-6!PwC5775d0t;}Px$~KjspT%_j!r&Z@_^Ae#AAUqhPiB3p1v8U zzbG5w<5=l#QuCnAe3xkm-KC!AR$6va(o$?`vsoC}!ROcMfUPyll6xmtjmLfl6L0y; zk^9m>0BYGB7ki{I-w9`);@0etx+x6t8p#MUz{O5L$l}F@wAv}<8e?Q*?p>w2LE{*LFn zVZDRULTtC6zv1|qS@vubwMBqO#h8Z(3VHjX;{1&*mOjBuRJ%$rXIS+} z<-4IYbWg5$YN$m=yhVU+VHvAhE`kNi5*l37d{_obj4)Oa&dSJP_L*_z+!r}YA(F$C zu)7$)@to9t(n^Y=NN>!LoaUQWtlXPy(ybL(@ggDogZbuEAWC@jMQdMqT-?f!F8cSt z3aB|AIys<_{pRDJe1{m&#HEYFwIok929k@^0bvl2@sA1q0c!Nq9BN~88?d?BAf>q+ z6BOH^=ZnceOhkLX(uC+3(Y_+=r>4Zr!QPk2DmaS5?~F+B_rQ!uJsr6(?Y-I;@v3!QCjPHWTE&LD=OodQLUbrR2`0tPmzs z^3yHpPXr0^2~bdcCPm*o0+iCbf3>j%9%|BK%;MqAf3f4E#qcT3oXi+2q?teo8pxDo zbxo)9BhWzodHeBObHe{phwYkZWf2k&^Vu=s)%`@ls8cbeo`zTn5HTyJA)g#y|Fy27 z>v|Gf4*%wy>M!rHs?_@<#}kb}3PQ^F0K3X;mdg$)yQ+l+#cW4eHY)B@4Q|n>( z@|j?_mO)^>3@-%G_z=1xC;;9{u-)FRJ184lUT+ZT!PjE?p^ca9_1QBDXTotEds5CJ zlEQ^^BYQTbh1Mu$GZcV*F#*|hs(NTM=XPWZgBIr|WsstV)FLRV*0&tb3i4KhrYPMh zuzxLw0ttX57Ba;grJqm&c^=4NU%9Sl=8#+1t=fBYCh4+iRI$)HIy$WEY>^KeKT7vs zOU=57%qEwlcD2&cxn?5BtA@Ul%z z(4avG)WXHo$0x2rj>0R1b*v$lfylQa4ru(41(4vg~%?aB}kO1qop_;X=8vK_>ljd3Ec1S zmq6|464MvLP9A&{o`h=;-e4vIO(1-)Cv!W?mZRizlLuX@SY<&76uDDWmZ1Wio@WzTk3tH%ns zK8XmgE{H};XP{HWtwPmDm|Q5Hd^lU}tUWeV+a;ZrB*fOZJsCQ^9b}KT8n_D`Lj}MY zc)RvmbnwRzbIv3LI-AbXlamVWk$n|SsK>by!VQ}T%d^rCdjhm)I*E9?vU&?qLt$(C zn4wMX33*?b3Jix}0O;>BWzd5E;OX8bq-nED2)p9Wmt>W4Vopcj5HsF7S3&ZF6If1o zqA@Q7(@BZ~ArjF?R#0sb#$;)hRs)PiOqDQE+ciut9p)8C)a$zrXyZiQ6(UrUF-5Y) zGf*`EvJSDrE!|8(jVtp+h0g8&;9 z%Tnp?8}AZIeg(VC$#r{nn(VA{W=+rMa>X=gWnWwLwq}C@u0JH(WVf&|4Nq(xyQ$Gi zO$KC98)zH(^h&Tx?!(un(m!tqHR_Q~3Z%MzDiiK(Ou!h(l6Nw)t}zg~8&a#CVLcD( zFvXPc>zi!({M5gi#mQiGPmD;ts)d2GoXL%Mee=#2tu=XZ{;2TGS;6q?7!>=Ye&(PI z&36ZKi-0hGH&YU_y`^vnoM%_SO3^nv)t~%GZ0?F~KHy75$a36BvUmPKuQAv*R3fM+ zEQ5T+7!zf24_L=w?dKur715>74V#m`El;I%|9q^VsK$79mbQAk9|_OY_ILlRzkh9* zh~6Hl%h7Aq#=*u5aKJ`_g*4k13r<=`A52D6UejJz=#*L9*{gs}&PF64%%C>gD9QNr zW@ol2T|~zhAx~<*p*eyoG2C9?NcH@NjyZ8fmb zzQ{z?UV$upFKAuEJMS4sbKE*j{Bna4?2cshIb$XDPv<%oD0@+UQtS>DBQIjqa}Zb$ z>A|1Z9nRNTVXqY7`lw}5W8LP_o2?LzOX2cH-C!<=8(7Hio`xjLMy@Tcpo&O?90@uU zv>CyGkZBc+&pC(S&ssZ*zH^QDCA9Ic!b6Pwa@FYBYAZumbz>nm2*hSCFIVwly=$EL zNBeDO-nF@uD%!}mErEQ*8zEq7pn+i}rYdKpS?hJ+_%tv**Slq%!4*zkwO^_#6eezMnJCLlTydRG^;mU2E z=08yesWL>wS5$l5X#5cp!9$NNK$x5L-K?8=(}i_0aQ$zj$(LaOCevG?zuzc%(eSV( zgsY2%>5P+2(C_!zx+@x95*gqO#ODNi4l%PU3bpjN{P>u1{gGP-F;#?JwH$M^AK}~$ zEKjjXeyT_avJu&Pqnn?n0&$lDf%$YL0s_-{Y&R+t0{P*kq>=f@_T-B~80X*N_;X>F z`{WiQs)99&IOy9mUsDV~?G7cTe;!6ztTW|?5Xnl4fh>!=YL1DWNU*1_n_rCp=^qT4 zKQ>!ok{#WIpa-F7a!G*{fn2musIg4H#BrNSsX&`p5FXRm5h_sycJQi~9E zxso#H1FD$p!uvt^bi*NmS*8P%Y@IjE)>&W>f9tdvB%$~qC0&;f_;Pd}BH(MGWR>U1 z@Uq=NJkl18$7AvF;pcYxl4g%2Pkf7cN_k(n8o09#5#vy^61=Aza@y$k7)*_=>dq~qM7y!h;=Ec4x6p7E&EEfAdXv}12l}@Q z&nEtv?9ktn`#CjUuSZPX^bvJ1^{WYh8SIqTso_&D|zl5^ue*M_p3W-$S^_`%DuyarATt(#gw#j7}xpr z9o8s{;amB(w{z>iw8s_3MeRmYS^Ws+dD~}7Gl?=0I9B+aMa0Wkk|6C9J@?TP8dnA! z2vVu57PVu%AKYYfgBBX*l#t41=m~99^CfO$B}C4=BMm#M7Ph)!=Ub29>Rj*|EpQ)fBO@$I+2s z8J;#APEZ4qAvCJ;!41g%__s#EhcNH}@lS;%ZO@t&Vv;pWi9}i9hO-?xgujgt-)mlP zB=E)zDIgCt%dBonGapOg+nFrvyDJFcn*Od+!ouM1lGz!sg?Y4vduB<#w&WWXchSNmK&DrKC8aB~uY6TiF2KjXpC;S0S0aG$Cv~qht@2>$3j_XRjXV@tn+#j-j5%M=5f)H9oC~YTN3hu ziPYLrb-&fbJln_*tB^^z(eiY(!KF!gs&;U}dhc~guj5h(RNtcUDV=(KbF<;_0~1Jt z{nBf{33ku8y4DtSse0JA?uu*+Fg$5@7uZZrk+T^n^NeJ~6;rXi>#1-d&LCV0W9zKn6pMA)J&^q5qH|RvpU*~l=oc{P^lk( zVbPfO6QPEPvH8Uu*t<@IXQ1YM3Wmc-b1u=;&Vj@^h}0gjmodhhCQ8!Z*(ySD0uEk2 z^q#TPQjXZ>)p&1X#a!|c4_*n@X*$RjNM(JF$0LDE>HIUWj^&pBb{TdvjHgzSdMFsX zAb0{Ul?=gg_PJ}#F~yeQ9Ny*wahaEgsj~}3z*-Omf(vM-$2swNP|c2FacJ{9dRwjM zj^MO%$Dzo%7y2HMQZu`cqPL3zQ9{=Gds-1s6+>9|P+|6O1Oj3XNVX%2VU8A-rNauOrna95^uPW^wbWNw%Ymy4hyD zK0BJyQLNNj7FsWu+*;mA$K`F|6aZItOS~gICK@}*NiJz5TkRU0yZM#$0dWYAUjJ1f(F?-OL!`NtSU%AGqOP{ficQRhqw>{Z1m*GROOY(e|Z%Huk%ktX3dKAd22Y5EV;L+gVvTe%eGxYd_(1f?Azkk ziPmBEkZ0>dqABZ34~?p@w53Hkv$~0Q^DaiL;akjmhFdr)s0Jkzcc+$K3dNI-kNV!P zEa1E9wh+%6HT%(ah+3uoZ{26d1&MQwXT*3*sKg-uXK*aYVVBMEa}Q9}#+{)H?yQ`1 zqdv(iBco4id*m0jT!IYMjP*T48M_uK3wgeLPJ3MRU#O~k@ca$4Xw52iwA1gZ*)DsG z7XYX9v1x7-!Vf`A@<2Q1Nu}cE0-VagpjdUu#Na_DAvD)!I-rZW#2_GxDNVhc?L-FNOJ|UT#9Yo z9&T{HNdyqcl*-F)2qFfv$H%*=74CqB3>ecL-Cf%+SN`2_=6FP^SSF5ycp@pK=2zRh zXY@?ky*+qL5g0t~);xTs|J!i=xv+G;QzRP1NRkg{!A16C9%F%7;vZ~zh%lvZ8-ox5 z!;YV-;ZOe6LreWf554A@)-`n!6!&(mXg9_(p^KVAvV%fWQ=x5Wb9dXhxp%!$Zv`$r++z&Jee- zPJQaAA9=KKVGH`l`B^ucexmr1jk>0^0gKD*vPRZL2l!v#Iz07&lcMs3McAck2ZP(Q zLM+TDDQOED2)SaiUwx=W4g4DwX3jg3xWrx@wx?ImowMUKVk2+-KN1*o(P@_;rMD7Q z=jPvX8?%cJ1;tcVV1K($?*J=I+I8V)B(EG zEXrTyq>VUW!}2cN@A}3~^1ysloVv~8ik@XEY-_k)lFb(zT2jwCg75C`ZoD6)xPL2J z*IO*(ix$%;ZoEGg^)HUrQe7}t&) z5Ulhx!RABj6ft(N)#p8(x2kgJ?8Vje&%$Q41#hvAO@@4eTxHyTnD{D6!s5~hp#uVL zNAN*jEg4StdenQj#kP_UYlDixr}{vg$-ZZTU*j^4a;?bIUH3YTKWWz6HLn_%`$+>2 z-=H7vnmH#Dgb%R#LWmsCP-Mn=A7$L2hozDXV$%I=sdzfGQL4GrM`!>ct{#k+@3LQHXv|%Ys;hC>fes7?V1!-mD^o~GWLF^ zaSU)vD}rJP$*{(>S{^SK&A+TK+k&T52sPjy^t)D9ERQoTS=l4ZzJ2~!z*Kz)$tDbo zwoF&*@l)v=?3< z;*<_g#0Jdp+idi45=B-5bijc~P6=OL&6iilsXnn_0D*_Na!`Oo^Zz76>l8{A>--Hp z>)y;j))FADL~gmGyam`CGi)6RI{;1gq3*Y|+a{7YG(1>Rwu7{N^01@yU8nbbpWM~W z^60O((y2M;f6pZ~@R%6ilM#1)f9V?!A>ikL`E-XMD=2y(kr41Cf`lPzPI>QlV*`s; zUAmdh=T&LC;n-9P;L6eqM=4IQCsFu=SfUMmZQ1Fs3M$TyJEzNW+d-dpHwq6aKpvMq zVrPV&#kzE=@RMc4k%ad$rLsy?K!`PvYPV7*qveC-7dag=soNrX7tOH}9 zZc9{72EjSeGmh5aKr90cNcSj|vdvYW{=TMTP!F%5|4>}4W&KH>Dc`#ShP@Q>T(&R~ zH|B+R7f15}2JnaWY8@V8RuZF5=zriJ@g*!GXX!1d`nz*U*jXk^k}tAOG6b~r1OI2c z3{Ud1BuR#g&6Gm(n|y=%B()-nY*A>Kz|NDh6RJ(2K8mK zU{8k~jT%8A3!_)TWD7F@sntF2F4N}9zWsc@Sxe_-w?^ui16vGw^+vpmb>&?V*%{@mDl%#O0Tj2AFBn$EskO*{c zfWLwwKK?pIvc#dI@FTZL;IggQR)z}4b2XFWkAR=nY#=F^UfaNc$MYsU$z))I#}&yYtE9p@)@=Y$S{~oK7&PbHqCVaa=u8Of{(r*~_Wy<@ z%>T2qdP+mwJoLaKK+P< z^bcjc^+=kc^Og$yHFZhE5gLhsO_MB)>Y*BWdO6))lCoj;*)GIho?``K3N=oWct+dB zY17uTy)6xndV27tF-_g8CQy#8Ajhj7H8;1oeha#t{utapEV<0+AlXr%nkx3k znho{Ikw-^#Zm(8Mrd$xfU!#66uZIQw+>j}q6n$B{nzuE(q^n+R%= zz;E4b7_^BbP_B^pT!$nk`50@eOz@^$WcaN141Z_cd@`q@Uj$`I6>wvWm)-o)wq9lk zuk&=ktknA}?)U+~qTtOLho|m$(oT-B_q$ZxF#A6~`E??< z6ocq0CQJ88D|vX0qK~~U7W_J}V>Wj$w%o5BU38EhZApF$8}pH!>oSGoH@a(UG9+r4 zoiN{5_Vj%q=40t>KK(!+`bxt0h(}dKH~kR*p%F4~YXwXO*5AH{yl@MSp^-Dgxg-PW zX}gx=D9NodEgl_8r)2Fonc5o_Sgy=*8soX%x!2uY|Q+|?9+Fc(LW05RzSK|z_K zp%rCk_fNk=$pUl@)W95{KZB}=GJA7hiM}%~D?ug^kuWos)6^7!Cjv3u1A%Qu=!w-J z{RG0oN=DU>{0<#8Z?T5exqbv6pS+bpLM3M#}I$tWPASC}C=T&1UDJr3=JI2?02_u6(J(ojt{}AbEy~@29{rfx+c11d4TAr_U zQk30=Qj#*Qv|pbGus?Htmsn5c#WlM@lr^{D~cuZG3o_;KrvOr*#*}W zpF09N9@DViY_+Fr3V~5biH{1zJ%=>`O0VF&_>D94n75gs-@|Vj?$qdv$ODEu{?u>S z7814%AJnq{iZBpi&MCvgs-rDa44~R0T@2Lj`&WsN`L)bJx5e^&Wum+xau%ts%h;F= zA36KGp+THTEEWy8=hA=743;}A9wA5Uyl{Z@9QkAmfn6uL=Wm3!u8K0F-(tI;b5p`v zu9!Xtl_-%ivfcWer|AJ|CzY7O2I5y4agR8ogU;@PyztZkdwtaO4Xrr%_X9(Voz&7- zk>TBYD%IFi$XDEAF*Q~9puoh2~w5|bMk(_Sgrc2p~Kh<+q$`MtrKU?65wzFgq#BR z*)6hWqkh|pQ(eC}PVfDpzJFa2?VJWrDC&S8kErq4N``G%&JP0{@O~E}i;vz|R(VU# zn`|_q1k~-N0rP3;`Ko>!PN`miC~t{k{0gN!M|KC8N+2WQ{^ee(qdbJb!JrZ&P4}~+ z)C9k>PRp42W;=gYZY31FDxet$*3DLIeXbK542Re-dS%o#8|sWZV!QVP@j%Z7^zjY4 zY=^aK-UU5OZk(K8n#_E)=j*O8#E#wya z>ZDkTU1z1<0#D?JsbwR*>=|Vy3@OakHH2*2__d56CC=LHw1Hecw7w2iio!2ps?EJY z7Ere_P`WVB>R+;lY17AW!8n2luc6Cr4!p{V!O{+b8N~8Ti@h1ps#w9L!6jtA;qtJJ z;)uZCmyUzZ7PHQXnAX%l$!IN45Bt0_z77kv96_7xN@nF<9!vIRWpWok(*DY<)A^g+ zM`7r}M6&l;D%A{3l;-uro`Iqa<^%i1dUKcjD;<^8aR;_I^yo2hQ#PS;^YB-Q@&rbI zrS_olLFB>Ziem!V#t~Qoj22QlN%V7zO#msF=ASPVg6F(1!j`$51%Ns}QT-GwD z!CBHQ85rTC%@+>*^B-x5xY=WVdVZ@2laFksM!xd33CUPrEEHvK;?^bSk=db;NY$nS z0l0Rof$1C8RKCPQa_U95Au4QPZrtWX$m5OVdqgUbjL5=2_w?j!Wk97;#bA7e%<-UV zA$YZc)?iZtJv*?Q`~cHi>8Eg(LBM)@p2P2pduT+6s^U6~4*Mu6l0%K}>|9f*HWRj_ zTVwrqAwV&*O&BQ#6}+vh%puwqZnRbsy)2;*)0A3~@ie_^(?3{tKxvGyXqG zF~aKAsN)6YogZ#G2q?mG z^+N5>)3QkrcY!)SuDm;_Gqp(B-I)8YPHNB#8dkK(0ttvgr0Gn4!;q8($dnNI0_5lm zhl=BfGs7Us^XL)vY9^6|POK&pg*IC;`RPXq2EUGcS2lMi#&;ef0(^2K`e^3gm?YVb zNY3v;GE~HW++s9ae>8hYBl~QubhSuM3V1yPewR|1ktkcOg&<@oQXr`3q3p2I|IaHX zBlznf4KqSvF_Ia8$F7JqIs4cT`RZSom>gx%wS;>46dX27Pgzo|IQW&2wiDSg{Z=^G zO{pzmZ5We}p94x(Fqh1UF|xNz1l0O4#i3g~f$g_6+8Q}8l00-~pkLTuy*rwH-Y$-r z$tp{+=q zHnQ}98IFuaLhVyvQ>7sblUBR$FrhD<<&VaM6L08jVaw+qF>~c5YK8bH;=~~45hVhtRC&d$y5&E z1NcBK_D~CW!N!^Ne=PvOBzxgMXbu%3EMTw;_ipdScDh?!XfR)5r6e7%X-mblqCHxkYY0j)@#{PjC zV-mx9A}DyYr&pMN=%)|x+TFoiMZ+lCvO|SO{v~AZ)KFX2GsW`AhD3bnSXzO0IU#l* zv%xWj8^ambZh#%vnN75xZJp@=p~lGd8NI=@=TeEMtN8;XKa9*(yxa0}CW!@Q)cGL( zIMepdupBLubSL{0IzxBJWyvAA9+sTnvei1R>QJGraFpg+D+%Y+#r~MQy$YE5H86h; z5Xf%VvrYp$!0gxxWtlss4} z?6^u6K=Oug;vAOtuvQRml|+md_>tJ}=VV9%2Wf+BF|;$)IjXu7?c>a?*0|G@U z(D(r1Zo2>Y#O))eWND;%-ZpSvG(dV@W*aWfs4K`(SLP<|`ig%6!+WdwB?EFJ;=l=HHQZ$8j~t;X)jDv;`Vgqi_(3>QiC!10*@>6& zpEqy3C%cuo>QXKT1jpZ2f5rgOQCbo(UT|v>;_1bZ)da~unkVwOcsiqHjeh_n4Nirx zu@pI`Z9VHc@@!N;a?;{Lt~zYw`ZKcib@9{#g8vkUO0o{-S4Q~DsUO+>*mX)k2G5F; zt#fC-B3~IOtUwg}Dfycj2B*$~w8`l-yc&Ai zy@{-X{LX0#Fm(&+B>T#T#q*`2jJc+77TZKnVcQKXyx?ys7Or5Ln(I{epD*3t)m4@o zyi#y1vIUCZO#MoIg}% zw%(|}vQ2D_B$Pma=+?)5P@wtg&5w#?T57^;b;reG7$hOdh&19Kj6pb=l8r*va6D== zBMxDF-w(QE3>8wPMC5iGSxNlPf6mI~zvD9lT%p1_Uo@mCpOP$Cz6+7U{>q~(A(1-? z-AgH@^23%;{52#rS$O5OK+8LCSp4P+;?Yp?AOw%G{7gU+VY7uNUbQR#G0AL{Fl@&7 zDMxT_lpt(Svh2rxCr9y_Kh~1R{PpjzT~BWo8u=Gc4~@0$9@DFYh_P&%gUE#wXJ+uY zta>pu7*x!H(c`u>B2Un@2Y%Hnn^+w+Ab=2Vf}s=)n67~jXrjPGbWYK_y{Pu=oKORXSYd~8t;Q(_2*dHy>cY82EnT79 zt#;DK{H@z&tENOdkTSyXQa{A-GAF?G@;kdh_%B@*bWFtm?C~kWVO-6hjuUe=k4}3p zXQDiKoNM>M(@j_A-c3z2xFAAV2XytwqYtch9AEO3W5j>hwRI1+uDl_l^;q*M>VKcw! zX}#35n}yWvvu`Qa#OX&5YOZ>^f}t#`UAVAvZW&UGw(;3#KTG-P!*ASF9O{jwZan4Q zz1jxJhc}7I z_yFLu>LmR)%>OU^E+Y%W|F?xtx4M?nAsdR%soLATPnfEhYyVU6X0VH^St*Q#-kA%T z42%S%gd~p3$E_#LJ6T~`n#Cso;HmoQOSW9DaN=hWnVMPC$JZsyT_RZ=K;pj!eTrcS z(CjJvS=-(PV}Cs#X^Q=~Wp=ZVG<%D%1f*YGa(1)TZjq{GNaiGlCJgl;vMqr42(?Hr zeqK*kN^77bbxXqZ9nwC|eMxdjpc=A-bE0;c(sY&cMeD}g>Bwc5M42|k^Uv$U!wQbs z9$EgLPLOpSVQ4a#dDrP zJ@}vXt|sjnRqO;A5ccy@o?Vs8*$5)vE!4_s?_*P~+Me0m2qr_C;7GVc*L`=nE__7v z?Jy3zYcJsuvHLxl%&@NU{@!*?HE7hI`yYnf(|1F;2@WjzuFj#rXpVTLfhjw7z2(Q^H)VhuIL48odaw54>rQT0tT8krRu^)o=?L?pGlKhcsA z{0vDGAWcM)<#l^ixp0d*{4l%h6)ul~)X$~X1%%(rp~DudS-^lp?#Rh`q}tLRj2LMJ z5K4$6!=ppZ)Q0`~)Il(T4yyZ-XG@M+THfPie{4P?f8V~`8Eh#b*?BPN5kqu*UkvC* zjbE9;25mCz(0t-h9{fBXj!Y39oV>^2-`!!1|DeWq^dH{T>5c3Sfgn~um#7zm7zE_v zDNn_k29?W7=?c&sV(>N^TrXLWt_(V1rMzuAHqRJ6A7U`rB>XtE`1WpFJF*unUd#L> zVP^6jidhSD!nGL+sKkFeC%!;;5oKu#zVWUA$)w>e20;ERKQEnU zP;JrvRWfv466)uWW*HdBE@t((B~-Ho{nVyzrXZ?vYseV=F-AcSDts0YkLyCt5K@Rl z9&Y{77tf6OZL>n+!Y^YRk~3<4hH|p7A9S6 zvE*P&@&BrJ|3hPr27i1jQUc;f25qFuZqlaWmrBVY*;KjB!S9911x<}vs`L)0U$(M7 za$Je7SLmq=?N{#7sfTw1Uh0rUQ#>z&;Nvu?U7OsAMDPy1+Cn0EL~{)nck3|hwiJ(q z{2XE4WYt5jziIAI^oDRGc+tj<=T%)p#tKxc9Oy>D%2B9s+7L}$cM~teBn^bujR7C6 z8ZU!Rl>DZbgHsOHx~evaLb>y8>pjVNScl3Q>Zge0RN9)5HMeLEz;$hLI zB%Po(Hx}1bHI@Rq-GO;SZ1ngC7z)y1Vi|y>JQ+^ic98@X zwjIC`%ut4j*#;zy&sVC{f^HJta)y?4YcR>~9j6yBS>>_8hMG<@%ACb8qb#V=tl38+ z26c$o#7VN~!y5`Ehrm9(FeKyGx_c)MeB4}Bg$4A$(-;q5!KT8)^lx*UIs(2)Jh1V> zZS;1AVoh<$BI~5(F0qfIA-G?(ly+8?MOztbQz$Bl0eEHeMGpY~ym*-Z($kKfWj1M3 za>}YWMpyo8b;@$~+%I=Ba(yw_Xx~K@ZOdF^-@5JTvYZ5Ci&`5vn+Tg2*%_Na z@$o@9IXjvd*g&~$RI5qF{)YnlRJ)@9L`}UNMdNL&9kS+3r^(v46bmX_QvMB9N`C#%?hXuMJ8CnB$%`JSbH*`P;haz92F{LSE>U$ zJZ7Z0e7)hydL{$4f-d1W)o{AljLQFl+`k_lAGxX6Q9u|^am$$yNR^nhd= zhJeAwNYV}?A-?CqjaBAK$s=!}EUjknI=&g;B18ub&@rvlKqZ~mx36e&N8^48(?L|U zEqzr0K$M*iP?J3JZ>_}m$c!Y&v!r-*^yqEoqqoB`$+uV$$2^0zDq~R&11Y|4RJKu( zveCR;q!w|bNLA=c&{7%tzf~AS?7>VI`ITjwpb-E;*Wr>TY06Rn)Sf3n*evrm$v~lj zE1sOJe}u?E8jMOvqM>0kXMZy^39shBF<4s9=DWfgjk;jwlJp6)jg;;xG9+Vbgur!Z zHKE8(Q;v7VBwR6_kKA+#cbsH6AF4uvk_LNOCUYw9y!V{voeMR*G5~g%m)-=)n`8W1 zWh&QNCSKUsJcuR1jhBZW1vyk!D!y?7#-tDW=J#3x;W*6eQEJHY!ZpazI!Jbx!}D6v z;zUKyw_SN`?#}Q$lirrvRNteY18t6Ku;ew{> zqzTK}XxV%m@04+V3G2vXn$Sn3&6T{Xbe$HST*N+K`ATDqu~7OP`Cfg@%ER;;ozVJ* z5XbDF(#3E#qEST>P3CRSF)~U$f#8Dase1Gda@$NO7)o&eJi|+yDw5 zh%~390m#Ty!N|zaI2;{WsnxXhd{_3zSp8<`RN-EDe(wBft9M;AXi47>?YingY0k`9%ft&Vj~gFZIt)u1^dsJQL6Q z;sGj`ECHC>+RAqH4gwCi{4t|eGV=86s;?xSyCE(tuc!U9+`j~McJa)wC4aBc(ZTh= zz~tfP#(=Te&cMN;1tGr($Wx_F3rO_`J+F~_4(3j6901gad#_i@$d4GjOhdE%YclUb z|K`Hr3I@cBW=%64=vs$QaO-N?Z^ke73P8nz)31Pd?niIN@z*gA`0LXiU67#L$S^ZVG``tslo(eBFD+QiEG zVgIhx*~l-bj;ZghaKLx<)bPUk`0Qd}&(P#kEjr5A=Fws5;Kj56&c)@Ary6=&?^C!2 zC$J3v%y#7cZmN~h?ViEyBR*4W13U9?B@8pWnCM?mYI63xrnO#gzLdS6Dh&V~z$hjKLBEg z_$IUkD17q`PlW8FxDQPOj7sng1MQ=D4pj$?TJSw1?!3bP7^8a)Trkk@?SDDB3gsQ@+R{ytn!M7rldT+&%t>yy-(IUsC!{ z>Keaw30w<)>Su0p?@ikL75-a3+8g{=oYfEc7rmps(@*GkAwZ<>o)~D#Z(l4@)sH_u ztNJf6zwzc5Fn`nf4={hw=68QAJH#QsvTs)-2JTO!HGU7iUbAl+e-PIXqwmq*FASSd z-=+8aa%1NYa36?YF^wtw0iAy@!fyNF436EZNtx;6f9DJFpI;u_#|OkOZvO`6mvs6C z_ZfSB3#7rH&-wS|>+XLK6ubRK|1z7E*v`!0>t*)t2H^|6;s1Otf`NDd$?U75;Yzy; zRb2{l;aLXV^Pa(zy`PT3vn-aKd#h$~Z|B72DV0?=^4W3jLY787t&4yfsM;btytAxb;=;3;|1X=fM4RD!^JLPP5 zAf!ai<9CA*_gH~Xsg4e%#iPJibs8J!O`2X9Efc#0MV(DN%`~RpixX^LKpvRO)A1mh z-QAhaKbGxAo+OzYc+VcK^LdTx!#DRl%l2$0eUBK=0v|VHE z8$rEk3HjG=(em(IplCd=8jw(n#9Y7oK z)X0-*rA}rOX#qGm6Z{8P+DY?$T>3IANZ(W*!QzIs^U^GRFXMr}$*CpxPclV0hTC^L zJ^YsEOMrbtl=?XkB+Pb0vmW|z72r_yd zoc8hC7iAbLvmiCkQB*taT#~*=HjQf%Ff-X>ok!JnrD>TE{gaMmwz9ZhX{B$Fk4HH8 z&jq2ith-n6Qar4DLWi_{%GgmSdMag^=gi*lZjOQc9X~bfQd*@$Kzw38YnFd~j@4SQ zllP)astzT=4ah}%f6=5475DIi1&JgyeaJyJpCh*GA8cDi#VpA%RwpT+1@qq*iX+=c zlcDBdfvmpO&MvLeKB`1^v3xWEg_N}TOUTqGBvY_rWl6W>Mk@t*zb2ki2JFq}ugfrS z`spcwAX;uO_O2k1g0PbxWT!GJ#PLl6bwp^HLr%V>uOLJp7W7zEFReK{t!GD`vlN8$ z0BixBSVxAgZ3iWFsBGdw!>#m%t(s{p5OikByrEZR$tpCg)Ip=6^>YGN<{Nj!!|MWy zq}lw+j{3t9NMk+Pi?mI>srdZo7^S>)S$$4lB+N=_%sfStt}z3xLLV&R`o}e6i{B+% z{*x}D6YOWc!jKmwI4({J-;Gl3%C)oaaGN**reWajazo!~wR3-wqb0-c{=*~HG{0#Lt3io!;{+F@;2tXGv|tlPjQ;vP3RnI z3JR$!YKP~ji>Ks5!2tJ|co%lfPY!E`>vZY1!K){3y4tfD3^l2qbBjcWz4HaI@<`uz zmL(+G+*&u;U=Ed0f|mwjweEj>wX0Mk@zdV4q{dgaAy@FB=NPHf1=mult)sYRMN%eA z=AS|?oce0^kq(186l|)d_@On9Ocs<;OA@t0?m*=DpHlm*?O;vba)Mta8|T5!h5Dfo zcIh-TXQc-`V_zzm1QiaSB znA}(jV#=PPNu?zRw!D@CpTEoN9wBp1WH5Ioa`E)G2ISSh(u*q5TQY7v9usQFyy-+} z*gA5nbgnkGGMQQSd>hN_ykH)#DclJ%7voE=cJGi~UXwCv=F4`cZo$rs7`8*wTmo5O zDRBDR-etZVXESq`7wb@g8c?3`=EOUdHd^xE+p{!T{&*C?zRJIcsw{k(=*9DD;E>WR0&B4R$mztz!w^(ro6yAzyH5MUiHJl~OIPaubvOb(DsGxC=<;aRmDja?5Sb=Z2M$<6A4rlp8VKsN@EC%vQgX zbs=9OR3m{z{VNJhBdG$e`jo@VubWh}zU~yuT*^Nmx{H6rYm{kWXSrYTwQ8{c?TaHO(t1WGEoFwotM{SX~a~ zh)o&^9e@EvgL`lg09N4{#-J#GCAZ$4mI*u6`OTYPbG}0dfvG<$B14hl-CH+zeqhy%~g>U;*!Qm4YWGQ#evui1pGhe?i1IL7?3V z9cD}YVC0I<$eMB&Oslz1g;KZsh~mFYMKCuFKSgV*BAZb%n`iuxvHqbEk%;|yt_miK z+y~OwjOagG{)!WE;xVq<95acPI3D52W|Geh~J-5aV(N@S=_t;eow4;Xbb+U?$RZZybHF=3{F~PVGm1UbfFQ_e@N?(0$ z9abZ(MYki;7~mAOEgM{+NMv^cfw?y$a7V0htZpPXN% zRg0V}c!AvWN24heS*ysF)4GN@Ar86X(BR2gUDSu#Mz1tu7OZXyua)k2#dQ@79c2;F z1*>I|8`Z(Nlo*K|_8G*8RcG`qLYDAN!jZYW@nGth0)UVl z%90U!)X0tq82g@Nm2D3tH1%uneJo>J-w(S>T0o>w7wo+(0XeMwn|r4x=F7{h0!+t~ zV%TQwFXJBdJeDvB+ortK5?(HHc}Zm$o?VaqIB?OAlmQ`$!ZvLW=S)~=YHBTtXwR%g zS_eN`uFY%PedZS`p)nSaxEjF)eQI;@%CU{{V0w)I={P80_;{KB68%m1#6I!LSi4=B zn|`>Q$~sQk5od}2UT%cX8~ey$sSlBnsa3@O&%x&2d=Qd;cbiWKB+JGt~CT0-qZpvb{D$-6Vu;XO*OPNpQO`*y8vl3_a$ zxeoyPfpiYfCkqs<5g>fRPP5Qcdl_j2@LFe-I0%{KcWn6P)cs3NLeHoar~t&n4JEU1 zc$QWL)KxYzNA0DXz6y{GjNq+P2fe{y_K^69E@9WhMVsY7WnJ074-VM6cf6b+h=_%H z|12SW>JGfY$D;i`5EZGygpiAn&P(~5}zQ>xK`!%Wn_A`3S&Rind2SW zt;MIRP@Cc0m|#sc+#8%rZr4ZjU3nyo^W0Db*+(Cw-BB+1dO)w=I^In&r&uF0nYUUl z-Goo8M0EsHOeR>D#F>ZjnLqK;2-nbIqY9|~tBguwSO0(;>EZkgIv?CV%Czq}beeSP zJC$)fl zw#Wugn{R33Gjg*TAzaLGjVH8cvR^-GA?w>ng44KGp;={S*XzIvz0;0YHa_F002VaX zWutBNORSFY*HiN zgAS&v1`71Q28cFCMTyfhBD!OULFv}HSp_rP(&74>!ft|^W--JrnDTBwMqi(gJ^if$EoxJUa0BhemR5qpx0NGlet<^8z~Hs37=c|68c?=R z)k|YqX@&U5N1)qYUU;3PUu*&6iF;OdD}tz!Js@nZ>VVe4NOq?NbUrMawcnEsyVa44 zwKBNRz+a2)ZyrG}2cC=SnNZbQ@aheN{E4N0j!sn4x(V)|qu5W+K7hz|iI=HLYll6r z5j>T*!?4_;fPfhE3PLi7mA&$jj1aOk0W^-T#%h_3o0&j`MZSM19h4=ol^d&F2L)4d# z)7&sonQ~(9^ciLk+$Mk7jK*M+)WYJ=s{YcBMeN5;WxVX#Xt zS%?@X4@A5HB56_~yZ)2<5532wJy=9h$Og9zD|?q_$;;S)CsBoFw4IA+uk=|*cjbpZ zy=GdX1YYTD&;`?%Dz%LU98upmo+!bsw}>7^3Hq!)WL9s}gd#`gIIH)$SUPWVjS0WH zBU%K-F;YJRXyfse0iD*vnDH_WeZeuCo%a`52eLM(3KJ4-wxFskex{I+SyQZrANR91K6R<>DRox$kKPssfr86NnWI zF4g|vN({D6_jRR;bWV~CbZ)#jfodUDv?pKHqEpR28baf8emw2xy?#oro-8Gu-q2g? zl}vaJo3H?p{cZ2O?zs;T$>JU*<_wG<0+=PAamVI?HT4Cpr3NXDF3nXYQFpTR;r~w3 z;Ln6$1<&qc(d2(9lo1w6pwi#8#Mg4w>rhuplPf!RqfhJ&Ih3u#`W^xTVae<0Y%>S zlS<*dUXL6JV0<^sGfr+iP)7CZGYihQ;?1(=x~n*+`-DYvb*o-blCnBOPHPgPFhlXO zp55lU7=ZH%k2xI~*4=P&FpC1>ImPZ`}pOU6x?`o?O><8OUrptkyp3NC(xFS}&@uYEP|I zX<@WiX*qz(U*Nj&G7j)j?4%G^Cq);+eTub!L26|3C?$FCfKIw;5iHnY4x)F-)nyT` zWhrPO459B4o{~9wmC~n#LQQD+o#)Y}gr0DQ_mhzDnU8}6NGq)oSu2ftwEJO~I_Qt+ zhnL{na)ujaR>QwhuY*FOy5|RW88MSV53&ax6^3B zEc+_EJQAfLs^ucEJ$+3k(ToO|_2{D&4XmnScZK`1g7p}TN_Ik3pcXMmC z-&ODLSPISmPz^;=yi|A+M^{OuYhy{NMoJK>{E{3J7&#?hnhTewFap{HUB>+s6GRht zq#ZArPZ4CsNU|%36ChAzoPnBU0u4%}8=2*R$j$FDOz^ZRO(1)iKeJ(LMtJOrs)w4d zb^D82N-mJ(y zBpL-cIIhKOe7pn4{jFT)}b^wsc>s%$-dDp%P~0XDLPumr!Zqq>p6X-B>jfIFUsUwVWZMUwBP34<@@r zUt&X9@`2dV7elZ9hGMc!rF?yQusa^Wb#bz$xf8KQHtsF!_aIGp@J(c9+&+lvh#?kN5B z=+;9LpJIxXv4jVv(rhrps3#{JWPRND<>DJSa9aG5c!y}fX zhs9<3+6pqG2#cmiLwb}EyG+lml`cy9OU+m!6<#BLfdj! z1SRFzQofxtdGlNB1uL08nYGu2J|-&vLM-zlS~ZaORsL8eUBH29yec)Og_FxLMaa)8 z_q|qiff=HuHKb7|qJkS`)ty|AOVUdyz3Yo!&Vk=7uB>VBxyES2ZL~u1v4;-%%$+(T zrSV8Vo4`|Bmq{|QC$I>hlR5k1?LDL(J13D{7EDOD!AqCurm}?Wrv%)uOXV!UD;}NJ zLH9c>SZQmcj`FJ)dL&tpYuv?<6_3PwjMfDLQw>lzDPFac&?{=K7hPJpI{Wp};lH=Y z9|T3sft&)bFH+|9A+0b-!>USV0O}87^kMK$X66sjY_D46GJc_HFs4JIoDy_atcyY| z3-#3UaQh|RanaiHUN8y8hcT=9q&4YXocE2BE+H-4d$zVEX{ROA#(#?9lT9>kGJTNK z5I{&|>7LlJ^CG;j^RkOvaU^jR`+|9UVD=>`7Gof=MVj($T3>m6M2Tr#**uXfE+{Ql za>qdli1u}sXc#HGJ2tQuTjXgGilch2UAPg<`>R#@+?8l2onO}{tODePmuecmULt+D zB+YfnQ|-?KTooep?G|h|G{_bybKYnX1~kS*06g#NM0VKGDttRFjoH#Nb$2RZ%8fqO zrNliJN9d^P&X>D=@4C)+0Q5}AtQ}H@fdC`9erk1!fDqH2P8>j@Y^dTjr2d4s5Kty= zQV!ftpV(8WjVD!!UK=yAj0ZHyKxeI91A_Ohr%Gyk7+RT@-^9cn9e_k4Nw%8$a7J)X z8eM=a2csvI;$4BE51HzR`S1zG{l#jLGtNA0-ZKjLI4Eers;-6o=E$^7x$QrTN`^Fj z1V$?W4VNT7kN5E11BqiPL^Gfvjl?sQnV7buZ~k%;byFhma)SzsN4`Z;RcHQPr&@!& z2#S#=3n;dHY3xlz(|?Bb$gza65joZWc#M&;3(<+gk_!$lxl@T?qX?lKvzJbOgRkyC61uXv#SP6koNv_;c+hQAaN87dbC!TEoP{wQG#hDASVAmiD zgv)ka&+Od<2mc{-as0@AWiX*7K1fX$gox6^F(k_Yd`uVMn=<|w?V^6nf{Z*~V=Syo zpm?DUM+@-WDOupf4rv58m#9wW3B!eZg|E?m-_}@i$X8v_TG|^JSUg zZpid?G5@izx7yfxvK3??v#UxzG8;xxh3TJ;G)M-@vfS^ZGQjyj(-qN18ex{JE-A^A zr}1pA7lBElD7KsN^C1r+x4S)1k=G`f#b#0CpHCO@w%<(2PlIW0690P;i}V-rUI5gr z0V?QTTAtW2X)9kz& zZbn{=Tztk-RYT!XX1n;okSH_7*1%SZ;9n9kPDP%=6ofVk_)=I&qRWjUoyz`*z+WD! zLDxOi4?1#rNC~f~-U4?7?X74oNeyD!y|e;A5vEORI>Q)(dMbAXmf``?W=1yif%Qn5 zD>5GW?5YCqe!_n1@Li%W6XR)+^wJMU@C2`HfgStN^7WvEr%9l{7E0x4^+OOJG2TLy z_5v}vP3P298nX(?d2wNnD-H4v=HV_TH$f@rE?CVyDVe7`f12r{U;b{aGjG|Q=F+Tn z4c<~cAkSqVN9wr6|Lus@S(;gV%YPc{*1gH3C9bEE!cCYuoGZ$D%F;V7QlpgX^gY7) zz*6tz1jI%`++W9iSNB<(5!tgnts?dfU94nmO!PMpHj&;5ZD;BD9=Y-WGQ4(<}A>7=7R3*1_o|8Xn^s~^)-n@Xaj z#VKYG#H;l-L~(prfqw;F^aP|hCy?%tk?!9Qs4*Kdx;fKOceu~~OO>4qqiz)P&K*oz zP3#4hz>XL9jA5=%Qh4dx3@f#tr5&0a(yQgOPBF-_d=&8~6OYsGaE8Exp9Yv4*pi!D z*t3wR>4WKnqF(9H(@sMobiwet#X{k>{Bd*?M!FV`S7Wh6cP2<$;%sAHq!!8J4589% z9^s}mg=}n7-TBSt{W`Lc7glM!iHM@BZYY+kKr8n3u|hpT0oe!5rH51sbKp7+%Vl>J zvE8A5;4Hv>U5*ukp=up(Eks((o%DFiJnM{)Yr`psA-gZRVwM>$ObaFkJR+~hNH`-p zWOfmBKRB|;ai$*yH8gefy-qPa1C`EPjyN80Qlob+FloG57sx-;*gXuU#^U&VrO3!?3haH z7>yJ&vWTo2O1IPiQ6@A{6dOI^V`Ea@hB3-YD6!F%Vy8S>02UgRUvn=D4Bk;$v;hB4 zmantKBwI76>A~HT7HJn3iwojC6{M%V^DZew?dh`M2}v2rAqS(ag_j`O#>unRM~;xa zvtchRd5NEd&7^u(9xTmvT)(-)4Bkk<3mI)z04BQl?M~bO>@~?PPMkzPSepDse}{>f zmfbJd=9h24Cz6T65W|>Koo#KnT!RfREjg+B zk+>&vk4SEx2=4xaZPywm6Q$}#XzKZpt48?EK!7AK3 zDa@)bor7Es`{RbZnQULN>#eBU_8{U2iFj&3 z@LW{Yxr<)I#xUmtlLsF&uN9(nn*Z_%b$NjEEf>~E>omQVo8E7E&o`&1mFjZi!Ew5X z4Rxi6fuaZP0)btVGy)lv-pXpVDk8Gax%GloOV}pt7CBn+zuc_fXy8$j&}(hQ%!#i4 z+*qS>>WrQX9NP(Y{wo?OjtVK64#s~(b5{8gvH(XU%~P3!Wd~a4@h(O}hv+Q~N2+@+ zjTixw-S^FlU`uc2@}?j}j=eGV{SC2*R&CzwNkhPqnE}?ll*_h=DuQLg$ovCodO$f3 zshgy1dR^U6a*~0luzLsr%v!;H9ntdWj}ExNfIQZUrvp6j~kB(2M2G6lL zbP$)_P#n0)8g0)H$)e?Gcb@=MgU?yqsw1B;gQfhE>Kl~he3)Kez`jEK{lM|yc#B<` z)8(9s?~kkb8IE=eOfLPHYsI>Q{e|Tv*o1D~$b*AUkuy?CbjXjI<*%1p7WTPn9U>n9 z+!7sm7y7%z1VoTC=flwCfGPRhTx|e^kg#u?@0#ONNR68t;l@>#@Ls%Y)nf{1r~4pf zNEx*-wqs1%qedGj5}vj?zEZ1UD4gt$bMCe4mE-<+x8TPXvTL%Aj7KCGn-H&>dyq3; z8zb!l9gJC0=&GD7?JD$rsDKNpZ!>AdQhPZ>L}Ep1^c|hku`m=AD~#HGjER+n|8t>o zk7@nrBaq#D^EFc&$#zR}Pq2Vsbeqlc0NJWfQ=-c8eME=I#hIA!cj>fC;q2r6Bh5j` z@s7o-hR-!*o$A7SKhf^cy$6HK#20Faxip&$L3ZJ6Ot#D-p(Gn~67Yud*R2&m46^zo zaFdz5M-T|b9Za~j=Zl1xIN#WIZZ4U5 zIU9>j$qAbs8)gV@?8K)N3Zh3xV&||qo+RptetHc}K}0j00?abJ-PG-QH^T{6$Pc+? z0~yH2?TAEf?Q7h_X`!T-mrFyR;Pc52i9v?@nrBoMfF(bvFIUDx@Xo`cxxN+J2c310 zWiue6%raElPi4Zk0G_LGL=Ph#Q;too+W_~?`GXj+ogYm{2-argj` zABfLyxbi)u3G#&eC9`AnhCDXpd8#0bVY_x-L3oJ*0|HT5en6XgEn6u6Twb16v=Jsp zL<)RnmDms8ibSOhkW9#w8)5I|pcR=1RXi_`W zpcN`6kYW^ZWlC(S=xQBBgW}|_HCKnjJIoi8+fCJcSLJ%cp=`9O_CY`BR0j$>vNx6? zVAqt1@=Iih;arX};H-M1-LOZ{l-w4sDTx7JILMNB6Tb8Vf`Esd1^mKXu-2Z26t*hc zf~Sp=Xgr$V7aLuhQqAzUx}H? z2vxnon#|S?++Ri{YXu@s1bu55VW4Tw+V+Dj@f;m%Sfp#n8mqf2WI2;-X{6$8)!EdF zhKVc>Y(C+QZly|f6a)mhyOHW^5rLyEo^7eLZ+9t=lS!Evi^1l=BNOW=l?^7RC<%P; zT#Y%L13cUz{<)JFt*zYUk3Q{xtJ4XIry5Bm-J=11X+sLmH+=%h@F{P}(&W+W4!lb_ z0?+;x^+z^`oPq02AZu!o9``~0jJ9xnRc@h-CCNM(%@mG)TQ2x^2)MWYC8s1fg5>Gt zIAX5cV6t|4Bb@4aUA|#BFT^m zLih4-1zwD#rRL1A zchWxdeJ$VIw3C48#q>ErKoDu=<@Y__IpaFjPx zAp>L92KQd*w5!y~Z#Fq6Xef32T^hO~hTLJkXbyqZ2#yr13u92Q#Z_nJlgg#)OjHbK ztKItCV#A5aY}(RpJ32#tn97X^xg_kFO`AMxp_-(h z98IVul_L2@fZHvot)Y2YP1fY={io#^;jn{05 zZuyNtg|aO9=t<^4Ug{}MR?tX0e>9i^9&0SYS{rYDRhHTtztc;tVR4~zMU7>49~T8% z>Ig=33e3;S4LwracawwPRV5hhCIcGMj$amJ(bYz1@S~bj9P`1TO9Rg#c+i*^ShHT? z5zSQflhfaGlXWj|t!lQIcceP?nKrc9s`$1wk#8+pZKRREbJqJ}?yTNJw@)b%dMdF< zLUE2CQ2M;Z;h<*zQ9L|87f_jpapD>qb!9h8U^qgg->=GlqtJ55bMDDkI*FwglB1Az zpA66eC0-aC$)nk6xgm8+Blww}{xWHYv1cgRv&^u?3-An;xt(P&o(9h6M;=s@sjEHE zDoG*2AKgHR#~u}78Dg$|1=LnPD`sFx&1BSiQ|YlPdcOnlsgZnK-oYrR`#N+Znj?7G z-R16&vL+K`sTshSLKd&5hXC#7i%nTTlv<=sV9laN>!@*o{;giTAfGfM;bwb9%C1wj z5dSHDj-fR3p*Jf-XW{%)H9l0S|C>E-JOXZ%-?UE%{a(Hk#NRX+x=`rq68QMDgDc_T zDY#dfx zkejuOQ}7(^{obB7$Bvw|pt84!_qU&EkakzlzK%f%q*PymCDb(Ev&}){&Kn=LMw&mo z!L=2d`TATjQ+1oioN-A-S~dF|+I#h0<6Shy{pW`o$V5w;q^zso-{20}w*mI5;CKCd zm1?kO3|(n029aN+vD+PR97PM>Q3t({uq-Fx+ZH3SNMD$>3WlRA%O$F1zgC#j?)hQ5TCH_3 z?w{ciq#Z8X?mlVSd73({!r_UDpob!)t}?g??TyY{d7Ig|eCzG>tLe#WouAsrO3kKi z884OW8jI>zCnA}R5j4l3W+>T-JkTq>}~MVq6%Xy~Ile4m}Ts!#Ykd%d@#t?L`9 zr;Y){J^$xN$w%2LH$rGF$FpWhDuGsMn zm!v!dWr#ie6RHK9OcHQ~k`9ScSV0qoDj%k)xYlrX1j-j-aWBl4@gU9c8q0lMOH8cF z4IbGi9@5>U>Dsu$F-DEhAqni;es*3&tmuyiC`?!ySolNf%inCuY@Y5qEh?&<;D3Y2+@;_0<(t($-0EHu@| zY@ds!UKA~k^C4-wf*{%F%uDXv=QwV?G|)>EvJ&oyuVLXCXwRc<@rOXGRjDhRq9Nmr zC^wj5X?j+ByV^>sCiVWDYr^Jq5|T$48DwG*%T>7rQ%pDYZ)tlL5G|bHv&fi6$wpu2 zizEt2Qt3lc!xkvRtRo`AKU_bav>ZQhrY)OKwARzfgvEdxib3xR^@oep^sCJC12;^l zO;KhsF>6oN{`o8KO{y$dMssz#Tm-q{+;0F8>x0#9{m;GxcloLn?cbO4G>y`(J$y9V zLkLGdSQf2x^x?l;vq`X2hH;C;?Ms*3`$Geq-hBz_Ba@{?k8GaiYCgy;{t43CZ-;uf zL#Q#(zw+ftH_lX1pHfJYf|ln5O6zXz;NxiNYSXY)pzdd%78^;W>^@vIch%wWu8S|` zx{J4}*Ev2zm4YN!DadLMHSs6=_a-sQyOGA@Xhz9;D2^p1$4uy4v1Y~pUUN6lYV27j zZ}LHmTJ)p{R(LR**6oTGk;?spR}MSS7DAdth2lugOh(aCwhA;Xl4|NN)!o@=9!Yxc zRvmJP-a$)YxGr5#FU~Cy_tU=5NU)r1^P6XBn;8;D!&HVSDaRA61y&;CzV|JUSGEUr zeJ2H5O3o6t^|W|aXT5wupSO&7x^^1@CBEM2CFH4@IXsQOj1?Z_g0Ry!hw&q_$l;^7 z=*t~kq8g+*;9|IVy{6oiP(_x_sj zZ|C`idLLpriLOgf`H&ko#zM|OC?wdYyt7SgKtQYJVHiCXz$R@ywk($ClpV3O;_YQS z-9$zHS)Nth&ng#Yl*~A@TEX7>BeD_~xD>N_-ek^o`w->5sN|o+ddZ~~7|BB7{m@;s z@*Y*5naV&`G0F3t!s7ratRpY-fi#sL=9v|$g=nIuHX`XfzfbHCl5_j5VS!MN)KMo1 zKb2p_b+*?YP$Qr35yT1iTE7ftFtfW!b#q~-oe{pbHq!ULkxJOYp@d_AMr!6$wbx_A zVCpOKhD8jR@$|M+xe4H@*7Nn9^=V&h&&P~&?esjTd{b})6g2g3To6}j&xD6LEgUTf z^rnIkiS2ixx_$ww4LnP6HqfCuNypArS-azmkqj=Vrl8=b{$0V-XmIZtXF8Vx_}D91 zm2TDZr5F(F;+wu=)Kn4ri{*&gr27U~)K)~ss9G;M8`K#`-;;`s-1)~;;Bp8JOl|Hx~becXU6d~BW7A{HP&soVC^v6YukmS|`VDQ#Hf%f%%L z2UJKtY}0;~;4B{-9i?mp6rP$GTcHc;9S?HW$DbI7sN0O;4k3+pPVXewVNl(G5Sa

hQIMHK=C_+5WTp?DdbQh#2=2Il#29ZH9LY>D9l^A_9QWweDhwok!hC9{Mv!|dDY zC`AL=mD;(0!+h1u&3v3f*@_ea(U|_&H7j0M74f30z&p+Mg!-#hp=2aL zX;!8mBJqL5K_t={m0VW#?n~_XQt72(ZV{F$gw+LfJD#M=w);;pw01n`Z|VkN)*X0* zH6KhW>$SQyji#-v$)Ike28wgr!v{D#)}TMr^>1m<_%S~6&qy(C7P_uTp%HuB6z*uk z6;?TIOU64?$ddSkMqOL>_icrrVD>m-!qrTuZ-d$(usyx~O_$^(G%WmMV zFl09rvd7>Jptk`Pkf@xI5H5T)vEK_p#!q1p@E&Sty4k*o>>Y(* zs!@O;HWf=YM`R0zMC5)eC*9H;CvB9Xr+LCw7!INFU=&1;~?qVQQ+b6m{vY_r0oZ_$jaI*9K!7keFC^4Gz z?9nIUv<7|wq8goCV;_&qAM1sjizgNQedZzNg?6>aQSSyJZik`mreO8o^cylDe$fc^ z@4X~JbLlu$eX4R8!Q~&_?&c=#n4fOwKf{&q0zWadOB3Od?m-@-`j#Tq=^Ga*)F@m~ zSU4*aH)x?{HZa9)8LNFQwPGLv3??4&37o+XPtX+9|7Z=B9CQz?=~(VX6xch${dS~^&a~w=cStCO4P1&g4+MSlPfLd5ghtcq#>H(+O@2% zn?$~P#4tvS^V`|h`-GORQc~i*2Ns@=gw~dJVh3`WZ&*^CUEl0&AGuyE$>L(=_nXi) zPuO}dj+@{ud>Z{@-5Ao(Z7Q6QY@4}Crj#J9S@I+eG7rZdL$pf2_TbSd5`Rj$>c*pP z&U2?UVWo!$yQoX;lKV~2$of{rLbncxB*eqg4u>*p?&{Im{n@=sW$Ib5m%@?hqHYFZ z`vqOfnM{Kn!L0}{;PAc0K{`zcW5d^xE8AjYAy4=v$O0)<;uYWSq6rb>AbA7S9Ab2y zNBde6NW0D56S<0E;%xoAl>SvzT3IjY9s)wES)$}eSi%~wHrR&-cRgu)g0iC|I*^c! zWdbp8DiD8Z^iGH>ghV7W!zcW13@dEYX@+=}6AbSX48m*$19A$9g52rTwP~r&N3Gam zNw_msIAB3Cue3pvaH!MHC7jASh=%CB|+Ty+7B?!f(V0?V~ zHszK5*VP(U%q}!=O`MHS=kCp~FatmXDM-78id58GX+obrr1b~!J=Xfe$4$9cV*Ez7`pUi^Uv){yxr{QnSjL5X~HPMW5 zI_-DpAf=%gU-AuehlpV=t{KhGiiX1M&Od>lY)5a$?U+iY>4}HZ57~bxavS^*jx6I5 zxwjn*C^#;NRY2QaG;(J^&dC%)%tCt@QVgx9<||VWRHS2ywvg-L43YN&SUo4+!9bG1 zfT#$N>z6u0d5&LpoKTBV-> zlz&R(JaX;mx@5uv8}wEht>U+IN3pTjvUE215H5w+RfCT+$mPv`9HoQmL=V@r#pFun z8%M{DAF&X%uutbFd4|Lu}IXhW`|!`=bkKp8gLCa3hhDtGnb!r+RG4xkabcOy`zaUOUgwI_?zX!PJM zBZ{%~389q!BV3%-pa_(Qo;;)u&02K)`*brTCU)T2aB_s;@qhz^;0Z2#Twz(kDc7O? z@cQi&mcGfHtcT99DEO8MXH79b7JP4?-lY`OZ$&!~P~tejivA4+wC_2Vt1tcJ5>-ik z)UgK`BDO1>2>tDogp7Pbuj@ZrkF;R9Y2hiC6E$qIFw=V2Mzfy=S#9YLbq3TCfxwff zzaVN1dhDl3|ZrHPB?S^#7J@__x?S>=kH zjtS=8Nx%-XxS>T~uJU-$&1pt_-l?n1C7^yd`k>N((Zzg*0`%87%6ZnuL|riBcOj&! zh(ca^PaT%n5MX;)3nZ%~KJxkF^gh=9gkoII)M)YS#P2cc%z>=MJqhQ z<^ol#U6!0lfUuquRXuHR;!BOkf~;(=#S1fV2M|tAXFjA6N;Pcncsfl7iBP#H$*ai( z5FfR^eG`iTDMw3xXcgvtEsj#*=qqQGFnTHGJW~Gk^3PMAv&4o;RWRK7zWYF|#eL*n zJpHdXKfcht!O9eZ{K(;N$x1mlp}GWqCxirCg!y!N$4+RhonSr%1(w1iEasrZg3l9v zXBtLhOH*c4tO;!*Ky`5BbJ|h^R1?ee}#*l0h}U-KM5jlt2y% z>TVejq?Aahn_-`XRrBzLP$|EJ4Z+=6F-CECDR$5)C!Jz?uGyxG??S%FLybAwxoH-S zXS-?IZv0AN$l9txDNmZYEOZ_qj_p*c`}{^1+b}v@j}XV!<}z0S@O3h3&TG;xILx2; z5~P)j>MW7nor&C&!}2#2KqLfge|VuZ*46L(eVwVv&W+xxXzadT9ot)JG~Q26VE}?A zdc6)=*}r80z}z}X6z3w?1nSAfs8*h@?SGf@%@QbA0C)cVQ7W{fB30PkRtE>I!`ZMH z`gIgY%TMHC!B@L`f*_z4)ONS;AS<@%Zl*xuAq{14B+B4Kldxl_7pIardh(%qGVT{< z{wm-&$UXFx+_YrziZ>iTRJev6O2dFT!GqK&p9q(@tz#M54y?4OA-syUz=XggGvpx) zx5+MHYFE16l?^n20A)XzmLUTD%#hNCOv-;ycS~%P#qSPX0O}FKJ8M6ugysiPPO;HqNJ4`T`e@`Dl zkLcYv-e0s6uAb{ALW9to?b1q6H8IxwfaUKH3K(JVkn3~6?AD;SZ6DL`>yg-$D-azn z2!@lBt!u-STw`?(_=fNuNQA%h$inS;+1o>2uPeM$6d}tLBvXNeok4X_W%f~AAYnNo zuFVO6(h1VCH#B;rt2QjH?wxZD`JttB29@+vf^OU;f7{|M=LgO5p+jsXEoA!>7vp^| z@3CuwhZEl_Ip2;Nr7r9-dX{w>-tBCaY$X7(PXPj_Cm@D2>+9B4i1OhxogJyn+Z6-% z0~z0tmI16?0^)@#N+00I5pBH3595kR=#Hr^5P+fVkeZvV6-VOQNMWJeVCt9l6umYi7Q$=81{z@h3VDMpuH*y{jM}ipI%dy$xbI9Z) zslD+jDA+xb*cI$ICY9bkg4Va2MjLcO-yxb4X1SUgeP=;FSLqK8M471p!;&rXm-5Fy z?*xGItrTS|6XdiBv+&TY|AA!IgZ~@pDS#APM(f4gDu8U~O%fXF;5udgjCUz1BWlSd z<#2Sm)@I^$Lr)l%+jB>R?lVJlUgc$Q1sUhXzVe2^A^s%#BW}j>$F&2d!;h$bX>OVk zA;aOP@der-Y~C>h`35>7r#N5DYkJI>N5<=oCS|B@=Z!mkO~@V!Q<6>}ft$iPi_E1! z90a}IaFI|JXmo{WCQdS+=Ankk?Y}d#*#1kt1;alK>;EBH|I92#4vzmzX#Ia?7Bd6; z{|UnQpUte8W>9(UP1KqwA$y1#8GAQ3_#4D6%u)xp)x7?o&dyFD`@l{RND#MPYYwi@ ztk;Sgnu?5{&}qLZmJv%wL=hBE10*e?ykMHKh@8Ix5#acgWCi0>K>CJ;CjVLh8JSwM zeFNCfD4bjkoU?sX9q9cRmEaPf`Pq{x5|gthbaEpoU{QAUe>Jm!Y;fEG3`1#ANdY*( z{F`VX&LHGyEv-&(^bJgJo?K`AdVtEJ%K@jhwz8c4N`QlH{NYeXLh=2x@{>5`POgQG zgsl7{tLs7M#=mmQ z0X&4T_I9fo`jUW^r)jW%9n01-INLEf0RZj5te}nX$L?u{F^qwn0lKo`$=3h{ z{6HsvQ24>$J{|uznU(y>^%*CKn#Y#}8xvz=a|I)CgNo1OUmn7OkcUq&xp#CT3{DU7 z_v??j`L+3Hs@<8Pxq*%O6Y5>5v(P^=3$gF6da(D?iP^QT&dJgE--`VdijDA1bm=s* zj|nfWjDSHnItKp=<)()K&%IsmY-9ZTwyEN4sl}iAjV%Eg8-9pkkU>YbFn>qw=JW=IVDV?3 z+kB-1O8b3%Kgzt?3`koGtlabt{(28Z6Jj=KP82lxwSL7Z2@kEn?n%tfK>f$d0;6YY zbOdH!{|m75t0Oi)cVLGj-Ro4$U(y0_(|gpjz34~W`NadQ>_-Q|ve#=hzS$!~i@^Wv zHRqDP_BVQ$H~lwv^m{h>x0m9#aN^fx?mu2uWAk&aZ$&TA51+3P7PQrCFVPO^>iW42 z^pW+hE8tUaK4xF9s@l-X#*v?oqHL&LCq4{g4O~FosGxOr?^9Wef2n=*2usNAic9a zy>pnECl(2+tE)fR<0fYY!1hm;ApjBZ*vdH#=-o;dTi?7bz=1;RWB&cgf-%TlX^azws3@zxjMO$@52gZ?1%Q|GJ`czPA>l=n_=g-tQ~F zHk)jmpOiaP!xuX5B$FRH@5`+Ny*JXS552dNsS`HFUJBrE=-uS?pCD_ydON;M`AA9er z(b9Iu9#s3j|IIsdDEC!+&g|-H@9rh``~KsOwf%dS%Ll=qhc$=pYq7JE&NkM-TJ}$g zMBh#EbqoCEdan_MV(C6j6bgF;X0#@BSzh&r4a3?WO3+Xo0AO9lS?*JTd19_?t{1;sFqI>b98c(yQvv9QjGNU~f*?xPg z{YkQY0Vb(7B6#AK@>0QWjF2N~cE(pJa{h)kop_3FGPUpR?@&M%TqwZjB=ont=RG!u z)>ghXcRI{Bj%7~2?qXrd@;E3g8jX9)rOdyf@CD6>lAlg~n zIG-Ya$2*B1JTjk03szzkQSbchXoY-6&c|~tL3Ko6O*x1GC%hnhA}TwogA+J-n~y2^ z<)NNW$N{QGv}0D27dPy7d@L6M`|V6O85xOy{of6>iriRG<pl*s__HLztzRTM1D6S9y~#RHbG3>}nh zO2h~0CAqYzV1h5^azuuR*NjxkY);6ZvQtdbFhvbkNMaNuE|VE@qh;~2OE@$1%#N!> z)4l`svMiD#DTUz_toP@90sJw*FsN2-UVR8ACP)XacxIhEiOGi35pK7l09KLf;pU5H zZ}6Y;9O@Ub-KCb9)KKmqbHJ33=$Z1b9b5rdF*_zIOvb0Gy1kHQ-U=hR2<>pgMH>o1 zv!>4SNU!@9@3eW+4fkBYR;GNNr@fei7klgGuEs&`X6oKX4~Zx3zNxykAw^lMxC84V z-2^sSB_;-Ew^!;f_>Tifc$SQN6vCAUpWMvsIMAB40NhSVZUiTyI-8-fN3BB`y%L6) zZB42A2W|UAsr#}8pt<*^H$ryQ#WfRE)5VLv-bGLDd}=in7$CTDXk6jv7)Ot| zHNN)ICQ^j+i26QtA`W?KlQWFgbZG9o&7cST6gWu-`OBeC?XsWZzco!{jGc|Iki<%O z5NdSomj%gyUs;-HJ0yB)MlKd{-2VOS7j+xuyF2^+Ys_mg52Xawo~^CEOCM^E?KT>!f=p+9?3q5oYv`&NcmQ92?XQEx6VCKzX zr&Oh)Y)RG}lL00ZgJ_Oi%XjJz^3jJu{pjs{Mb?u4mm4z1^nPqh zL@8e#SP1i!?C5G}S`>d!w}K~>BGUSN~c6*rJ)(#jv!JbuI)>vL@}Ly)2aQZRrwwsM`# zYOb-&L^`==RNf%4kG8YND~a<4mbUL!d8qk>lq_1!=iJqS*(JW|N$~zW5?rDhQPH#9 zTF%=n#X)ah2{HpNGiwKlms${?fh40IDm(s`*~Qg4+( z1asIFPjH^@ObPFXTJeOasTM!vo02HJAkp%pV~hoi!z)X>eV{m z%i;+sfFRMV`Df-w+&LoaivO5UGKqVs^h+P;-M5nqfI8>$)g20btgGS0kP4^KHwug% zM1wr)dTt$DvVG1YV_Eo1&LP_W-|D+yr#4>S?x+V6uw!!7yC2$=prR8$^QgR7t@69*MTnpH%l922K3rYmYh~ zTP`fO_tWN9`q<)4+t>Fb(I|Q?8l1t+5btO_3M|L@vS`S`bi$K536l)K73hX9BWV_d zlY2~vjUtR&M6j3(z9R9U`6DnJp#Z$?H}BIh*;W?Anh#6lJ!LOJbI?!UJjo-5Myk4klk?ypL_56{)sXwi z%IETLdIq+ZtHOwK0H{gzf}a->KM9{!EvlA?9=!Lp{oQnAhrOsWt(8NZVspa?qIvOF z)wlZ>jzhK=r~eQ1v(IXw6RdYD6HTZ~_xLk>OK9#iF3CtkY(AvATg3wX2KU@CEt0!j zzn4NF=Kh0Qf@3;!wfOSG9BL_9LrGc*Vq>&OAcTj$k)E{z*~k;@s6>NWLW>AowsS2H zkTYl9V*cc6FF8{QXWcO#4z|HK4>Fm-bU-nkc-seDF3>tfq07HxU2_M@Xv9@$e<2zw zCNxe3NEnI{Y>=otsWB4yIl(>n9$5^-yd;sDod!uav~s`0E5%}}lOg|h(V!;~0t3_f zWAggb`Lw+6rEevoTXu~jR7JgtVeA4$q2ycjGdxc~PTei;H<$7%S7wbm<+8&e* zqU4%;(MQDN5Cq`B6mQua7(l==Jt&oz0@u(KW2td~Xy^at} z-&ZWRBl4+xH8YCYVo%Y<6UE{jM4J$?&&L?ujJE@wKHaeiUd>lKVKg&>+C#r0xKw}v z)woX`*re?|R}07hMCN$|jUGcpuuV$1*jCsA?T_0hvUI>v6Sz8upU#!AnE%qm+b}i5 z!g&J+-+I>DS@6bO#9!t_J^&%uhU0k6M8f&qZ98l+lar82~^b zoOvWjcTtDy6|JDy zWBK+eka_e937mzFShuJ-E8FH8ri|qxKTt*D<_&M*KD1Z(To=Arm~6m93XQ3vpymJN zj>?Ux`DD5&nkBWUMd&Up{r5f?yUXgm;DR6Uy6?8@QeGgJw(sFe2cx0+2wJ*ZTjL34 zc6%dkI&C%px%>cgqMpVF!;X35fJ5yf*)mfmmfoTxQP_N1<%)|EEfMgD(Uc{;2%V#S z`LLjeYtS$5SsDKa!;@ggcAb!xS(8I4r=z5hSB<3eGe`ov1f+WNdASk6po#d&OKQGq z)hH@l7OD-pgyJmDU#PW_q>%64nrsC<;?}J45{!XurYv_GYaY}WBBee>pOGPwWytC~ z-}ooK4jhMeewLmu;pI3O;S)uAxQ>KG{~2c|Z!A^a6AeaX&VdKO!`e0YW(zlh11(zO+!mLnI}$=!mWwl`jX*oRSwW{grju4pXfQCFJ+ z*K``O5tEhett#nw&zi8#VaCg9o-n$0*8?#Y(vB*hFWb`Eg(jC5%R>x?am>eFqc5w_ zR6)H8a>f$#d=5$aXBrB`RYjA-An`b8=TA%AlA10-h)_Fv71FX}VKnA9Cxk_FzqQSxrHagqhA3E<{-J!8xF0)98GVPA(~J^ryLQXC zP@o$w_!c*pk&9L;9V;B|>UpHve33ZN{kpxVuPvGl*GH>ONMVD|^tT4YjR`?9WWcgo z(iQpQgkW?|iI`-;>5d8MZpVHonyojg8}@~7qOw3kGmn|fsBY`OOTm+MR-8|lai7xV z!XhaF-u*c(WpiACqaZr z&e8zUO@yiNDgN-UtRi~-9a6I`JD1>X{AaQkvGOu6KHk1g;_rU}gglmpHxM6Yr~X~E z(9%y;n$iY-`dVYVWc}dT9n{5&>(XK!)#i9nJGG*$K}2!Ur*g)u=(;^>@dj}ni)**( zT`gbh<|<`*>UL0CELw_)kDhL@4KB7=LlR`&tofg@QXlgllqiO?LJUtN2CG=gqde!v zSGIXB-W&zhM?LB?8Osy7-?@A~iik^s*Byg?7cmgYzEYqCW7t-8@Vq03c_o3mjibjn zx{bx8Tz(6+@Lv?S#WHjqig>es(c`y`KZ7MvzUqrA3`Q$p6m1W;E5zOqo}?7 z#NSx;C`k;3hv0_Tt0!HftTRI~>Bdg6tXYM^8$!eNfh?V&jeR3A{CES4XjCX^<^$JVps<9KdH4QvkJi)SRa>6fU&~hzG zz5e3}=$nM~F2hSi{2D3?MGwkd@7@Y;Kr{E&q*rg!m4vncD>G?MlgM)&1bF_(TdFrL zW{v{>TLt*BJJcalXlm&*w|{?4A+V?W!kN@}kJGp-2-y_|Nx8599a+w(ywWM3Bz*PvvRUO&K^$sdHXp2oY+}ina*VyMQ(3ZH@Wza@dxKmz5 z0sVt{P*%0ZQwv&@eHBsX?hOmuV!$7*!Lr=IkPJmu90=q3%nk~?a1AjJ7^q+l5hHJA zi|1EG&J+m}KZwd#u5;1w>P&o>m87mrvxm)+d$%2b-TOAij{qH~*1ri#JGpf?OUZ zg_o}@+l~C%hYtgZy@dX$JutaoAxFF)k2i7RgfojZ;YZ>Yz(dXs5v47xs*_6x`#W5D z;BJ{;{K*Z?@*|#6rsxnGhj_m(5Ir4g+)L|=B-EqHjTUZ>l@7${oJ)`yO?~5BGtLlq z5oEa<6g~iwFR~vG%m8r*nWSVj0YdN8*)2!Ulyd2akQ6OI{F9-=(UN=&|GVsGgYuct zlQk$^j<}w_R*TX6YoqA|qXoyeha}Z`TMr80;38E5?XY{;Ts%_;@BGd9` zONh(JU$*NQ`8a_iKv4kcd5gW$(}EV_4&bD7`RqwrxXoFLQI*E1ZEza*gKlm8M5m21 zrrQLW%c!l8FLsvZ#y<)~{RVy+c~&{7+rg;7yObSPVxH47{FYh1O&nGdCfR z1t$_C>l%tV9*x8nUju$Sk;^D6@wN=txUBJ?`vgU^i^&cPANDKdAgb97%yV+akedK) zDmzXYB(bgM#g-iH+vv~2B8kanYJorC5;PnMQytr;*g6|RGW;wxf8(JPbpnGvDH zVkXsGOjQaPU{$18P0_{zG#0;*r%$|>*xkUWl$O|=;0O!B_K8o&h@8CA2dE6BbV2a$ z(dkpzZ)X)Y{;K=Pb~%*^nc$6s*y7+*icg z+nF?ZVmF7!wH~JeWD`A6^vY#4mQMt){J~LQ0X&`BUh9e*RbX7?4LU?qXyLkWw66Bm zST?J18S3Av`A~elZOrQhY;rjbryEKORPc=j$a^B8Bz4*fcqLbd-C^8>B_oaH0)WKw zQ&}2bjzxeh$-x(D9`h`ScBFas{63L~Qy zbq|L9b2{o|&I9&oJ&wz5RiJsgYrOC1N4_kKs1w0jvo&&SVysz*ojkr7)cc!u^96Er zQA;sRLe1NB%8Ghtue;mns2=yvR$iH)Oz4i_u6cPDN&hzF*J<>NHV|2OfSz zm~h+R4Abkyj?|=TH7^i4Dic5fIfjfg){i6{6G{j;qb=C)QJ7sO=6m`yFWsW3@A{KYCaK}E)h7X>+@w~j4Q!rsc;aF}Vevc2^R z5wJii7+`BMYB+sIuft`4UbDf*!c|e{%~-3Dg~2INq`t6q?U%5E?^Uj~zMBXO@ zOj~S(|Drh#sk`nH&@&0~xvjKMpON9zmjPbc_;!#MXpR_rb2|fxZW}ndU?N-3O?@<( zF?&M8&A8K7<%6OWLot|=*ZVXh%u0kN)4f;8;% zuHWJs3|6T4)2+*|u2hLh0SGN*|G0YZH4JYdNf0>_e>aK7XHLaESg+^S2;yD}dpoWD z`MOFamd{j4hyFrFaFD3O#(-2_a}<%Y!NxzvTT`yf{>Wu`f_ZBPmK|(~{30=V+$Af( z?jJ`82fDc-*^MTyUyfa8ULc_(*6TR%H*e&VGfhkoxX3k2(2e%+ptUrcQ%P~@(5+J0 zHcyuui`qlNH}vJmi&I!dJSRA>6r#De2z=DLtPbzdSp7RLcS#&HlY93t!T;zYa8JK zClx?o4U5WK-M2BE=9##ORsaqNduCB;+ID%FOPP`U<&?X1B8EnH_!hWWbZjt~A!MpO zORLRiz`~x(y=I2H0#k60~9{nNimR8 zG1X|b4)5j}IFO;fsI)@1SfBV(Kt9+ABp~9#Xy)KY#k^rRY&FZ^nBLs@jl#m3A*<1p$j}I1Z(!dp=`JJTUkao-m1=TbC zsz@XwSs#u5@3k_K|r*7*BMqm_~z!u7Z@8xKq7 z@PD6ckgGv|5R}vQS*Sjn2M$fP=4#{`OIV#z(C-`5&H3RUU5ERMv7^G=UagO<*WfN$ zmsbWt7WKfbpCBQt|KMyeD_9F;^|XyUMnzxTonxM4b>o)Y%yXAwcid?HZQ{wGH9fH= zJ*7~SF~Ye{g#P6uTHwu*P=KWrg;k2;D)#<+-xbWh1wpXE@VKxIoZAEoPCCiW2uz%O z&C3(+XY(*^iyZ8P!E-jQ`s_I5w7rRkeYm&zRo{tU*|_@m@8N2j;oX=VeDY;|%vBn) zDhrAZ!|Av2^MhT%q4#bFp1V1w;bPy%+t4GkU%s#ms|_Z^6c&6&JpsPk6yR}t(mT=N zWXk7UkIGDwpUFNPh7m_)=i?}?j6&lHh6 zpnl(4xSRvl%i1AjA5=^mm@kMl%ch@pF>(}V%w-tJCe;9`3bS)1?Q+gIiV~+QA|oTe zkf~Kg;!_4nWGk_@IFAbZXL2fS4r#z3jt8jpH%W5tKve}e?n04YAA)N-jyH(W7A@*E z9B56vNdJz!Bt<^OBW&Lb@f)71s+(5T}UwJ$x>=6=Ii$ay;OfZz~N;mc^U zNXr~AlA|PoLH|s0XMX^@o9#@}5lT`_GH**M%;iMvxWLSGM2y7)DaMz&0vXG?h_Iy zYxhHfl}rjXWy^g!QV#WqHoAn^F)*veGdJbQ+#js-oi^ zf5;xHZIW+-4e`wQj%j>^18!fSza$;lxq!-&Ba4h&gK5sc+Fx^CI&9t}oWIa1uuqt5 zx?e*hDF;@}DaSLc2(A}D6wM5)cw@rxt6azOEVH&*Kq1nOoknag)$2#So&x;gwMT3i z2!Pr3hDFy0AKOSav{IY_gCi@&dNOd91h=uEm=Tyh3gcjFIddqeBObCW8#2KAaFXRi zw8nwqVr50#17`m=Y0~!Sxuq;$UVBMuIak)gLE|6$x!00)D+|exg^j{31NP`*d5MjT z4;pUWKxJSo^=4*GEV4S4tKO9C4UFzQ(d#tHue2WHL}~A)^98{2UZ=!#bR@+O>2w=G zj&Zg-9SL4kLE1Rs)?Qp5{FN`DoWO9&gK|hq{pA6^)hn@SeG;Cx&Vzml3DierF&0i~KCH#ldjRXeI zNM9;O(aM19ts96C{ns}dYF@mmE{sa8B<6IiOqPTp&O0ex%FP@;7A z)M!ruGmP5;HeE02bhIj0Z{VsD6x2^~g`B$H%lflyP&jmxewwJ*IaDFLZGxdrhAXoO*(OmfKmG*& za*Bdl>uu`5C_VZSuZ3#;ZT~8fvn~$Bm1k4OYJBft!K%ya^2RMRGJepDjmvWD4bA1 zYfya5+vbCz=9nk;3UqPMj`Tcb6I6w4m58I|6*Zv(rY7LFP?6n%NAKxV^nh8-5VVSi#y1{8uX@-Q z3d=yl$L%7%1^60y(Eoeu;&w1=9!s;}_hg75(UYo?p-yLwp)>&lJhcX(S#gB~449cRIYJKSHxM#8%U{HHc`aDd^6{tkoGi-nVzLdwkZB@+H7rVEv#r zK!yfqi4>@k$5_NzMN%?vl!fht0eqm=JrbR;w|Hk)X;}oeN#rzs*B3ALlv<%Qgq-05 z4MOqOq+9VhPbiQ6r;ZM7B5xij`80ZPAAaG-$qn66>qu7WszoP;Ocfj)<)~dbBnB2y zW!Ep{x{QULa~MGP(()@Y{V&GOsY#G6+_q)gwr$(CtIM`++qP}nw#_cvUB<0_-cH1M zxcM+5BQt-%TDiV4CrEix#yITViNR^FK=ch~cROrnIpMSfTM21+?5{P%!)qz^y3tah zrSrV%NFiHu$G!?8Z$%PORbUDxnfkT38xp9z%@LYp=@9FtWP&_}b%()z#S-O|PtJBM zA26@M(VK1s*qCdxUP&8Gc`1{Cw&C2DMJ332dx)}cy~E{bx0Ai=P-GS#X?Lm&d{UmRCb_of|BkY0~j6-oA~UA29)izZ~le1h}TQ5g;H~G@|WfU&2-- zt2yd!G7@?ls^z`^(m8TwjTb>WXh1MzkB&<4+r;F6fJ!t^Bs!JpmK$xwB0qzK9*1TF zOyOlh+*gJI;)wl}MxEi0%~K2Z52H^83;Oi}HI26CL+K885v7NPl6WVm-oyM;IC*9;Dsj!%VU1rx*!BB+?%sR7-X^dZx8}gR18wR@PMj z@XBIo_M3CIT23|3bkc7;Q1X*BYC@zaA-vXBMO~_b;pI2IylTJN`zRT9l5(G7y}1uO z`kWaBOYzzH4cUiUnXm8XFb_8YSLZ6~qZDU6C?{>cX?LU?tKM)SmxEo^KT<9<$pIj0 zhp*wP9%oU-3O~Cb0y!%ZK!z&f;}q4*u+fjm9ZbatGRnY^TQHB!p_Tiu*{;)4rj>~i z@ZMZKYgmEpE-f^@^-vwlYq#f&9#&plsP*-mcXMfzuyNI>B1j7ePPP2}+BdO*y0Gzn zlB8=>Iz?onu!P>O3ad$H*mJKVI@D3dc$RVgA-&eK1zAFOJUYGze!9_=fE|>F_BvqwirHJt$mTQN=M-`j z3W(dAz_KC&GRuR=@WZfws~_z)6+x2@Cjo=b{sq`=XuBRNeDel2WWmrh$Et6aLI`LA zf@8r8(Q9rBjw~{{tBP8IP6mavE`lq^N%s)@O=3cj;t{LG;fgqlD$jQCrcB)Ax=4wm z%cMSN0m-Mi)*jV9d5fLUqT|TCFg&XeazN=499sgtOQL9M?nEb=$~N7|S3BeynEDS{ zt8HAx4d~pgEhJLa@*ea>lV|S`_~c+Fzt@VPZ=bcT0iX%M`TpatXgRq-i+edKLASNH z4fUo6KM#DdU)kcbdGD}Q@XNn*B{huI?FK!)tX9gkyLFo9qq5_|=~ohkUhq8kYH;gY z>j~HZA275F$V@~R5c!VPB#FqQJT8U^R9>A0S-i(>ypz37p!_jtCD-mi!BfE=< zPj4?$^a+Dfa*H)*-+`T1h%}tRR+_(}!)B$Yh#J)g6rY?57-VegagYf7KZv!e;gkT+ zCvT3B5y@&jKd+0znTGmoo*DxRU;E8G<=qkpEt5V^FK+;d;ppZB{S>S^w30}FM@kuwsLG^bD+0>f5L zb~XV+rQz*w7h@i@nVb+u5UMRjB-jgoJV0&`5YpwGk;!mD#UOo2RxJ01YUFd$4|EXZt|v1gF=ydd>i>w)hqnb+ zEmXbZ7yl!vvxa;afihdydpg+w8NNXB0rlgrKlk_3QZH90GI2!qV;W1TQX%0+FCv6S zwX7U5zvdqy0N=jVs(8pDL|~wTbFK3UQ6~jhsA>7fTVv9SR|mO7;an|TaB(`GYi z8fFl)b8Glq?SSRE_yn-TBY$=dY8GCf&#IHiY$8S@xV*q+zsWq%DMPLfqkDID5}LJu zh#)p$U$wG-;QW+IeWi&1xRv?ijC_zQcif-I))_f!u32lc2xgy%p==9z@Ey$xN7;^#0A~IPzk9p;E45)-Ii`DYlW#!$cyDxhTuyh(_*VL!JD{SALbC;!s*fIt z4DCGKHDcYniciAD zOdxsbi!Ns!$tMg@ri_rWhLY%jxTJY8BXZNg-qo{|V) z1zy&9Y)KbO;%zItb@mHniYwLOi7ZQ2Ev7DxIM*{IL#}Lrj&N6S(?@e5``ZVacYw+* zZLqgm^;n)!WI(cNbh56f{79zVou_3o>>{U8ia}DZi?!`x=8c|zg5)16kD_N+_q1Ta zQ_0h80WX4w#H*?({>JWN6Eftsp7UZ|$J`f0Ki( zyLs5Nkb}GlCfDiJ3?jb2pE;+BK#H@uMdLv$@pEh3HN|c!pVMaZ>Bjyomt=H-1kj6u zFyS_?CR#3O-R8-z2EGUF1-*Yj%CuX)Da&}Ce&)tY7^;Y*Gcn{P{T)J}(MxqkayJM$ zoOXob%5_{`ni;?BMH~t>i6%8ZkwPbpi4%t$KmetK!w^FZ52gh83UmY0UO)uC?N=yL z>8Y1VqV7;4+gu~Z1X?G>nD#nnKR~5xC7lBEb0q%6IJtbwHWT3hfflReME@CPM1|B_`M9a zWa810((C~oFk}WF@?3vFIOof_TRPR2rOnrAgD_Kk49(?s~8v08% zrG}+=YM7l4UpuFfo;m4qTUNXSOT)>vX<**~w@8)2kB@0b<(XE$#yfd4en`SlUbl|H zWVifKpLAD>?5F8aI!vDU`dP5NakiW=&iMTV&^^OjOjcV;`Zu^YVby^ySK}M_-r&w? z#n!%Ibmz*4s^*=qSsz8uNIOA@VAXIaf9j51)LIy#zw0&kp{n$4rIR$07B7M%=-O(b zRB)w~JEERp3kqk+1ZZdc%dO#)p8Ag-c>55RqL(!sS&a;&t$&_*An^1k3k2bYa|%|| znw&z&C@5Dj_lQ)nMmsl>E$dqT201}!Cse<{A7&%LZY%Tmti8z-crlYr*7VGy7^lmH zjduO4IXE;-ykITyq!!;+7+Gi?tszl^}QCC86r{OXz{kF~)xyt}Df*HPQEiBI48Rd0+CZbfM zL5tks-DTx<>9PFhN?G3WL6b;bH>O)Fh$&G5*VDfl8jz&hRz}c7_U9GZX)(r2tA>wE z4ih!;CBP#nJbn8dX;;J>?dvj69)?ONg26uCVU=a9~{M_8q258&6x~zWh1Ai?) zFbKiGOM_eS#=Mu0%Grtv!yCOi-5vG16`wi$cqB@SC8*4GOu&&V>l|~;bBuO?!N$SK z!Dtu&FfdatFfcL=B_&j7a;tqmbbZMcf4EpQ*VaEj_K2+~~l-{t*EIkNmN&aDM<0h0&pr{b_IoCTG#kAf&0Re}@A9$MZ`#NyYTt`Re z9L5Ic{0e9KmBIp`7ESI1fHa?YegM=Q=(WoH*Aj%i<8N6QNCZ-u{`LG?3a!m9jx5gr zK)f-kX`=&LWAVMRZDjr7{NtVgOe8V=yLsoI^=6rV9PjSar>m}|Ey2@%K@l?!MUP=kpbLM z1DyRH!2YO1p}KkP@BP-}X7E?uJvX;Cfv5YT06n!bfckn5cyeKN0szXv%?jAf`O$sS z4=^zS$seY$#>$*ni`r}U4M^# z-D;PrqbZ>zqLzHi&;011A~iYzyE8Qcm}h8U0z|`HT>(7Df#3eb6zQAa+u{DKQ(0J9 zg1=I)>Ur|RUGQh>eD8pv{NN!;_5Xk+HGQ1v68Im;H(fH+Ghz6B8hrk2Kls_b{j8n) ziN5*SJ^xTkPH$|!+kr>=d42wHU~Fn=bp31{7y0Dm@QMAZheia}KXp}HcY44y&@2u1 zt#5r*iE^R%#DwNnreAwa&Gslw_Q07G8XTBeKUHSmChA{SyVbPT0Z^{7XnsA00L{@b zFh6G3M&r>ne7eN3_%UyFq{3ry5l-(PXg`MgV=`ZLAd1>X+`pZJaSvk>{{Kf*Eqo)P@RF!^bp!uXiQKM2_Vx+m~HM@e78_-{ww^WuDOKSzAzu8Fnedv13KMa(w{w9pCrSdZ$hStLWR3Q4*H~c0T>USobv4H=sVEU=| zeu;K2`yGJ4>HpIY&uRJ#-jg5E)L-5_pWxH)@#EpMMfvTANlg8MuId*Im%m`j;(ukF zefkOiyk{$H`vCXF$@=2I6Fyt}eWwui3#zgIQ1%~A|3lh;SmPgmDww>+e=V5&`VWV8 zpLu-jzZT#tMsVNWuLE(1|NVgu4&e-pHB?Ktf$0RUxeD&Ww+waAv4|&kFYu?B*PLNN z4)sEi8#Z$G{FaQu%tt$k4qY2pA7!cODi!(tJ-(;qbL%lbpnf6eI5t(`Dh#tgaeMIt zcU-9O^vmpkw0KqClFr1u^vHTiAop>n z=kMhBgud^I%11}p(XuH^q2st`kZf*~88UV|ehG3eRhl5p%+f!Itl!?tZe|{~#sDV& zJ?wM(VC_IZT8ux@aV(?Mg0ew0RyHmf! zI1L*?vv6^by*UReD~`ZEpXmn=0smI>uGg1aWc*288S~%xiM8U4&_Shb7wLxD9W=%@ zZW*0gH%vvJqu*k+o4?&f6OHYqG)8z((KxJeu&c8K-wkEXc=9HwNl_EviO1%&!e^5i zOjYk)djMo*b}S^?(hN-5Ahim?eXMPGe&9^oxAfjr0P=eM<^1w__Be~3Dy zWk8k|rxiv18R^+5d>K*mmh-K|CuE`-gB*x0MR52RYKs!)Y3ymQv5f}Wlg4@@E%~dZ zC!cF5-Dz0D6YoA&xj4oAL4Cd#=H3-DS`N0X;Kdw86w??Eoiem_62wuo$!I5t|n zri@7Y$*B zH&qv7o1Jc=#1S0bBRp!z<|F_s=ZzU(`TW}w7G*XubdbJ}Du9Zdf<2nj(|tI3u46K_ zM&2FG!ge*V=;AyhY$cM37dPowgGL*$M|wt>dBvU@0`ra?+mdjIrOs=UxvyG{?R7=t zn<5@E?qvBO^uJ%sDDip;hWrd-NSYGqt^z_84^nY`(n z>};y+Ks~^X2_}#%Co`K@j$My&&4X)gG}n99N9$Zf?abppkIfR}{`DE)Dr~QJo%sH6 zo>PK~HD36x8ROs26DJ(8ZDj%Qit#Rr!VZ-%=FQ_4N61WO2@(H|IYv8@htE2 zibK1UqKPTJ&p2CpXxYCIB`$MEiSjn`W!)P`BRwP39Yrx#JviSG87&ooLNC$sVFr0U zdgH1oJtaw;5$@#|W|rP>^Q|z5VywD2FNm`kXcqJ;apMEl0O&8fYBhLh3if!R;l05l zRNQ`}lhG^e-1Pm;DY)%X^jrfC^oQo{+H0jfugRbJk7K!1ES#+&XF*H`Y)&Oeg6!7SBSR0Y*f#nu^KCO@*ACz)gsm#=+;4P zC3T+pQ8R|ql~VT1z?OtfWy+PL3%%cbfJU1qB^GL-f~(8bECGsqAAbfomUFFy(aUEx zZw^4Cctzn>mab(e>6|aOvk05$^g5}OlzkigzqM;Be$(tqnf-Y~Nu{Uz#4`uhoC%;v z%wC^wX?@GoB7{2uN;*^80NJH?sXnH}YL#4%y_B!Hc_u2hG#9X#6>4$x zyYq-Qz8W}f^g^8b!ui*GqIGnO0Y{Gusvse6t=Y5$XcZ2UC^RT((r_uN%=0aG8r?xL z1aiRb3F~tDoR#p9SdfzvG&=hzJta^d13>k(3th*VC*Xa$p!wZfT#aC_?~c-*51~$s z$^#VuZzU5a<_7O`PYtu`)SkK5Bu+hjYNM3C!wmH42i2m4ui{`5647}5%npylr7r{e z-sSJE){)Vy{lMZ|U(++w5$0*iTWKC|_Vq6Juw-h@(eYm>>iLqin z4c~#uA4&?M&#h$YA0NXn=lg$$HGG4ZL3HbsKu}gb!4ODeX8;b_0-?Sctu^SzPmr4x zd_uY`QlNM2%r5jks)px4({%xSxnd*_c&r|l9J;SW@pppcIiBFYq2UQNJAaGWCFU(4 zz?fs^u%FAp4waUNciSdjWL2aq^6sT$Nene4{^tH&iFS2s?6dCVb1g*?o}mGyGN2vY zg<@wbjB>duj+&NhD7~b;s#uh zu|A0o-KRCKytntT9KUX>p0W3nnXL=PNdi8+;?Lus^vr7OM)lwl$PL~Ms2E-wpC1mq(&}Q*vH4cgS_Zx z#QtWag=1$gK&fJU+hjN*%R7?C#O0H8`5%fsd90Bv{Fb)*+IOQSppeWVrZ`}Uz9kr18<&Z@SENXwyQi$Eso;Q z(rIU3D!i5z?E3L6@SwpL%kPbyvxCrqzh~G$rf9mes#U9miec{Tv9!TYV^4$Z7%AmB z6sX`@K>LD-?p=UaNHZMD2u9^IG4ifN+xqS%9ekO>>rE!uAV zjkFbn)r)Vg{U;!%+YEcJ&W{x9oqmp`%}SzO>z;6Z!W?XEVO`(>r8cXntLwy951u|9 zp9st$F&03`27t88JurtOLH{P%vOSbg%ONNbg zTeRBr(!c8`#mL}`Q~N6xYwN%;h)V}+^2^<~u|WfyX^1d2D*Ik5|5;BeKb202kIy!q z&u=5h)MOTbgcP}0REHS+Asc#SCi;tpshLZU^%|1d=%>)9%sU2b!`2{t9b!5OykYFP z?4Hgbs~W2*NzE^}A?r_S1SOJC>@>Xa`0+K%CFgjJJ0rz{Z=N^N-CR(_(`}{JF6f4N znMID!yYNkqM)pUR@H0ay4ha!H({q@Y=Zm_kT@nekDw7kDGK(SD&YK`-9o)5At6{)kR z7R;gSRzhic;GeZ#u$|X2OcY2j5`N@_4b53`PF4!J&`lYS4IAHX7~F#PE7UOO{6`p> zb=uc9yCqCj-ugbiCoHvMPRMe46YmF}G%z(oWnvF~R{qgDFX%CIp%J-HU+yLjadT;jD}Y7MK2l*WH@h-DJ2nYN4s&5g!K#n9E8=Yt)(l z$MkX~1Zl-TH}|K!7x4R#U_{Ts#H?lUl)PQ-I&YcYC9hc*8=DHE;LFOeY(^?MYVRV1 zSzccOP{L3ywQPOb7Tix>hQ4hLK58RnD+K{l_huz)Najq;N~b@L>2DW0uZ!ESz>rVt z2ivss_mIW1sEJz=C6#A8^uiHmPi8k7+dc{X$wAq&4<3XB(X*Euwv4 z);-Ce199Rx!$_clr$DD~7SRB*S?Xxz$NyV~)vsPJgf>Lfp9h zAiRJA3=*B!+q;JR?7X)f?^Ka^Pb4I4|18gOy(QVB$U|X=+eFb0ohd_1Qe;P}&`90x z3shjlwCNoeMDDL;1gzYv4_u0dXV!p?04uE8JFpq)5H_cLv^&Qg_!gX46VGZ1tyUR_ zeURn6^YFc&x@J5`hMdZry^9y9FP?B7M@p`+Kho5-BlAXf;38Led(zdrcn63_RXmG# zfZDJ*c;4WV)qfp}mO8LPrZP9Bgv%cC@j;!cmeBX(EDz5=0M|y-^_^}}od|q#{Fweh zw`;bWOHO!ldj`x|QZsJ~fm=aL`@5&;PUHk0NG=u|USmx4flL0}oF{aT4oLzXKcBS* zDrC7GVD^_jCS)7ZM)f#e&b|fG8RM|L383@cB423`vI)eN$;y?#k>mhY#DD&NtDu%5 zN5&)KuLqy?2pxdMwaMjvU|a0kEt7${$wIs*z)XSk+7f1Ccr>!C*IQ||S2p+!nZK-; zOF|H?0w!e?2qH|1^Vo1yu~UmWV}t=#`}tU{Q!DBdGDE1glj&aW#bg1G`!Pv*ah$?y zS2U!W^1W5RvSg5o%hINEhX65n_m-4hj>)$>L2)73aY-Zv%r3fs)xJ z#Ji(($8*-CSaT!k6$eZSckFrY#B3KC;o3_GO_dkSW`z;Po8wyGvcFNxCbN8#Q(AZO z!J2tNKvX0U(bozGIrw<)qzW;D6ShfOWNXA$&0)xu19=;Et2kK7?v3WIyWN)xlx~zy zuf!xzy8Ekv1Kn>5zsa0_Lm1aODf>8ApfX!PBEpMueF4uOk7Gv zMGcOompGfL+lQ8D@Fr0CqHIxiW(kI$Cis?aZVZynDTPF0Z$(bI!u$hS;GF#DHV5VVywg z55p*}(Cz;2j5^FszRrRN&sJE24w=>9BOWK~@+dA*_B?$h+O_0bGq;}xRzRznv86gK zO$|m${SnDbV|WVzII*l_7eo6bi`mG{DQffz+7(UpzbooE6>ZT9h(zlcnuo!y~CA1I9Wv4}ArD+9dJ4 z=T|jvCYf$YG;1_5YChg!G2#i4!I#sj?<0lk(zj+QPdC@v>qnt^o?{eGghyA>OV5BT z(v-2(?xIQQv2k$X*J0L5^Wu2ewpvWh$n70vh!~|8k3(Szur%Hr>DA%&UEIHIxU7IW zM*+w02>F~z^P7RXKpywh6TM1!mGJ1hYhjTp0AlJ^?-dn331el4p*OF$$*elcmb}pE zZ#!M=nVb)lH4peM=Ie>k9O(j+?hv})R?H!xXO_u9(n-8T8zE%w7NOiR4aeTeTX%9h z&{Ve`k$w1(ckbqF8@vBVC~jIo-*W08uuiI;+9SCR8?(_H@3SwkE)c}tKuCRd6@2!| zga7fs(mIJ`IRqXH(aUC9?#?zBde|DnnpU5t%q}xxz;`BRx(@j(C4isI;JPYKURx07khv9U5V4g!7)-^ENw!2-wP@0W zJE6uf&QZdD(FPgFr0ignVB@BwPHiJ*K@(Kjk4}gQt(7kQ@BS$pX*(vlr1R)HQRuMS zHCb28$GLn3qRli5D||BIU{xa$t11wp@l`ZT8Is>i>!9@Yp#tMc7e|4ZEDQBdL-Q~$ zIX;?~X^S;FbU!j^=y}g9IvrD;f$W*t86YCRy?kJHi`p`D=9#Er zhO0O2^+(uzXUJ;v9v}MYM?Wgv#jOJZPKhBa#7CcZr#O7@w=XmMh!&d+jZZmMBcsxT z|BF)r0jt(~sLnY7*s@=gG_N0F$7t<_P%g5>$c$ z{daGDV{SO0ROo8AAQHu@oOof4;FNUw(ry0fn3B$OWg91RL8*IXhoT8rB_&9A4>ul> z7_SIieR%Up0910Ft(SMT$#h;s2L8EkIEtNrbv+;Q4vgGh^tj1QF2@%a!0myV;@sqD z?#=|Tn+~KNMeXdH31FQluvFcV)x&vZGQDNJLmPn&RL$);3UkZmkVOwf$|s6kPs9oV zRUZd0d1&@jgM~5nhlq-6e;vnIgFeMXy!YRK92eK`=E`Z@Q3r z++(HP|AfeRdgcQ}f}{xyX!^=#v$n8X;?+0IYOeO!D09o{}&S6~XE22R=5@0>V zzMaguKRgHksU$;SdDY&xPa#Yk{@!A|oTIjzC42yCq3R0>2wL?`wg#%J>(RTsI1l`7 zZp6zU-olv9%1=VA1T(?Z#>dw^^f>MzkO201N?W<>tY3K~uErd9z@)HrMUI!LNGE%N zLP1CE3$40JR3aFRc11=mN_yQcMfor~7MPP$95rO#ujWR^6Kps$AXGK}i)Kf#Bm~#1 ze)tvYz*1uS_c61_P;lQ-k`%BdH)DB6fQ5`4y7ZCrP@o;ujeKMx$y#*NU`DHLGhO-> z6s+8;R*`-tqf5$Jm+Ojz^d0cIR*lZHgbtT|P3Zt_ab%hyqUz<+=@}hY{W>L#>waZk z=ka8(S;k!_*YMg(KW$j16E`A{ed6Yhk!UCL#2iT^XCKeCX$BTY6DhCY&XgZm)$~707-6(f zhdkjbN*0$NnpX^G=v+t)_u>`)IOP;9SWw!q2ke$3F}3F5Q3@T5Ar$uJj^)cS_89<7 zBEl%S+Z~=i80;j>e0rdBI&Wmumy^pVSe4UNm;bK4)p*OT>n>L1eT;<74Pg>01JqJI zGNcPHD(i$Jn4%`_=`c)};sRVg`zeCqXjB%(J0cDD%$dpRC3d#L{t*GsLG}zrkt&ZR zZ7-(m?f1$SAC~&&fqv#52|#8s-vt}sp(1F~l}R{nIM&t=6-qm%76iQ_6xNcY^n5^Slw_MW_3$rT5rBq-vDc#)MNYJA z=cF)izwF+_;ukwV{x!E8rC>|+=AP50Eqq|XD(hKD_i8vJ-;OesW-E)*p!E(Do7)%X z5qM)!8lL~KFXsxMn)Ia%B6zU%A!#8;K$aqrsTq4LFO{y`-t|(e@R`r-+0)8e7cmoSy~9kW__Wj{Yv=4#;~1KW+H?DB{o}js>`t!H^*zIbw3h(%*S?GwIX|$9tc1jrw z^nGe_l*yZ1-j*NrGwLf~S};aV31ckf-9PJWhg88@spOeSAqh`zqD)%5)V;5qTz5*X=G{R25nFZ}`5 zrv~mtJTDGI_$?BDEL$!WsLNT~M9DTzSO|}S4IATH{pmDJ9dw2tGC!~n`-r+AR&`Om z$63#YTYjQ_Sx(=q;zZJkFpvlWY3%UT@OTZ#Z!wS?-Q109e$H-|R3{pGSqWs^IFnL;j~r zjnLHP3?gF3zmVhtzUWVGTAuZj+`H81gQjRB77;l*L_ktKe0?B>}Fwe;PA2&Bq;mJFUMEB?|H{xC5P2iP|5Ux)w`kZx*W zG!f8c>gwDG<|Il+^rOwBse#m+JJ#j3#)SPl_*Us9bBmJDR?AHI^zvg;5J4`4bD9lB zd%i})=iaB4nzj1Y1@}frm2D|I3ss&h5tq~uI{y_-d6KYtRmvLad`#Y z7c+O}t4URa19HvN7ZgnHq+Kav{BvL^kbl1Il5Z<0|Ml3!xr{(Vc_g!a&Uo{AJI|QJ z9clkF9B(uLqcs@#e$RUwj5ixAXCdwo)b$%oU9|YpZ?wvH6iT2Ba&Ol{jX6weaV*36 zcPl=cl3WM9iUyef2ilnf{tO_LVct``iKKnCRGAECF1CKwo@~J+mU!e}RK_35HJCD$!(RF1Y zE?@B>(wG+- z)?X@o3^D$QrG)ihqDNNvLZ}X?bS{QYS&9ySa<&bE>azCQ6UB)O#?+sz8Z=eM5%(M;}fwrm66wR(Rv-w3Ft|TQK*CC`K6i&Ln@TU+%YziZg9x*c2+(jF~{-< z@=$`N-^_EUKUMw5vM%0wz%W>tR-0FDd_v~>D;MD`yfL#G)TL*&MGQ{}s9{S8cQ)5{ z!sPE&Jj>YIiy_&xAOwruYuqXv+-prU@@ae2|Db3F%c4Ddh4$y10!)jlTP(;luErYG zWFJ(<4?mab-|6Yuq74`t3sAZSoEvAlwIofGa>mL-t4}gGcg}~ zwLq$jE*I?=joUb3kn#GG1V!7zQ}_PdeJq&{VJm5n?N)^!bI;ra*Z!@i7pc1kMsg_$ ztD@%oaT+f{vB_#mj_j$PMr?gM@k}iTX*(fTC{DjM0t$I`#00qkRP)F$N+4h_muM1)vZX>zQW&-Cr zSxeRu`lpFRdOjg^SJa@G!{V?)Gjs|BuCH0WuNXrIC6JlZSEYexuB-B}_kKnrY1@q$ zgOFL+d2@;-Mj?$CA7@{+&srg<_X^OUazrk=~$WJ zd)BgYIbMXb{Y$;9fm^Hcjo52TJ|~ybpJI2f%#C)&zuXIw zCi_A?u>wNY_-<`}KaV$arS0M%$Cd1olCD3~Zy8SrwFSmjS?=q`Ekus4eHhw+?S6qq zZ7X-q$W6WKW?_uv3+*W1%k4O9Zj)uGfw<({#59Z3TKO7AGv}B^DgcIl)YBC~)=Hek-w^=;)$2VyV;(KI7y*5gqvH-bPVn=cF*RGXz8Y{scZiuZIRu zEdYMbz0ZQI*2quls>#iU*tDcNth)+AmCnCnM(qKjU+oZ)TV3$YdfY)_hM?JP=cQ#D zG0jRVCzt)}te7k$X~TX7P#cmk7Lt{u%EmgIQr|NO$pl70a7k>>i9Y`d!v;pz7(Vfg zAzZtBK3fGcGpbv0@hrMxd3Ov2`EupHvv^;`kYof!S!COt1~Y+qUM0L{HAn|FAjL zC`a#{Bz6EftbaAD2b0K*N)xs%$bE_~a_6>08?SZ3Raa zX#MX3vm`r-qNBO<-M$BYN7)yY*%xVSka#N>{2H7;He+&Q-e@a}Cf1VuyI@Vl6k7Ko zWVbvs>!L)V!VNsvHsfwG2g>SFXdAKS^7uWoIQH+|N6x14#b8zyV$M>`mxW6!ml2S$ zKn;7=K%yPoQMkkPQ$>0ES&Eh|> z#dJW(A|ysaURVWsd_$;25Sg{fA~DT;`Tb;pc+j3byloQ|I5JZ$)9%jEklRE>%=sU4 zgnv~lTPo0kafr<-e5YNpXKsoRje0>6R?6ZB&k(B3q&pCM|GZuZ9;SB__tP*p(+(QO zY0dW3!}3@vtVvueRLVx`yJjLgEV=oW$qNjnjwd~k6C73TaEhEhRtVndTF`Xe1jV%Q zT=bM#IRvJJ?V2yDcVn!4c91*{K#50|OyQR}QU)@HlYu#^CMvn9{sH;XqatIx;wWGF8 zs?@=hp&Q=5N(Np9uKSs&d}d0J$W!TeSm!~Y$_J#1-I}uN-k(vxRg5RT;M@vfhO&F>zwGgzYs~iQCCO(zfaC)0y{}A`KoZpCReUTyf zf;Z=kRIJjR11JMgg=73pEHoPw*?9KMkIe@we{z6o_ll3FfKWKuiVywWDys1^$2gl! z=`I)`;0$I77H2bS@~%UvSC?c?H6@~bZQPeRCM&lU8e|8B{ni!9Rq7}rr(M|*x0bN2 z+zX@r^>t}w4Ld6%M3Hg&Cf%Qn{T?;wgSYK8vnvfn`2f!Bj?kU*ON3&NPEqM*e|fAz zuO#Y-Wxg?3B{t!E&KT5wA>JtAy6itr^sWkbKra?aL3-6Z3=hoq+#6 zMB>c2@Lo$qtBYMa7)YY@+;YMMTjoQyXnHh>JpOW&)ugb~?tTSOFiJ=SIsSvg<``sv z$1Ez$E-x7d3UT_0vEW(tZOeGkn4C7TdR?P=^sb?ZuM%POXC6{DOu>Rf_a$UZK*U`qnQ^|LGH#2{$n<)u4C`Dh_9dPd9pw2)9mpj2l%JQN$g3i5Y{0cX&wPPR^; zX3eM6-T4B8e`}dYg(~)^0!7aQ&&lv#CC`p9si2_fB!M8` zqy{Yf32QveneM(8aBbd1*-dx#V*32)V5iRD)M_4SkN5tGzUO~i!6D(AM61?-iz*W9 zQW6AGnBA|ahi0Z8Yf#Ncwwle!>=3Wvl8yMp(7zTQHpO&&{@g5Q!+;Fzd=HO{LKpfX zHmk9owUx8pp1Sb$O`qNhd;)(}nfe7H1uyKorNNaheBcZ49^1C0odxpNuj4l~k_u7G zuZNyPO5;k~r`hGPH$?+#XMS`t6c+0;F`CZ8pR0dMk4DFUFreGEs){D`SJJLftI#HP z1EpjI(cd|61tu(%80Rm0J*Jv8{%JBVx5fDVf6~wl5(tc$#}l%Z-s4nKYG5OHL3~oy zbc75-=@zx}WO`MyG%c1s_qP2BQ=;Zt0eH`ppl%4#%+XYU*$~SiF_7o{m+CW;{=p6Q zI5JEKQ7)I_{jL%`dt}+>0RJ4i5@ayZ+ZW$2GR2`s@#D{oAr|llsg&uzc&nfDmw!V_ z$8cLhv9Sl69!L*-)dD!u;)9vBZ&egs($Mpy?4+NZ)lW0xdO%nIHZqYC(QB)DM=hc+ zRM4W6ho!0CMv4i?JrjuiSSLmT>dc5jQW+x%^{=+Z1KypX41R7lBoFQu>@9hEq1*KB zCP<3WHGb+Tc5YX!^GRnj9ifrGb-3>{47F0|>~li_d{JHswin0P_5A2K5*KB6%cm(* z$aiFd+_MCX0daXs%->yu4*jR`Z^Mp#`K?QLkuajq?BS7CxbJ*Od++wsBivPMayuhz z0S#YkbxfjN*QcV$;939<(oi3JwO!w{N`#nDw@aKKUD}OhyF|S zOuv;^>}{Lu4yTNp(ukM^KejyiKw0#z&IL3Ll$tm8TtB5BmfP{MW(FhN-@t?6`g zxYSCK8i*-0z@CM@P2naHG9&UjackBB$$DGHs+GMuc#O{A#q{{DN3>$Rv}m2S{j@3K zzBngb6t}Sy#XD!v2`?yF&1ha={vNNYN2xY;!Fk>$A@OP}*!zEs-BXb0P`f7JwrzLs zwr$(CZQHhO+qP}(wr$%y`#&>hs-|i#<|dW3lFF*eMXHi~-}9n&a*5y(H_0r+mnoVC z;MNV&$`ee~2wP}2pNn)PuaS6i1%O3{KjiKfp-#L|_~s%LKyknq@zN~&vBjpY2EFGz zI?io&ua15QHx%^;kaZ5y7Sd+}CZ6gL#QPIEJoJNrevVwO3W@;!m1EDqr(7o%r8oLB z;{R;Sw*PkI#jc7$Al@Gtc`zwrnQSP+u-fHo#BfD-iiJO9wY4IadqaDmJR!eEz=!$D zoN&T@))_(4(2#vzx>hYhsU5Xn^po~TOkI&dwo6wO=*}2XcN_(m^k4!nDu_lPRV2&2 zxw9hLaTiFO!9z_y^erw-re=5^!5btdE(nm5P_5V;Lo?9k;3HgI%wVo5#3tyI6^cZ! zz_NUglMWi`_zKX>Nir-A=n1}i*qR3 ze)J(^9LI2zJ%nP^94oa%i62XYZE8ay96NQIGvimpO7c9_Me&=v?AO|=kGSj!wH!>6 z!qmUSg}$yCg5(Wu1zs-)U&^~3nj6ws)!0Hi>C!zLx}c2SwroDV8cD2DC(Q<_>?g#* zdg*HqqQ7+meDYl%hPg2&U~zl-h&}?H0DC1tnO37#B0bDXMFn$Gc!bNx^Wa9Zvx|sH zYh%$PjM*g-83*nNBUY5vx8F$F?|AdvBR`bkvlH-ge0qKLI) ze)^X|Ee^Lq1Xz@_&QGKp-QSn-g?JofQO{hQ<8$d!c9UIPGuR$?)}w+4_?z7(H4u1N z{*Z2h>WA*lv**x;wuo*cl^o?Zu|q!RuB|^y+R9}d%>xx|4;S5dXXY1N@ixLKxFWgCnXQF*-G+SLC|a11QMj}Yn-15E znx3JOt2rIh!YOyw(6cRB?)@7Crz>yleZz{GJJR?nvIPF{ITzRm1<`AB-JHaXc$|vi zA*-WgM3peeja&%q#-MDfZq(*0=Mc1}MIJ_AIyne8O2^FRwmmAMaCe#pTgWNI@^bag z&uErIFp$X06Bx!}3L!;2eZzrPq++=Cae*kz^q`5#aw$v5sQM86jS4k5wQ8_(%cs4p z&Ax^zcm-&&0zOQ@WrErnB7CtR*xW%k2BN7MbZI-Um0FS&c18?6~<9H&Up|CY?^l?Q2gefoG$W5s&Ne<%46d^p~X5 zRO`|*CGsK7yduM+m^7rDX9jGYPy#ri%KuiCgn`3@XbrsrwDy6ZbNDAD`|B0j`eTpH zZZWVn`EpL;83k)reOqD!mRH#gb-I^hXktxFd@1nS7*SrYm z!;6YXwYy0g_OTrCzDZCI$$M#&q+*wj4?v!d8WD*sb#CXcpn3fr9Zo{j6@3Db1r@~~ z!I-8q{^Fsd#e{+baoIo%m)1;nm)CypLO6sG`;9n;urBIU^_m|G>O;kXKOXh556K6MJi+*3{fJ)&LQ6F%d?h4OgL%|O8+nW5|n2ZQ`W)q;-qxCC{Ate!*6nO9*7)8mEbzESAt#1Qw((LUkD#-)j=eS z9wnpRE%Fb5HiTM_ROY1D1mnX$o+WFz!@7l9FFVy^=RWkS@SzF&Urv#BrlUKZ)jJlr z&vv`98Wz4Yr-&Q}9>AHAjbK>cyOC3+S9&OpfgZr=T$_!7Vv@K1k}xV3KqHelm=|;x zg-k<+f$@r%oT5(U7Y>j$12@@e4roS2?P1d!`v=7N3<$}sGv!#JMzW+}TBM@~&=vKc zDn|AJE8u_WnJ7X7gBr$%Cd~cU?~}dlqZe)I4qLe7lP8{GD(I)*XjL$K%yKr$MGH%j zO`YoQ!G)>tiH)%mYJ(-eaq^Cr>-XIARE$nBn3IzOk+8f|ipm$_+bXwtr*7n}2F9GF z=j8xVof0>MRyLtOE?UV}aRM%8N=f{~)7ck)hYo)kqL zqO+M%4xkl$e2&i_oo904<9IGR?;z2PRdki2Xtr*1nV91(0I=mWaR%;p_VChYlKG7^ zHGO{!u4WAgVs<%ZOB$I4r3l7lKZk~gYGSI>*mN&#OCv`H403IL|5!noQ4B7wNI246 zrgA#}fiA2Zsia0DcN!;41S!Pt#Kujojom2hLdt9xjVqH~uR9Tl0~^PByRsXzcw$XJ zzvg0LcD+20LF0%-@n&CL6KdsL>%PwGUbHM)KZkQk1a!+u+Wc_DJTRxH41}mXNRQtQ z+-^>eyvnli(EFA~mQp`4YcrH1&*#%?K3$V9cyV|}LLb-H zHPSP-C9wtbwLtAvV5*qfU^>pPOtd$zSQ^!6y}Gs0TZpD#KKT?*Z)6T%Dn$>jgzfN?O0sUPA1~l#0Um9jqkm z-~=X)mA!16&t$dx>M#NTPy8YV4caQp-qXNwd@Ce=W$$DdT>w)a0^diIgV!RdQg)yj z?<9n`@wid-cBRut;sU{OyiG@xB4i~5%3WVycaorJfQ+DzXO+()8 zbplBg>elAPpJ;fkb&jHZkH*kC1Qi;HKHy#(Z^KFE4a*i@N@vwRBg+)Qnx-};2?FSJ zmr#ZcX?yD_DPPK~yK`%1$I_A{IeKT_#xOIlv1H%C`hC%T*bPD(lNS_{79K1!_x&Ju zoMx+KMN2Yd`&z0NjB&*l=z3)Hi;J`nFLDhg)pA^g56M=M&lRa9 zUo35_hPd2A$hcktLQ-zM6OkK}gkqUySoAJE(D`OrT*=25XVjuOH1I`-ciyBK0GFAa zbydKZ=L)ZxogsuIrWxaMS1c-?z2&t)eab#qzFi}!FWVNxqBR}be1EG6dpltSY>hcs zozVsc^6amO2t{_oF#w|RQXbV-&ZNHXG@xU}27!qf@vBw4!7ht73QClP%}rAcgT+kS zmmcZ;KQ~*A8T+3h7o&>M8Pl>--dc6;#t|n?I|tX$Pw@%HkR4@`7_1FU3q&_86Mt^p zG)Xp?f7fsu@iv8}TpXc6vpGEsZQx}i8o9RqY2c{SkxYk#Okw>feGR*69icv zIJelI6!0BGtE0JWuV!yP_O#$GKha#fYeHAx8yP-D)mCF-4h>|l!F`}MOWVb0k?+Vv z9*-&shK|aG<4nDE8IE;bzTxUtsUFcZe7J`|=9_SERc3|G*ah(Kqx2 zc7~Qv+}!`;DKHQ)GO)A%&*)#EG8;4V{~7*Y*aABn3&Z~>wh;XaDz7uO5(0Df!PV^K%&lw>uE6M98rT5+H@4vFR0;@S6XORCRFiYH zvkI)^kG8@;^y=SN3=C)(7zwcVuT1}o1Cv8@jf-oZLqq-dMf}$*>vfFU(u&&J%*e>_ z@(ScrD=$2xC}RH8$*t`7)V{|0THA)_SEv-Nb+ok42E(tlOs%EG{>_?@;z4fu6X7b4 zfeG0RfC?C#iwT^ne;82zz|>gwmn$H$t7k$lDY`Fgm&e^}V{;u?de%b2(H33oI+TaL=o|&PkKIFUU^RqtUeD}-uCl<{Xz$Wzd!3c!@ z>*xKJZ2J|8skN!a^%wbP6>-r@!o0i^TItv5y)Q{n(A5?|PScbW;2&BC?4J-20^I-e zGw|v6ssNz*RdlFlMPeg!4baE-S@ZfW@GaIf#V5jlFBc{c^y9|F(e=8(0x17g;fxEW z3Rd*07yX-e`CUiy`#bsdO8Z-v^5cgZ;nUjsW0iYw>;IeQkslM%^2vvITk^U7-UDlV zy^}TY@*7&2^|jsANIHs^XY+GgUCeaXg9~Cavm=$6nh+8jaD3|kO~wGthHL~1ZASFi zH+6FBu)GUvW@BJgsDEmF{~76_H`0ISce2wpJ$~_x<=|O2(^uv%drNcrwJPvqn)F)w zp@&=8D(X7g*YzKA0l?WAq;sej|A(sqh#PZvb7^h(y6ysOZgG8sqmK@-+v^1^eS>50 zhu8eS$%Wg3z6e}?y00*HfG0w~aAbbE4?ROOkh!SeKcoOX!}x|^b5cKl&;WXt@IC*M zeTS_9JTdy6@pN7N8`8dp?KDmOrmQ2uA?;y+U_ z|IV}eDtNuh{3V%vz0&-5faMOpr;h1M==L)DN2@mKfNvApSb$3bal6MDxP6}`&|!ymVI zqp15P=7HY2-hX;se?WS@KECL@<*pw7Ewc9kp0PvI{_i?8?Z5vv?q4Cliw>fBxVXDo z4|P=!J-EL^em!5((p%Ee`sk-u(d~WeE8Z4ebx6Z5CEK`%0qjz}tNMkwQ;R{aH|RMF zyU|Ls;(Cd+h+0=h|KS>vksaIrYG?I5Y*mm{JDGKqkf3a`5iv(+cy)c#Bj0+2iE+O+ zKpu5@79s$u6&Ue_1Nnj|qQmqxX!J3zi`B`o^g!R&9+`_6UMULLIO6K4h@3NGxMy3A zX-Uq`gbGo1Rj;b&HMUw80=E&l3??2aMwEDP;^`l1Qzfv1g@vicf8KQweU)efx&D<1 z%1IFE2)h$j&!=2gbwtQ(g3^gqRN#gkQ2>pN{*+p!K)$s)qMdQ0jdPA5N9tUgfkl!g z!i?j=yGyuMn+s0FZF2Gp!KO0{MF?bpdl=c}sCX__7k{cwvD?>@2mz7kqMLb=wdM9p zl}%j2!+#SLAi^H|)NFOS(ve%iJ2u3E*?6d+MD!3##v;mjnA>EgH}hZOG&)WtG#Aa0 z$Rb+rmI^ejL{$x^4#u&KL>H z+1D$qKhqoxpGs@{6tshwsT)3oLK{N`q<}xN3+o4PqSthlXHQ@Y zqUODgog%#&hxTTEEfvuzeTzi!FJ(t@M?lA2CvA7w&PEmB(23!40@_s_zJ!+<)96m~ zb)K;fjdDtvjS?^4E|7aOeKReK^Q8?gOKtmMN1z8hi$knuFafRtRkGNhCpmgny)Y7 z(5VDDGnq5MQ!zi2TEDKubT=)Qnbps**DV4Zyz#XIi^{Z)t=Ymz7J}Qqj4}L`d%YaZ`Q4H{OCmHosYRk7e!YTRUOiv_5ss3`= zj_}~-+@Q=7rCXq9ePLmDom1L^PyhYDzXV5WZ=cNgX92Jd{Jc+~BGRDrCENV;=2_A?F zz$EA3x!sZne@3rA-a3Ur^1jAR8Y7H1Y{$spg+7JtX}+=5?_^8y{pFImR5|hC8YBI? z5vq(%5#p59N=NU7z6eFmbP(0_5MRw$+keKf9~T*9nuYt~!m4CFu_#~S(>#O1=zdAu zwZ$ZDdJ)duTm|i_P88(i)vkYdf79%n6Ss@+Vo@eb;$<*?qddXl;+X`^8cZ-#{0@`) zgG5?wf{$N^4L$UIUJN<^DdigeSVt0*-H&GI+9w5mN{3whZ_6FWRE} z)FO6Z5Iq(NuH=+b(VkYn(A_3_4crx?nSLdEX%peB`ZTOHtWE!9wCW!H!VYbi&BK!r zqq{c%Jd2n%jg|AQ1RmOJ=Jo4PW5hO*Ee;C4<$P?s$SZf3FoK7Y*dp=*1FycnZj>%a zB|#!81DdJ`(Jfp|$$KH`{%1y92B`}wI!16;A@z-Snqz$JFP(jh@C|vo#OgRuyT_E{ z^Qc$o?;=CV@`z8zf_7Y`mY`?DhSaI20Mj&T3!c~ud0IwjWiQ=U$Z}RKab6QZ)4$Mf z*frI#X!Amk_zPU%QPt@q8r*UF$~x5$h7~Nx7H&zs@s(pHq)~ed5)^dKJ4>I*Q>?a9 zhw&zo0!$SafSZ%Rh1iShHX>1}Ownx9a8Y4}TLY59a^tgU9jshBcp=lw&Ukue`BJno~N3EzI7ZC)MhvW+G=8p}KGgk%G`BU)F<*qeys*iT4 z2gw!eH+&@#_~#~Q`k&?IOU6t~O_I;4>oVQ##Sdv^aIl@+oIsEPwS}SUz;wv5Zf8iqluin+K6{?b zZ?$8}@xFcH@V}LeP)p#8^G<<|@UoJ=>^SI;;si8jcS<_>X<(pDxojp*PHW?%VGdz6 z4$rl4@$va}C4^qutY4<6dhgX5*S4pu^ZFVNnMbKwMO(QrrH8t57Eu$si50E*1k3(1 zcK{MH?7>!`Fq12zcj}ldGPN&45}9l%#hS~kBPXCKA_MxlrNmZUk;A0p+=&+X@wNEaqbbO=@1-PnY%Ai%?>0=bGP-kfGMjERXEN{Y?8dxSJkWMK8l#K={?tmGW1yC;UE*a~Vs&6ls^l*Qb*0v_i_M~EE z7d%jhM8gLzxl?hYWu$c~pA>s<`2Mqdm-=QIvAf%i`!+T?!72x*Dl5{8w->GpEEhO$ z%ZTxnQ&#CcjHh*neJ8*n1CA9;olL9SZnRj;S){*F;S!gD2t9%sBI@Ka3Q`V%*yl~-GgqW`m_Omf{~c{Ne_s(o%^rRlORM4 z`^#-yXBM6RC*F)5ijf6x4Yic_R)}&AvHnH=45XC1GSH~6OC8?B9{B1f^oe;L z*ftx14v!7RP%L;CAhBc0f83?>)RO**#_r$R;O@v{w>??HI5cAg(;)-)TWi0x)2*Pn zM_I<8*0>Bo0Y4RFp=%yjwW*oO0FF?7&svqMSpY$0MXUm{`tS(M^PO?CXpwd+;9rcV z%;;a64mS@<5pxk)|BTL7%MKe5u&f@2?zLZo@TA_kDha}N8+SmyEq>D%kR3&JS*vT? zCM{qyfQ~{Z_!yazF;?-p)_Oev6Bo*9{Nn;yyE_Aeg>mJq?l?abU>In=Ec)5gQ;8O@ zz|**YL9NXK@x2kP7kP}yyQLCN+ydT2{d$STZh!3pSJ+Cf*;GEBM42&R?VStQTy^pq zm(6u;)oQE`=5gA8@x(3#fiIW?%ZE@rHxy}o1Ed*dz)d6l(YmkHKk449cHoK=+vol~ z=Ru085~9A#(IVbNYC_=DMCgeqyZ>;=y|##7Wb@qmkdT40#zk={fPr{=y-3BP$6nlA zDQ(s2zfYeX+k{k>88~WoOOyY;0U5V;osnJ0bTH3h48o)0E#SJ~Ipjqqb z%qUYnHKR4xaL?3e*j!cma}15k_+f>+Rj2ht|1Sg}P_%t;zcP*cO$7K^oRQ5&pA>Ve zLC374fNXDUb0sM;A}2s<%QUKpp_J$M{!LoMr1QM`4ujL!%Jz;A#FzNUW^Az;yu_f+F9cUJc`#f+EKu0+?YCG62V=E{- zQF!9DGl=m)0Hs8ycaofIn*54G70J;G+ z)5gAH`c6%*Asvr4diLM%sJ0l-55Q}Yo~zQm^suCWqyjf-)1w2aI;A9lOy6!LY#wvP zlU8-})3mq;heDnbiIp-$s}s%xuuy#401zCHikM;)t280c%IWeHxC$30HnUB&L4@HF zJK7vX8Mf5AaRP;}35_@^qQJMsm_GIM$n17bXK;rOl61?zT2q;Dqx>reJPp==nA zY2WJWv3dTYE6~-aHnGh#JVhdtJ|b<^!o>Dzw9tR=7E(Y0!V$2%T!i$C#5h}by}NXm zJGI&U_f1c5Rqivvf1M03ztrCGU*b-9^4p7^Tn;yJe59oTk?vCro@W2r_o#hZ4~|?N znCfLE;rs46`gkK89255hbYgH163P@=8b|eb;4r#)gLj-F!R|Ey*AOplGM6BI}f=J+%h20B#(*FfaP+K2)Q%pq=bSUr*1h z$0oaqn2)r295d%giFxOoINBVkBM=ym50v}hxmE|OlmfzqZT9p&(xag=v1+5`f2$^EY0v8qIccV#48rwIUqf1??$L;ktdUA!N*E?y z+5#Vz#QRtQ#kAFHN_y~3?OEa;9}gnqKs-34W2Ldow)NKDOWyI>e-s>=w^;u}tZs%z zjrXf0>p{O|?r(9@VX}i5LhJ65<)ivmm0K_WVmj=LigjCQG+iukK%&{%BE$ijbYp22 znGIobg&JYt*iV%LWsrL7!Vlufc>ZL=>og&Y>3WctC{&h^P;L= zTMSo`6hra)UEH&c!|MbtzKBL<`PKw30O46VA8b9=|A0%N{lQ%&HekTF3xm>&%)J{N zWd2Lbs!C7@P)p>ZO2M{dO?-2@ou2gdeor!*E(&%Bq~>!09D2c$GJv=QhdnDHWA{p= zgHt<@@*Tj_~(cr@=dkpbcPeDc&uWvv~W zCMeRan&WcqML|%_g?z!pYlS^o;ki~%OVIGrW-12q$$mKyK?y!qFLuFFrSrRwO4KG_ z|5uVk#Jkl@s4>$Si!49u`;-`1)pd>sm^c|QAV?Q=tFpW%kvL-2AIby(2!(hV?aw)C zz#^@m&;&nW^ma$XQ}qsf<%*_C&H~6rCy3Sl)>u~u4vy5-Sj6LD4{MRWJTog7U~~2n zcm$DOt!FMrNQyOK9}}^v^w45(j3xku)7>NuF=osnv|wMp`u1cH#tnGfIW$b0xW;Cd zF8oeCr>)zT`IYJ$9!?S*0`#r--EDU-PPZhHqV$m6H4wN{q`vF2$GZK@yKvWeF6Nq6 zDcCIPR^w-u6b9FTqPcOh2Y>L}*hU33j|GYpRrdY7p+juYfIKPM!=FbyOAqvxdUh-s zL2Xfg-_X4Z1n~p}$KJBIAYoFzhRg&H2^IhAh;Gg|u$Bpz9~KM)bS>Fj)EDELI~?E? z^y%s%#1knnvSUxmQ0rGW?i4d}c0hc)ic|hj;JR*^$}*uobk3k~VC9nEkkZNXSsT=~ zfby4MpcEVDmc^!1iu|qVjXl#X#q_ZvOkr}`<(QqO1oSKqtKe`ZRH~N4|hCu-D~5}2YV-F0R?Yfq7LPK-W%$+0S#LqDdQt6vUk^hVvl)M zv~hfYpZ02nKap9E=Nrr)qM*{g{YmZvkfy&!X;Dg6E-}WCBC2&^Zg>x@0!j&SA64~f zj&EQVM>?!uIIq{M7nlM~#IYtEcWDnCCw}CFfbBJ zY(Ez1-RLy>fR6d}w>xU38L@cP~*&(t!F-tVFHt_EUmp!7$uCD9hm|@NAnLiXlGzq`i+bv=No_p_S;h?Jo z#h%#DB<_jMHd?2T*--75x(!J};gT9*9spT~3;QL~+#G40rtW%LlGOT})x>qWqv>>v z)F8@Ol&}JO5gw|i${MjzhhDU!WK4HKfiC7MKP;0um?T1t6J;b_KE3v=!PmV4 z_>;yM@9b*}2LiBaD|t`DL|MA36e1|3YsF9MR*EIK2lg`9_aH%R+ZpaKjcl&OCof5q z;Kn{_UHIlHjTHangi$<$(#Y7bbakPb&6mHL-NfhWd~Vp}lt()=pz>JSC}b3raI84; z+(O$8!%c=hRm)T`+Mh`Nq+Ple=#lUaV2W>6`_%U<M06C+*IWcu|~U}l(I7-}TZ)%l@c>Kt3%2qgaHA|Szp z3L{0@rv!@N2ZJOm0tpcWHnb_5=p5;TfknEUBIbEGe8wn}=Wz^~X2@nZZ|qXQX{%ow zQX#i*R@k2p=&C<3OY-;jECV(>`W-TO;YDy+pJJuTO^jo>_d*qL zpiTY>O6!mchOmNV=nQ0r!CgL? zJ}RTT{-B!gYz73ea9tNY-Y4Q4pWJwCQ}#FEg+I?5n<*H##whm_h2ZY0g0))M)--I5 z9Fkh%MAvsc<4WhPq0NM9y`T>=C3685{C5#LcWGvcFRWfHzsI-o(ChfHkm0B0ZGH>3 z3p=~yf<}y_JMqq$%rAG3O%DThHn;qA84l^}Wrcs_mGUwt_to+&vuYtiIV>hOY}I1v zdL}5=_rWNmU`$>zfdICJ(?Qf_|8VY_kh(*)*kJ1hBPpEH>oMzy8DMzCKkYhof($x8 zkiTp{@sjj<*2`OkJXxSOE)VJgYuSUKydPn}_^L4N^zC;GproBzi`@$pDCskDxgwNFO z5Vvy`=6XKAH+a>tyM$=UcgIT5Ng*ZJB)fVw0+kwch^z+z#X0;mQ-5@?`Bsd#B@Hcf zR_FcL-%51W9@ltBW_vELYI_4Yq}uXAfY2w8iF0Y!jge4!%=naTL__csm?N~~Q<@92 zRmT?pVm!ztTpO{QF|0~_8Zu3|N=p2*tZ!ERf#jNi!l(&0n^Q8*?KY@3S)_*$YP0 zDY#D;8E5*r7W4Fq7*5zqx6V3p5oSTn=AUFfPQ28<%gSrXGw4^=a3FAORV7&Q(ms4BbT z;h@RugmS7{x_CglzJ_uM(riih)`lPxW?nK&->^f-`G8^n6y4jSa3OgpLfPzt7~hIm zq)}H*O~xYUz$*v|IG7_GIO}D1aM*R!d9dXj$d8H;YynHACo+$P{fzE~@=_?fb3Eft~gAR)X6YuwrLei28H zS=;oeI?JQjA(^`7tC-SRr922;BIoc_oJVidgN2y8Wj9=AzaC$w&gYe!HwMiRpD@e> zPh7I8z()e`*YRXmH?FUyoa@ufGa3Cu$ADiid>vTCFoyt)mivaS;(s5s!reZAL)av@ z`QI`(%o~kI=c93|EjilM76R@yN1@f+SO`I;9fTYDmsI6SGLO0`(;f*HlW~eT=*g%@ z9aMYJ(SGaUI?{p4bK*L-Y&wd;gwA@cX7n~8P(t<>K1uZu0e-jb=1G}T zF1YMeo^_Kx@{C0TjystB5k@RAtPNevy-p@s7+<4zd$b?NocTJ0-s|#NW*(WyR%Y~L z250BawB3nwTESO7Pw~2bc4kc75LGsv;T0aTT`y7ESWFxOgSExR`3!k>j>fJ^7Ro&o z+l3trOq;B-z_mzIa1)Rz&FU`Yp*}Vck;yI+=~KL%irZ~ z32?(D-K=wbny~zgKhg<-h~$rVm5LZon1z&DjUu<`xp;|dVNvm$2Hqd!C9=9j^%(_6 zJzN!A%oC#KfC6P2Wpjo34LeIzLz6k~P$(A11DkEdYluI9|={T9Vmf%Z{PFF9CRh*WpcT9(Pgi=+FTGQRA+=4HMG^Tisw3reVn8Bz+>NYVUfCo~B;3@7T?r!+%8wG>}3$n(K8OEbe^6HxG3;`Drx zU;#>F7P`#|)+%|$=$LmAU(={Es|acz!tcPlfbAhfZsH2;rxl=J`v#i@PHWv*#4L(^ zsTZvr+5$>*qcnMc|3&M;({_G=Jk=FAn-3XC65(H+tv6lWOxo14Vzm`}Lix?l>l86w zG{@&aqmf;Ls*8EO)nS0J~%S_9`(Ay_<{18+wllV}Nx?hY-rdbS^ zt-aFAC2(_rVGoiW#HK?p?@kx4Am1mnWOq!dnW6Q4Qn`FlFy z%$TxSU&Kk-n29m(oCvgf%QU-KboFkz2$}2M-Cb68>dy}^U+wRz z53;jJCncu(l*0~1f^Ow>%wSo$cDXgmJa#|_JvKfbtTa~G&$HdBw~_@dTKJHs9n2xM zL|b;$O0^R^3)D*~vw>WC`NVV(s05}H{q|~v4+S!zckd2Pl}@!*peCx%b)2$)fUL2s ztCEDs-#kJIWc}i0e7aR^p<#%&kJ`b$4GmR0_IhfUvIL->!w*MY=_DT5I*L4eRQtRj zilQx!-M;~v8&~I)9Drh#m@TUQrVc=+nx2OC4n+DjSz~KhMM;VEfiq@yiROqnBqxe4q3T zYC21pOpmS^RT8KP!1UCx@{mY3w79pJekRXZh22@7dnT*Op>%S$9O&QrO;LS$0F6>J z0gp-DfheWd#;#E#l(F_%qu^T~#J&WksR*t_2FNu=Kr-F_G!wgB=#Tmc-7nP0Pf(Md zvD|l=-WPGV5M!DQSmIG9EKf>EGArCwEob>Je#A+BndG5idRRiPy~Fo3C@hnrN9HZv z(~;XFPMenciH1briAl>8L9oj9H~(Q=9deso88K2jL{?`_y@(Y1fTz8_!NHdA(GhTb zqe~FuWEBJ^xZtelH9tw?*=D-;dkT6=`4QfvAiUgA3|?2q?3zM7e~`ef8hi0bh3a!k z@OY#gsdsRj%LYya+XP_9+0tPYt7P3r=3HOmg@4IBilvcL(B*Wnvw8_hH@5M!UFZag zpbbV*-1t}`7Z+_N5RRT|L({l$FZx?Vn=@RLp#sT>TVJ^$z{dPmHC~z5M@dzxgF&tB z*U^_G1N4|xzR?QsI<5p~Q>@%O4adD%o%LwfWT7%$bkQBoV45hVorj^(+Sm49=n5CdB>P3;_m3I6p&8wG1Zo2)uPbPdFrg>y!>{ou>Gr-iyZT$tyu9Aw+vh(^U?rnpzew8S2KDdSHH&ML&H>rh$90qoCl=6K zE4+_T@!{4Q<(gYLyX^NuUhyyrW1&!KQlpm_k72o1Cs9 z2431#>%a-U3!;T(Nl(!HuvUdwvR23MQJR@*@JaGeK;u(cbESd)nRNQnK$B1l^1(hHNS(n=X1w5KFv{`q0h#6PsDu;JkZMr1Q*Uk@B0S)6% zQ^MXi2`QXHCuX*I^wsure)1U|mugT0FM73`0A=qZiV)YhD>n=M zjdr2Bl`yuWwh5d$J?}pZN!bmoZoy}_gU%KOjhSIO%5ltRUuWG7B8KlwHkYv#qjs476Uh37ZPZ3#J|NE0Z8QeZD zm^0K3QF<&I+A=773x=0dKrnbA(spPDaehiSUH}AU!j!fpCc0(D3N0 zEVv$SaV5~#DXWl+vQy{1lF%F0aLB@&gB);SfJdq#F| zf6?uH{s~(D!2m8KUB+)uh^Rj9*fbC+bHb8LdXoKSsbyv)#YhDHawb+WfE82jhIqrU z9=@Nwh+MPZg|DgH;;v)nnnpy4faFH(ks3~47R>ZeDaw1)KW<*NvT}w?m#?C0Jws*= z*~pqEx17={T4NoiG>mPBbq-rbNesNx$87>UQ z66>7KfYRX1LgMeePa6f$vg%Hs>NF?d59H61jNn!=AtG5;mQJ(KVZ?u}#MVT+<`Z7E z%X5O_VBve9RG(Q{bBmixMJf7LKC03S8d$e7FbP;YWDX8gjbmyna3fIacby*Q+VQzS zsNz^$_;7K~{@my@f^bd@0Y{I;84|_?!}!W}{>1oZ9A-i^nsG%QWTfv#K z?W9MFRT3q#TwX{zG{vYyVwrbz5ksVBQx$82v7fZGF0%0k&E9az`e#n2M21!fMn%Sg z_ff5Sgbhu?M_i~w_aw+rUsGf5QOveysBkP&^YkNLx6~#ia8F)B`7Jk!Qb^3D1P9EP zl)-=KT$_uLsv|%-KS+8Gx@_<*T^L0nK~pGjq3|`K)gM|Eocyt^7ANf3Hfwd|;zRB^ zG{iYbxSixd#lT8%1U$$H+kh@d@pwnjS}K$TrBa29q*nVJbV6GY1W`XiTwybDGM+CH zRe=N3H3fLH_Hkg6_iBQDnN82m2M1Xa()oQq8j-_CLNk!2ns6jBvpL;d7e5o|C1Cs_ z4Ybx;6NT=qMU8O&(SZ+|wK81~Yqg@79!OfZtJZ|4v8V|lKV#0lFpk_C{&CBeJR!b* z@ytv*RzjVhp}B>!GX&p9()?LkdrCD6-FroV3L?E>LSvmEy;%m+um5Sd0g>qF{BG62 zf?kn_yB~2_#_2B{(h(V=c+F&;+{msBg))fldbnG38bO$N3#hMl(b3EtL$P`ED0>pO zFU%IioNI?-?hFcmD=iFx#W0y$RcfHb-9ARhP9i^x> z7gFf{_~(06Z6)ATDX2~SGs8klhU)0` zM^V>g34K(B!GSqYeHwbcbKtRyL13|cLm2OqWc>-RN$k^)9PtOvV(WaR*kUe8?Y0Kl zj6E{fHV?jjSdQBC{@SNd^sLeplAarupZ>#7 zq(Orqbvvxcyv1umUt+(Gt|`WuERxJMoMpW%n7ns3CD!7sW&^+HT=l z`-c%*@3UXqNQ|B{A4r(kF9uKRZG+)XFXGckQRm2wJ?XsYUyP7yXE%Y`8KlePN-ym2 zDj|oT{(liTjdr)lB*uYZo7>MshHKtu1qI5AF0diFW&&GHgb;C*%A6j9$5Qk!^%XdG z@`2Oqn6dfo{PK;l)+)C1kgHRQM6jbSeJF8sM%8VDOt%5$=$WZ_{5eR!Pa*J{>7e#E zE@)c}fNW*$d49D#F8mX8^fF>$f`9)+Y?Y=!b;{?QZ%eC40ADn+3^0$%JM(-;@f=E- z=W!lYDRHL(>)J0^F*KyD!;MBAHwGZrD)MjUBzLxmLAC`+uII$!2pBJ)G8BgO-f^R( zIRUkr-Y6yPkLqL3&pKoXd!!uT04WsQ5!3xSpgYPyKIfz0{+i_dfOong@qB<5Vr>>k z2in;~`qCP&RLjYP?I}Ja6>vj*< zpO7^>qskF(xo}u_F}c zPwajLAF^YyqS97U&6AM>)Qk80L>Z^;6NAu;1emG0&rRr0W`Os(# zngt*$>d1W(XTR>cno5I;E)IRycW8hs>DlPfe7nz64M5PlPvlPebzZkhk zp||#habrqn0g8%^eOA+VRSaumx5O9mYkeErmTv61){jt@lPGCl3-WjI!p#)9scB!q z-=cbH{hILkOyAjW-z!_`)(QBkC6h*W#heR8hIHaS`fqnWa4M7td?Meh=K^HuFsnC(jS zGcnBDQ+BUyQkf zTXaeOlfh;{jvs)ktwL(MsI@&cBG~n$zm?*{t!(#1#$Sj6MZPC;08|&PfYCbIOC%hN z>y-bdhHN{~ET%UV5G~jswKyn-v1F(DV^Fy#9wcecK}EMhP=1^ZVmeFX+M$)6u85Ki zCt&u_crG*8_BYWSln)6Te=vdo<7zwH?ZT|| z?HMGcLOp&(5JqHR8mWR=n+I#jkm6LW5Hk%T4S8;!6DXAa#0bX2H0C3Wd4WTuxCmBM z#66T(VGVD5{S@3-sG(oVTOq-m;e=X&G1bNx{-=DnAW{irf`VP}i@{hfdvc3~-Q5m@ zx1dg(46N*w(6e33l}V7(eX(G}fkjK~$c1E;Xo1}TifXvz(|L_fAZH#}3|M1~ZFI9& zylse!qj2=`%~*&_D9oGfx+Bs!gXl{NIPop*A;nsOSY7%7-_Ft#OErzqMCQPd4W4Ha z%JaH}sFwmUCEUEVIS}gMs5h|!$DOGy)Sm zFInlwZ5n-UIVOnZPcX3mM?fbZD<+NxhTh_6mfl3K%d#Bc_ha?MvF=SWT{1Joc??ffKh}6QSiZW_-THy zWf`MaJU2EUUd8yNKkcD;V#VRaw1Xi&{f@n)dcuK;#zU`3S<5b|jC)BG7Sl|nbLF@Q zKJW!3ga<2aO?-wOrA>&F6va+l8FyY03u}vubeox&2A|{vud9-&A=co1rs#1?86<1H zW87n`0HMnJ!vVAd;zOEM8~@6ifvl3Q@-VPpR24VguEX-DVy@DHxzwpoTHxN_ap>0^ zI!CbelLAi1`3;tqOS-ERDA>0~Vny&5xO7MkF+#lCC-xd3M#wLWRk8S$yDQTP`$0d=fyQpC7h2FTDZF@A4n zXK64(!-GTC+UX)|?1uazdkMRG0V;oUNy}x6U|Sq2v7W5wuH5>(cO&;>7>$BCt@a$> zyA7sy2c}eUv{1^`;J~{xCcu-xm5E1|+NYXvYO7lRM>g{1n{-!U;yssHJ`X(q2|4jV zaV(N!GOjd)<|X!Cy0gUrogoYSlj$*TI6KoW>muHOc2O@dd3Ur2VGBm2wS7o+7?pT5 z(nnT&k3tb*`prCBdjeAeoHz-ql;NG|6D%hfk&}VkZ(jDu$HOSke?hJ%%TpBBI2}$; z9v_t>FNU;qcZ+3V6DZF6B|0NV>fN^!%s7N=Nelmne;DzS%{Zy(^~}6+a(v?{1vnb- zw0C82Ju*epDX&W6Mfic3&7= z^_6Cc)R%#;UGOzrq%*N5CEe3rH$oT6I_&01*!{J%cxlK!_o_eG7I1#*koE->%4E`q zWWAp*m|o3RnC>HVo{+A0cqg)BOv>J?Xc2n$Iyhys3xT^qq%)9eccm~mdNaGd_7rHZ!toK3Cf zpQqGj&yny510pNI+kgO#Mrqc>&%5Z-friR4Fo~@gFVGSsYxhb5ndzt@4RXR-WqG9O zRUUR#&-h@c&qw&e*>F>0-|qG0Ue-|C!x|bfEAL&CQW9c*i1zpIf+jGo*fqo`dY_N>&l7#EQV4g1@SaVJ()SstU;)vBmNR36xJXGw8urJd7HR-V)# zsX3k31QunPtpis*MshM~Zn0si8*PPucXDFD?fU~2E8BFWTksDnG8=@9LEwyb6L5r_ zt3n4D?DikMj{Eb0jbmNY4PWg>TS5Dh_Gd>~GPYGeTb4wl0ddsOI^}h0t9ZKf8 zp?2?h@GpJ2Oc#q93s*4xS2F&{`j|-a!$bZAR`_DPfgV?^E83?5SZKVjNN4qbyvzl{r4T zXI;3mM%B&HgTcWKkJaYI!m}O?$6i}?)a%;@UIXU(vy?7rx>0wVO+!{2uVWhWAhMll z%U#D5Jx96>tr9K2)1VtW4JO?{m(cJU-U#8BvcNil!`Eh5dqKS~$G4|_g@?2(GO1A+ zfpT2Y;(x=#LBP9O051<4E*xn|(+=x~9UrEkRPz2JDL*t-*OE<+FIk-uU3#ZzF?7Jm zh~$&kU^{0*DHVSg`64*}lU z41-u&6krMiDuF!b<++MzSPs#yvYw|62(*0=9tzP!(FB!%L@pPYwcf;XB-wv+Cro1#U$^t7fXvvOQ#0}hLc(|sk| znDqQULQUE)`Y@>gBCd8F7h)F_d_-#UiKP6l zRLq2HreE#mpByFXM968GWR)Yb2T9~P8D`aU8tKp)U#f$0Hk7BDjt+Gdq{E^(77I*i zYm$wlj6ySQm?Sn78SBG`hOir4=rCExB z#d2OQOlR7^_r>hoMUl0>h78LKZVbVoiO%lm9nXc@zz-%gSlm#5U>~5ZK7qbD^cYoy zF?x*dEpdSby(?pnE}B^`-dM8M>HyEwg|28LfCiz~=e54F8IH%)$o8cZgY~rlje3jl z)}6J}@C#ELkc&O?+$7e9@=vwQ$HWK`HL)e&VA-Z?mi5r>!> zRk3h#8$EJrgisM`l{b(boD}L}6*b@jh)c&mLm6c>8Fe@WXf}3n(7W<6lo3w0>n`=%=#I zVZTMSw?g3u2_M(33~DSWVcOsp$K>rJr+O`xO-(RycC@(^Bo})a-JQI}Kb#=aVDqm{ zr4JU4MGu9jKbSpHf(_YCyGRXc(@f#eS~^NN3nxX}dBdJ~=PIcZ+TeaMn)P7jJ^d)3VoF70H4(do0YuE6; zmMApjU0EV5>l1=LuF0VPvF1YA)n9G%nFJ#M@X#&aD*Wp}SC23s)jK;$B!hp^;6dQ~ zVc+3<0O08~Uigj^3+Ne=9Ikxa0PX^;Kns#{3t6bWd^v%)7u^S4AN)&sgQyeN-MGIP zYZM$0PNs5_+|_ymnI^r8sV_?gqhLP@I8ji1(o$D-yA_tV`)uS_PS-j8;%I1DXId4S zGN#JcA@0C^r*+xe8g`VBPdt@FcB(k+|9gZcu)&>W8Ju;DS;#DtnuS) z=@+>MT=9(q%UE&4B<@|kzN^!C0N_y?mUBBD20v1Dy^0kvo0V2DMB;YQqvWtm{ZUD0 z{jA3x+Li=+hcMOCZI|MlYemb_rt$-`g27vLC^cis1@rNFkvf^sNCxq2AO@JI`LaM3 z3d11_;B$u9Ch7Lzx>RK>KsptQ0j6JQBzjLrYS>;Z6bN?sG=`)ha7QNwTYA}Kwoh`Y z{gM5<8c51LGRKyiam)**va^DxOJ<0`{MC^rXBI1=`m1^DNRn&n&)X9W@QoV-YSF&U zN-PlE#8Z_Z$DbLtNMkdv^AP?9$cFENOE3i6w3VlOb5OGL2W%ErpvOJm!sjKNX`gdP7ivd(G3fQt^Ek{cN>=2FP#50_q zp6m}>B0nUt0{3hE;IwxDf=un+uLY8PL~dwbaVa%dDyqBe0F!k*A)}Lz7<& znS=c>Cv`>*5|GWMEfQ@d0o+l~(H(c3w#xQ!N0)6`40Q4mvDYr~amz>HkB1+W4Edp+ zRuYP-b~vPG!p`&JY*hwK0&&2kh-AzQNRX?0*<6iZ2#KcM)*v8}F;ZWW+x5Y((U?capcy`s07tffUOt9(P`<# z>BE!cl4r>vVDdEa5Zz{1cHc?QVAr+K*biZA+TL2`1ngJ0^Ec?QMUd$LwKEfFX%o6G zdf-_IMJm+2aojRW+H*H8v*ne6*L=w(hrmTT5qnC|#vc%ungA{zZ|?|%=F2B@BpHue z;>c4O1m^tEaBc|d6mO8zb_UHeU9crTS@kGXE%&B&dDRZ5mGGX`1o8?Np&q>Zkr|7} z(FIC6_P^^|OG*~}6dWXXiw*`KLFgx@O6i$(-(*SNg42b^ciiNbWL6b?#gs~FrlRw! z?koirrSl)zQzaaek9K7|DJkP*kMZM+m}-`7c_&}OWYEfCsr=V)d{XvtcSJ<5%(#*i zxBYDt8&Xb%qI`EL-)mnfMo3WL=i(6Qla8x~WjK8kRn54syf1d=a^ejPT|2W#p&}1S z4YYN8t6eXS`qd91_mCEYcVwkbjmSF95!?+A#dKzSLXb`rv%JB8A1v^@|8%s5RCJuc z7WYEjnlyI+8v0~RG3*Y1-%^0jLvkkP6Xqc%KcKb{g33y9)BV?v|4N6hg?8)=rn6WM z{tZ(|4JGtj*Pgs&kpi_llzNjKd@&U$v#GnxwUV$)ZdrgMyJ$55V4MV}!B&6&Oa(n| ztYb?->}dE{>5jVIPSyC?g#OyvSD2b#So9tjBPW>E3**hbjMv1U(?7?wbo4H+$>6WC zrzz%OQQ1}Td@+_Q=^l_qWlwtfqic1;;ve}p)+Y=Cg8yM>l$G7n%s@jAITh-p_@{A! zF7~e$cpfw2Xk_kMii^z3P^OC(CxGwywr_rFgKAKaiZw8j8PS@3BX|sA>|j2OUB1s! z;j+mxl{=+<`Bp}~Q7M;INnl>})%B6GHq>nDLY+2dz=8aWoLn$~-0ts)ecSk|b!h>o(Y)&KDZ4oh z{~eVOS>_!Ys+@<#XuQp?RA+pIp1*&^3p|epo7T{QJ*>T4wkhz32+|yltHnJCC)Kmh zAl=-uSk%C|J`F@aUz1lVP*O`L4tP?QlcqAXg3Rzc&oU*(1qgD$TriZzJv)jp|BP^6 zKnq&M9NvRroc(Vl-h1I-1JD@+bVC7(4>A0EnoDnX27i{gERIg2#BXvQa4CE5mSj3b zy%Z#vkdRa~DXlGL>E*0x$z=5CKaoNinTwAKZIIZ`a>K0Q50wNbtTaeEK9rHk&|7(E z8|0Ur39?6Wdp=wYw|rmP=4YR-mFU*l+FPE}6stQ=V;{4ZhMQXI1)~a5=pE{@?gAB!E?k4OqD|o}e zBYy;vcb4D1wT42UQ(_E)QygrY9n^Q4-(5DdwQ}IBrauahz9RgL%6Cs?eE+gw`|cGs z(()T$cI&xeBUn7CD*fN1rBHE+y#C?*+s~ojnnu%p43)v)kht^@O@HOA7HJ5AJ%CI4 zqLViqoGq0$*mx=-8stKzQO;wzbcYz5jl#R%yvFXC?>;akt2$zZ5b4OZ5HnLNK_A z3)?!1LC$T-1Pcobk`fYZzD2pOJg42ieJigu8kZTT8$GW+*PjNa?gb;1Hh?VPzy)yh zvbwZ*4F1)bnTZJgVPSbuVPWuD85-C?O~AjTqoymMTwVP6aE8D1@h)Il0|<;9Oc;Z* zuonRGt}VcK55Vpr!|ov??(P8I+}wq~48fdHfJf6?1ZV&(wEm1sfLDPt)X*+Z&Y|iW zLkJc>k5Kxc4S?<wwMq1P~Y}?{x^(NGtT| zMHp;t|HM>j89mVGOY4$}F~ECp0UE%}1GM^PZ1tdfYC`}nOkQ_+v+ea6fEz0aX?%_} z<)qWw)0i;)5LW~Q^+C{sL(2m|S1|k};O3MSK)GCjxIY+GKMZ>SUo6-Fbm+H!L*K{W ze-MWE+nO*TKw8|Go$i9!)d8sqUG@8&@~P0Lfky%9o$B9A%uP>$MBcJGG6b%srxC&W z{;&Z9l&}DphJ*cD&Ve4mI0Sdtcko~Nwv2!OfiawXfJ1AP_wuR-o(BC?^KnElqlaK{ zmAm01x4Hyyal8A0Ru{yfruJho*uNOBIRtQU1pPDdZ4!uJ@JrJKGz1XHO*HsL)D2LN z3~&Y5c=Cg)ySa$?rS+~Aeb&FaiFg4~FU$=15~3c6=(n~*Yig?xFujg?b@?qf(uc@O zN7oMo6bLvypo#!~)mKD3tUtwv;qW$#0Pjx-AL!K$kgM1C+uI}fcM9l2|Mb1>{r2%V zRV^(=b>8%M@!rpckx?GrpN)u|JdXh0KTXabogmu%{@X1X-88BV z;>^-Se7YhCDy`lk_7<7;r29w@~ldLF)_n0jPc8*PjT$ zw#$z{xbAxwVSv;&?B9~^2|pgb|N0G{0$|Ve2OI^&zUvDZ3b5Va*FRkN#!rY>m?&=d zPA>?sgOiJx>@{mHyy6@Dw>PI>AD$k<%NGm^3BI)g*0!%Ywbb==3nPV}Pr7UA^bp6# zDjM=KEst44>hI*NrhEYK>8E?ZD67r|qih<-R1^8m?#0<959ln%dBCRX(}@EU-&{f& zQV*Ru-V6dW@$qHJ_}_>W_E+Mb{1_7NxaaA?O1-Fz{;-l(H`@7c1_?@eb&g`4o<|=S zi&QGinDU#QPO|C|Z+S1FyW~Jpw{2e5Gpl3;_e#%EV$_J)KUW~HRD;U_1p^~tKF!++ zfRnZX*nH_b{dkj6@C)OR`pW0DXnVUSsqNRM2p(B~5T+6>Dvp`V74_jK(MVcw6fw3L z4bL`>L$t^ZZxs=oQH-roHH5yE>fS)T0)-Ww%J4!oMclQ-sR3EOxwK4$!pJ7*PUh9Z zde`^6@$HOe44)c&)jYp8Zj+i-i)A!P$NiI}=-|19vUkOvhH;03oaPJrv#9nH7JpyZ zYnX(k$1+FG%xJTFLxhn9rMduE#$vKqjkkeuytGG*ob$noBaCSu<*hnrsdJeib3CGZs(U_hG=|KZa;s?fIp$Jy1dhsi1Uw`jbu zVAwRmyeFfns$c4aMiK899CY2%l57q7=DxU-(8$;Yl~?pBRXrbQKI75|jR7biSlk{a z%Hn=EW+pvaB9;L&uFqf4WAuK2F`|y^v#da|ea7y6}n_jAc%60UtiZ3f9 zOX|`k^PbwHFwQ)%l)Z5iOHkvA%FyG`Ut=e(V>djLd2ZmDH;qjl_*pK>o&~ks)^V{n zjT2H9J&^m-_C`^%Km?_k9h)U^r!!xP!cPNUdxZ&k3kF4|q0lm%&IG1ib$!bPe_qxB z>kmI-nX+}QHwVYts4boACG$`CnN<{G6GDACA2g(Kz*L=W44u!O=v7J>W!zf(JAN}Z zD2@k?3Zk|aLz07`XznaDh|MKE{)47aYghv)HHpDaeSe zU51<0Ylo;HXR(f?8Uzx6CLJd!wrDGy-+kvM&E{RU3+ArjbED@~`vq`OxYUX;*34cI zk6bnPB_UG9F>yR!5lJM(qo0odMPLDUr5`A3(v{g5rs)Z?`DU6v4C%7$U3Fc`y7}ZS zZJs~fwOf@hjt1s94X=9noBif&MJFdQCct_#KnxBVFir>aln03?TEO#x=et__9Pn8? zMQkv&twH>VQ$vE$D9Lk&>_Hv|`}O@bsS(68A{$I%1bwbT(-n_8hS7FV)(rzDI0e_= zsW@b_W3C-Ik9vy;*<=V5%bNIyneEHeVTgS&B)4*5rX!Tt(k$z38HLn(xVnjCiaVV` zOmLZgT_Usn5xAv$O_tOkX8>K>zL~BCWOD4R^c^1AcfS8BsW7wvYMU@oc#MDmJSG$B z5d&Os^{#l2|AeInR~s08#SzSoZC!zea(-S&KkD|v=J zzy8RwbBNk1iii;0(~1(G(i(2p2bBWU3-$Vt4z+7D@ zi&p@jh0$>Dx158KOv`1DEd~msS0U_+eP^6WHIiJj!@uR1&H5+SjpOtZ?vNiOKAo2R zxXZrKEti*`2)m_!=CfzNX-#@-ChwW&6m=rR)Fft zw0PIUcJzm-jIv7RkcYTYqv3DLhE{I^a%|Iak#<3}naoR70kzBtd1|rMW{0(WMniUH8dc#$ zKh!GVc`}_YY?@}W+xZgYP&p{WCoM~!{*s+eUh!=i(}?kswOBTF20+FC;5`xYGG36+ zIBmIT_zqrh2iC-C`-wfcL(>~OeOVrJgHhqbqFDd9ff*pp_U%k^so^4(o-(UE{_)h@FCgM9Rn^TN(kNK)PsTi0^!XWh+? z)~U=1L1)aDbD0%E`ey>3Tv2=Z0@F|0fsxQ=7L`5;VNyK+lDUfuT@f-XUKt|PDn@}h z`Ovu5O$e(>H7)OHIlokc&eCEQ{^~BUOex-{a0Z&R=w@kJg8zjQ9EbMPTf26WV;)i!j;I%^M z24HHRP>`eLL#E07}h`N0~0M|+!sBRyJ4^sKH#{K7T!SXb1< zKtcX8aAj>V>_QvnWy!!!0Lh$|#aE)j=JOey$t_Y>38u+jlCVAQ%LySMW>J8*CNoM- zYO(ID1u@BCOa}1`?8H8x_D}{)kKDZ$e5_N7orj&+fd%e_3oz?Bj{u7lFh_c$8KSP5 zR?88zvT|P8OH}6BcSqi_dbYz36tk#D$H@{LqfG_e9~{SgsbSp4u*>9B-_dL%S>&b0 zS3azVlruU5xBz&hY}SH_f3?-~Hl;@}aj^rvC1`ljgjESuifRFy6w_KiFBO}{x-u9KO!mxm@% zQ@d=t?#0t8H=Ud=TMOM8n5ccs=zESL;+!MtxZ0aX&yqIsf3=6_&~^NNf7QZP+K$Jq zixJ8f#?o8EwdTH0|J`u1=^%NeLA)%t5fp`URAcus^mBP8bnP?t-W}?snPHYbr8}tP zwWCqvA^^>^CO_KY4H6jwHc|wVYk2ffdoH89=xGK9Ysjn*|?1LBc_4$CPj8VJR z2-wz(xv7tS+-m^FA8oHCnyMYXp_Ez`3#L-3kQK!g0Q)P5Zu-VFXROO8bhU8>m*=PE zP=~o!e+J5hg?~eQ1*oZ+`&^!AQ|PT@isyYg|8%+oJR|`aPnuE9W=~V2D&We1RuLC& zQn}G6AU0gO6Lm3C{~@oD4%5 zkGVu$rZ587!5-ee4!%+$Y0$Fi+6c+gySb6_h9kg4a;mx3hPD&e_aG90vs z*Lu45`&8?*aB&z7mJ0Y#Fn1DR6YS3sZMx{_I@#4CgGPh$&q6;rT=BT@5Pilj1-jDm2=2;xMWOYU1Q*e=$un(gi z5e}``9B*`cp^MZx+*aR$2`v`7Jdjx4@_vh72EGcgL`b%$s8 zpnmfSs8~a2Ig7xl3fnwfYtM8%S>vdXD=6dIV^%c*PCOCp4!*ZLR^O@D#pnoY=q1`} zGAu^6E^lGWIXfhrY8H+9O8{`h9?oUUD^mm|(48jFxI`VCX|0~On{>8JqkEym>meKQ zFEi;J>a?>?tzw*&?+6ecU2$RJ)nM?iN~m`(17~?3LVjmR$(P#6iIp28!FI{w{%z)2 zaTR^dSm%d0>THm^9{Iofd*F(9N~`@)bS5j}%#XA3@Xey-OXr1DJPi4#y}joGyHD<4 ztm#|GUHrB7zA2266a;oSIMc60yU z_b4;$1rJ*?z0*ok_@apj9KRDVoF*TEZpa!>DTDgmOe!$PPnGjl*|V&h0}AD0X_9{u z?2GigVSn` zv5lU$#+L->(g(+95<>99;L{MZ^W#?@{6aS4yjSQiD=8M>)i2g_Att?+(j`*P_YGLt z{qH}JbJIW~sh`XAOBYmL#aR~fNl9j3d+ke=TSD&{qI7llk$T%G`05xC|9jRhn~Xt}NLp-Vn+ zW+4&A_AINuNNkWR2Cn823C=Uu7qy;e*T{;^S@B|25ZA zKtZu9*IKSBcdWG=elWdNz&}nOY zM6rv$)`?N{WW|szMY}S!v7-`PQCsnk2zV|PK1F-9Sj zi4^DNy^+lZ{{R_h>%%@l!!Po|TUWf%H9VO(o3hv4LHAL)RzE#3ioV3rpzOm&Tl{|2 z?Gw1tuL|B|gX%)V?PEN8L`%84dFO6>wtcMe#GRv?B@f#~ zt#s?I(xo~OEy^4|-}(3X-2*1THIncz#8eCsnSez6dJUUXOnl<^9XCWWuA5dm*&9=? zOchAwARW?40r<6rmpbhirB#_F*`Ll*DeIf#;-miXyO6=E*ykxn+Ry7v2wsaROuf5} zQbBmY{Y;a;Z1^9QpD8pBkf0`>zd0SAGQ?4x+ zFXjgeHh-$ zqovYziR9g#hE$s?oPrJ_^dUFOX`?Xx6gX=TmKvj~8(=t#2%~ z0WD0)mLdiT0oZ?Q7M!2F|8g~6nWhQsY4$8&5lXbG0WgUZ$#rFHl(g80o-|JvQ&^GV z)@u}Pi6IKy3pY*vUGTV&rQHr2apz?(I=pIHV5`MaPi z$YCt@?_cUWY|1IF)=~o&Y7R-$nu^bekys3-N*q8nYZIM1x1x9w8YTG!_my)gg|LjW z^RzlPr2~_8tptUZbxFKqw3uKttH0-lkx}+oD{xgW_;V1^aI6f-vu31RBSgzKyLiQ` zpg9vvU;yz;;jhfXg4ga<$H%nkBB6gWOF)gsAfP0AXXr{8(U{Hn*g4=at^C1GnxG(y zdssyqix?t{H}NjtqbD;((!>@BsiJp7EU3=aHikIM^eW9C_T!n_V+zvC1rm~|#N7{J z@vrX+d&*(BQ}46c(7Zs_m@Ja1`aDG(NsOH;N)78jDf|^k>0S<%JS+l{Gfn~ReNRvE zU*w@U!(d4QY(0RYtcvoew?9dqggBoFb7!>+X3alm8HaQ*6S_ortLDnHu3Vm~ng=@K zd}J_+`X)o{WTYn5X}W2q*Ar4_l+@78Uyp52$C_0U#IP-ev1`&?i9Dk;kCs_&j?qU# zKsEiO#$u7$5%@A3@#zdW=kk@;vfeG+#_E@Xlho&IaB+!j@VO-IHn-G}RW zoI&4kRS+tO)3p>^&H&iIceo``O`T4!#5@%rkTvY088{giei%ZS<9Bx?ThB^q_ZGg` ze%p&{@Yc^~WI%l{Im8>KR4*5qym_CG!P8!t4^>RQ18<5=#=Z$ZprWQzV@o@xY%JpY zHpY@B{$Y4i#nh?9=HljlCd1OtCs%4!u2t)gDz?zviM`Uq?qmSF9t=UyE9lhWq8xCs z5L7lwmFlw#qKtepLXYUVq#?bXJr9zDBQXQJpGhP*uX zStusD9z!g`XFzy?Kmuz}Bor=@x{ksV*eJvC)4P}XE(gKLVV&J=xQpHgP1k3zNXw{w=Q$%T|?Pl!o?n(WPP1IHMt@; z1+=A!+r+OOkQbi`1gh5-Z7;K|bdqKWeybrJEM&mxZL~KPT`$DFHXnUfiiDz_&m=5I`9e5a7R zVl0>SeH%#2S;I}Rs4_ScbcXVw>vSFr5>;W({bAZ~tax#l(U~oX(DDt78Jsu09hwux z7{hK18xS^M6=T=%n8i{UyD^W;8Dtb<2ANbaG^x{9*_|$7oYw&adficz+X!le@U~J| zyO1(%RaLx1*{(q?i=CnFrK7sM2Z}$DyenxwPv7;tGLo32Q2`v&In9$suxhO&J2ZR9+1Cly8AFRno(*Qbsx} zrdPmFMBC;h{&kmGjriSv6aiwJ|88_a?~!j(E~QQK=g0ew??O>1oP$fH2)k$Rjo!PI zcLR5L-iCaKcm(_9Q0sLMnYGk*1Uf-Y5VMl3h@tf|?>|%V@aaeAFe0h{+p`yYAyA~z zI;KVDhi^fSI2f|7Fs(Cvww=<8B#krfxsaFxu5so47dy>JD`Tm^k^CSnNRDZP#i9&l zdL$A!7oJ!Bd?H(Z8+poW>aj&+s>B{#u6=up(EvdxgJy!u0dPh~cI81kD!0AdH#69G{sYkIyeP14LAR`spT&)~X(00y z#24pVj>6g^p12*4m7^W6P_IRsmCu*QcKcPkVxu&!vSkDD)Z)D`{wd2y=qQH(b8lt& z3wHenVCGnJ(w9N}n17H#*R zK1{Fn?hdhoj5~7Ij2PSc$^ejBy6N%8bukoem-xYl}i0pK&O+%?XiAo9UKgvUem=v9Ty^H_6cK$rv@@TknK(GHv)WV$cvjKvs?oijIBIRz3Z^8T={IIW z2aVaB%r_7L+SbbP2sclO03e(25t0y3GkJKZtX<@l0B_poayu6JN$!5_Q;d@yp@@k|Z ze~uMMcf;MSI8#0INw$R7Q@T+F;khtP5#|+h)fezF3ph>5$h~;{A#{85K3@-q&>?q< zq}9-KEoDpI<5)s#NcJcA7}<<3<#?`E54JCayU0sLtBl=ZveM*SFPQ7|`m=hKU^#{l z^zDaGdaAOzR&>$aI`{Iw4|screx{`P2W3NBDhpL8L+329CV@~QP6g7NA?-V%lfZLt z^b2@@*OrPFKHA@z7zzdwwo_%RQY$Rcf4}{nXB0d zf}&`wj+vj3aTia}yAloMig+4p@KL=+rx`KgyX$Jb)@Ch1@L1ud*1rw>uIV^>qq(P2 zs!Lsv2RJnVkTiSz;Fs<6TQhO(#e6)&GUgVsD~uk&vb<$~bS{;qJce*86i(^am9#9YncTX#EE`fg zBZHxqU)F;cms|vj*s9=9?`(^yP)Vbunc#{54&|_=7f5zh&i(Pyx?+(;{zxhLv5lL1 za!>i+hJ@s18w#2Ko4SFb0J%x5K*#y-fbAsE-BXB+GjVQ*r*6 z(_&TIu+lGK^`lx9+V6ymk_$boZM!v>ogy5Ab~Ca&2O+~0Rjiogy@;p;>P(oMY7-@% z8(1?@Nj*`w$xzg@OTrF~kKO&pf&kIY`qbiQ>HOvb-jdw=v2~IM*b6yOCKz~OBWEb7NPJVTb3L1JLVNnzDo|yhEdz;kI)Z3=1&qhKUbqtRdrNNjZ zlp<4GU4nuHjvE7QT!Jp|*qdWxZmB#z;*V;qGpW@rdJ1*UPyu$i?#{%4V+cN}_kx0F znD?_dsmEufFZ2A4JPmw0$MZy{KdeFe3m+nORT}HKlB4>W~0HjB!F)#EqUE7$^Z#_FPq5l z5i$9W3)ObAI98#dluSi~Y|~(|6CyvTsIQovqaok)5G|3P9*N1R!)}O2rdEP7UvB7Z zkHzLN)pBXD{iCXp!rew9Krtk)_;pL>@1OZGTs5xU!PInHBa`>zmG_4uBd1<$3v_1U z4d$SQa*R0ZAzWpArP0YdCrG}p2gu%m1>${ZF7(g$WViK~`;Z4xeMg~YlXlde4e+6I z=DJK1Yz4t2PYJvynQ8PDqv%r_6WwFA8%#2#9NS}28d#*tyGT2I@C_e{*=a1QCf47y z?#;_Siiy)Oxyw)`#4C+o6Zz5a5FC0vpBRDOEup|7Ge|Ho_TQKeWIOgiqNvSWsYwWo zdqvR>??jB1Ka*V20JUO*DTA|?^)95t>1M7^GACe#w&VhA-1FqJRXbUA_q1?11Qd=M zKd@G0BTBQ~*QB}XeL=MuJgA1OUicA5^Hrl2$a5R$@RLg32UO%Ad@M1PGti1QL6tQW zy}RN~t%oA1DHN|fl_31e2g5Y#(m{&MZ9%|)E}#JGR*LhKU+Sl&=eiAQ0I%hzcXbF^h^v)|DP$qm}XG<>~%Dn=wO5`L3{J9?QOz#t9mf3 zvV_s??QNcRuRyK=LU;eY%>04tgp3Y1)0wfQFdIUERz#Ls;B>pmV zE>JFd{*`sG{z*9f;{&7P6C(q_`iAu}qWWtjEogt)0 zavhmD8A*TsB7(RhuBm7G_s65do7Gc}Bi<$Ao4wx@?(c@NAdjyHMam} z_GNf0awOoFR0aj`wz#W%As~xQeUU{V3on_uMa%FD^T=o-YbK zlo9>;)4#MZ45<&p*akxUgZnK4!}y8Zr`=mrg1rYk1^xPunCtWVX$O`&{hYt zHVt=Y_$$^y=ky^rLF>ohe^Pgy5BkNGUbqcyBLI-ur%^jLIAQqkVDRmC@`3*kD?|Ck zJ^mg2hn0y>u8*F;v&`Us{KjFKTUi|bqzyn@<>ctH@(v@d@R*Ee63km3lN-eL73c)os2|>p8P%2 zL7N)-I0p3ZLU@!zMR;himi5&-w1x3l|Boz#{I?19yW7nwOA8>cka!OF{`f){fxIvA z`Co=X(8l*KcHb08i=&4ejIF{)i3-Qpq|SY zxR(UTJNOY01NS#@4~fG!c0)Ib!zb{MWf<8^Obor^Pv9p#@(=vaAtkW?mH=6p)T0a8 zm!QhC;5B=50Z2_0H!_eNxjFlWRTQ!am~9pqi75;_(o#cX%JD>T%7=O}OjQTUFpiDG z3K|_xE4Db+HfIfnMA6H%MAYF)Zf%|^GH)&9_{qFR{zJ3EP}sX94tQQtIMIA3Vvx)< z_!7l@SJ~w!8nSlzPKu=cBv>o8i_2S-G++#K3aH~SeqFxD5fVq?bVlp?K-C?u)Bhz z>A27|F^>$WO0m*67e8%K%+#J1eSdYV)h2c!jbeJ}zlnP2TBS*?qq@MYvFp{AisBh&M2^_xO;88Ksb^%^`NkycIMy(6= zLJY)PW8^+ z)t0ldFgP-LGzBe)nH~7hAbI>x#eVZgttLc4{kA0=?cfrIB~6 z4S4#QPO23@)BS=eaUcMIv*tJwg=t^sL8dt5arqwacuVqi^wSV|*`}zzCj`5v+gX-q zj}o+U8>wMQj6RP}?;8l=k%XdgzN8B=`jYnwqJ)!FGk5(Wi*Od94j6+(y+&%oNLY82 ziHe$f)^%MAWbvfJIRp%4`1(&!hmRW)1Di4&fO-R^p2j^>Bc6uwI2f7^6c^Z8)sd); z<0E5KOmi=!4>vdacvQuz@w>HI=Os_=4ilzH z(xgre8Td6BHP18+=W^@+DlStkNjKRcGGeD^0>S2!7!lyc#FVmdIyAF_rPac&BnS;5(K)^BiXEZi9oQPOVXG(;kM24-JBxXzIV zGoc3Dj_p`GxX$RF`}18Pt?SrPM@FbH?)NN8L8urlQyEfLEXcYsfT)cW6{P-V45;YZ z^sr}xnb!7fl0Jy~uRjWB-q+()7O2b^f+O~4_Xi-SQ{lfm1hk=RhR}P&v z;G)X9GOz9KRBq!Ytnh1*r-vib`z;;wJjh@W%n4>%IpWDq^b-Ym=Iov3;pU9**5dFX zyJG1n$5nBo@rxd76m$RU<}>`^a>UpHsXf!K8;E3r*}2pjtvF<-0Fx%_3dDX{9zTbd zi5WJ|vD5)Ytc{@1zdrWW&f+;Zp_`}vA_DyxBdgmf{e~y<_5q8Tlx1V7rU#Lf90mpNOkh?$ zZLZi^J?`79tapriad3TFXmqLVG+RlJAgZ0V<)sN8syFI`DZRO@4o*RCqN8UMcCb0N zdP3Ggerr-JG+hx+(q$zHe|+d<<2h7SGhKNTV96}i6>T94+|pn{OJT?4Ldj&XybI4@ zpaA`pjxA~KdADw(8>_wR8b9hYHgafrFNm^4o!qt2^QEEB0|Flk=G&yyvmepAfpO>) z|0~X!EPG+Da&stm7*V@l&44lVd~7vY+zaBaS?ys=GB~e5+SFYFx5}|L&deR?hHrb6}2uqT^YmRyiLexZ5H+h9=vH zWu|70eH#eWz6KO_X89JRN&nX(WW7A66;qCsyJ z;8!C*RDAe8*1a)lqWoy(Y~Q6C<*dLIF)k+j^UCqB&Zqb1Pn2vMUGR?=y*6uf&0C*K zSS{jyZXY%QGCOt4so2oCvuuyEZo6nr=ZJ)eD6T`Z)?qZ_mZ8qOzU?gdvTtF&^&>A$ z^{ZTSt^Xsx?+jAgA}Y)TFo?%ek2EZ@$2`_M?3%c(8U2pWdQC5v?UeDujsDBfDt$y| zYk#$kXkR*AKY^!M{s9r^qjF=Qo%dgIRih_U`l%dtLSE9_>s2vWm=wpZ{#(mgC^87| z%x`N!+$ykG)-h;|#uC&Zt~Q+||L|6SR3P|XCkBUIi2`a=$S@{_u8P>gp9ZYuz?!1h znx_^;Y0TFZ@0M%R?o>2J{E3sO}@Bs=%Th8^z5(0szkF~E%W(@v|oBEjLz zNzF97@WaZIOh4Ks{)w_PVF=70f*is^u8>Wnc@81o+HNzher1_#13NktNMv8s+K!#; zs`fompB&O78|-&?zC?@f=i<%N)pqiD2|IiBq28Ug(|?=9*j)K$H9EU<_f6QNcr;*g zEt&MjS7E~xGmErj)N2r=n?0^a%Bzcs~4kO^`ts2b6Ep-qq zd(izyN1&%SjTO1R3L<2LJ^ZL&Q)*m_LpV+tt69g(ZHM&WeGs3o$T9gTp>g{m4lF6i z&qF+iW=wH1vGCM!n>cENjFE{Cn~WO;A{E{+nXr4FzjW_EuZ-b+1$dY5HDw-U zi7RY_y>%G^1R|HC<9a04J-u#Au1t2F)f8@Ge4lh-&sDU-M>7#_yO|hxo~q=hy_dee zX5W#84_oN&H=(RgUcW*sA|wv2Imt71S~i>Az;fixE+Kuag8_zFkC=@sVO$Em8H8xx zXoBE>?{K-Jqdx&FBLQjCT6&afUE#)Z7e1WQK7Z%;4OdlFu!Fqz5k=ZM( zB^$rfPuv0fEtH2O_?ca&lpxCv^Vjn0t=n4fMc=18M`nfxs5MRkbGsWv{DHV0Jz!nf z8n3AdnhHGyPwVd)ibX8gETX!Y?+Z#j?PW2&UGDMx0XBt7)3CIcTMQoAWf4J)&`Yt1{ImT*|1@ZEDRHegl0?N@w(>_%a6 z{ER^5(IKE13;vvY<6A{x$+6ag{T-?{YQ0f`X7f zrsVo$3I%k^pbu6{xd62Gy)J)&4&D%R^l0AiPd;JWQ9Vyw>@Sdoo|4*mBlzjfrlnIc zrN)@&mY`8G<){6`skPaX)+gf$5dLo}H-U^(tr3VFogWxk+X zT4ld`k{E;;;@kB$^zt>Tah@gj?y8F;vCP-2-SgdYx>cEj6RFACWk!()>Gcb;94x+& zh!!_ykE*P~A-c?*yT{J%Dz6Uji#bmIK2ts!J(CA;<5iQeLhMBYa$CmliVojl$Me_& zW4U=j3b-!IShEeSM58^iT?BVORMK5jwUEDx`wA4?Bf%^jn0M$%u6Rti;CDRhkD0fM z?Ci84NQ#f+?AB0Jjw)r>`Lls-D$f`r2RHbslPc7{VBwdb3j_-xWrOTTT z6!UC$pxU?MlPRuFOIUcqeGzr|t-u8??22RR+LUQ+SjJmSjC!L{5?>>4qNF`Evf#;( zE%*xjm0h3j#6w5ZJlP%j&GehDrGp#Be`$+TYnMOsh?rj8=8&L;c}|;DASYNC&xWf< z)Lyi$RLTUJ@GRQBl|0`!)HCT!GBD6NS-+VGEZ^?okR>GXF4_G)|EQ{BUuB8*&2%LD zbDW5xI`Zu50eR@X`hRGr#L5U6%%}BZf}$#LEurCZi&lq!jx0J4bihJXn{#JP@i#z_u#g$inje-&k6 zu-z0eK^XBUFUVPB57+aulNY3|ETegj3$*}U=!pC!@n}1$|t6m%E_C72P{rGtjbo- z@LaC&c--MA7;lE7LSiMFcu`oHnMJ>$DC$uKtyJ8MS7JFaI4FWYHd3wT$|p@m1i}CN zeDE+g$oKf<2q{%@2F2gFWM05Oz-Vvf*4z_dSq}vX*nMW1jwXnwI0$SVK zNxn4%QFZ%Wm>TH31cx-eAK@mcWy$5KorNuncC@P71-?G9e0L5@;>b+Vv*sEGSW%1VbjXuqB9J~wi!qpe z&r}z7kn!!F7h~X!quCe0S`&j_9i!}&1%<6*HW-9`oU@K2sc`QUv1Hj}d;Q8YB5(%_ z;u{$_dm8ZLuY+zz&>qa6P5QSimS#2}39 zS2EcjNnVsj=zV%=abS>=p}z)9fcczhn;sbaZdsey86|916|mNg*Iun)E)=6{^Bw@* z!a-iCuK^2H#d2ugBnup!GaRPZL3)3lM2xwtVNElTsS#yAVj(RfCX6F3fUkW1V03*X z=xs7#3R$7AV^Zq*0^QPAl0KAsz(hC@I6JeBVolssH+qb{Ostym1GP;OOrjn^Nu2DD zgso&xTEJ1?$)-uBD*BEVBrF9YaH5Aagix@gzbxpRT&{VqK{clJTf@UzSt&I@P^AU% z7=Xc^9(m7hT=>W%O=eb)ur=nmi-o*fD^eUppFP&2Hgx0_B8SFcf3S=NG9a~;p)?MX zTfC_d0TRpbRh;1T6AOAl{!K=8?@dQQKGvb4==l|3 z7D?jCBx`E7f}8Kn*&}2+m>u%s)qIvkmaKDTT^Z=$3k9#RjlM5&7umg}o*sn7Bvn_& zrD3frSPTs|G1CWE$V8(yv7&vR|C7Atg|A0ji}u+-<8PVQE5CMgf0ETBmQ$F*%31`V zNU9CI2*?=G_VrNhBwBz$XaC7OAz4sD;rvs-VLTon0;~9CeY(;XHVx2 z-%aE}FMF{~B`*HnHHY}R89c>M%zZOoJw{!|#9%0LJ$msY!y403ymO*Hn-czh7RyzN zZ)g9J6hj-pCz;~i8a2T?H`rG=h~fwxikO=#NluSlc%M#)j&s$Y8zt5*eTBWWLXDZ@ zs;ioAcEa-}Z}Vm*S3I$fT|>rg3ow?pmSb)Im0Y#meA(rPE|1U{l8{`NtSyik$eA{z zF;1W|;2lMVt5nKD&KN`nxt3mclZCNtiJ=IEN5{F^ABogn4^T?_IG>$-#U>h724PA9 z&PFM9Ao*6VeYX5(Cm9z=1^uzbk$(kjIgEX8M;ks*UCX|4;KV zt=6{kEQ+etSjwWe!Y&PgE@5X{{hKI4mz_zu@mM%(LQYgHbP~2IJ?&_3o*DssFx2-D zh^rgS9lkWL$bvuU!1~SmGvvUKOh&e)b;4`!U<9unR#U-49b}67Dq6qe#1sD8PRCBx zQRJ|XwNYrd6h)gzn(dD523YT-C7!paaZ-y;*o-9hp0c5Rr{e!LDd?& zwk0$V^Ob~BO?T&uy}xCAec_KS>nO;Z7a#-}hOEkDj)i`)tR4PDvO7e50)&n4{L9(LGdtk3Ah z$VH?guMOf^K8@v+J#zg`hTGBKO*L8xavM!+N z+i#fLnwC(ao7VF(MJw(r>TG+4b5{FTGHqFpN%j~tLRjyPph6^xw^`3%7TojieRs9D z*iC@UbW$p*MkJ_VyU#}@d(7K>c6$ff%G^&k+?oXQ1SCyFgA21KFRxf90|;soP1*Xm z540@TZ&1GU!qD9wCk;fG;>PE1OjQFGwt(ydoQ8T)}KAR(_l>w4z+<3Ml6V) z0z7#ism#DrEeWlV*z=b6rioCWHiIh24(LDFKRQBJYOPMw<4{t(L=E2J*h}j(<1(W1 z8|(0f=mr-?Z}g*V$JVHyq1Gv6Y(6z_KHYZRImgrt$vC0G7 zFf84Y+SFPN4v<+mrJM6T<(INefbx$0jt(JSwRBr}d~9B&@bLekQiG7sFi^vmSGKF*Z+H7#vKF+7|(EqJU@!!#o^F#OK(06kNyAv1Z znCmVXk9&Z71X*I~4a{^8K=~x=7qp?ovz6Zw;Yv%?nmUF>mgVwAO3{Fi!4HS!Jmk>% zlojF<#&^l4rl2cNmFHNIsJ;?>2HpgMj_~REfC_84K$ZK{AKk$NYPF{uCR~lj3%7S$ zL>APc4h`!c^2{?6tpD)qh(GqSi*(C`bs=Y2M6;xFmY-PU-2(cPZH2|eby-C^VWHw4 zwCo-H_1x6qLVsw#Lz1L}dtehSCXp~5@N#~nBvNnDs~?4qjyHngLYC67IlX)ExX}1T zr69>2iz;dRIDUYwv-{XY7~%opeaj(Xw_cN|+A*AcX#RN_0dddZ{RRnb(Mv7Nf>J;YalOhzUL0SDk%#2*%l(dMjC_G zoWfgBAa3?2M5In7t?1X#WLCAVqbYDNug6C{obPFnY*)^ttgh2DHfR)%Ju*b?24{Pv z9^;4E7pw476ay+CDp+0vGu0O6b8GJh6mR_s2IL0^q?xS0i18TnY44AYphNtu!lE4UWY4%b?2=lCGwn@a4rv?=EPFBd?Q5Q*B7TH8or@n-f_&OLMzp1$^`%`-mpnV zATq=t_tot#6yFO={Sy=%TV4{r{-M^FIFcfgg#YGd$=+7a>KygSP}{PToPhc?jC&zr zpnj~^sij{<5CkufOm178iF?Gy+p9GPyOYtb z{Yju^-pOvMkhnJPrrcv<&NnQOm(P7vM?I$qy-5p*DK$Ma;M(L&-D}qp|JV`XqGX?$ z02b9XTLnsdFV>-gnslMFX zM>hwDc6^QY4rkB`O zIvX?hhv|JK0w>+uJYdJjvD<*{vTM89&*f~$I5(JXx{ zJVoH{gv~lfH#{cSfF3cgnkEOOUA!LARGqAij63^j^XaUDyDf{wg2u8AhEW zx_{R#A(Mi~#@PokQXt4L<(ayPdkH+J5=}PA5p<6nV#ZhG@0DUmNT1wt%qVwr%d`qC zWJi3Ba5SxyH4_|$BORh^CbUKpG#fVoF#{lH>jkBW3OJ-*P_|KG2k z84-(IW%4g7aVX7v_|74+t9$jgnRxaA_+ip>#i&IJMbvCFUEGLOYE0_+lfBO(^o_o? z+;VZbP*HWV?+7qm5t_B5KiqmwN#(*|{*1@p~Ra28n zGJKf8T(U|;uQt%Wu4{B4;R8}V+$q&lQfYfHvShpp`5=<&8RS`2#^|8#ahrW*d^Cc! zxN)n{JHAz-6$u(r;52`RLWVvz0Zbq6V15zIN19jGGGUUSJtN&kmT=@ZDNhUWymh8} zt$fF92?oMtjDlt)vRh*mU~_(ZK5JCk4Pr{WFXiTkGdIUzVbJ9GdZ-zQ`-bHy8###g zWoX8qCC(2}X_nIaSVy|-fCU7dzA#q2?D0u0Mg*4_Lui7V?Qi0l96S`3@DkGjnh?V%h|3L9gAH&p#@c30{~TUgI) z3j}O_v~0jXiB6pso4!?Se2^y!4sJ!MG2vHeF}3hg6hEwP2|muOOCH0?zj)aFPT61p zDMe}@gMW9KV95x@b{8*~88_HPOPL{V+*_axRI?J*(PoK*BQqG7v1S;=bE|~ zhX8>D*oP45p^-Ck3TV5HMg3L2Iu>p1h5o=;_1TgcXvGTv+`xO=>H#@w!nVOxr zI6fXWI5`^ua%e^?F$Hp4@7M%b^0yICmCGaDbL|H(W)|DyPp&7B2WGLh+Wv{R^sCzH zp3?FMsO#%r0XjPZd%3e~0Oj%VghyXw}&0FLjiTK4}0Q}j;2H=MN^gaK%{>~g* zIoF*rK|>=DGi4^n*Ce;}ORfe+$RnyAJGi-?0by`#__8)T*#iUm%yiFK=SoWtg#Afw z#}v?zhSdY3yW2f(S8vg=pK+t6qOp7LL5krnq{>o>?-=EvaiQq{Ee1H^WFT?4f0{r&lzhUn5sAs`3Ox=!9dHicV z{%b3=fCmTntFZVj_4`}M=Gf$X_udNVX3o3S51t1Clq$M^F`fi$Cl38EEHYqhbx7NLzYX1A@-#tv=n&f#@W799MhM;{gc+AH4E-HUO1tw)6b|#3I!XcsFRBI^6$Sv^M3~4 z{|2jXfo*;4kKq1}^B;kI?PkA%dsTAILw#d}4{0=j} z6Tj_Yzf^t2YG3H~f#Wm+aC_Eou|biStwZzoa(%m&0~YV-AfX^X`T*o`U%3!hIC@Zd zg~jQa)4BKGFZTBUxtsV{cBNH60(Jh&x^bWEUy~oL_1C$1S-ttEy6RsmfjnBj1>l)# zzo7k`-x8tF(zb_(R=`dllRz|$-_QY<4WG~frh32VAg|ZA8htX(UzKQfgFg(8f!f)9 zqtB^$S~O>JOnRnszh(Wy9KZ5`<5~T;(xt!Y^yr_x#lPIzbapEH1_ZGMF(dn@_M$ny##4Hr}Pjx{v?r(Pf%JK6fg0M?M!@FOuiS-nE;%!SFzjN-xY$+^RVS<08~A3?9EY zfuJ~CO^`sqsvK>f)-yLTs|LbVncj^=i~J7VW*qpxWnzLtId_{iWZ+tDgs6nPfsoFy ztL7=O`0oQ@+`mA3CmxplMQ+*p9U6p`N~V-eU{Zi%w!k!>)k;%(Vsi?mw6C~D&i(8( z3IMONmtvJ^Iml{IsM=t!$?Df`@HwyjNx-#L4lHq7H+WTC(uXF!7ISp~8_PV{#QLjw zeR?yrRzk$U(r$^Ed$-_QqpV&*&XmHukO^B}3{2x17{k=H_h(7RgQ{Kd120wpC-(Ii zXgy0F`T8?NAU=Y1!uWgicc}~w7f*w)193?Cj_DqH#UaWp`BZNw7{!%m zNXZvlJOA?I7-C!)zV@k3I8~x|Icq8s+eCRn zEckHLK1QJ7%f98mvvs{`r+V1ww`NMVM5)m%eRf{A^Zjw0K$fwWa~Mtee&gU16H`bxpKFzAaTsLPWTL?(AvvyCJtRy>JtBhfo?Tg>sX!SPSI zYJHV5vDpIjiVn)cmU*}iI@Y|>4{R+V0c-#L_r0{y5#?gvY`}Zbm6h@j#CmPOb!d#K z3@v=5`{f|p>7~d%wsUAd#Pl>i-%$h%az=z!_cX_VFdHUq0IT?I$HB7->XAV%aXXSj zZd+=wny47+-nfk=tex_m9{=opk-#@D#@kJDhDgF-KDSq*DsQpprm!%z5|-o z+wmk`DpV?YyI{?D_Eu^FylF00jve?5jlpi6b=;B`)i^%|>P7u>44KEry%j>yH1^Ib zV4fDVc8DVd2W0R~-r@sm=`)I&3MzyT+~O<4{$v!zTe^tUS>z;&6H$D^Th(0tFvXnc zi+x$$w3+G3<5;tj85u_gXbkM=4t$Rwff9yx-hN(-EVJ!hbVnjS z>6FO>04UY6!xQpbjc1V&y2$hc!%-7+m)Q48qz3cz#H(|cgK$x=5+UqtK+fLh&^G*_ z6#L)51e!YvIj#|aRHXZ&IUpox{icdCx zXR=u$2e#dLUbu*1T)E=?yZ6lXc?2|O8pMhaFSi?^&eT;vl7+J5eUJ#*Ev!@>7Ud~1 zW_&CtBd=TPcON7O*!hyHxoAGuG2V3wezxh)>oPsSxrViTR9@N>9D9B1%w5+-7-qG^Dq@OCM=jP;6U~VLV>gy*&VXBb*==#-=8p48*Rvv(dr| zgx-QFFX@Y+Nlc!bK-v=K`U)qRKaUxJO@`N(=)2(@=L8&oXhJVSu_AQb_dN)v3R>I> zVmR#kdf2>?rYc6}B4`N}@72dVhSRywbf|UQO*@^w2%MZtE%+t{ONpkG>Rx%LJb{n{ z?X|DXq`&$rEt?C#lw_7yv1jWFz)AKX6eAudA2JzM2{M;PSCrtpB8%=g6h*;%*&$p7 znmgY~kmaep@MhQ>uq#Y*6nla+4BN=?p+@b%o-EfSFe_Za4(qXyk}<%ivjd4izg8;5 z5QQ(6IPT$^nnBSbxkn02=l<4TD61(!v00~cxw%`ONDRh&5)oHH2g3xjiBJ}?{OYqH{2gpPF3{f5 zVAOyUw`XqQr?hXB9&;9Yl(`Y~tEJwiKg=app}8Yvk?iBCbrFf7q%VhiR7$yuP)=~4 z6eBG)`xHu~YB7^}8XWf@1!t1PW0cn@epmPlX#XniTqR8^C>^CY#LUEM=Dm?0WYYVt zW9yn9?qZH3Wa{p~9#zsAMEeKuPADvM&?3^TljYjqOy{md6)4=bZdX;h#j7R`8EHK9 zcp-1B)7o-6TYIXoh6{gK-6~+%uTse{esGIAdLC#`N7WhVtD;U241>3i)$kctk?K7ZUoGer7vJN9uJo^)ZW{M`o$rl1)3S4K?>(Kd?;0ajY z$1o#j&${O3X^A_gBUSD|*aCYTLF2&sV*@yAE(sIor^~%2X=EkGE;p4{sS0m67v#&R z&#Q%${6wduPMCz}aoM_BFnpW{Gf?q1zo{awP!az5UL5*iQmw&`FqG8~MXOi&^|v!n zx+AT@;e)a~E97i~7rk8Q@+AjraSw&8cZU4d`@= zEM*MI9LDe&4G>uHRCJZ(K>7!~Wtz|#Oqaq{jiJ7JsN8qUbGqP(Pob-L#%w+?bDBcd zmDdu!WqS*J#KO=;nB@h}@FL6#RcqFJx&!#AWWd%GvSP^+edBnnrHtI9UWzUmD*-*F z*&Kd0JK9-qtcA|^5buIhAeghaHt{G0JkQoL^x_YnAnRqMYJzb&~Xm*rz7Ta@_6#QXVp*H;nhx2DrQ&>GsS zdF#Pyv|ai=eTVhP7Aw!CSx|} z6&=|2ot)su2WgQXq@Pp7#Boyp+{?$lCPCd9$hj{cAMv)~psUahi)&1si{f-ckwFeq zmo-N2bg?{&(+^bOa>R{a^uaD#+UAhw-5AqW?tS{ah1c9_=>l`qRj5Q^o9(x_lKv5E zXxOP0I1tX#Y-LPla2cPrc%y=Y&OkepSW#3m1(0Pese>Cy&}VDsLYPMZAC{;;I%4qO zb!T`ug8TQu4W&N~QC@NmEun!a80SrYlbWMP7pcl&nFG)MR(dMi*66K=5o!JA-f_vv z08uj)J#t`ptf6EbtZqrE%6Y!>8^KbVSitPvtTT@^%F(nX-@aF52XpRBv_|slvUb)B z<&Jo@lyufSj3^rlIij9CCV@t$J1} zVpguz(%DEC(Ak{aJ&%1RO3Vl7@c9zADu@72jqIF5&8WlhxCnXTdN9lXJ*HRbYzUjS zl^Q+*yeu&<6-!{@8tNBb=6^ z{5voEL>}WhUCKbo#g>lfe^1{=Z9(@JQaRB4C8Npu6chspG(dvxk8C729^nmK1kzP` z$2Y)DJ$t%1-8l3&-QqDJ5qEuJ&lM6@)b>zu#g(^zQJ+2qYz(8(4AwhL@p1f}6L?2t zx(OA?0)3x;v$l$usj*FPYaG{XABOKoPx7#HBgT4Ehs<|NIv8E7TeV;9?#JtA4fVcV z6M*8;F7wkutgjGH+^o<9;nc;o$_xq;)n9zIBq`P=yu_agR3u%jWM(3$6+gpX;J-T4 zUj0pi*sOmem72;|40m8*m+ZXn84tlI$}h&1re&bw9{PunvHO)|_AdqZhDF01z)P#F zrRTc^J7%TOL}zZHEiqG)nj@+D*IWjjhhd*}rUf<(&rx-9vTV&La_VNQbY0HS^Ty5c zP_}eu-t8cz3tBTa;$Nm&tud_-Dc4gP{ScPpipLSWg0ICng8@Cb1n&$&kEBA8ux%`T zt4_XLj8(`*Zw?phr+pa*!xZ%dbuPT&G7&BK@}HP~Z@?>+0Zv6lZ{}ZL12<8alrhts zKbuq}Z4~bsjXJnAW&!0|r)jG_zNha;#`+tfms|`3j~cX1>!})QAqEX0jmY404ME`w zutwQap0y*$R1=ShO)*wl zLz#?>qd_->h4VN8oC~l~&TEqmvu?OUygGoW4W<%(Tl=qFmo(2fVa-rb;#(v_WBJ|7`@zYlsxuG zuXjnl@i#)%+g}(R+df1V|LHPeN&OXf+Vzeq8CC*cKi2uvYdT5qslHT-nXPhDv%o?YgA(7pyM7{PVkX)Rs%uFbO( z%B806SozFciXhm1GhZSeN*v=g!Hf3@WBP_0e*F?qb!MRuw)|(U(YumsQvPPTk=*(& zj~E*5I&H+I*68yrA#fHMvwu^m3kjt=(>+}7-)k&3=%SiO4Mr$Q0(#gtRI zOIKB>B7bENrj?-kC{jC2Nb*PD*t~I<%bLC&wTjZDvnC7p+h&}LBI&23jlQMK6PbJS zp$l*isC(gU_PJL`@a&FrgoD(Wfun)Iqp+nN!hG#RcZ3Xne*~({_9-u+qlrpZ z0VdTqsCS7T6%2!QMbQoGGzF*jC)bq%GSGSb3A_X#EA-G6g95) z3of%z}vGdVu#@M=YOZ^>TCK-&h#jzrri6wOT|S zIHKJWTFDKZhNSJ#shlIciF6SHe+_j~R>qYHCbZopCyq4t4L|%FDy`H@i-5}LlbsE0 zDdE0Jh?hatIk$3?dti0=+pc@ye~f3zOFgpy-{EusIhG4J5ry;u@S=H^=7YB{z{mYqz=Iy$zxc)6!f+<6YJ3$LT(#Tpx-Lo zyRlhx%`XjliM0#;txbEMVAZkjpih8i6!i4;T75+ziMB1LE##OTYKuJ$(WHJ*J_y1MhSj|3rkMabjxd^pt2I9FEcdkC|p9M{p$G3 z^o8{X+~!_DOUZwl`+gir=h3GW9Ar6#@Z)z9w>AgZfZETjq+%|aVc`d01v4xiI&ckh z?ABl+51sL}UAX^~dCGmbu4I>T z+C`~}JjPKw#v=M&c|FA_r&6Z5eVn+j9xN82tRwd*uGPFM>;c&w&DYfL46$gNjrziZ z4a$AQFW?y1!2t6TNnQ-fkd|alWnOPxX#m4dRrx%oJuwrbEtlXNY4(sCMkkrOe@op< zG-!GDQdRGbQdMl*y1tYi3VnXmV3C+&RWG5Xul`j8DPmg%$UQ8LwSRiZ=cDV)=i#B?-x1A%sLM4+vf4?`2cuEh$Dg9Y zUhK|5r6r1@ZHX5s2DctZ!|mn!tnwD_tG0QBzEpR&<;e%e*#gj&ZXyiTE20&|EL&~7 z>mR8fi+j#PeKud*5Qe=RJ%Q*^USy{8G)G?j&?QFy@h`7`m3G@k*Z7s0;*xZ14O`hZ z&vr)_ICkugYeS%P!ZwdW0&14t8or6*{9}1=yG{%Q;;K)5tE`>I-T<*a=q%!wT{4$LGo+c(0G9VXk?t8zd z({;UG4)!SGy0d3!Z^cz^xfda3!t;fRGVWcNNQX^Cy2!GYob;H`nD@9Mm>sjK2HN8J z>;ZW&=pQvzW7tu!_4&giZ=;L4x_1)U1%LStf~l)5Fwef5`#Sz1@K|SPJ-XHwe+Viaq+A~KvwZLDH5E<*Vf$$NlMIBw=bAdE<>%!4J%C5U=t@Y{Wcvg!K76EGa)zSHg zc2?Lu4aQcT9#n%?>|y}w5sLk|tT?6l3>y+zpv(iZlt4l?=}FE3ZX!zMQ&Zkaa{{k3ia zZH$(PBfSgTdY(Q4o@GF7|^ zn~KPlfC@~=Q7-{j6>JuxXuJNIo$|QR=^SI2H7AI!A1LAs4c8VaGz4ckOMo|E65zXPVS6(jU+aS+lwCjFM`7 zCW1qXsIw4H3sdf_Ol6o1%J*ZMxF37V6)7k+gt<~>DU(f=`^GrW$Y5>8PMloeI7T)# zlD_|ZS>;@Q7+-o&pSPtqL2%ELow>O3Zr+_by6!Z$mI%z3=WVPsJNDlh95#CE$Rzvv z6E#%7bUg8L>}M4K_I`$0-^FK=I*a7PMfKaGKtxQ+&mB6MhAqOrzFEY14?7T-m1IzD z*`CM57c=-i-9Vw~>}$yi;^6JHrD+`uGJ>xj`$Ps#&J=Aq<%Vh4D8BNqxtaHi*Qi+C z^K0`5cGHvZK!poIFNu83f!um#SZyO!7ety*Tbz~51qGDrdOcrpKI?~gXgzdj<(o`L_ZcZ5iUaoN0uAS0Zow zHrv#LYd88e`fFdmPFgMrszm8yxWUu_7*_Y@`*94bb_lTWUKi2-haqeYa z7+oA2Ds>rXqn0!;V--VX1QIVaIj88^e|6WfuiCSja6chI#laWJ#E>|fBz&fKgBB`- z4!;VJyhe1q-V)$mzf@~Q+#Jfh7+H!JAH7k<4DTDcQn^VStrLy6U8ccG`ogpkfnM;2 z!fEnas!c!MCTolF+LC>be;nI_YXH$3*?QlhM@q(}F5z^qTr#Rso@_G|8*`eAO3yoI}PtjVN(B?oUc-j#|jx>?NmE`CV`Wh4*8cpH?6YmGo9ln2NQukqUV#ju|A7 zmeNDsxgLce-Dh?WLyE$+BiSpi6GBI^Kkz4S%2;UT;Xz4-`&Hp~G2B3pI;qFt@ZZyofNpCp~{HEPr;dWlg*7h7NlFg+K zZ|{w?n&GszR8yWTb+?w(1>>44{7L2|JA|~)qLpjva~?*D^>Cbn$t!*Hj19O zBS?~=D~ueihg`51rom2AnCFh^_B*^Jf==7f<;=%5fzt8|0{Dq7pWWc8n}v2T^6jhW z1ZnbF1fGar)gxG**;IU`dZ8k|6?3Swxvml2hJ@M#ss*5-Cm0Km(h_Mw%JJ>e2d#Ym&^$Y_M49|7<{wE7uh*;>a%7OqGGnE$^=dCF* zdS2yIonx_yLvA7Z%#n8=%J-;X--tn!vaVYR%lX%lHDe$~GBI>x{jl$4J=^B+23B{8A0G7VgD zJeXjxo{>$HK(mefzLNs@s`BnM6T4AQ{a0a%0T%r`l3Naj81l6&RL#2t%6E3#Rd5V0 zIP}}_b)+};1Kl%=8xDj~4Evf)JVb!%NwfO=0w0BQzGav9N++R07`Tp>*XeJ0e)>Xlu8`l@B3zDbcUig7Q zrJ|Ze+D6xkWHs34YoJ(9_X(;CqXOH#_EUB-Zz&UJZ%6`qr*|_6Pkemrn_#*RZsVB~ z(8xTb&edFH-x^}`PA;$szKy@;Y)r+WpOqP?Z$exwt)U@}g`3>>27T(&m7Iy5(F9RZ zyGN&IP!QJ&jm|B~+tiudbbZ!thrnG}L<_N2A}#P_u)uS|<$%=>Kgp!CaL~hCD%#_8 z;vDzzO|}nz=Y{%lE7Gm8SON;~oUvr+Wml!gPkKR(d|lCFK0VY<+F(~r4wtYEb0yi9 zR@XPJj@;`pr?g2CqU;(Y963b+B|S?Y)@9FEy@!kT7hK&dz9H$<&pKkdq}t>cZXZ&{ zz=$fWt}sQDctxQI@2PN`1hHATD#qbfUgKcJ5*lUTUAZ#OPa#rPoA<4zVpKNTkjPP> z_jB?s3~B50#v9#hYgcDAuw;g$$3jzQlj@G%mm?aTZ3Mqm2B)NCK55AbXFe{@sTSXo zZn*I%tU{Js^XxftGVHUwd8bw4FCM|gu|U|O^|esgJt})PkImF4$D6xD5rSNYNbuVS zganq4Y`gA+j@Kn(S&dvh{l@M-^;JM>G$Z#=p)e#7;??$0?XLr+aJ6fZ#!G?~#A@bz zXwz=eTKSLV^HaA!@$f`Xz9btn1E8~AOL&QR<$k0We1#sZ=7w;Tl(icE+#c-m=;v>E zD+Gs>@-aOeKGY!6iY0LEu!-8x@S4^{pF_K3@- z5cNfS^H>M#!yhXL(6lKPpOXWpu_ew5ds*+pmq#hu6R$jN&#hXYUy+o~)jvhu^krHC z20??cA7f5Z7M|BK?J(@BFK=n@SQvD1P?%KDW)zjr)#06|16Dg4P$rBh_M)6Ewh;QU z&5N=ifyi_Is$bGU29*wa8irh73WS90ATCy{PiE%FUP|g9pDvQo9d~aB$IJ=1oYo@D zi83V2mA$LARE?$$>C}^zILbR#88d5M5QeUXT0OrVOh+#(PY&T(`I0e<11&0akH&a! zJeKSNo`(adUMGt8+xZY192!;phz-sqk!fekUV*IZq+xm|uNh_0~LQ5K}Gb zd&9guY{yhTj&>3Bf`Yb|PElc}s4GA0Xf}J^RSZ!VN>EpkHiwg#g%kb~uMveZR1rD) zixvI}6m3_6m43to45SuGH6+x^>N_0CD)E*fhgbI4Ypj%4gPjDLQGMP-UjaUu36byo zzO?hJpm-#G80#iQdNr(2uW)n(f@dQ?1xhCjf`SQYnP_FRb-~!JcLoZx2Hjd|dgz)y z=Zg}azedS2pnC=N*bIFc9LtN^s>bx`%2iyU^s_|OAgj#YkS}e`Wb@q()uz7HuAyX0 zD8!7q;IQ4~8CO-c&{_Sku$z{)Y2Zc(4QqF6lLS%xBR(`#^NVF3abGx}EqUQ#U0;(3 zrMM>o3x2l1(p|3>nvU*70^99?)f-UzN=48!im~ehDnZx#^i;5*_68p{Y%}j?lOUoNEbDGRMVcSfg)Kyfb_SSyE#X1fU!8HcbOy6)_56ss< zd(z&w`mCjOv&xt37%u5%0l^B2J4eH$6Bu#qt5vqvdE1vTD)O&j zx10*{$MsvO3TG*ni?T2qO{XnfT3yP{9~m#}2T>6}rkrnJVPMDKDvT{#@Z)98YQ&-8 z73NWhg7#C_0t%MBa0aDI#XmZ5{=}=wPCGoc?bQ^?{}H~FJa9El)?Pi?5 z-VI4Iq3?dPaJ*J##qW83EzZ$o9^27HQqz4OE;fDQGBoWeP50Squ~k_ zo+xVXO`bHubk|59J79DT1DUD^qFIV*K_>5i05X_V5i+)ys9-3AL(?1?WQWhkLrTL- zRS$wug}I^)m9Rs~u(qa-g3=_vS{!BXMvfNPnTn>K(fFgED14T+UK!sS`BGTFdHOSE zU=4^S9rCJ|&dF`ydS#sbekw0yCCkfMQJ;gHSY2exICk;V2}DsACQG~fY?>P?h7_4j|;Ryw(r**olOj1PjZoa zNgoLnzFo8VS$3>z*$(I!k;N&S@ZMrJMQN|Q}06aCKlgU_+)p&P2j2TFVYBCT09QDhYr zS`k^MF9CzY!}Xt2OgTLzD-7D8T9YBL^)?)DnA0+Tlz!oL!XR7CxV3cGIa(ePYU`zA zTgy>faOwC_Qj}@l+E;0}W%w91$ZRez8D=E$b?Poe%@28~r{?P!2KK_CAd$GeCbfgj zM=Q_vYSQ8D<@D=skeTeO45VV#!E(KEk%f3L{raIit)}p#IYEwA=&Ve{j8^6hm!~0~vvMbg_b!DZ zy@hgM5DH=>KnlR|Zgy@Edcnc)J$-UgV}?~y<=UxrkUwP(tBk)>DoaOv_~u{Q3Kebs z9IhNr?=40X{B`E;nZzHu@>>LUzZdcv#5c^pq~?CFXZW&B*DV=m2a&`IB;y z6p761qvh`Udwhy};zBZ-CkN*1d?DsCo~BdN)hym^skMz0kApR*ddf?(`LiS|D_-0K zlJDP61xfeRXozVL-*KNE_C%X@>(}m=lkzUSCNo9ri4`){q!mqw>_tdVv&k0|7ucf~ zUe5>z#P#uhg#}r$Ihn$pD1EUB*D67GLBOO>9&>$bnUp>^@jR!S>)36A#dCU=UNaYV zrrj26n{fF{F7OP|5joP|zK=ZQ>=ISilFhY~Mb*!qK^kt6v7DlyFrG4w5&4_m6S>TO zY6aBOCtv)-A8SX2a4w{xvIWeu>*Ak?Bs6bOc0Rt3-`k$H3^YTwi(3g;&wDdpUT4nB zAMb5`Y+>s}#I$~5;x}P7V;?Z(#p>VJh=lv*(3Gd@1*{%!zuvIhjC_${*mIZa;%-f{ zw!$pmh$${_9A4y8X={PaW+=!_q-&o#O89BO+SXR)y=sVOirMS$rgrv(i86}isDYv> z*kYWIyH*n5zZ!kARar1zI?Kh()4-`j)O1k8ASAnqsBT{_Qk#k*jB?I^wAvUwNPTW`bXw%r4C4>H_F9-a`FCvJmQ{ zN9H&;YxQ$*^Pz#UbJ88DTIq~+@Y6ivsA`3bG$a4QRE$T%$biidZNv+qKtcgeF}%H7 z<7WY*pK|+|b2I)&PWveB_*EKdqwks4)Lvmb#D}rDdZwoae!g|V_hP~EZ_CPw)#Geu zwZ?yus0XxOe4(Z!v3q1$N!6nM;ReIRii*<@l&3jjpJ+e(!GhN7ZM9q!bV$sTSyyr+ zA!Jx=tUSQ$;bYXYi1T3CyXio`o)NIxKGnR`A|)7*8^D#6g04y433tZdsjtlM-s$iX zapr9{gxUx3qANN8-8K12vhmc_^m1Ewae(Z8p>e;3RYr)*jEQO|{fnBkP*e3`zMuUg zOK`4PF=W=%G4j_(qs;mJ1V`SPp)@14`x1%Xte#@Ho&;g-40!6Bi=OrI=Dkbu>}$io z8PCtW@Bym1=s+Hy%y%x8tAQp_B;Q$E9&5E_m`=}ET%x3QtIrX^l_ zhz%JjRCdpaD{ckCUZ$dswv#;P;8FY<(!i0iYtxp|cd7Ttr!d_cszOJ|t}=uFE;634 zr@MpPX_J39E&b-gepXB^8L~$X?QKZP?qc*?|7e?}CoQV?<;U2k5+Ag?Y? z#sM`G#pJy!V$$Gh3UOgNl$$Ny;+gxAAc4mY658skl@nXdWaLC1BEefD7^le%WVpn= z9kR)WoGG`Gs&Bsa+`Y&Sa`kwx(lzKNx(=P*l@sLr?UH7R1m`lNl7fPh%4Jlq8dQuh zzogDYf!v1El?9<+ifUxxN8-txDiwTS#%g*3{3=9h{JPa+A|6?MEy?T8$C=3}{g z7$=W26SXk0Ho4N<*4>uo9!V%vCTYYExE(zGpKjs z!l?FfriarhDk~HQ%s=y}IxM8@8B#<$auA)X3?DXIaF?tSRBEq}I_`hydvzi|pdkvk zk)3e|K`%*veHFDFAg|iBVSshRmnEzMX3Ok=2R6N>LhIh42yhg%Zy?U$e5|k+NMSlJq1BNoY6aL^ycT z)s67@GHs-S=I+U0R`~Ig*!s%X2exwmcX_1iE1%!d;9zEIE*~jI56Ff+*oJ6d+S8GA zQ)lImAR$%xQIth<)}iOsF%t`?Q8z^eX;N|(>|`F?SKoeFV$YDWtN-AZ@rlVB@Nm}E zcsS_p!+ERIDN>rWE9jI9atx=;29t}Pj>4YrdEtid z#ubwiOGMZ>{J)XC!Hj$6qp7SQX`@g2Tw~pJVE7a!C9B_h;@Vkt>UrDG_0`UURo~&Z z4zb7$9eX;C_H*4uQhvhMJfXvQXQ956?~6jlw~B2z1m{k*6{mnX9?we`^($kHcd~Z( zG?e?kH-d$u`>cmot!T)GfV@U8bM!>&jsrfQMBbL_Yd4ZNxn`TF8_i8^-@jgScDqvN ziPR{+hH6E<&adhygmS-lwM5iIXcR=|D2m9Gwpx7(`Rs;5miHr4)d_yHCO3~aHt=)Y zJz%&x?$OhW84wU@OaEEpW7Q+W)zBz4SKNATB^hBRIs6VVDkx-yLRiGy;Uq^hRP7cAdKDctNP7<(1{Y2WqfWHUv%DXBe9@9SRZYUO3fAO{v@;zovT z`*Z>i!+lOm2%g538L@Y4mJkjhEt3PR7HT6{p>7o-cYDE{!#YYCAzz%{+zg|9-3#t~ zqz3f&KL9BmTypy7I1Wy61t#F^LlDFjaOe(=xLn7H+B+F8dst_A0#+kkj%6k9nK5rd z{U$HCkc?CMZ6BganF<%A9bsi!`q5`u-d<@Cz9UCkh_cny+@kGs!Mjxk6%TJveK^iD z%RFS)56D3MkU%EbQ}!ugrh-4lgoJRyqSTLRO~ATnemQ60C>B=H+(bHszuA*$A`S*U z;#ng%#>QP!se&r1-%Z1e*6aO1_r2JtjP*~K$g^&D&47cgDW43Cqo1jtCL59?+s-i4 z5O9iFBC|60^lS7cN~u^aSy~4_ox)4`QrEBwqDqaR6JDHAbQb!_D;OueF*9ZgOHRw| zRQN)k1z+f&fS45LfOAOj>EOm~pW{BFr5{TxI zYHTvil9p6j5iNqpU{18%NudS|Gv7hA`WoV4{ z=q3%%uiP`Sl5995z`4c-K!C2Ym#Jo~!G+eV(WY@kX%L3d>xiCJ${DGyGW>jY66#iY z@7*WaRU_*fV@yW03OAG!c!A>oy3-Ad@AmCz^;6a_uH#xu_>pIUINLqG?$|U~r2Z8u z#V&q|)c_W^0C{z5f)cI+%=(V6CmHpw->_w+HBtSdZ(x7!sbiVsI)@YViwn>VX_FR@@2>bDc@5 zhSsF=WXa|~(TC1+A&I?=Qk^I&hqaQ}*^r{2D%<*cpqPl7oDNOzcq1$7t7d~(vitgq zh|+l~N@uM3ey57eozr!KCXXFN{32~ym8vi@Xm8A4aymRu7^%m(X?tsGb>rN|m|?j% zaVW|ZAhA;MOWmKRcFTkIlt=Q#vM}6KM{c|wH!D$b0I7`kNI73C~`sjK(f|D;w))4)=_bTmUQVj_FywenxB7|yon{gX)<|kf_-ItHN zEoyUDtY~So7TjD+3v$SjOEX|LKDo)wROGhp6|BJ`{fh73RNeRkKS=lj?S#4TeoRto ze&Q+Ycv-bvIQN~{$!nf=)}x?aBI_21xTprf;f@-H!2Xt`+Y)TT7^RBa&yFIoxbv`z zTb|g6=aaR)JN5^0_Zy2d{LQoAQpGjW3AWE9?1?+`XI^-z)RxJT_4f}WcUy3T8wkHj z>)$R~sOJWn>?8j`AJ=~@rm*KoHgxanpOAID=cPQ!^t^uTltZh(q}AF1Gf^{_V_ST zdML=g@`KrUk+Rarq=gMbPaq=XlVC-qY0eV12rHv>JWJ^NO+hs7ichbH>4ul~gPdYjX@oQ&>>J75CZeA)le?}h6}|?7S-I1X7)C># zwpQ0~U{HX~Wx_c0P`TkystcR?At>~DpV7@0k94@A#uh$iZ#u~qRX$rhn|hPSLk3l* z9#2K3l;b%W=tX+G7MOF!0-G}mrl!A^AAEuP&Yw_-tFFLt_$D8uBqYmG2GSQlC_4nm) zBX06aR;}K=L{2&L4V3|jK#@Dfv+U+q0o}1AXyLVwYz9>rW>DNNQJ`lr7tUK!HrmKX z3g291wwXQ;4qaO9^k`P@{{6;ADr>Z3&LLtjVeR5w8}e2+v6@9x-0!m zu2e>Mao0jsD5Q|2nwT1xUFIy63eLihLV(XUg`!m`*#xY&h9y0zr7-bM5v}A(-K^6{BD{f-%La|^w!&l$b_cCJs=MPnoTs zzSn1y8`>?zN>5kQ&D0ehVZs`*Nh4joW8@H15q}bcjNa$#$}s&hj(_2^bI)I>8;w;( zQu#uHh3Dkj7{v4c1Q;6UtgU+yR3VfRL4m$B!^XO*(d7EfmDo9fb2cM&Y752=wc01gmfRr`<^aB1d+S}Iz z(orXPgXKh4dSx<*Tu{tCRRH&_Po?y^C{h7T z1G)RJpb=7=RkT!yqln@fW^3@~Hr2tYq{Xl#h05&kb-6E%u0_Ll$S^lrwFZLnqAaGW z1zZI#BwWQ4Yt^EqBzCRD&TgJ@*1PN+b(Mm>-6I}#rdq zA&v@ZOd*C8t{6JSvSu+oHWU#CmAIzqa~7;4Dbfmn^ujSd;{-6{Jx113Ic4Es_pZ+& z6EM;18j>c{>N8Wz{kakr9{v5^^xkvC4ZU6<9c3X<_FlV?0~-E;gORSBcW-`O#tS9U z-o)6|ePXi}Td6h`>^Gt)E1e+7eOKCNqU@;__!5HtLFHjMwt(eNevUE7Yeq3Vu45i9LK7(0Q1N1qkk#BSrRSBCEB%p z=1h{H*Boo~;bxbTS@jn+%<(qlqz6qMqIvPn&FT!M@Q~8ptyU%>32*!5qmpL5<&kYP z{Y~21A^c%f-u%i8#NlVy&p0**v<-f9vXmM3G3X%n@XljXrA`;uWtlihi=h~H;u`^J zOlLVyqZ!nEG)cynz*{MLjqRouY|8Z%)Ls|e#w-Nk4V_e8kbIc?QW2Kd1b&N=vD`(AojRbL&JW&=D20e z5FO#X0I=2ixg|{JjA_h9hzR*+eKi6z5i1Halft84Aa97ezAQ$2F}Y5SoeMd_oKUr^ zZeqOj67v1PdpEzY%?YG)m$Gs`HH*KF~g(Rxu!Xy8cL=ZB19h$#4xIiJvYCZ;JrpYA)Q_824aA(OB8G> z;o@89iYGv-$!_1QXa%V(pNx(r_|9jB3lhc>tDuDhxGYvspvI==1nFId~gk-Egl(<~)gJqRqQkL-^FIzC9(vQHI%V+f)>7PO$ zmUjH8q?+J*gOJ%l?7@&&Cz2wvXvjA%xz-@&F5PjjJN)?o`eUuzo`HXYmoiU8FkjsX zDHtIsIM#iOg5ZiZ<^ZE}C!aiSxJ-`^GDQf=VqL!R5qEC_s6H(KfcsKTerO_7K4GN6 z;&O2~*ihQ2`waFi_)Zd#x`qfj1h7nL0Q90^OFHkGSczf+RyIRL7REQN@s2*AijWKL-^;+-O-$vJ3Cx`m>ZeE*Ko(}8rb|+9<2Q!->J1QIOm_jn{AbMbGltoV<@5*2 zd>vQ@Z8e43XD+9AJArVMPNxE7vU0l7m@{y7=x@m+QZ6WdQ`Uf9fQ`sPZ|p^lEGld0 z-KL$t^Gr%h9VKsHZC+9`FIGEC=#unq#Us1Sz1*+3hF#jKf#{hylloK?8HbSh@RSgj z0DTpZ6vKF@pNp>rFbcq78d-6YEU8WTIz|3@Qy|daI3Go4d6epHQ#hzn*Uh&`Q^HJsj-1!P_q9YH!sYyU1hn zVvu^=sy>!d3IZxIU{z%sqyt)|X}XX@|I0_=gAMg?b{g{_k!4?#-%y6c=L~kP{Xc55bJ6 zQ-TT`?jQ~PiiItJNa8%&E2o<(34dmM6pY5DkaG}{9N4J<3yOdg*d7lv^@#*1$8|kN z92W8?<{NZ);1B!zQO36xZs+KB+k4hCvwx^o553AZv3Df9XF9g4b@7pV4NE0Z;Vi|7 ze>hu3AOL3fjd@jcj~P06U0^>xlB4BYjF}i_VCo`x2TQ#*TbI(qV<6dRlF^qnrSA@RODieonY+WCMvIXaW2djkBMa$kHA=1@1jNmujV(7T)YJMFo5b#2qJQJpD~Z zulk9}N-naJ@K@8f@J*Krp(QH@De9)B4~QrIajL+<2&YX2g;PkWviGa zsPVtufNP6>&ISUF_8(TH;ubK&CihWm2xUur+Gd8%+9~08Y~dc9wW&$+nzaiBy;5%Z zMN<9%`LL7aw;5ysq+t!qSw~ z@Q4RE!+s`C)y18E^^ zBgkEE8zWB!@4wni}b=cB7aXDqHlrq%%)5X+A@EUT#3`5A3=Xi+)0 zcWQZQASfiXl2~OHV)g#{J(}_nYDKA6Fuju7?$UKU#B)$~p;l*wrA84+OqZ zNA;+f<0s*_TWtcEtri#Z@lU$Vmcg=lv61c-_+k0G96zmf{nh2s7s(Wk|d+3qD}h$C7Y z#8NRR#fML9g&6CYz|-aN0N$}0z%VkiF3_l7C@A~Rj!tFgd86=?CVA6J;T#Yyo2oce zW~`U>`1FwgeoMRbohUu{`8|sqpDPAM5Qu=$M5SXaA&ps0CAexkgUJJz7@i^KRwh$- zF(>PUy(9sYyyMx+JH5K$KJ*bvIbpo1C#xT71D9lCDcv50Dp!ZZ;{pqi@njVuFZcol zHPYFR6)-@^;8Vl#?yC=1n8-yJ+AGT8%R0#O1Rgv6g~AG-=tT8N``Tb!TSddVZpjY&OUV;bTyd)AW0t!EQZX=piZ;=`LU(rXXLk`zAy-i*rB_e|Gjw!~F_RwPq5uJBQp)zk`$B|D0Jx#i`P-cn|7uqciVt~5W{ zl_5iZKF-J32rp_He+XFnJ-_PC6MByPF12$#CKkqv{4OlRiq-|WW0}dxC{6t9#Up$m zCD+zn;#B&W;l%=}?~%Vtgc&ql#lpDL=N8?Oru&tGf?SD_lI7faYu!?~TMXU3ulH40!s8Lvw)cTLTN+(m|uiWC@*|&L=I#5Hjn{G*{ z6?so#A7qxG{rW}$6P>PZJ2u_|BTvM)-nk}&0sK^(IFx*QZ{$ck^!DpP-3;{tgmoskx6%4{@S$i6h9xP4>Q7Bg_D;pD%Jl zI$`bR?`WL~MqI^Q#@@n;oc!!>Y@Fqb?!0hHacS4X3NU>{s$gO7bycPng8tW=#nDDD zx9rEGp6n@S{6Ly;j-Zf6f04iX4VA<=7yEMa5@eFaRRWapG5QGXpt%j9)b8N*s?6dZ%E-=U}#*cC@?iJ9nD$< zB?pp@lkqx`c+nd974FiCV`vG{YtmsZeWz33l=;lgqcN;`~HfBTjuDJ>8vh02cs&d3f4L1TA{Z0>@o(y&1$!!Y%j zc6#t%C<$R>2z>WzNAQv+;W}xTlWIF|Zs`2w_d=!tRTkQzq%8^?b>;D8Ivw;B z8itZI9Z8@2v~K?Gs_u>%Gy!qDoyxvBW9e*po$mKoz_x3;uo8=V3(FlFOlGwTWIcvb zM^Tl+9ux|lde2{3Aca?4CV~-TGRNPmt2_a|PW+Yk%VDO^q9EKs7RCinz30kiOEZQtxai#^}i@uVFRPh}E59)Y3dyMf?(ZOc>sbH8MZc(gN8%T6K*jK{2w z_cNU>W{zxL#vE@9$vw!dI->>U!nxGeF*f2Gl~!M=_zz5WH(wp1u88I|h~n-c2~6*d z7%a*s9kaRx$`fYs&lQ|cjYG_w*FLRGisKJ@u-U z%)>o+tAfO|GM-%j9|ZBv4^qhtFqqd9Stbgoz8@W z-CwqkcwRtjDrTb$UmcD#YV0#aNLmNu11H%Kj#`<%*VcI>MSq;P9x*~dqeSLFKms&+Fw3eje?<($_@u9m@N?3z-cJQ%l~;@l zsiQv}5~GrJnZ5KsCNiHzC9}1e?xxX$*fZ>Asz?&oyvzop+clw(m#cq(aY8il=IZu9z+zyb>Zr9SxY8xuJbXY54Yjur}GeKBcZOu>#J6p#E zblll0Yv=le;o}w=bN$uaKuN-}AW26>jkH<{adIhsFIEK*Tc2 zn#9|ik&ovYJB}`HN%`1@~F?m(wH zAo|=2(SMDxllKi)@e^J=xnG_fXsXigtk!Y+LsC^s%WZTgVT%u5EF0%nhgR=3XHdR=xYgj5b+@R;}kK8VoMt%siV&1b}N380Pze% zrjUtbiankuoa+t$lzmf>CNO|zThsQmHEr9rZQHhO+qS1|+qP}~ZQblu?bX)pKHTLk zm6yCEl`o&FC6nV_Jj!s*S`;b05A!TIEr~ni{D(&>xq!NZuM_SKmR2(+y%ISGwl)^|OXw;Yxm=h-*eh&;4_xfztQGBBN!C4NWmYP8e?JcRTC&`I+ zkl4izKUXj^(uq?rUY1bbIpZ?57aN0nK>~@O&rV4x<5(IL?J7aq{`YM0|C%hW>|t+0 zKqqHtspM<}LnlkXKu-@tCu-s71OtZ-ZL~D#@FW zSjnP)VMNdAy4!^?5b@8MBX#4a2M=qelRE}77dMR$SXPGw!^wvTT!y%g2w*q4PbDeV zg)|D1#pJ1{rQo&^L>D?Be5E_nkMeg*4Xhc5u+bw-5E=&dpN8iU^BU>EsDgoa7R0s8 z&4m$K)l*crqF)Lku7r>WhDjj8#Q&PZPON9}M1rPY5h_udh=e-YKv7ITyCR@ShxIZ< zpE1DwZU^E5-WKtfj+bMGe~T8`Tedorf-V)(V;#^s3xc`Lr-PJ_MEJdwi2M6N0x8Wu ze-8<~0qt+Oepd_gK>%qW-?4E0zdQsm?$A;oYIC-HTp^OU{tChX61ZKFkP=NUf8uFQ zU>b`R1>s3}P+B4}AvHh~4qj9M2$1q<`ZfRHEQ3`dG!exRaF8evsAr+MD;Pa!l-VWCv?1H1V}SKzxRZ?V zTCjcrz&}G+mjG&J4s#tN5Pz$O=U>lHhk39!H@7XsgT6Pf)r^^vL4tuC#DfGAZf_nA zi&8N*0uHO4=-}5qiZ3ssyD*K(DkO^4k&o6>0F7ob*H`H z9_`Wb;rQ#B1rrLL!Nki;u=k30dW4#jij>XLdu4(LHZex3jb-emgQ=^b-0*H`>2R)v zFO?rrsN!xxN>+j+r`{7&r~>jbtGF~BeasFT@=?Bqsb5Ha;}0@*<&_gs<+D~u^EglV z{m)+YF{_@aFElOu$e+1ZIGIhSa-G?N6`HWl0Vk=TJH;_qZX1}AG?NuOaFdWXv_8&5 zOJx`3TSnHL3jt{@fh`q#SHO}346f`;gO}1(_xsJ1T7ZvISGT?Ex75DQV6PTFO%KK< zty09^?Hd})v!%6m-H7Om%1ZWH)bg1f)((N;g~2YO-No`+dM`{D zSGiP$npaZ0!J3;VzHIlJ?DxpQHloqZ@=>yDr~_L4``d(Bty;TU-lOLehhNSU$BQBR z2X=_tB4l&kfZXET=o)8Xn# z)=9sMK@Y6v?L3!)x!%5#vzN0-tXINLdEsxfXMKeUBJz;=d~;f?xD}3GjI(L#CPMOR z=g9XqMonax40LnFxyp#*fl=u!k}xEUL0#ne5gw|*0=Dj}TqMHLZdj+TWz19bJZVWU z{l}6RB&+HfFL+TbK&wSm?gHs)ZPSiAkfm5t$TkwDz5@&aSq7|C(PD6_18E&bqAUXs zp*|~zC)W&!3`FFXBx<*bPiL6o@b5?5NSxNTV9$+0ESLhOp3ucGbY`2IBxK&kQMkcJ z;gVKkbNfHY;xm+>`mrW~G`gktXCR%)@7L^BqJ>5)sF1OwpYz1O4T<2_#26I)(Kxcw z6^aF5gNf6jc$I3I|E@IjZ1>mqf->yh$dj~BX8@Lnoqo{0#(P<)SV|cW0uO$+%NRFf zmaWW}+#852VnJG}PC}l3!@M3)S!oQ+sD<&fw)a5~dDr8JNMzh=v&!4lM;6VS4DzZC z1yM4XLK*GpNS|nmnhXz+(lkt?{uOT>V+d|A#ue2z{vwwJtW=o#smHp`ViJ{D?lD5B zR%m*XT)KQ?(`uK*Y%Q3tKBiil0{}bb1H1}JG0+PB3R&g!17Ri11hj4JocRURB&vy% zNBhO}i3+9iyr}!cRnQE$Vv$DltqJ;Vu*9P%7(M1+>l`9!WD%b_i#qP@>Q9gdo>#3r?=Q@$3H;s@_p&V@))n2HNm zLX}Gi2yxY0c-_fTx7bxwdy|+>V{n(3C zZPbYvEyBwx-||m&g$JuGHfl7RJq56Ty~F1t897e_Xhd^o#TrgIRaI$=qk<1RPPkiR zFVuOo+9aZ$k{t4bo>bj~8@W8A*hT$IUUD$7;9PWc;kK9k)b zvszVR3Dg4vlUp^l2>?_)PKxw24^!%%fhi6%a8HDkXH#9*lq_kDHLJ7@NUXk28T5Hh zVAk0*r%{dtx4!!nC{Z=8?acZt19xv!k?Wh(uvPjV^Wb$KDJ`Cx>+I7>VL(qduRG4$ z;wny~1C{7)(2|n-y#e;Qgw|SUJU3SUq2jZqhRj>Jr3JGNPcUd#m+Gz?Hqcbv)jk<9Oz zB4;UVMQwGNk0tA+@H5&p4T>^~FKRXwF}Hdmxgw`e?g>4nN%5AyDQCu$U60;HgD~u6 zNeAr%^5?BIZ`B_Ua=Y`rcBNuT^TXEM6uayAaI^G}SHnR;gJD0M>|1^XXr!`Xqe}ac zL%e*TZO1`v!+vH$eh-1+zAW)>rD6aQ0%LJJSh^dwqC*sKX#)Za?99#*i-)lFJ7;I- z)6dr?B>5lbmFzFc*(1mYkxr-p2wbv3XrIQPurA`4szDcr&3UAVXzjG6(j^uii#2(DNxOtOo4GDI~oA)0P+^J-aBtKMsX=M-6xs!i! z^4J~(b(xdqUt_$<$Q&un99*9G}ZS-q=#_%N)E_3wj$Oemcv(_yhU0H9_%=7SVbiVI=8#ug} zvUA5ywm^^)V@I799xh!_Nd8r)V z${k0|7EY{`5VBc(_4CdE0a>~61~jv(&(4f!x=s%V(@zABCylEA4kj2_IsS*DA_gWl zrvC&Fj0CLo9IXF2`>#DkZ2upqBE}6|8O3^+EoUJHhFEm;>?YAxUJ%o$4~!KPRFo|+ zm^qMuFx*)(B3P15ybO4Okbv-B_V&Xg_r0~d4(Fc$S?cGd7xV8y7(A})7tAps z74_BSB^8+K3$h?V4w?rzP!E0buRgE~aFKU@T;SeOa7_R00zU{8B4CgyZGBz83K=9> zay@!vm_G6?eMIuDUnqNUZox#r=^H>wHaz|Xkf`5jjXQeqApX5-pomDwZ*CpEMSf&~ z{XbwqgoweNkeD}+L0q8O`nK}^1*N%&FfqtL{`;}Lxc#=eP}1CMkg!gmT>3cQ%2Ke;Grt0ZJ@UJPyYy{)$xy#@AR3L~+KR9SwsB#^JQ6-{ z$)_L0cdLX7S^xwXRAeAfU?DPre|UH_CO?c5)VmovG5ysoE&or%awr!e5ThSU)cQg% z*7MuPv&T0n1l?Xk;fn&PvH@m?0{yll(V2rDmAP_`U;DGPRPy@6fv`2GygS*#Ju;KWV zygw{^h{1<;t`U;rAbsK_YjI#s!Mqx`GM;990TXg%IW<#~&wnxfaY);yP=Wc>&j%W(46}(djGX3_PM#ffI z-09>);g`;?P76IMPq%58E=`4$rv@uwx;~yAis}Qj`d&QO@R+PWa+lPQn2&$ytMh|X zBBvc7RD~=fkHnlzpqjnUQib{6d%~|`KzgYvDI#e+@}Bkv`e}|`&Hq9)6n;>;@A3fc zNd5%%t8;($#)8NeoBP`eg4Aqm8XuwEzo>y*I(ne3X~W^3s^&#;OKMFKYRCxz-9>e7 zgRfWBEvas81=sxcYMyUPR|DS;6KQKfo&DJSSR;2ZreJd z#7A0=OC7P{7D{^I5d}x5P~B6ahTl(iP?WBCNt2@Q!Is;6@?@b+H;_G^GXJIfMk6x| z&Qu0WH4}3fG!fBPJ^W-kPuR5Sow9Fq``g(ovT-HUo`5yr3O)^(`^aCZOmEQLXZQBu z!!h`EdY)`p5rOZsG_fl20#?`?RB|Q9h`e%LeS~0< z?SxRnS=$m8R)IZ{D*&zf@E|By`Y^z=XcQa-jpv|c6;6$`d-LLgezsTxE5k)9)+R@F zA;mVfOdtg;w{)|3uw!E^;b|>!EGc%$O+}8Q;m~DL$lcG0aprpFUxH4$#d?9STcp)z z7^bEg0*bPii~gI1cPh~{opPme-jP+Kr56R+O)V6qb@2jnI=LvX20QW(l0=4FN&IMf z4o^O&oKO`u3{EDHQe;=-`k{b=cL~OV{=J{&ILaVWtsQ;x7C8t06k8D?+qMH!>B^w8 zI-D=L;{};Q!&3kC!ojJq;-!cP2OSA6t0u9+GIW4~g@Yt4XVA0D^j56{53O19DXL9| zZFbMpzPhY;oOV2;Z>7oKoEA-BxQIsp6KLPBfSvf5I6i;7I?6x$I!7w4?q2t^%DY1PB#>jdnhKQ_zs~)}nW9{a>w!{_7W7`e zfb4e{PTi@Z;nOLj3hK-sSav<8NKCU1RmHaCI=^E0E+yQD*wspehF)>DMf5VnxtH(Cw0eFN3V=zS8I{2l$<${L=&*F;~~F8T?uAQTw#5^bKpZ% zZI!r!j0~l+riD2|H%Jri($^BzWdW99D-3Te-ozV$jFNF}e=Al;WxAE=xuT!$B_N`< zIPRD?{ciQTWzDNw_~p@ykh>Vymp@C2Au)7t;u&z%ET*-7-Cz)MrMII3LYxq4Z-B^BkaOcBNVIbC?@*N-d<%tR;K^Cv9Q zSa+zcLhq$`E!PtnQs>kbp|}6DA~3Ym+whEpo=-?&w7*=E0%uYnPv_VRQ1!iQ)vb%A zerALJ_W~gG)zHw;9^-XEEQ!)>IY+%!4e2fZ?ifgu&a`s9K^Gc~iN3^}`i=kI?E@rAormEg)$+)lwk&5eI}VW0GTY=u z55Jl8>44nAav(DxB1c0%I)gds_}wyBQmCeQ6uAb$$D|Q*=wO5V4S|iP#!e8?QxN8na43!_ga)BhmS+HMyUhqtdiDU41XbGbEj=SW%aM~I7 z)X_MTT+fGu=kg|+p}3;Z_+`9(Z>olYImvn2ls(3PyGqhA3}WFkTsy`#J^$KP2%mPC zi6y)=v6TQAE6T@fjI2;>FVJCSY;j$naAyS}W=mvz1)Ay3{Vpxn-qlGOT~L&=b|Gd7 zH?B)Y7ll;bsL@{*3?d?4X{-(Mddc|+h2~Jc3?O!<9$z?}7dfw|^RS??;2KSv8UAIg zX<;#*XrZ{8OqB6JdbJ3>C3`0+FptATZl`qnE`>Ik+egpvwg?GAw}3snj9cqnWhf*n zBvQoBhVB)`)TdchX{{L2Ifb^m;iJ$EUW;Lw*7>o(vOf_yB0vqVkO{uxuEi`?@-4Q7 z_oiKXTmL$R&8~4_#-y%m_$M^zT4tmftyGu&=Dm3R@61L$wfaeFZe?vt?$XA5SCTpP zd8n2dZmsHYohgC3)TV8LIC$&!OBci^PF$DBASg+N9$;9j=D(wjSybq zsfcv^!CN13nEuk(vnbIW1RJ6SJAM>yBwT=XW#CyXvNH{c*+Xm5uw0se<(?Rxp0178 zlT;PJx@&s+Q})}%{c>8S9~8%-KWE&gJGA+>^&+f_4{xte{3Y#~nZ^>-pT~S_6YJHr zmD1_;Y7_v=mwt^{N3QaG)8yy21eH!0^ZcKl2z42Oy4p3hsVc)8>D zhSWk^y#Y6=nsv>;5{E|h_#>iXVE%I>;wFK+lY=V~pt8%+)PlPQ zrg@9DDg5MN4xeKXI6!Gl&Kf%M&v`Jm4Fpz`;nb9UQz|2D__&5~mxB-EXR}!Q>y8I8 zjE4!V@u%Z0z1~hJsdWPc7LogquC1OfP4~T;`lWxqlHe%5Jr4Zz`N76{C*cp<;YC0r zjOzm>S*={zDJEhVsY-xudp4^6k(kw~7GsOX0v^^~ap7>06r-7m_i3g!LYX|@;RKIF z-eKfWFp1@3|6_b;OZJ1q%-_$PiMg*cqpnS!fI1Sb1`v(ahQ#+=OP-SD8S93hv!e?S zY@Fg~%aPWDiDOycy1N+hYW9aD&|KGuN8CpYWMQPaxrv_KD#aAH<+a_1pmK0pDc?Ka zva}Eeq$hrO?P}v~PSz^20lRT4Qz@STl#?{x%A0zMwVMXS%2|)ddWUX3VW?r-$shTU zQMs=MvviGxR`q72(v@R_*X*X$OhKo7szsn0Tdr)f*@3&e z?ws8<$6L#vsS!#ViNi=GGK%S!m8MrgqBSEF_jp2Se+5D=Mp5*(D7cO79FBZ0A>0R$ zM=oeNcA-}R`Rs~bIH?9k*o`%vZ~NrNeYMeVS(^FL@C%|4P9`BJZ`$hY&@G3tP~0MH zW*=#;BI6`c=aLK+E|967qp~7TJt6F`RJuC*6#jN?gFc$DzcrKeL;-QWNIhv1TD{Mk zn^u>G*%3I$xrsSXbR(OmG@N0H=@U&9D>%k-kCFdgQsAv<9!~ z_oNcghy1MC6jdzw@?E_Ci^oe{`^WTAh{=@nv(J{Ix?759r9-4;ZNo#ftPw&6#=mjp zfl{?r!7RHxks_Y7ejZr;@#&qi(SFqb*D`U{Sh)fF9lw~F9^_9c`7k9zv-P9sQ!W9v zYQ5ejSkmayTv7zgRozmRoq!()GMdQ&4oTT&+W-kEp4Up zo2l!{nzL6u?DWo>&()nYxBAvI@0;?ar{u@^wi%-eqv^vf?=c+r>h^%o@8vWyA-fvf zyQwolhCmL%z^x(df#F6q(H|$|CiRmfDHXT$JTf=LdJ5N&kzbxxkf@fUPGdouC$|bI zXpw?4^IR8e`wP@`lXk?e4XnJ7)LqwG)*EBcL_}wIA-~kIaE}&M-}pwy!)Ft+PS_#@ zL^NP``93S@r7fBEzbaK&a_YTa#fOI+$X)ef3VKk?OO z|5fLt-upE_QsXF2Hw+%w=yOgWty_wH)M1j~(_Jlll_N(x-rrk4P98)sRJZ2E2U}HE zcH;1yWX}}C<-PSumEWBfzjUz;Rpmgf;f+H65_1?KmR-`AubU|3kUM%#N6ME4?gxTr zmV_)(sB-3eD{dBIJCJ2X6RHNocv2|!=Q5A@>{4sVvR3ym&L5d=;;)5}J8@yhyxBvG zOxu^oq>SE6eC;D{t1DcK5(6R|hlDI*yv2W-2U1wtOJwskdHA8k5BYE0cos@&Plguqt z4<*O!%da3k?_`f2Z|_`A!g=K)G!3g3?xzaA1)|`)^vt6sZl|pQsw-WWtrL|7=bUBTr zWDe2POj~^Hxh;yVQ8PqjaZ3KIb;8mo%-=%kJl1J*!RQ>K6^T#mEZTiYsC(BvfD^ty ze_;Qz9Y{7L%$2Ci{MEkP--pHpBGlMbff4-DN@jee(R4>_r{jpFF@q+6ek^>oWLEIpK^*Y!=VIK*$<3GQc_v5{hT3Axh ze>TyLNL%U7crDGkw=-)F*_^J0>Bp3LwBk$D&s&=~;_fAo3y1E1sU+k}Y2A9QqZC!#RiFQXtJL zy7)P>s)4=@UKQ=%ZP{6Servbip?YzOi{*=Y_QXfp# zl}uNe7ycM}3?8ioRC1`fw#t~xrn=E(=MH;jr)xmZ?qe;}7~9`P64XB?&1ZYb&3(x3 zW8PkjTEA0NH0OYsu09a!n|`9@|%YTqQ2f-OK03=vuoUDkw_hE>K`$5I9hCvK*+ zr1GE2j%8|m$7LUPjVOwBVT*iW&ex`fLBt6@k9AhVl_oDfzE$shc6HK@?jJOiQ$6Jf zDc5S+RQ#p?RI4G+sKa_%ycjZmmRP~Ngy4H}%;5!&H3*A;`Ky}1fbOjb#JoJgv~A}| zcYr*)@Co4~xM)Js?9sd)!LgpmCkx&Cujp*sH&A?^ffB&9eqos(NzdkEAty^X>nS^l z-UlF2yIfYsXo}(EHp~&D>DtjMIq4?DTTI^!{XMkF83dsInr|9Vim?tHtl%b{XJT>K z+fxt{7lF$m?hYqkzweyy zBU!yPb2^kEI|DJnbmPg&?ZzS4Rk3?#FI{PG+5SO(RAyzSbjKY@Izaz9stT+!Lu>0e z;4fDSKG2%EVr;F(xdk-TuwMrBwU7j{VLj;B$(EqwFucUq0Mz4fs%Tk5cy_bC!7m@_ zNWM7wVTOw=jSLf3u7L=V&Z(}3#WqecOhX$-j7dh2*qp2B7CzBEB3aH}IsGVHmF?@e zbtB@#35^^Ge?>GwO=~w`tg+tr zI|g-M3ARq*XpxNwG9k(m|9HTWHo5G1#c8ydItDk!h%4uiRi9s^2zngrzL7-H780$d zFtx^Y6bH%CuIO8{qxBh7NKAbdgIRefH1jiI1SpS2;!G5 zXk!sA?S$Rv!T3ijNYGQyyyl~-7Gd9OnDM^}Y z!TYm$a$@opcL{fXPt&b`6D;cGxd=o+>wqeG9;=7j027h4|UdaZRy;ItQ z{xG50_&5ZrKMU2|Kr-S1BFm_uVS2_6a2+mp=3LmhXuJNg#mb_2)yV`&Ws-r+LysR{ zDwJ%g<1~3hVLiatWw+BF>dm>H{-sO zH*v1LprYJJ0ZBP1hj`E%+%wsx`cmW!$;O%>x0@z8KQ(oagi}S!#OCzR^0uBz4Wt~% zHVZV1yO|DT)MiHuOi5zfM)}}I1>Bm*GYh=;%o+`WcK{2#>do>%fZ+sz8(V=98qT(9 ze8v!P(Ue=Wl7{aNy9&O7(%CTKD(mg$ixc~AY^^5u>~PNgJ5tIUYI|NWmS!o=ny)t^ zujjb!Uu5l!=ry12RL2{biA%8ze?Ik8AIo04+P6*{j)56#7K(=e%7}Kb;%a?F5^Xvy@1>k*>gHhl z=x*cDP{i}7f7zKjL6a9&MQSM*n1{KMxTbq11)wY4nH1t~9-evdHVh&M)28`8NMXjhZn{B+%tX(_F0}C31R5AH%+-+xUPs3ybtdTgc+KFnu(6O7oRVO!` z7dFp<5!Som=z21hBW>YF4y|TVWe_sH5pElf8;e%o9f~M^+lXqh4kc0u6^K^4Rr6^` zq>ZbORU$rF>!6%PlOwP(c2L~Xq%X;Zj4vzRwkxAIQ=bIudbbS|XxVDAfRzc~TRS*- zw|8eJ%WzjnrMfpb;$!q`=}Z4gJ7Ts|ByaoWIqk{#c-cum_ye@E82k5sVExz_|6f?Y z|HA?PU#uVV|ErcT#u;3B>3W4uXW=tdH z{NP~B+gp48>|dC`f5H2KGzpT4*T8k=(ZRqm`)mmHlVA80?Ri~=3$*`$gK_ilya~{X z`?3%0Lu1hOp#(aDodqT62ScvG-&tw-)zxkn&CZ8vX^;f91;&m@m%GiFX7X$S=TZ9=0131T^93354j`(_1FA zK}1kXPQr%_^2JinSI2xNr5d^lYLD{ z&-I~4yw=_cgV|4Dd!x@X+{-gZS@?5?(?{+fo}Zte4u%Ni3hLi&jSKGkhYjEg{=@pM zTJ-Vq;u6vcay3uv|3&z$55P~LLp}nA$Va@<_uKukc`XwL0|U7tXd(B9TLT6mewTA? z!m|Dm6Sczwz6VK%E)0MKgZh5|`Y;X{NyWv1c>0d}xIKnB%RDQmJb(Ped)+1V@My#6 zOS?@V_m}(kR0BdqMF9&hK=}TdD*P4rxgE0SXSl5YZ0|pmXFA^ss0-!!-r+sn9ohr= zWJ+VjiBYHbf8+yV!$3u967lo@*`@!%eee@{siXcu@c(h+o4Aobv!gntd;Bo~Z|Be7 z@%v}0a1kr`TL&)21oXUHE7$LLRteq`)YkdgssaZt=)@0y0`&FrM=*W@k*+?67R==G z6_U~qAleHB5z^P!VNY_j8U(Tj3Ip?<1~*NqWdca>xEx=v7WD5ia`J`uu!3E)uJ4L7uBM$eyR#FEc@b^virSO|P z;{+j>d$s#V`B`rxK`~GJf&y<3_a5zLLj-mAwFo~p+G|zFmu1eaG+J^%$n{JsoKv(+ zm;KiE?s*Jnefg#M$y{5ReM%LM(%#hna+JqQ<$yOyORx9gYgIWIXD&N$)eh)vkX*=H zvnG6Jm)&H0e$Udjp+g}g&MUY|-EGdwbEs!>MKKNI)auFVWUsIVUd}L!i&V3*TR*UW zy(-;NO(`GAscMkuVJ}jD+&i#nQDbdqa;|nK=OqQPodBh4I6q9Hm`F2fow|_V7XI#Z zh}(J1tc4lQn|-BL!Gs~?I>%qgT3|51SAv(T|Cq4rmG9*`)p{@mXUOIi+GK4wNrVZu zb$Yk9yv!qX_0?=>jFE6$ScKMBsu~`uWv(LaL~SHFgg(XdR@A#!{GeDd4mw(v2;I8i zAGQ>)@r1`EwPf0eFI3dBxOG_!a$LN`qWV%u)^+L1vBbNWkMqnmIJ)*FZ|(b#aHjaO zpv$k$5ceiT3WN+lZj@K@upj=DRl(Z2mkWY8jftJe#JBToq`;8Utn5p>eQm47kwmyn z2(9yCFk2tZPR@`E_WQ0TaGrxGv3?M$0FbWn>3V%xNU0H42sGqctSzsgG4f)~J*G-j z)_j4{O_ZIX;}vpoSjKa@Sn4^#6UvkM!;rX3`)L+UR zQOay%2J6}8)c}hs@l6}rjx{G7)@zd)7BH;d5zpG9Cj6W^OD_G_YCr_ya^8H~)`A?e z|AxcI*vnwi6FPR!-6(@+w^yZss>Hv{s521bqY+1YtkW@2-Lb7J2Y!_y70?& zm%ybLXp)GCHq)sc*s#**O~UqoK6FhTyL}|FEv&nV7NS^%j9I4{B3fyv*z-N=`(e9ZP$c4 zl!R%?){RW4~5u?ZjcDv;F}r-xDrgSG=ON z55rJAn=EbeP{j-iqw^UX-@HhEVv=GPPh;9+jOVh3VSy=Y@H^s_j(yxghp;|>U|8Dh zNrG9@(Bcpi1NF~VdtDTeVq~`+FOyY>OmvK$t!Sy_?Qah~><(S$!V_BT!y;dGof2s1 zG;iPtSt6-K;-0yNJ(){mP&`wBA zspqig{284#YWc5ixVSt4hJnATx(3NLo2oDPJA+KDAHET3g_fQW{>wXA>Ul3i)rzXY=yj zQwODBkiwftTbJE3dn;lQ2O{dAqzkYsy(g8#SbhswAyEqQ>aVqcRQ1e*dMksh@e|>A z7VEB!X?e&+L6lkU@|1Pso*je+-rFr>q!-vEGaHodPO7 zr%zI&UfOHb_UpvSnF))2t?ZnCX&qsgU!S*R@zM1(F7zC z#$nA3uZ^k)tlO1@C}n+fj(Uf|b$uG7&!c-U)fL9yEEHUNwHYjGAReQgVB7;}o6t9+!ES9<6Rm$& zNa(Z`cf@&24TX69MhCSBk4QQ`7F$Gm(*@o#Ro;Dh3W?o*GnLPM>X)+2*Sb#^`X{&~i<- zvK#cqOqt@zQ}WjJde@Gf(?9#MzTw2~ev?2Ok5iG`XcH!lnu+6C-fe%r*EMBoo}xU( zo2-|vT50Zh+}^F{Otvrc;cbm9C>#tpM5yer0L92Ds5QcKwzF2EkWr`&q9T|+sst)>-&vhI7+5P#_F+Ho*SUizp;OVX18o`YN(|mKTeB972paL}GhsY#OJ}Hm?mWO(QSg&-^HXS#|cj-LP1TDLeAQJ0bLIdoL zEvWw94LJLp{Gr*YMU&)d%^s=YYy746MSiPN+4$k#c95v&;X`a?SzPyb zc|2J#cq+F?#rj$03hiTQfMnifpsxV0Dcg;+$Kwsiof*PeGA-Nx9_O$U^={@V_P%u9 zS`l0@fh|rmEtjfl8>_OGw45rHd{PU!MA! zN%GO$l~I|-gn!X)Ik*yh%Gk&ReL}G37r*x6SHNM$It5X`qKoSOy`gXh2s zr?;Et(-D@v!P;<%3?)2;WhI><&0UK4OqB#e(q2IKNP8<>mO#xwVm1<(&`BPg9-q;O z81GH_w4Juke9bmVqh#@uqrZEcaahrNkP44pDj`>FRA779CDx)whPaPP=`wXU7W6Pp zZ)f)N-1ZCffE}59gW9IuRwGPHbzY71B7Bo~e5cMY&S13Xwi*>@(k_+X-`|?n(X9g| z5SNXL_p71`5o8uhYJUtX_g{_{%+5CYP{dMt@Ihf1mOTZFn}#;2thJaok6VSDXZa$S6H(oi-VdZ$ zT)x6FVq!*m=9YO?tpFv*Cq2iDN;t$EMt@s%J%RNzAwMK92A@HQ(%%J#hh*0$zlvQJ z={2ZL8qTR%T(P)%PnhulL8eB(dDp6S1~ROG)a^^zXExr&zuaSaIhlitiOM;C$?vlg z`b+KbXLWswo6;80QRRc;yQM~6uz)iDh0)kjuu!@VTS3B5a-UhRnwZjvkxi&- za+B263!bKl+FYmvC=;Dexv0vC@=DixYXo?Gp}N&YRAsVU+R(>f5$)G4xTt-GmNd${8fNOSrCEqYxVv zko7f*x>azpN8sWl+Koq9;_3Eh`=Sre4Y9C=U&K!;P&S*r+EcZLX4 zm`T?tY@#owZhG2dnS^FJ3?OnLIm=q`#^5_xXUQ@BxfNNB9k5%QTQB$EJ(a^I;Zk3h z5N!W4QnA?-za)+Pc-Ila8oPm3ZYua$HlZ*P@r*|u8!YXVD)QA&>O9f)JWPyS=Zbs& z=w^$$5sXYv%mgAab7pD@c%YAd--Xppy!k2wMzb`yq1D(hGi;cG1)bJG{AB#dh$gjfz*{)FPsmf zgq%4Ov<~R2jz?md*b)IZ!(AXF?FXA=eJ|B~p`wu(l|mT!UYWMCs=Q7){RDGY)!v}M z)zbxwwOA7mZS<5TV2N+l*yLVYN2$SEeR%-KyprA;vw?L+9OMl`L@G-Z9SdyRMXbrF zS?2?*r+>V{S(;x7fjKNz2!rRbL@=wS)AQ-=9*Kg=5F60e5_S5RFuhcJfQsy2^rn4& ze>@Yo?m}-!=44jBrUCNNT=4?@mb@3qZ1n-e87>hE0>NWP0xARV!0pxO-mC8# z%N3T6``*i-EJ3pE1x#}L`V@B+d^S9tmL$tD9HoTnKx@LIx32zr)RZ#Gi?H+zJ9IoHcnhP8&z*hA?N0i8ChnfYC0@|~aHy)0< zoPEq+c&O_oBYw1Ji44X*Y0VWMhcUTIq!q^y@gvo(4mrc{>lllG=D?d@h}X4dwUhkP zqxww(vV+&Jj(RPQ{Cz}5?+oEA)je7c@c zM5eBcRugzbbpNvNk%+Fa=@o|yl07jx92A=xoN4IejF?opsbea`BkX2Mq4TyZA9jo)lK-7y*M<{16I=0*2@hI@M~knC z*!U856?sa+{Sg1(rgBsJNszX8E}iaC+Bsx7?hCx$+Q+TOPp;BtBJgqrzV!qk(-~L; z_<1QP$;xDn)1dh{u&u>O1>-_XGFE&&akiu#dcNo^l;9Xoy5KieT`6{9EF@j4b6Bwx z-3taC(bFLKIyCzy;4S%;nrh2Y0lGG z#pgV0g!Wbp4bOOl$eMrLT~PREX}J{3ViI8@rh7S(M(}ESoZ{>4VSiSZ@1x74i|iP< z*a=vmmci-2u7XH?bz%4je99H7_OzUPd7t^Ht%T2)Tj? zNp;H8yIW|%wDrq7CrLL4;9cAur-+Fe(aF3<$IjbJRy4fhX3MLWTNJGJpq?Ud827&G zz#9mnnHAi2t#^iIcoIcW&b}G*)P?_xv2zF(C0elTwQbwBZQFR)wr$(CZQHiZcWoQ> zDmtp820x-ZXF1I{laqU|wVhQ51-*}v>#h=g6D$-lUG64E>bKmkI*iB|>c?2PD1(}A zeNm5F#NnjM9(R2v)Mx*KIzMd;2H1ZD(d}&cp938}T)WX1DE-?FmTr+|7w!kV+$6SR z-KaiuNBax{o4G48BwZNfj2t5@;*rkdb$Vz5sSez`OEEO2u} zGr1A2=1OJ>QX+=1P_$htoJ^t zj1f>A=p{G+r@bnq9BUawqP7bwvyBy3qvX#j?_Oc-WG!ccOQ4wfJ8q~R>tnCa(n@B9 z9O*}9f? zY#F{n2S1fnr%qZ7YDD2}D4{uoB|PU&@a{QBc9Weqt`|(4?X=M^&L>7W>%2jGOIR00 zQU!;INo}QBjqcCjR>2oo-#bdm0cS>c9IU&#-g7gpZs@aqdNUXde)KVK@8%>}-6Er) z2s2=hY%SSv71$sC&lmXeR@y#aO0O?CwDe;nehh>bGrnUZL0NR&qn;nd75*jz?_07l zJ=XSrf*|vnR5KQlUY!~=JW{k`vLPwnKO)ZBltaD!?U7NPa?PpD8+>Ic;G*d&P?$E` zvWTf~Bp^1gKD`~}J0ZzlmE*Eg3`L=NpFYAY!X&bns0qZO-&hyocz_ecq6O85bvHAAzF@ADDAg&1VIIgJp7g@Xcw!@p87aACV}uMg3)qFoLTR8zFKT~LxPpJF|vQm38+UImVV zE0@Wq#-dliF`HFpQgpD3sh9eHbe9)Rys@n_-I(<$UU)sot4A)zVhC4?4(eyj$WJ*+ zBU&HwQ|7BWo@TtY9c0k}LrV4IMo^(~Oj7D!hye!3e#VLV)J9d~`t7wLcF0};DMaQ- zQuNLZ!pjmF@mxKUNm7gEegTFmn@;~z;>Yrz5XF(_Wx}UiD?CA!O=oztA;KVxfQ#O-u1fe5yylP!yY}OwS2-7A-8-&BlV2hjldHj z4<;ZCly|)K+I#r5^4ZI5F`e~w-8(3eRD@czH)lUZpxuE*M_vFOD0P!0>61)>7RVp(gDGH8^ye9eHiVwv2PzlR9l%SEc2r>3!x-KL{7L_B$S(?(+Fx8mB>Zn2 zx4jmJMj83V-f5)WuJ6D|Rs-+-sWzg;>AFfbmVSp@9*s$%qT!6ENzP_%<`T)W0 zeLNjJgws7-$6vTd4r5StZO-y2=ylYafcGvw4+t3RM-CUg8~(M6BdBNB=if_faEF$b z&z-=UGVHbh>fs&aYNFq^5#r!?yJ*bA_FFKa*?hz4N(AN>z6|L%SOj%j3^v){bP-&6(s{`Yj}r|FSjRg z4C~jlFZV}=aQJ?hHwhj*fVZaP01gjOe*ip!JOY8UdxW<=#b&|yzAAv9E%lKVI4Feos`W9-?<)PeKD)f*+Yx=x zFE<+5Gh(9wfK)%^FHQvj^T)T5$6uL8e!5@3s~_CsU!l9-8u5jtjrX|J|HBIM&$til*p;QTAaLFr|LCfoM}Fa8cM2iz{?<)| z_Lw0(m3L_Nw;ugj1oe8hW#ETTK|uQlfPmj3uWc&qvN7?YzX@@ED*Iz4@3+ZNwQLGo zds`#a1P}lN`UHB@W7NLMAOJr+aaQKgFYiP80PJbhC_iUF?$-1O`rsi3KfUsR1^{ia z%kPZe#9!z{5c@w>6MhJM0ByKO39G*V-EjcR$S-yR5dGI58n^raeGvZGANUVOtlN0{ z^ml&62Yw=W@85CL!26-x{r$fOz}_O=1;4TH8kP7MFX(rA!u9vT19*A<{3Jfv^LL#xq}>=b`Z5a;K|-t;M8hk#QZlGS1VAJ2H%zG$I?^JV5h~NgQ0#WLEL`NK5+bg7Xf0d8Mw@M(mZ+*edlC$gEN79o+*5F;_#Tj8I+W$ z52!zHFiCw^f}KQ~`OhtMmZd6Bl?b+Q&qoXl2YoHf@r>@jv zWa^lrd#%2qw3y3Oiykp=@UA?jsA>a6uTiQ6i#@>?PTMYI+8z^%?uZ-ph6OQ^)CMPX@9cR z%Y%7;V4<*_L;vxSnMt*EUW;92aibE7ye#)HFF$hrlG;1o!QS9L6sc(jp0{n zTC5*>4Ti(K$#R|3vy!yhRzs2Gox>A0Do`Mg7JL6c}j;s8gv5m^|F* zj}r^5u>*sWFv%UZQr?HO#2YnP8iCMkajP!zq%$`}JI}$A_A?5ahPB`REU!MPw{Tc0 zdDU;xAM_Z;$QBM8!^da<2ZRj4x7#^`Hrdz%p{wmS@9QBm{889bsH`$mR1o4R<9(q+ zep0%>sE5lbp{8gHXg=qL#W-F9NaQG5?SWn~BM5PwI7Y>qgO>P3644 z_B=VPG?v~fyZ$7+cq=)v|GZNdbwZ3qN=)W?9;j2%5%FEr+R0=>#X{jDUSytFzzhn^ zyM|-er*XyS0i{Zj0=l_qDiP_k&$&efgk!bgN69QhcG1HwhFDfplgLJknk}TU?=8{D zdo3zO9G0CW6>ZQPGF#lJWmaYMBDYO8C0&`G{{_Pm*%#_6;tl8U@Ft>1bY;YhOFD4r zV-sm>b9(m3cMEB+@$Kdb&XaiTUs`U~q(WEMU6VaM(iTq`a*>4?DKA>TX_zVDOgFjS z5dqn-g&n0u%6;b21S$W9?D}#K8Qp0N!B~o{D(g8gefL~oZ44IY)3#~Ye+=Dc7Y{dR z@klmDcWuMYIL8I;p(D;B>746P?O`Z&-sxS-xordy^#$dKFhms_!QRKy1lGF1;*0Xl zT$iGpu6Ny1(JM{nN}mOZ&K+}vEt*@?UYtH7IKb_gXg(^EYWtT zI4@`^)}wrE$o)Pb%eQcyE4{o-xQ{&0SkC+`R>aazq-3sqK@REPtM&FEK5J2!!I^=N z&LY=N!u`t&6805$!H5Kh`w0#uX{$C^ssw zWd2&BFYb&PKns2xh!|c5hSKvQDbiyt>JV@~kr*R+T*jYtYOL_f(HD!;NiG7Z$00Ji zld@HsZyX5=R$xdPW563mu?*!Q9eAe@iD3C1m3_c`VJF5AWQX@Wg#bz|?fYn?@X9g% z)tvz4cKj9b{7|hb7aUx7b6zzHvokt0xx7m$At*)?&n@HfV9oXp$gmJ)vHD04_RP&b zDWVR0!8vC*esXv*jCIy!az%1InQDH&jpfB|LLM3qJ zxCkGk%jw0_J{&zr11|vmK0wV)hoaCQV4d-vNz_M30_x5@G*NeXBbQ~{_ zTRSWmQr2Jfl@I27(zEf5SF(!7Efb8S^urx4VUp4S+}y|<kQo&&k>1{g>v=ar}W* zm^L4Zwx_+#&gG6q%{#dEO^CZ>+GEJ1l)F)Mp+-Dv$7(qB@g|$N@7c`@b^$}ICwfD& z+lp#$kW6YP?~Q6-fP+0usb4>^&C$EC4YRO090lxdz6+Kpjhv$50Io>gMl)&&<7wib zUVO8#x_-rPKsEg;{*!Q^Vmg_xqLaNtpj7+QY{Fkbwug;us14rj z&}P{&T(4bfYEQuoSJlNh1jNsRO{QCyzQ*A`msS}H80i@b=`!<(hgZcEsUZ$d+>h9! zX_^_X0FSY9=1e`|BOZ}&okM3*8gV1d)s*Oj8{HC~v{#ePOV4jB_6S23V#AJ=zJjfD zrQ-at;!%q-$i&M}A9!Hjbp?kB7vTM(}TYS1mB>|B7yFXQ5jv-Z8C}$u8 zg)WXebTmVn_6w>u9QfAL%($_=UNRF_hdKg>WhLij(_RyrGM`SI094IQ75T!)NRi@C zx&mxwD*Wg|OYGf1eWNR^@0cr^YT9b}LgCX@Iu$V0)MmUqMhLNpNLC@bD~leNN1p+x z4^;@AqcXDY$$noQ#w`iaCyeqxW8RoU3w^R^mcAe0Q|_rP}0&LD<0(fdD# zu`colsj)dF+t=dz5Z5g77!M?4UY)}$@JiZ7k&kzKSGD<6vlRMW=nE;gs#7CGD!F=5 z_m5RI0)~7ygIlLf?8JUOWL$Jl`@qa$l|z_t&}aXYjfNF-BWPJDve)zxSKza2k#_u} zWn&uvx|~}N1cZFwqh#Te`0C-LujLoeCLNEowkcwC2pFLf!poz60k|imV#rzDAiYJ^ zB|b^m_Ez!`53eO!P{#Z4N2pw4qVnj2^DD~%O|4{Ndb=|!hrBrBM2kKCuJ*x{@vuT&$gLH@V&G4e{2I{0~ z>Qav9CIQyD*mw3ZP^qy-T_DrPY=9k6bv0TV`3#eFM)Mukeqf@`HOo})AOlG6?@%N|0MJl81O>fTgj)KZ3W?CFOg_0*QNhG@# ziaS(Kt&metUN43<$uLGS-Ue-5pXQZ9|3J=5N0lq3#qDjRPDa~wvqT3O^^K}L|i6>{6e~u)XUQ?@k z`5BI|&t{mvTF5?EIWA&SCQM%%K>4`Kl)Ta0uU72@sl_HUPmEKPAI)_A%{xx=&)I*l zz*S81g1@2w_4E@50C}yYu83o%pn>z+ssS^Cx;jg2T`{c5B8@C2K|px%IF7On(pjtv zK6QhTaOyDL6u)EL9I^*%a%L%diVIbqQNR4LM^j%so%5d7x==e$l*zAFEBsh{*e3c5 zmx=jWkR}GEG%RRd1cc&7T)o;`5%FU!RDTcR4aWV4?MxQj_dw+Up{ zM9_8M@=z7qSBXtkn)IP{d5x_~tC;)DvYU=N zu2;o(D-lryUtE-)EDy`n)noK3TKq4a+gw64 z(-xeJnfy;Jg6!t(gTlbOn{LSAeCobf&fk=V5}`K)$@Emu&KRZ(_pqEj9;u&cdu7Zr zwl^oObSg=E4#gW$*)K!_SUNQG#h|``xZC#*-{S3CJ@{*|>U4p1b9JQ(2h@o0A*j?Q zEd)09)(h<+Tp%}W;5|9Im!LxVuv0rwSV+NKbjGK*=bCbLz{{`RPLe^|j1?u4^m`>e z;|-$cfI7JX^SZ6kmlIWC2p$bNq3b9y!!0y;adXAqTgd>|>2&{`Uh0Ll zZ`QOf0mFH@S0*Iq6hr`w)6qp-+(kA+s-zs6aH9EJyJ6D$jn?{+%8a^AE+Gi8J)i@TGZbMwO>ua%VarHPhhEmyryIu^zZC!iZFRGR$aQM zxoPa*7yH{tYP#wdg29MF;Z zYdASS{QL9IuWfm8W08Hu^>X5S?DvmK$^Nox%B7GBk++WIAMm|g3@Y;Vv}4a?X=874 zdmdIB`+NQ;Oc`Y*4MLjtg<(%)IJt8%+)476G?E$X9Z86l??_NiiXX_dBfL0?BG%YS zYqE`F(v|FjiCNI?#^4iQ(x1Fa$5j)uyH=e|A$A?>(^3{LHK20mXpZuv| zN3;B*WXs=iyF*7AdU*-|G8n4w?N1hY{2MV?aIz%+R>dpqbw(0pyOH-YDiUzFhXWxF zprZr)sHWqrWl_w?@s2fDmwYJ>4fDk491(=gs{s6TxpFwUT3VfI=Aw~N$|_1MntsY> z{bYjdg^$a(EnAdcq0^suxgq%6c(Y!rqDjlz+{_>_=vX6V2idxrw5Ij70@d?jdSb#= zMwy8U?y2aF7-pNd5If|duEuy{e{{`m0=9P%Ho78g@r4f^hXOWQ%{QiWkdYORU)@kB zRj9~lu708$s*DQM?r;5RK)(JRT~%JLpRh zg(FhstgS7dH{r)=nJ|?vsO9{+E4;2>COfiXGq@9ryBj-M!piw&cry2W~_qxfFW3Xlko>Wb9(pH)_+V`8+NKqRgv7B1lKSFgOx@n{|s+&iiZ1G)1!`%_2o9-B$-LZ;&CuWr*^Np@pR-L!I@bMv|aX=rb0`b_ZLt_k&#Om zYfbIVXX+ED$P>+^CfR^?b`^O~Y@gEUjBzXRJV)Pt%^bDaMGZi2q}dI&3q3W?uZKw%XV`NaCFa3T zr@tO)VcVcA_AY#>9Jb1UT=0XIu9LcQkk@v#&KXdRkMY|$3iHPYMdYsOD)4UQ{1yg< z5h*qY#d{09dGM^A;es+X)bqPEMr98uMQKFAedQW*uT_(cIu`-`X8aASE(Jg~#`VoS z;N^56QodVWq=a-fdoA-0mk54kgxfJFS;|PCJlUUlVtQ>%()SnmE0jPN727Omq1@Ea z6s_7ETx%@7-cg^Htq9d*_j(DXSWz4>C7Dtk1#ttv^0?XH^rxn}HBJ+s-R15B2q4s$?&dDeL z@#O31!BhqwYS@?7&nR-C#%{y&cz~+qYGzA$bBOGVXsx};e9An<`Erg==gx!s?Hm#; zRIcZqVF;;Wlfe>Bg-bJrOq5x%Wi3uhE(u@>%yya4`R+ADi!!d>Av<=h=z7_~ z`S=oQFX$pa3rGxQMEZ#Pw=NMCUMWOhgASER!HUQ|J8EbY zcO%2S3f}J|h1?V-2V}7gqnaWl(0dy92tQ-4->S$3sWi!)MCTHwH|Ls^YF#JR?r@B5 z7at}?1z#C2O`N4(`R!<~?N;B9`|q*p=u5kJIcs2_7Q9uB0Y6m7x!0Gp$_$EblMlp| zXS`_uvHNr`xz-skKYvy(9B!=b2Ogx!QCf{!s6r#9O};O^va=6oSnyUo;?%}xrWDBW zCeJ~Xb>|htvgkGiAq?B9MA{s8Ox8P`Ne;%jD^5RS^x?8UJ;UY7a$%6X+Lo?nk!OINF-E7lZQet^=DzJBT;4R*XSKQSj@--DakOPW^HlnBd*NNT z(WPVxf-HV7)WuaW=*8yUf>vqg?ZLSsu&DH5R4JLGRt%NziglH2PCbonN=rlto{=+U zUs$Ce5uP<*E)SfS%Twn;ej!DZ;IgYsl#X=DhlGxCt^EuYPUkxrNNWT}vt0%j)#FMl zRMQ}lKaLnpDIiTirGk~@FK3zM`QuMDESBH^dxNjm(5I1IwESHOXIcTYVSMKLlC5rS ziU7QgP!0#IS!f{|$A0m&VMT4rv+;U9ZzuDf801@xCbEG1=;(G^#=G%3*aT$!Zef|O>^$Gq9amV*b7Y+gplU|fOk(Yl zNfH!L$e&lU-K+?*Xrj{S0(?+&RIfQ^C#=dIKDOp(l%65X_c(E*!ELeG_>3ezxvg{lbRInhp%tgO4w9|lf0tQXvIplRSJ7&qJ_RNTwKcyUG~f42022Y2hq|e+ z8m)HV1I|-CSH@JGKrWw3OgB89H(rL%Sh>Ceq*sSlz%ewxTIZacY#VogG;M6n2%3-& zWWt`$20>S8`hMJ45{0X)k>3?2B`KTAset6Z6Wd(+Tpe!f5ilz((NW;-Z$H~xTWn|L zOk`*ut0%?AGjo#!g3~?^whja!i7xUL4HY04H-$qf@_x%!X?w&58p6{*-|oV^)Dn*B zVAASnFtlE7g1cWM?#VcW?xiaM`l0uZ$UMKhSmydKrhlj_yr6$HLaiRaBqnBkZ$Za; zkHLrg(fKVq?$qy;o74uf$dOT**)#;nJ{(`UX3u&;+M|5k%@{8`v4r3@Bu3nB*qk+~ zg|M%%7&WZv8LB}@FI2zN7hbQj9`rQ~%<3pYhWtw8f|D$*|INbr>crXd%|0}uwYc3+ z+?%}!P?p@;{h9G0#x48qC!GJ#eMr{y9VkrAs@iQ7ngA-=Z_LPVcaeJ823LmjZHnK} zv-Y@4tR*HVP@I?HG@BAJnSLAyv-WF&|6Nf8>8NeS`J-<|Y6hP6Pd%s~PR0}if?LJo z$yBF|6dI#|0X#Ggy0Ak8j(lttIzWNl`gLjUh2TQt0^*Hev!XPH{Xo*WI4HxeOWHhaW*gA7IJ^>ZP|@gSB;iV2NHKsnF{pZ+@FMMZi*YG4=~HJ( zkLu}0ys4w7kYElXdb$*4^dy%gAFQZD_a*&4iIeKpzFm-q!nQSJfl3qbg^?uKWPD^C zH9X72O*uC?Ow+4d>fi(Fb~agac#p8y%+YwKCgQpC6OSjfcU9+)J$aTG=n;*}5oIgD z-8kk$HEGVn(&YjaxY%sBq6nz`l635v}CXS&b6B zx}{YshTdL1o~#^Z*_S~XBIug&$m@z^J>VXpi~n1@$B3vsX1H#{s?=@(I-^yztRCoE zm;(*dcZ!3YTSQk<;Vn%YZXDWtxb_{e3RT?#G0#HjBq}JA9eIZTNi%>aDqQ$u!jzG! z50CqO-x!icw$L9V)QuT9tN=78c64_cK$jgXpg5}+m=e&KwHN+u*OeB_#<3_>XYz-@ zrKMfkmJOP`M?NgZK27C5SSuD^6%W0aw0b}2Yd$n_q#WffpZ?Y}qR;Pf$qPa`Bhjy; zkZwZ$O6vDY0b^1Yzu!!RuTfM|3!O78ys|_~vJq$WBN#wThqwBgDODLqKz@Yn_r)kQ z@pau?Y6;weVPKO)et(|zk6E8)r_<{ggozTY1F=V8Z{L)#E zJ>Eqm?(Tt6tbEVbhuC$y7WGT}j{rtkT1(kgzobKd^ zj&)0NDyPh~L~Bez!WbJZ!jbB_{Y7q;=QH8P+HS6Q^sk?1V24_XQyo@wBS_*CAKlu@ z1P9hiXHCZ{vJAQRe0Q7cDbM|3m8K!=GGU_T8D#U8pQ5f>#j)hh-=pF4pZ=VsyUi9m`9;#&-4}(cK#2rfsUTk%LqAxr*}Qs*kDR-Ikv6imxp>LGPcj z-ORCYezX-1ngxj!@R?1Ai0>b*L2781TB4CGhTptUQ%!PsSYL zWR~+g2l3;VvtvJ7WlU7_Po=T>4L0muCwZz=H#Yer1&rTd~ zww)VS!=BB&fxsbq_R=2v8cW+$g81pi0Af$=Vv$p9I5Z0280miR&Pwq;S83s;B8`I7 zo&9N>`UuJSbH1=gK)L>>2#8(6j2Ijd)p5^=T>u_$1=#i5-kyjWU;ad){?k0t)GvN= z$<*(E>Zh^&r+ykIJL~^6(wGQ1Sy=w>+yCyTF*30I^I81&^Z#DAifIQ`Nwz_!lSy-y zg5RMf-67c7fti6}5)N{9lOmP~3T9c%S%ml}qlaBA7{>JZ{QPa1)vN}c^SR#r+@4j5sTta@G@|uuPJz|Mp#iY`Nl14< zFhD{K7$$vnJ9cnxFAADjy*L0Q((`g(#-J175S==qs}O(YP7mf%fbU46tF+8n@5 zeJgN+{L1gx%#Xe4ruQFB|uB~c_1NwTRZ{m{<=g!%T&64 ze(Ar%`T%F^+yXxc=0P?Exv91S05JBh^BWN`U2EK!Sc0RbK`D zaBoeV0C;k_eLLS4-%|;|XKf87Ev)M!D@aGz0FA)wTpYlF7Y|g~3iK4*{Yb`-(hG~2 zK%pDjo*G;n+FAoBzGb$M@o9@78iQ0{O?=@kSwYk&gOCpPKdQuQ^z8L)Qvue-{d9B! z2q=N~)cl&@?D?$Kx4SxiaBGktFCgzfE{!e#8|yz*L!&z>(&$Hbi@4<^pJi|`gKywX zz(Rmu+k-$rUpxT&(g5>Qo4J?j0JwJQ`*sI*7-~U%b=2#q`q0$CXSe#$!QTUSAsU_l zfC}tx`F*#2pufBZcXt5RIMoFJ)cHBU5P$J+88FO0$1vIa*um-j8QG(Kc>r$o`2F}Z z1~E*5!8pBsi+;R@bvl)lHnpg%{ZhZ$6tu9eK<|$CQNipXpzVR(-Q7U|0t3GQet$m} z>Yd-1gH{>QCTxUe|g#x&8gI z^uvehu)$16X1@8E))O_k2W$}`qBHwM1=iud*=upev8*4hHr(Gq0a9&kQ+F}yHHYNB z0QB(I)iwcoe3LNyM<9WO4afjAVtxR$>#;}c1|Iq!jv7HWG&?l4xB(5?3TU6w2J@JH z{HA^|&h&)%{-jUOz+S!MBlBTE1hmWp9)e(3ee>7q4<9uKb_8%}3;lpPL4O7A{DAgv z|3rd+<;lGL1pj7%z|#m)uLlSGs)dL)f^qe4n!WP#x&NSjtwC3}>D0UT(WUq?7+{o;usx6TUChj*>?eGUG&BRWqCvWN<8S&ZQ;XhA+}i+|*z_<{E!Yzz;!nPv z$JF&eS*8w7OItu^xyg_Hv7$*0lc#@6Xu{j`equVGfYg#zpAH+GfVGy&vqy>E&V5}6 ztg!Akc1;SbD`LCvCAUyI)W9IdWG*?Q-wuI%dAL@wL*H=|a*Ym#2cYv$q}IuFb~Mjc z`Ev>=6io>G&E>p255kVOH^VWcD=z8wfJZn=_@mXyoBFiswZy^a^fZH% zF{}cR{4skhzM^SwvC&chHF#R8v8+z$ZMvl_Z%1?3nCR{WJI4 z^6O2W2}VF*L@=))q*COd7%O8+ZH!#+wQQ-R>M>bL^gU2|p#WV>^QJhP{5+%!VX zmRzUX+;s9iN9+Zy#8QSne+~&Z6AtOtwFK5IP!0rL?Xd*k8F(6U@=wOm*64S1-*$)j zSFRm_D!#=LPRb@La=pcmYj)$1IYTZ{k=I3iT@a4 z$PZfveUOM^=V(wW)ipY;xoywiR$276be_>QqPf;Uof%i`mENwOYIjOn=>9PSEg0mJ zE$=d{af+*`V9W}1f+!ct;5ZY`6^!XFfWzRXHk-k9$5e$M869_h8T=)r81-M_f#;*j zxI<_`ZCuc`C_?;8tKma^kAgfrEN^PP4wz-o(4LZhU6-*sI9Hixy~HW7s@6ggEIw^& zUU<6!Y8IJve0rAocy7gx(;b#m8=r^T7M$wzlc)jVLsnraj>fs=l^eN>SHNf&r)Ock zm`o*VX}ag?BZ?+g1GIxfa-$2=Q0A5|;s!~7D7?T_UU}9O@7xuOi7rXMRtPhi3~Xfq z$$Lk7W-W@4uxlQU;2GBki`a_+M3wE4dfSO91C8)GJ+HKCTMTKLi z=Y$M*tP=8uTeH32Z4b|ikXL=UoE7wmD=hvQ497I%(klPP*fgYRz6GD8zn5(u9u?Sf6_GeC=3trC<@BnnpSHrqxhGA#7t6q@GFub|;dt#o&yC{Ry9SLS zc0N=s5|(l~>+6vmUT6EJ7A9qoHpe!~T4)o^tB8$ZsWO{}j7qVk)x2PoFV)4IUb_hQ z8gO!9$b1gjDK(F=$%`?em_njHrKd`0f)O!`;e~O?P0y@);cIgum1hb0!q1FS5^XUZz$`=`7<*RF}0k(gj0#>iZEPYOn4GyxCFh7fYa3kUnPR+?9x)dsm@1 z+CYQHdswB41UFYU1+(d^IzZ3b&N1kR6@*dgqkT)PK6psm``UJ$Vl$eAl3VpT5_YPd zWo2{_=CHN)?f>gY+f%|1T1}BS-Sl)STJim}iqcwG%9184ffFHnPoM~O!q(w$MGP|%X?3$1*%eF3s%Ic*dr#lwN*n_-%ipjypN}SA< zHjOA4bLtQ6rD<#XI)ld0*s)Ff=DgPQD^bITe%j6fbkwYKPXPC~_-d(B6T$6%sFl2W zT>e#%NHg(BJs+tZ=HVtAT*yFe?&l;O!c!+WSL9tRTI)s;wX*B}!=Z?+A6o7pQgrSa zvzsL0yK_Nizq3oMq+^%dyT#gm+#o@E6?`6pF27x#esxVHM8^GKfgFD*;W?bd{QMU8-y7R#BM5bMs_IkIDjU0T^| zr+b7`Bbz?dZLf0#3=q2H{u^FG_pm;KiX@ZPP>)r_-S{JfYF#jK6IH@-+3+|j$#8%q zr0U!nX?U-b^)NZ&p^PUzN_sKH(<(o;8oFx%jEo=o*mS@MF|9ggbW6)Ya;eyH|EzE` zkwNE3cKOdVQ?O}5EXu)#q;~-m9uJW&3?n@FQJ1s)gbmvjcZ4qLR`Pwh^qPH<_NlPe z9GlKRC!cvw$F%QAEU~kL-x=)dAN3vHbb57)1eA-*N8=|}`Cb!qrfVMHMi-zbHE0HJ z`E_euhZOZt@u7T-RrB|Bm!6QU3J%Zv(9=y+(i=b*Mz~uU#59E7Vw~m5qa62ko(kg4 zC>6&Lx9H?FgY5xDB7wwpthDj{dYQ?tND? zx5wq*TE_?0vrcHMUR_cz4Q^W!CaEl!{73KlPAVw%ljrX`WJRQ}v{qEU`#$ z{v{bK9Ku`ScXg1Y^JKjdEafBwZWcWT$`sX@{Ocy!VKW3y-i;NNGNIhBKSkV) zkvKEz#0T0o7<<$e2JcwG3-g(Iq5C23n+%14z=rbDjdZpZ9CaKfpew_vkhAg4*lrM) z3~EogI{HwY8#|pw@N6o6gA67FJg0>;4HAEHmbw)iw*pAKO^2%;rv z>ZR2U<6H17pU-`-%R+v5b$uFq<$5W>KXBPf-_0PU6{fedk8Do_kw}Psaf3nf|u&kYUC=ak|2N9paiRjdc zSR(7>UJ?9u;(5(dK55de=M}1^8*ogDS0b8AR>pT4%$zuBMPE}va9llEzKXmx3;B`D zDH$EL_x67`hyBlu+>C1BjUI=I38!%L8!2cz`WecDO50)hKI?X8u4HZBo_XLci=DIY zG%UfNH8*Bo!?*^``Z?yFuM1x+ABwJcnu8^!O84IyJIZQ|WNX)2rGTrJD_%q2*p%)s zcP8S2z>T!W^ohMCD=a;FrF}5_8;6OsgrvCUSO@e{;TFf$twKZ&3re1V^cUk|-F2)0 zJAZ9xx>~a_dZK>V#1{!}VNb`v`gw=aImS!+)F* zj(R2M{D#)z=fjk@p(EDJxJu3&muac3nUaD+^ZKYAOAQ6d&2*_Q3PcXE8bdDOmy#}z z(R8$+B%1=C>MRj%l#fxIICj}hYdZvE8KSf3Us9Zin;l;Nh-+r{WQy!!?+|j5BYBh@ zFWhD4w7k(sttkH-F~J4SCJov{%(sLc`A(SazID*fE>4yq){s!Kvj@s-hHk@$T}l&( zF&=QBpvZuHj5et@y-yaFlyO^4wCBK+t#bpmu?yL zMPDb;x|__JI7axF4R)cETuB-2MkrKn{Jc9xRfy1P7aV+YIfinu4GL_Cv=vzZ^POeS z34DnL#0@82Ddwkd%3^qjp+ZD4&AyUGq!2c7Tqh!2+RB`c2Pnge}LJ7BDmv~ zv~NVE5L|XFcp7P;gl`DuN;vC3j;jBY>NL00|6@raoOzMsl5!>Ne8zHNGM$e9irgNF z*nM2(heDhWZ?=}~i^Kjq(Inr_6HN_|dEOoBb-EQftX8|l=C)>Hgqk&Jhz=dOL+rVI z1@n>~8UR=qjjJ-nlH~m7WqzZctAn#@D-b=LGN^oURFKjtQN+=hDWAhpr== z;er%|mSR!TYuSxu7PoXX<98c$3V4pvmCX)N8<%I8+kVFBY`qIBH$xXR@FSlc=Ytw5 zOrNw&ud(y6bdn+wi+7d9hj!Q_Fcx>&G`)%v(1%77K5M%L*iT$?zDjOlz^=iM;|>n2 zQ?8jqa|8r|AaA5jnyFd9u^k_1QosD_ zSL{|{Q7LQOm9W_PDB#{(-9uq4GBD-L5)7M+x(m|iv!vCNb-9*^lk9ngpL2b8q{@BOQ zBP5QN&QL^wIPzG8!{HI@oMqc8g%d|gVGHqdx5*#boyEaBK`VtudPXb1lovdIPG`WkfpR0NuCKgtPwLznr<{2_SKr}4+XEmp8Is_xFMfvRio zxX^vYEzx)Gp(Ej_@+|P4_d4A&_k*gG^R0&Y%?Zrf38j4Lv|_N67$lB+C8^B^$@QQ-=RH}k8wgWEyRkq>goUTMod>A zcR)q!mDEpukCx(Gqywt()^96s^9BzSU}6(5qe5kvGxiXjVko~dE2y~oZLxQMoYrOI zAt!~}`pss%>(!G;fAlSQ8j3~D2y155_SZqr0y%;3lO!o>x(gAk3VlNvzX8&f^gZnm zp=WVnL=@Fl|1NqaXeF+Tj&itPlX4NfCm{dEmm3K=#)U8fW0fJijiZzfcd`%n6m^l_ z^Hb7BQv%%LpcCdZcsvjbH!@vJNXjXd7tVlLvL8NHgULNS@p(g$SFsj85yUbIM~F2{_k9+)hIA$UFUHO(MwGC@wr$(CZQFMDY3sCY z+qP}nwr$(CIVZWfGnwRnn1_0(q>_56Z#uX8@o^DG@h7e&zDKDUED z#qWHzXHU~29|YJ)H-t$AtpzJ?Or>-QPon+8?Hxvlfffl$cNchQO-1(ez@#KzJ3&>+ zlNOF0w=$~oIc7Q#X;wg^y2sD+w?In3_gqwJ zQ^?7GMmD!1)J1YQ8tA^2+=$Ty*nlPhld^|n${rMx*E;6$AHyhMSNCb!yYrN5wjtlO1%7fRa2a&%mO(Bibye2K!n21-XH|A*DQSQU-ddU!>NwNTCVsl~G zmvSwgqGGGi6=ZtBfe{PLQFSj9I7g~g3xYhva|L+2PI5ap&BzctvE4I~dsgf7Wy{?x zhg%j4@oYNIv=5(?CHo$&l>7F&VQf;Xn3B1u%;kfu*&4HpO3SUobx+i5?|u3z1J&`~ zH+iX;aVYs>sh;v-pRHoku_DNH-QZHss<+Cy7sbO7rd4PzY#U+ib5h`<~?ptQJi`w3*xSV?Pk*!{{Hs zk8Hw-&njTDHSr4F|iDEtSn4M)pD!F zXWWj{$yqdK-#_Al*8qeU8S$0j71^~_`eWB(a6l$`%Pc`a7YP635^BSbUliBb{XM#v z-3%*8^nuWXV)D>eggx{kb7tet>F(uP!(#75e6Zy5htC(5Y(0?+e0({xpQ2Q^Pff8) zohRE1oh^z954Sfa4h~2J2lji`{0V$2_7|r@Dl_*JyQE3PxXof53HCu-=(wl=^rRzQX84o}VcVf8pku#+W;^EYAYDd8a20XDzU0%1SL=jYiZ(-b$PQb^YQKuonQ< zlTBHw`!_Y#*g(kt8TJ;GM)W~MWco`hVz~jPTh(PscC|DWG$S!j)^m1w+yo((Xf+Lx zsmP&yQC6~?mUr@Wn#0w+a{o^!*Y1RrDP%iws(iF=OG)u*lD1j<`k*Y=!8>${0p;wQ zqzfUhLN{HBidZ#~F#Bx##8YuLH_qFBLR1|mg*Wa{#6!jwqh=v9y7@p%X*i>}nc zpIyEpBff35GU~>Qo@47(`cY7)j)i|5)D1UR$?n3`12^9WX3?Ykt02xtf-4fiJO(Ey zVQ}Y0<>xT@89ke;yfPc06SNN0gHypda3BK=o?5whJ>qiaL(zf~XNA+tQ_s%1_+vFa z;1ec%K)r!QzmrZw$cm~%WS1CSl;K~9no&f3bM)+UO-oIUbKLsKdNeb<*KZ$K7T}N>6GI- z6&@b$Zi3F{TL1F>r7g8-!-*kV5niW9Bsd-)^DBz)iLr-CS>BT~hZZ@!K`kp@nbrc^G)Tx&5|V z-kn&R$%8if?ls~*>7HYBPUmASyHQ2C}KF_+-p)MyMi2Hl}&@p(`PfeY!}NJiFBv? z-sQx=fXpwMrtw)4rv+TuA2;JizBet9_v8BkePGQS3RZ!tKm|=v zzd^{G)8Q)PDqBkO+=VUyJ1dE_=bae=LIt%VAgl*(?6d7sQLrYlT>SBG2b95nM1a$! zq=jM}$dnk8uD0YG+C067Yg2BXvGOVAXN;7(5Pyc0rKqOMIw(GTP39fhwC zy*kbojwwXPL=(WpX?ORz*QEH0pF1SfvUf_?yo*R;iT3KV)gM;H$9KWp?EVEi4vOqd z#AH{fWPuq)w?l1B=RDonxU8<_e+MxQjMDGX!0C8^wZ7E1y?{|=If?Qpo4GLgk>f6w z`M#zut{t(KQ!>V&7hGlm76mzdMrR)UgU4atzQ{TJuxUmLz2m}lVO7M`VfqJ@&DQ|W zqpg&Zv(mGV=bYir;_bD*7_*s-V#GcT=}%G5g8-5BsO7>;B7Y`i<&f{F&YxAc>LA1N z!Dg!BZQQH@i&Ne!qv`vdQL~D@=u>P|QflUgk;Tp-I9{VZ;34+z+@t16?cHlsEO_Z+ zf-cwzy+=YJM788~3fp{oMyI1>lX#YeaW@ByG1XTw4O>oG8wSOSIaReTdM55>^G;Q$ znJ*Bq2ryh-PgKO_bQ7NRU*ja3;`bQTj8+<=4GAZxY+U+$Z;PrIrQENDLOV)PRMH5T zvwju*YX+XPr2_`L;W`w>&)^Dt-%NH!Rs(XYz@TxO`5O_0Ta+ zq`ShyBc$)=hl!Kl5Vt+p4Xk`*D48h#G|WgE+4}r*>8hj-@NhL4EH1GR#%HDp^3W^_ zn6^}Li4I^L(zAPl5n`3!U%ck?8PuH~wv-=8ye;JjJwxYUfpX3OBfP!{> zJEza74XNxv74-qDEZ6;G(FpWv>Bg|eVeIFbJdlE6=?4gWV-&g_Td>&j7m$J2UAGp; zT1;?gjvY%97dX)pf@mU-wS~z+40>JRf!#azwd7p6QW~0GS$-}S{=isfdSUsGdO{Cb zu{cOO@WHUD-sK4}t~`usr|bkJGty+dZ_uCKEu3J$$`I~|;b>#>F;_I@7lSG#roFjG z1)PIsW=wg~vnM=Fy0}#Vg?pH!Ye5%L=L1gM?FLCPl(P7k0Gw3ji7nUbHBAEN?(Ity z#iqmLUs{rE4_$%Di!pgnl(HG*H@K4)#}OuBG+o26XUPrC?QEF5vrmC`b%gXRrXSLJ zD^;}$V44+NU@qvJwWA52pa^if`%}D~^JI6X)KtjlJ}DLT!y}o=8^1Wn(lcSlNa==z z65J@lREHn<2qg;mV;S=?vK5=|ijTf6^(I|vK8=&9!HK)N2&z&D9o0GTa)|{161t7Z zu98LWd5b&#Ma9j0C;y{mHfoJM*mD0;NfXhNBHKD(u10x?KOlT9B7a_g2(9JrFDV57 zX+Z`92s48uBwb7j)%o0%HsDg7I%GNa7v=jPz+dYPs;d*<;&d&eV1*Rb@%x3@EV@Ed5mBCHJ4#jQo%{Zk0RgGqRd7XdB}hEUo@a zA8j+nnWW=IvM>tT*`CZlr5NlA3UZhG2}w|w_2-}O+1V1y%I|$yzCK(OSfz)kF+|B^ z3(o$Jyj*P%G&g)~(VrWvV@Y$$fN$r#t%?tC3M2b8VJOfM6~lyl!Q`Zp#VBqQofPOM zfjPU0ySN(Azb$90tAeKB#Wg67wL8V_IBG}5$YWQ^gtl`EWfvI{xxGV}MRSdt@uZv6AO=}I z`zv0st&(f_s1>4=4qLX`FN?eUyLgpKCdxm(~z z=qW>;;=H(7&iKktWK-Eo@KL z73slFqX7n33axHe?G=JYTf9Io?BO`H&E7fk+D?jz33N>nsb>{zrxpfYwM+e*(@@ws zKq?BzeOu|EI%FYJ4LoI+UVByd67!B%J~CM9s=rl221 zF^C+1{1z`5bWiUUoanaLt@4D;*4YD+%S0Z~j5uRz3s>j*A9PoUY)p<}=BQA2Zgb_@ z)ST4|lRKlSYGo4ePJ&61?v)8&wigQQgHHi``)@H_LRu{AzaopoaH6;X;)GEorfxbU zyWp=zsLv(p&l?Sxzq*~U)03|u7#Nka;C>7q^Y9-x)J?AGulAZI?(6)WPR~9arN74x z?}~CjdaO@rZ6H|d;RuHujUH)?@UV@qo^#KOXan7Zx4ywQ<~mOiQ z_cc52Mea_tg;D-aIWcNLf^VlMo>INN|Fnccw?{m^CZ^cZ$HPCH?gHqMKa>1@Vbj{7 zV#wb~vO%ArI?08EracRbMSP=T$#btY%`J&}Gxy$Aj@##xoybba{_45>N#-)s7Y$DX zsQxe*q@k{cszc`3-1-DA;;qWPIkU0+U@iGjyM%3h>VbMy5@kk34rh**Js13K@}xYM z_VV)T3~`|0mAE&3(<0P0xCu)gjSmlt8l;&nqPVgk*~!^#%A`Hq#7(C*@=m@yVidal z8$m>s#tC?Gu+J~1}(!Vhjwe&Z;|bU|ZB&WvV*`UmSz7F7_|N2S6rt{Dlw z9Vz_|69UabVP@Bln@h%Qn&C7B?i_VU|3W5N!%uuz>H&FPVvjQaD&zO`AnwE1qxcN1 zLEFFX<6=&7M6emN3A&JWVV(SO93#IMNPUcPF8k=VCyzWjM|`?k$2VZuhn>9-9Xe)o z`teaE6k{WBu4`)*^++IrmVB7$5<1sF7adA7LkpLgQSJjsm4{hh^X+=%&?Yd?2Rin< z;sWxOs=GB0c+}9z+Judyz8(_yc~tEZx*UJ@93_%I>~Y4wm1lC8&zulZsqz~IC&dM9 z9z=wyW-H$=;|Kt6EZHuCm_Na0VbxR?&(T~BEfWaV-044P&R6}C?)G%{xdmqreRgbe zOhO7i(+RxDQ{^a(0#pG`PYq%AQFlZoRD}TTA-*K-)Ma2-v0K=Kt7i0rBVIPW36c|7 zwU)Mta*leT{poquG#APV-!9s{#b#~B0mIKMtFE6#_M7mXT-!}pIf4%1ZeA2X(*|Yv zx9bcRdY=w0y@LHB$`gKm1N!A9-Py;lxNgPQ_^?1K4_7t-K@~nIY$!6#CZ7qh+nyv| z6?BTzmDRIE-n|tJ_)O0d4J@qlvRl#@JKE6pAPH8HyJL{2%7r?J>}X*ZEnRww5Nim< z`QwnNCf^aLHm~6jF%8%%4Wn9DuaAoQl4uh`^Q{trvu%Wl@qYPXfLH^6(0N@y`3D*I zs{Ruv%JH8#QD%0g|KUVgIhp?>C(6db!SH`JmpOy0ByP9RE(}2|2nvus;R)3)#M5^S=D8!|JXDTNeXBe>2{WW_;1&_Js!Gq)sx1@s#dFsP&S4I2jxwG;dd0MIdmfJKTF z8vUXRq6YdM+@)cmgr1(~D+*Y{qvwTTD2OPjiAbTrfJFrKGyM`3Fg6IVmXRaDEdc{I z&r^V)^%Y0BJoXdd;@H1g|2#t+1>XaT8W|OV_?7}E>mXu~z(fFD2-?R${P~JuD1)BE zfE6aP7N4gP;LA4EF=}g@x&Yb{73g zhXL6G%HSZX_qF@m#5)5WAqa5q2aOaWU~rF#N5KIEJY);Uva$er(MhP_JJ#U~5zyCb z3XB36{?)m)d!-i~SGGH@fB|!T0VeuRpa>`6Sp*GK<|~dK^f&?uluz+SPqYLYIdUq@ zn=gT0hMRNO=}2)G(?Ez2fq!@Frj7x29x@nECy}a04E0MD$Zzb7qK$fe5h1W&=co5w zPRIy>@w4U~^t)BX40Rp+@Yz`>UaxlBBx-&Eid(Tbr06ij)uy-2b0=#h;DgZ)ojrfM|hmUp<4sgJL7Y5M# zi~Dv8ML+};L~Ov3hdTfm4)j&Zg(tT5t8qw=ANT|&)aP~`4$$Ar_v_QpKM5Tp*8cVt z`t#`%e0!mpmVpiZmHxKNh>F_8J|`~$1A~$f9|sT-CLs+_Tx8@2_)GH_K=j8N_$ybN zHi;LD;<5DKzuR}^-`QTEy>Cl6zCC`+ZP7#3NPveQ@uL-eNCuc+(uZH#=UvKQ_Tb-& zhhL<3Uv{U_!Qo@qv{%=!UkG8Hcsf2`NVj>9y*scu;X`Y`58DdPgIM!t1iPZpOJDJ3 zJB2R9Bt(1h>yGM?Gg07}(!qW<^Lz0+c}i4}d;5&hUfVO?ePo366o!)&&0n z0u-!JdB6ix7=VpJMnAn(DmqvcmqCL2!$X5UeyKqo28^y@A8LSqDDFz{ca(q|vA*9q zB#R8)RykMKd7V=Fu=Lrc=fbR>rXdn-X920*R^sdzjx)WfMN~)SR%oiutvqv@2O=la z{_H@nJ;#~#b_y<2YQ;N>c-~HPUIS^;rfbvUiC^14g&Q}FWbrVKf-N6Ym!vzs5X`>2 z6`ws8;WAx|c8`kPN`OvFf(CF05cF&Lnv#Yce6pkF3dCR9b&lS>9Su$8;YEFdb38K5 z5@FMz`ms0Q~>e&DN? zjtbM~T8_}UFqUfZ(}9E-Q}*UR?w1n1lz^Hfhu@z=&rk=ME+@m+(>6RxXNtung=5Ak zpB!_0+iNFAOI23@v1&*M5LEHr>wjJ-`KV7qY8^)XOI?@oD2++2jj9UbK?g`0h+;D` zi|>J*n5v!rZR}Y1g+FD)Zsnk!-PE{lOJAroQs}s|yRKq;GSs#u!vzu6!+)-(#T@e0 zH)E;=6uTLI13;pL+SfUELI{7YK%=vk`k2JmeHSdW6x}47+?ej}xT0(9##r8EIZt@t z>HQ+#@T~XF`?5Z}wBDsMx>RcRbfJVlvV>^iXNCcBVOa0ZmO_A1`n=-aq;+8ZxM3e^ z)wi7coafXrSc&Bn7(6KG-?-x(e|qN3Ig(DUy~h>=RF>2RN^(_r1>kHXn@4CCxT#7s z-X3`2BdPIIZP1^A#R7QuhMbzWaQB=h=pXOAt>Y?TJ^7I=F)B7m3+j3Zr`5?g**Yu-qf$)R5dJ#_)z~NoSCnY3GhTvqebnE_R9`SW6{o#A(mIobOm@?6lNV2 zH%$g(HiDQA)=>aWVMhzo!Q@NBg^%R7dbi99J^GM+1wMya%edA=C%~`zG#+_H zsng3{n+7_L89OwCC z>sd4{npbfzPJzXvU^lt&&Lna2SnBFD;Svk}L4uM8i{UaUC&CbWdJO0_B}Yil9cpM;*J&gf4k`hpCy z4o|Z+Rdn3v$=3_B$|NNZnfe6RQZ`$mtfJNN)x@IYdo`ajS_F{lYj;R2^F5ooT9?&E zxn5Qk4r75d24_vP1omeu%?|3X76dWwkV*gJDy`4yI(l)CLRlpQ14h3)05A(lGl@w za!luXY=sy1g^0|?BF%mKdjL&c6iB~(kuzUG*G&$I!gz(BZzs7T;lNt}jcY@^s+5e1 z)8Wvl-fz7WDf?cNjodJfmMSJg#~P4A{BokcJ8^wRcrMML21>u_rQcx}#|00fjtWv~ zWlbZ@*vjt138mrfuRR~rv>AU;bAi`o^CgXRlq*Lr_FHV6APU zI#4z|Yc78gmJQp=9aAa?m=B8)SEg~Cd+`#hk{g<&QyE}5*D2QLoj>|I8}@}IEW~3e z(d>?s>NCb=PO`-$PF+OBt*%>O~n7q6PkO+?sL6K z|Cz1uH0O`RJQeBCTNYSmt~kFUt5q`qt%TQC8jt5w2AgV91`o$nhvtd?)oH{g>rJ zgBAL2BKtY-l$MwvY?K>{g<4;1c6uGq3AwFL z-ULxDZ&;|K>;1YPi`dQdVj;qPgiM6A(?nLz#WR|zV&iQ0CHlwYDhG)*vAA=fk$~)0 z?jUo3lakuq{3Exhky^?ozJc1vhj#~-j+U_nyVSgrqM0~ z5=Lq!hl{Jrcc+`_5CIWrmB#BcLh3U2FELyBGv9UtnIDINUX9_|kEn}=c@Yz@QHQW#!72bC>$OPUd6>-O#TBb@{R%M^V8qBql1jY zW492_`gJ887Vw}_f1CX28-s!CWa$woQXA#`3usVy8^uU+Zyei>(SG=N&j)9B>BsJ) zt8(FW^Zar9{%XJB_-Be-f0ER9?6b8~$?ylgzn7ViO(&10Hl`Q_;NXS2Z-bG`v75ek zL$r6~$r~J7x9|!cCIDoT5Oj=?oiaH}YBbkzZekSBTaoy>RheaIN}0(M+*sc7>a5j+ zj?vejL*LhXE5zMR6qm;zUgl30Z^9<~EstcL(Y&MM!6|1$6*z-?TW(MpAch~0@;#tX zrQfw+gREMKOwcccWeN+U(IWo@21Cro@BZqlb7mn*#pm|Q$vMo|ec=A4aNN0tFWD9; zI53bZ|3>^2)R-0j;rZUw*7HHa&q%}%CA4~&;uB&=wu%_*C+P>jcQYUH+{P$eSmMs( zw_ghd>wgFbFFZBITVjF&E=x7TzBp{K*ieW~$JS%g1EN>eCXKaT8bEJicvyG4z?JDp zkxjD@?J!Kq;R`+;73?G^RR%|u-djAMprIRZC~*_bhUvXp7U{pA;oZ=OMK#D3(Po%z z_?IhfFNsfsFTF@Wic%X%ou{k|NqMDPlIrzR)5t@VgY{fkp3sU{8!+5oMKL(2t;@#2 zeC7z-?cpiX3$bI;Vld_NSc=9}@2?M+qJ8%9@(ex*rVRj>J)z!|d>a~GHMWg(d>VHj zk6I>_mzL;CDEx2*Fxrg>PkjZp9(fibF!+Up+&NzJmU(=hi5YiQwFD2dce@*35>kek!-yuB+nA> zhwROiOHg!@MOR~MbBF2VhAJHJsTR{@1Zq{+K8nUS3cY1OyLQH-7075%b)&5YMKXef zVz8Nq&f(2$xBeap;N`k&R%|uJWxm<3QjRuGrfahe7#WoG`rU1S4gzZkU+Vs1Mg*V5 zmKYeK0=tKdN&le4KW@ysLtkj}Ix7rthJqD@AyVj3v%;=s+|4YoH(So40*6#d)GXal zI~98z?Lb>6{5$vEjPvsaXP2rgb2ZAKp6V$ESoJnodq1>Q#9lx;_Sw}`v6?Pfo6O78 zwK(w=Gf}uwGcqYg2>gDCuEB%%V8x&d7vG{O+doUw?#A~D4Iy5I8q-)P zvct5xxtkj(=Pr2GI51EQ(H{!)Ep#CBh&XR(aUD8izFF8Q&8gh_^u#o`u4G4BZYDz+ zLBns7su1zS$)7!6MWScqKW~Y#O^^H@$@Pm}LWOl1d{6CI9QQ3OcODPH_f0=n)E(_N z85Ug|5?7Ie&X|${_2G%QsgzfHjwqPHl`as-w@DerU@mG0&T@i;-jWyo3`)J909)`{ ze>2r013z?bXgTIA?S{s<(JNCCAfzB#uE(SJSZjZnT!2Hzhf~{l2AF{*89DU|O*Ye3 z03aQtKDBV)&@S1S)XvD*&HJ4;}Zob(>(UB9yCXFrKY#etcNmV#f zpk@*jk}m0t-a2fIr<*A2^-4?6S3vij!nn-|g)&`U@>6r&(})m#L1>Vju%L!K|3Ca8xy z;Y3+wV=!o<&!vDWPOagP3_T1kOP>@9ds-$Al?6Ad;=X*RA){p5hZe`0BpR|y_U>=E z;In%lC3$_{_gOHj6>E$jaq_hjJE*$eVWoH)URi%@OcrP-&AxSVWMa$M(rOU9*GODH zL(O+RJ1zEFx%I)-g?w~slRE<8*7(Rx%lyCW%YNNAO?ghb-ZP{8x zC)U}@1hIUm#)YMbgtj@Dv*Nmsg5SP}4XY){$DFKP z@=7p#@pVMhHSS4r&rJTpTO0z4`cHMqG`QW5Q}o1XXT{VgJN>CT~t$i|7Q zPphQA8=nlR$ccCPs1O%DZ#>Z4=ke=Og2-1jzrE^cn3y}ser*O;$Fb~hc+dyH6ux~; zF-iwoALNXHe$$p5X53-9HZ|Ey%#_kCsDqn3qh`kai)L#PDY#hCK|KhtJpx@jWBxG< zXq)GBnSMKm&*r?a%h_YhgTiBjvC67JoZ4mesrxLuOLxmeR6sr$1Dq=KWMlapOcZOW z3jq&wDpWvsvbdH z5QE02ql!UsLltTm2cw|1%~{#i*{P}G3?dpR#AbLoo))mYcZP4pAi!~;Rulh)*6 z%qYBg4VrFh32_blLdx&?s*hK<7R~EQ*EnY9P^=xs@>0r!tLCTWKO{c29*oajQqZGM%q9-o^=)2$h)MmiX5ZtLr4mbiUM9Gsv>?poab zodX)4#N&8@dlae zZFpQ6%@#g(DVRT7dGxPK2mq+sW-V>$XY=LOYc}mzdktP^z#_~!I?sy`@PZOWG}bI1A+ zHsg`3!Kfz!^2vXOA>d5udRR&VEKOct?VIgAI+pAWM9b0!XGC(#g~}B1vy|>nn7y49 z}CW5ZxO@g=jK%BGq1v|Rw8CLMx{tfU2wm7hfK4%^($vBgBHOMH<$gj>`*f2 zo;#mp z#z0_eXaU8;^Z%1HMgm55w*OAjmuBIz#}FpfI1XcL3nvXj}hx&7GaI_9JM``CgelZ4j8E z*9CU>^Xaw5oN1*oO8UsDL0%HRI zVq_9nAA);&HM25%0+6Myo797Y1&0pn3}=S8Gpqy6^uN;471F+*7M$@*y#Rpa6!nWB z9{ZthIM4%8>)Q0wNM0W>vcE9?@fv)Xr9mFzD>ENz6U-iq? z5O9w7a?+DE`C6s?(^-x6_xFzug9C5^3gDTjUHPKSJvo7VpC5erD|v_TEi?a|iMQ z;B)96jY96d9p3ipfzd-TyVkWn|4Z0TQ5!A0l;e_C{=R!2{c`PgJKh1f_3ncK?brcw z0%T=r^Z2#W2z@Xv4gS1iqMt<1 zkuh`4p&##iRfNymT(iqN_#j9_(143e|H9~8vm*Nw)93C57PVbdIy7|9PQ5zM8 zvGGHi)px!5XN_H}j#py2AM|S!`Vnck|K8V&UfRUu@gbtEOX5);{meM@r?n=jJ3a6n ze{ux$2sDj@JJYS8d+Qk*2&juwuVn$}=)N=!@XG21E-n?JZ!)R}02z~!!YPx{!m)PvIc72YeJr}O*Yy>+N>e`-g}Z!izN_GoJA><*d{ z;QQ#rd+Rg(_z&{uLh|FkhyKw02GEng+CEGgwEsZ&JYBuOA9+w--P6A*%|Ghh6-(Cg z@BeO9r&m5oE64Gt-W?zO2L5`f3KGD_VQvg$1*YrQIqTSs%0|CYzq0p^w7W5Y&l1ro zIJFc>Y_}`}kp|h!wm%Y|4ttvTi0{u*_&ljQ@``rsO;vI z5f6@x;*Fo>?(Q;z8cFms|ESibO!(v|>5GiniNTF>I zw!8x_zeBRLXCb#NJ6OjniNX#EsO=<`-XKCtV1@2(^N%v_K0ao88(1hi`+h82mebL;oUEjdr}n&r)DsiXS65#bCJXl#7t{eI`h&GBdiw zZxNgHM~>614v68i(s24$$O~KaobcWdxCU~--w7{N#PqSFGq^b8^#mvOykzeJdGSu$ zAYRDld+fvs<(zr!_sNczAWoP6b~NkW*Jvigu!&%aA-ltJrL>ibkbIzN%^dI|G`+qw z+$n+?FHZDWOidrzcQ6y=i*%=JQ>Yt$`g=+CE|J#nXxELfMUGS(x&eImn2NGFg`B-~ ziU3u;p&0oF@#L@@u3c&-%)Ek7A7OMW|4iJ~?K4_gfcSv5?QH!0Y?JCF`Ha5jfz`3d zl}yppRpr2GK)Z65=f(=3~ zfXEfC9m2{29Jc&M#Vobf>w-@<)6Q2%(w7BB^8g1wo)OPZB*IM^qLmXB1)iJ`@$_(@ z5D{IHeRIvzR5cq;fz>|l4tZCUAP{?hGN%Fq3h zu#0xE2wbl19Tjy3(sQm;Ob9Lh_OlAHegUb_NmpVqx?&_m;k#C^IO!M-nP0GMfv(;*E6Bjc@>+a>(*HZz$ofUzmLc-N@+n*zkejzlvNP}+YT-&^P?*a z)B-6J1Rq{YBBz#<-T3tTK+yu7#?{Xhw-lcavfMuaff|ai0@-Dmw`b5B=nE^@|5cxX zh3f?C=|ofwS`n?jnSJ&3UO;2(n;RfHdBn!u)r=(rpvl?33z{bY184=MoC)vH0ir5DRZ+0yYH8ba z^9mO|Fo-Y5c08rs!t2(q)x9ZVlw=adqAK}3W||FBns2xm=1z4;Q$QcrN0LB=Cym3= z@c%k7Iq4s-VyIQ`ymNf)xt&nxy*LQNIZW(GT2|Ub_~IpX_XMcoZ9Y`&p>ONvo>-Ag zsV5L50gAc2L830kF1{&kn+UQtWcb6G*Wt%@4+nWD{8<_Al!`|c$^)a4*5WBSw*Fl+ zo&qG=*XCQ{cMBv(&fmEZ;6H4@op2LSJ*@GQ$$f60>n@Os2cVPn7)7FD&4Ae%l6@Ht zpC{Os`BT~L6uZsfyrKPx3pN3sXs`w3Ui*QzP7C+w;>sQA{t8J!u!`qOxlE? z|FuB}@H<0*`pT-C#57&*;z&&%!1ub!F&DK;<3uh{vatA%#&QMl`H(4By*!xLnLFzG z#ipzhdIdUcDVG}TH^bxKY+bMgjv$A}_HEtBXYO9qQExMnB8x5W-R~KUGn%dHgR%n? zJ04f)eMMwDT&1_G9ru1idd9=k6>nB>d%y7tcoYfNa?{}_n4$%7vSs3Z?h6%=wy%m$ z4<<%M*&}f)zJ);iCB)rJbrP%Je@5qoa};79Y4xH}Uvvv}Jb=!<8kH|yD&#!B#+@*`Sk;L_8)9K6AN`1u?PmP=% zYx;gfwcU$2CXn%LJaD@BNgXbe2tBkzt!^7y(5Lzff|aN1y&kSkrUAOL{^bVB`TdMU zgEv&zdP>9k1+)Tc)`cb-wXIxR2XB!!hQ|YRUN)<=7q2p7q^hyw3LUPkP7H5m-^16I z1h+nb(}%;g97kiuK+OmE7X5h2~@{ zlH)-iyP=4IJ!9=sh#;I{QoRi#xnL#U&inL{vDSSBaBXQx^Nu&rMVXV|upBddf3_|# z+X&B&q??e|T1x;5O(cGph~W<#2~9gW8+fa3hF|KR`-h`PDXLBBdo`rcjb#2FD6t|4 z>9qFd)MU2{HZI?$x~0LZBag}J#!l!zxieoQonZ=bfCqtE$4H=&EBf2r77vh*T(tR? z7^0#b$CK|qIUWHfUYBo30dG->8tN%$0yD(x) zwPU@O@W8Hd*ebFaVEk*@N8xO4GJKGFx=Cdt06wh6rtnq<268XZxcGa{NNzcbI2YGK z5+LXV|Ec-sLQ&xibL9B+CngOBzBO^v_az*Ps@T{!deI6Fv}(=i;8)3b@U_)wS5>2T zhO-gRKUE;m;>I)Fyb9xFIKEt+l1{vf(N`sP;yn@<&np$Y#QGWtXJ6sp4e>O+r+&C`!nP^c?Fu3*?5(!RLPd6X9GUfL%mH_*a+ zKxAyA53s+B2v$GE9;EH6!FTNY9`y5u)3qw9W4i8#*=IISexY)^g9rB>pC9+?^MdE# zJ?;k)JZXSh5bSQWGxs@l@%--6NA1wSjfME`s>sSq?(6_Kr68v)>sl5;)M;`PT@p1D)g%bUjB zUbwr3>Iyksq2dJ?)<=17pXJ~2^(-uss}~Mq(-eta-0ZvYDw64->LmRj`+}U{VNk?* z^;pS43`~nLkyTPVuIs51j*&51<+M?(;G=z6UpV|)8I4{)%Yicc^VSz#vwUJGad#D# zv1qouFSL;q6}|qp{1NXeNLC?V@3Bd{RY*hw_W^gG2k4&})V2(_O~8iCu$rhBWUH4I zJ8J-+>3CEEP+4-N<>pp1;+kxWabQzW5MM6IS$W-Pmsx$k!A3XGVpBqm{9b40HOS|@ z`|-pbeby~sB6T5(fzdT~90$ovXIbZT)5Ohs)2DebKf3WwGs&9kAYjm*GBXH19h z!X!9=7Qwd`2#_Lt!U~=>CL>IFSb5TLvQ5g_3er{mN8C0Rn8ps3>q0|&!;!Sxp-o#3 z96>rK>zZyI1n7pFq&tt{i zit`kAo(Fe#cXxPjcc&D0cPLW4xJ!|u#ogh-t++c~-u>-nH`z^gll#Y=xpVHB+?AkL{lg&1Vfc5v&v_u>sb5bL4Xj{N z+jNQS_6XoS!w}L*oVt9~*@*=^uq7wCF60s!puF!VL+LuoT7t}^c8a>wAH}^;i`o-2 zv92D%_bYLlJkCr+OLEg(3O?^4DUF=-lwb)w&eQ)p9`>*P-Oayzexj7WBja+u(4m{8 z#?CP;MJ1|LgAHYTD(0wRXy@Kj<^|JlBvMD^C&tPjY=-sSddn#^LKLR~ogX3Ad^;mM zw{Lg$RGQ4;>0ZBRn@XzWPTcy9DRAKy?O+8rcE8#=b2@5SPcVaGs=$=nf62aHFxQl0 zlL*>NW=i(yFzzSCmnqAHfE#0s>hYWIgN@n&^}jJxGUBxv?H)KME?T|#@>%%*e=xH_Ii z(GmQ|7N%cr+?!b}_;Qz&SE)qZa`-T0Uw@{&0>$p8n6+L`v_Yg#?GeRbp*8&w;?NAf z09T0)o;fGlrdK{j*0^qpN^((x2QEs|Vdkkl*E^r?a~)E#dVX&!I> zODkdHDc>)zBWlraNmu*#MJG9rgygz8EG0gzMW=TP$(B6#inyv5Db#s?lG)jm3Lf7l zIx$(}EOjm+YPOMwV9cU)}} zO`@7G-cbj8Ow)|&g&#QT5;=W2a5aXmQy1h!ER0J_OiObE!kYl3jcM_4srM?%{?MHf zeiqb8YThpwrrvS}Oy)9~&k{;eB>da zJu`N?Wy91#R5pDE4e(f!+V|+J;+y^;_TL?2`B`h`QIPkf8GNWY$)nl=HGge~GNpnZ zCIojloXSUNNgC$CTl`@C5hjaiqxa#sbn=NPl76&1L@s|b9gOXTeu6LBoK-qX^gI1s zQS#2oFWc3f0;3nB2C9?3T!LSgQ;gu_@v1kYQ>>BA)s`jvq;U!s*iYqKk;|QyFZo5u;w$|AF4PL$%nr%=pXqso6SQNZk;wT9Q5`+22rXz)o2jMm()F@{5($ojp-)GAM;tU7#$Fc7J~L!hX^XahTSom6P~q;S7CFU zWPEubkkD1E+FbOnkoUYjZH>SxRzX`}{R;BG$3QKN%C>S>Jq_nGdgH?MKMIf^$S6~v z#A%E9=S>Icf-A*zLx8f;X)Y3X2Fg^9|DchG7^ZQ^U+|p`Oj7Kloc^srqFjaXuGD$6 z7x*E&zTzi~8~J-=s&f>jMZq4fct{Li1kec>KNB-tQ`0Ve(!Nt(o0+U&pG&^nhA9;H zPvxpsHYhTs%C#^j#}=t$yhIeRompiM8KJkjoFrDZ&n@Rh=Qg&AoCADvv`m*<<;qwD*YIeVjDj-RX5)yny3IH*--l)Md@+OluP{aS?qU@PJKRrUj!Rn$ z;3)rSO)4y*e;vyPkq_l?XAIRP(SkF49hIXoCrViwp9E;gkSiHc!U>7!miOi63NJ;~ zF%_yMS;Al{fs$K-J4ELdPDnRq;XvR_+Ry;&2lwM=?O_Ckf4xFJcRQ%_dbu6nT(}*` zPP~)B^u-{!R!&`uf}ICx0pWlZS^G4+Y@T!r=!nQ6>1m_ zpX{Fd@yj9GA{X`FCe2|ES2B*$QqK%L1&Jedw9$!sgfCAQ4a=3$phDh<6ks8nhxE+T z87Q!&tD0kUp7T}3J+;ZH>E8);?@!uI!g?X~p?UU1HtLkE7JibDpGKVnR#ix)eglNE z7w`Ga#pJT6g+P7X_cjWhhB?1kL(4beG-@PjC9Q+4UX4zcsOgl6tx}8R4rOPYg^R1l z0iTp)TnM9Xmt1{HN4m@WMD|Gg>Np77o_5W(1Yj9K0CNA~HLrQ>1Yde!5p5yhmE7(lI%{Bd+n{=KqnYy?vtx@W7ykqDyie6!eYho#LK(HX& zjO)`pBV%11>tbWyu`P2I3$!N1av!a}>6FeR*f!eRk!3&_;~0Rk)5%|1YwJ>-`-bUh zvhwdQWeT$xzN^fl1P|2_>Gl=qpN)IeHe{|Z%c^**=pMUYac+d)(-GvGe5}`47fig1C~ZFwmHK&d)Xb3KJ6rw5LR2Pq?c-YpcO2A&H{f4GbSWdml3&VOUujl9NhP&iJI)!OUArrAh98R6{ zi9kPX4GYb(7o%l@k?5F%fzJDZ8G|KP*97V3op=HXh0D=9r85R~bsA5An0>zgT|M5t z7NRJqDv+t2h-t6H`*|z|_XW^8{BPr{41rfv5_CO0tS7O`gZ!Y}%G}l}Zxnqa>{Xu- ztf7O57yS@|1L~b+Sz7e1jA4xJl*>7|s`bj?_;)sW7^blBCjuAp z81$#s9ot`ystg7F>;U+9mz+GIv$@eGAV@e210bWTQq(h5fyv0ov40iT9q0;alP?i_ z$cUNv_v9$W7+Vhwy$StlYnWnx)7p0-V*XaWYn0!HN)mxEu}g-~3jIsuagFKMerNyHHSPNL)zZ})Ft-96>_q` zWQz;TD1@#;y=4&8h@<5p_L6ttuE~t*m5d?J6;VTxo1j)kU&(ra=0dZAH-;Qnv~cge z6`r%XJ@DfFNxeDB*7sAh{i5k`LZ6JTlV#{TO z*>`G7gBwr!$&O6aoL&eneu6C8gLP)*Go0M|0L(>KN|>Cy*W-G~Cc?GzOc}9ErOP(* zCRFBd6epj!_9FD$0|>-hu*Je<7v1TVGq!U@!zY_|`Y=7R+C6(5jIFumpVHZaAAjqy zx(b%oL`QJRG`ucWg zGZQ<(l=pmEWq{s9#KLP+G4lc(HE|5>>0`>7HFGnZ<@E;nU`H4`LBO)n9CZ#;TBJjS zOK?YoqBoKjC|E?v~ZW{}bCSVK)UStInE}cLP`3*U$^M#aL{r$F#?Wj{9#UB zQf+yZ*q;yDR4)Gp9+6_#FhLDhv4psYh+iA*zAWr3P}$UEXPJhGO4xEB$gL3B?4eJW zmf{JX<}NU^?D)R3hiF;Ya28M)lD%Q4qgtwj?eAxJJtU;&e{IvhIEjG{)U`Mju-5ae zrTh2M0k##FhsX{-l9+gY6>B^t=#9q^=^rEP@9{nXCmT(%m)_4-1V=qST1GaMF>{ln zLWCJ!Z!_fbr}LS~o`Hdp84`iDv(%~nbN0pUIdri$U2>a0w#9b*{>Z&)@m*I%pr=vV zVhv8#24172r-<`yHqWKp*#7O_6_7H$2=Gjt!-e+EmaYyc57F>ucy=O`LsnCp*Pci8 zA$Df`COBIYyq;bCG@Z2+(`BArTNe0<<1azs04q#({)>UnG3n=g-k}7etlE0(zg>8y ztG~)b*t`8w{DwRVYQKh_JjhYVpyrK9MgZU|66RnvVfd^^Zkls8OVCqfweDu=u>;-- z#`2A6=iyoxzQZ%_p;3hWgtu=J9Er1x^MkV7%oTlWqZ}&>ZkE{V&}2>-YaTGzuk5~T z8}`DJ(Y7YL+=k|;=~|x!U0HpfiRH9XxO&rm<$T{9H#>^po2j>=T$zqn@Y&?%T$39U zAyS>0Soawy42_aQFKr~U{!}@YQ zsQ#ex&8mb#O3U#s1OeyUTb4Fg7~DEHNcc7J?EB_K<3**JdXL4*<1aeP3Wq^es*u^Z z2n)KV!@q+#aTn~K_ejHQY=xKQ?5NcRr3BfIL9+MZ$7=`&E)4ZhCZujh@Ooy)M(G2@ zI?B8T&6`_TOebagu>@27n>5YxFR4Q{;JH_F1wOtp*CYgoq%c>~-j&|W>rb$$?Y``+ zk~aZX-?@tM(El{mJTM9+Q^n6lVT5QK!ZXnZ`nWKhcx!hvRYo%UtjGHm@KhZ6z2j?s zH{Ow;6d~u;<|NU!o=%3G9@d$z4@lxzGLBz<*G1Qc??M}w`4oNGtAAdS4I6jQs_c+F zRCaTrjyYe390e(B^!O8D{9+@bGFKrw>FQIsgFn-&&`B?ck1$ZnBttI2klr8^nPl}K z6_hxxitwhATgLwApX&xFOwM>E{nxeIIy_yg4aJHoT81ef>?VO1$i zfP+Eq0E*5}GLnjw?3A))PjnO?+Nz5uK#u#_;@3Cd+sK9PFTowDXJ(WegvcdwMq`)g z)XAi@315@>`I=u6F&EFys@ds)naY>wzxT2ab4c2@y zY=2BWxg5hU+JZ01-{@n+8#6S8VyGuVHtfvqKFVhz=5(2?D5lHR>7>tt12r}u*voHy z_+;Ok#}~{u4BM;~9gpu`b-FSMT%lZ4JRb%UUT8#Bm8`Yf*EPbe-9MjFI#d@z1!ruq z^0eS~cpuax9Z)MBLeq5JpKsas{MapCou$7$eS7~~T%phL*}jE}$|X3=7F`f_c^tc~ zG;$1E05@rYZz0A|p=Pvd-EU-PBK1h4LRa|~ILZZQdsdM^Oh6!_(?9{v%uMx7TK0G9 zMw8`1Hb|GTfZeDAM95=AA6U=sF8dPm=N42?f`0o$Z4b9I9k-LV?s%D$qC66f{Gi6g z+0{KU`ctL&)4wElQjq3OX1z)W53e&{;y~0hu?9FKL^R^*2~kGKLYe^ZE6uX5WhPA$ z&5#)9?<~|VQ4xCxb0vEsJ^GjNUS?O1hv|6JOY4S}AKuX)s;&>f#Q=i>%L}{J5Z4(Q zel6i%L%tDjw)!F%oc5hieNa#!i?#+)(7Kyne!r1nys#y}#@S&LO8Dn_Wz$c|_~-?ENj{p4s0~ zceOj^N{AUx%-a@&jK#!@6)yEX5%riB+hAp2AKk0|4qNKehMLqCH5jizvH0Tfq-5OW zxE6Tc_xrk{L@Ol^P{~7XwkM+s@BYVIjgIC}OTfj9u`4e7XjXFJx8!v))3GCG{5Xd9&XS(y zjj(mhoAq{jxOA}k*tedk%xhBoPSNlhy|R9mOtJ7I%%KFlSlDYy_4Dufa(uBxaOV_Z zv(&UC-*f*QbXVuVAO98nT@jJuD@Z}c+jFl_9=~uD=UsAK9~(o;GLvEU>9eHno$eRk zfXD4*%0{(Bn}FYMxkKL(=)iTE-K?rOqFh{6?}2yIXY2Rh>Q=&3cuIz$cYzJ%igz{- z=|{(SD;HMorQ34d{(tV!)8y~PM&{8Qq)jnodZU6YL1k}k7J<%6`3-jE?X^7LA9p$~ z8`SwT;8Dx2@xj~O83?fs6s1yh#9+ z0Xk2o35F`G>>wOYmW&6){T&A=M#CInzXYL1YHx$@!(Tmu4=M|KVDea1dgcnzZc7cb zTz20Y{cr&w&4E`rJY@l=Wi4;t1`A4ZEJ}6R`kXLx&*(06@Mg`WVwUeQo1VK%OoKCg zx0W_zXSw_>*QDpyjf!Gv&r75Fz3;NDJpD`Wq%~U>ciKp7;iH}4cOACrzD|~Z{&WWH z7cBa3@q6A$S@A;p7{L`fg(@(HA~3YzLUCilpkPF4MV-KgFsK~;c3E|Xuh5%xM|;vy zd`URhS#wrShH$4e_SIoO%IU+Xl|+=BUk_bTk{@%(qfyVooBj8jBZyXxqlfiu0+LiF z9NatQU}j{@qC{U}yz&P(k&NA}-w#N>I^pz%OP2FZLQ3(Mn>w)O*=wWoYO<7HF`nr87kkhyTL&* zTXRFRDOk+T@T$U-AOblF#!$Jw zC-)GADmC(XvEz{e?N&HVg++kqXm|+*A2O43+;K1ApT~|26LiJc=BO{y7yq(wdcwm0 zZfs_n8lg!mQa25kZoH{J*$xn8hQd!rlOR%l8f|Nb-}z3*RE8*`T_do&%-RUol^$TO z9Y|fYp=YBhY-AemeTu4Rsi0m?Gn`8y4LpGfF{n9)|0atetka0BiZD2Ue>UNt+TxAi z=6`tWkcxCfv|W>DgI&h<0p#Ec$)82C*tzFIZ66wCs=*y>d3)CTu68KLEn#Oc>0cSh zP>8osm=B91ggoattN$88H@v-&ry)wxnSf9=fNogU)vMm7e+3`LOsUpoIUK!aL0557 zqb&GFVMWzSa$)yH4o&$z*)nq|WOEG7P)gXIp+<~OUo|rdtbXZ~zx;44B<3fBJEyaH zbKf-fI?A-gxR}6Ld)9Jk2dLJr0G5=-l*;6Vtcn|w!8rR{Zapxcy`7#@#Uu9ch9G#c1 zH8N?*-LiaYXHzV5`PKO6`AA0EGl8Lam~O#DS53eDM!mju-_ z5^g|pH!|f;e*&X{dMxqp=LL=6oK8Qx0Ybu;`QV!xWab7LkR%o=A`Xt-hPANu0_GXj zxm$ai=2T<;Sw8=X3VcUx7zJ7O1Z*^H-!|Xmi%YJg7{09;T{GJEC@{(zlet7nHi@#! z2Rz1e@v~V)HqxghqRk%oKEvqSm(kJz{uJwJO6z8+wj>?x)+xL{<)5c3rj~)Alr)|D z`hd44uHA(S=G!splxD}!LY*h6e9`K1y5PLC)UYQn^~&}W83Xcp#qo#2pK;VtI!`B4 zznHQ*;}zO(IBMWH&lFkPvWXD_U+7`y~`0Z}y|7EJ(ulLnJR_GLgzm%Pim=pgC`KjDtOga85 z*&y>Mt$2b{hd0gq*}k2>6Z3S$+`HkfN!T)2Lr{4?T5p*46V2QB#e#6;!x_5SU1o7x zn)pLNpqA_OB7%~~oz?h`8LE+?U#EK8Nx~O!Ih1J>n^TFB6I9i#?IW8i-Qbd6HVul?%azgIB42qJGdlM{S)b6zJpYrkQq`JkU^~D~y>uStisQAYPN#WP1${u$H zd=d--_MeqKe_{@g1^Fi<0Pk!~Rr7FjSu zyd@5-*S3-umJes30L}-dt2@$TQtUH_f0}AJ&ojMf z={!^2dL|-G&&)Ukp3%=aV|*S&YsngqZ*Co z2u72jbTpows|or#%*i%TWg#BxzdU!PE*NC5upw4#=)Gu1x9N3gN}Bly{WRQ$uMM||pP35D$Cxk`?{0q_=&RzrdB_I2Ghq1~c^Sy! zpH6YcCnTPb`Y>FTZ*d~UFUzUFT4->p7s;sy_~e#i4PN}TcbCVX%s#U#f6JOJdNpTU ztQD8j%B`2)7?Em>A;%6$#AguDG_6ENVoNLpB&*W@py_#f+W>q%4I_#R-rLtk$0(uN z%l&0fz8{b$eS5Qu@2l6#ImUlms;3lDR!D}c_~!cqgQ z!wh2K0J4C%=#g2)UBRaAP9IM`-2c32XATAIa8wkY70p#Rl2J+AX zfwUiSicS{)uMiDaQ)g$e1%Oq`)ZPt@%&MXxsly`WVQ+6{>gf2NTr{k0+yEcff91pk z&;q-p!^as6U{yA=Rd;vz=p6}Q2mVJau7J*@6Uo)dmNqV@2%|xKO$v0egX)VAHC#^fJ>W|Q(6NT-*e z>synSNc?Q^W5Km9esv7TN!B~KO>AQC`S$!~LbyLBDWcz{?N6}&n@OTj-xBPe*H%$k zDm<$Hw*N^|ppd#Ow9!}gi0x+`$rx%+>Ju!qq7Ql>qjEGb-tx@?i_zKF!k~P?YI=@l zpJ#IlS;^RLX?wQ)NF2Hps z(cuTZnX3&hcclnga@OZ;rMd{Ex{{CqNGSTIyjA*;jaY2-_zwihgJT*>*-$3VIRnA+ z(7hQ-`t&Te@e$=T&Z8TyTc_u@t0R)*gdP56OYvXrxi$I2Gke+MFWE z&u`-zxYDcnPmT6>Z6;yyIL&&Wm3b#M60pRmByoo<$jWRi7YRmcbSxMo$me^AbFqF3 zu$H)EP4w|7oVS`Kn((Ma~X%)} z7h2J7QLtCm{Dt&B++B@%y=NqLXrUm+M$KLn$sEF~n@Alcqo76gg%1D$w0VfP$GJhfi5{g%6fpy7E zH>0hXfX8*agdnfj7lpYVo)x5vmV(VMPZ1gY?<>v051s`#G`?F)(|O1~i)3%Np5C4u zTt}`59ZN8RM=FnHA-~g<-mIW(LgLFMaKN)Cbb)MCqur|su@f$yD^RDd+8@=+C=YH-uH5H!A`0-A`<~ddLLeia$-W}u6Z*T6&8(6zC zwEVs}$kT|Q**vVk^ZUD;iLzx~nFQ?#*ET>Q>T4HXZ{y;b=qDqulY11nQyjEY=6G^& z3q3-oK1S7DFA?%LHk38{$c8gAU;iV0;-)6a5{C`f^?mS3Pxx;}f~xS}EnxLkGcLST z{@N7_&0(K~a*xI)x~iLdH<_RVeTJ&Iv0vv6FMst8eHxfva(8;~0$#txDclMoeU zlM)93g#iEWkdJv3z>Ze#)&Nd6j{n+{|3CW6Q_t}#Xs8l=+H!*s`=A0sDA%BNzZJD@ zN5-Qd(|rI*z9F}5!3=~BB@&?37pM>`FfqlZ)z?j%nVE)wKwoHv@X+flfqHyB8gN`rq5RWzAciZ$iNUMJ~I}m=rQX@y5uK` zh6P4s2?Ja^F-;Ab;%C7eTqMES~cZ(s`N7)`_dh?v48H{ zN;GC@6|tFn*44g@M#UoyGpNeC$MKd*a3*!0phbsj&qos#JtV!%7R3{IU$m)J0m66U ze-1L3?S=0WsVc`BdD&yyMd{xJN|o&R*2fdDUS%44gjiFZ`(HOGno)vW)?8wi6$Glh z))}%RhsjA>qfoDI8y{};9Y0SD#2h+~M3WATImLGlD48|PyCf_tDtPdn<1r5)0( z?hp|69H*!3z-1A*2vECPNG{gv-$u^u#PTruO6DJZJ{<6I5jDVtwXj|6G@z)ZHZ-Kk zfA#bT%O&;nNE0=${wWIIuBt7LI>c5JPB!Rk?YIF(l?e^M(V#1lNv2JtC@f9KS2YBrNT=Z`4rmpVZt{v 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 + * @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 + * @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 + * @version 1.0 + */ + + +class Commit extends Entry { + private long seqnumtrans; + private long seqnumcommit; + private long transarbitrator; + + private Set keyValueUpdateSet = null; + + + public Commit(Slot slot, long _seqnumtrans, long _seqnumcommit, long _transarbitrator, Set _keyValueUpdateSet) { + super(slot); + seqnumtrans = _seqnumtrans; + seqnumcommit = _seqnumcommit; + transarbitrator = _transarbitrator; + + keyValueUpdateSet = new HashSet(); + + 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 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 kvSet = new HashSet(); + 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 updateLiveKeys(Set kvSet) { + + if (!this.isLive()) + return new HashSet(); + + Set toDelete = new HashSet(); + + for (KeyValue kv1 : kvSet) { + for (Iterator 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 + * @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 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 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(); + + 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 + * @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 + * @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 + * @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 + * @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 + * @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 { + 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 keyValueUpdateSet = null; + private Set keyValueGuardSet = null; + private long arbitrator = -1; + private long machineLocalTransSeqNum = -1; + + public PendingTransaction() { + keyValueUpdateSet = new HashSet(); + keyValueGuardSet = new HashSet(); + } + + /** + * 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 getKVUpdates() { + return keyValueUpdateSet; + } + + + /** + * Get the key value update set + * + */ + public Set getKVGuard() { + return keyValueGuardSet; + } + + public void setMachineLocalTransSeqNum(long _machineLocalTransSeqNum) { + machineLocalTransSeqNum = _machineLocalTransSeqNum; + } + + public long getMachineLocalTransSeqNum() { + return machineLocalTransSeqNum; + } + + public boolean evaluateGuard(Map keyValTableCommitted, Map keyValTableSpeculative, Map 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 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 _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 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(); + 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 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 getLiveEntries(boolean resize) { + Vector liveEntries = new Vector(); + 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 > lastmessagetable = new HashMap >(); + // machine id -> ... + private HashMap > watchlist = new HashMap >(); + private Vector rejectedmessagelist = new Vector(); + 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 pendingTransQueue = null; // Queue of pending transactions + private Map> commitMap = null; // List of all the most recent live commits + private Map abortMap = null; // Set of the live aborts + private Map committedMapByKey = null; // Table of committed KV + private Map commitedTable = null; // Table of committed KV + private Map speculativeTable = null; // Table of speculative KV + private Map uncommittedTransactionsMap = null; + private Map arbitratorTable = null; // Table of arbitrators + private Map newKeyTable = null; // Table of speculative KV + private Map> newCommitMap = null; // Map of all the new commits + private Map lastCommitSeenSeqNumMap = null; // sequence number of the last commit that was seen grouped by arbitrator + private Map lastCommitSeenTransSeqNumMap = null; // transaction sequence number of the last commit that was seen grouped by arbitrator + private Map lastAbortSeenSeqNumMap = null; // sequence number of the last abort that was seen grouped by arbitrator + private Map pendingTransSpeculativeTable = null; + private List pendingCommitsList = null; + private List pendingCommitsToDelete = null; + private Map localCommunicationChannels; + private Map transactionStatusMap = null; + private Map 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(); + commitMap = new HashMap>(); + abortMap = new HashMap(); + committedMapByKey = new HashMap(); + commitedTable = new HashMap(); + speculativeTable = new HashMap(); + uncommittedTransactionsMap = new HashMap(); + arbitratorTable = new HashMap(); + newKeyTable = new HashMap(); + newCommitMap = new HashMap>(); + lastCommitSeenSeqNumMap = new HashMap(); + lastCommitSeenTransSeqNumMap = new HashMap(); + lastAbortSeenSeqNumMap = new HashMap(); + pendingTransSpeculativeTable = new HashMap(); + pendingCommitsList = new LinkedList(); + pendingCommitsToDelete = new LinkedList(); + localCommunicationChannels = new HashMap(); + transactionStatusMap = new HashMap(); + transactionStatusNotSentMap = new HashMap(); + 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 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 strList = new ArrayList(); + // 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 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(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 newCommits = new LinkedList(); + 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> 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 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> 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 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> 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 newCommits = new LinkedList(); + for (int i = 0; i < numberOfCommites; i++ ) { + bbDecode.get(); + Commit com = (Commit)Commit.decode(null, bbDecode); + newCommits.add(com); + } + + return new Pair>(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> 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> doLocalUpdateAndArbitrate(Transaction ut, Long lastCommitSeen) { + + List returnCommits = new ArrayList(); + + if ((lastCommitSeenSeqNumMap.get(localmachineid) != null) && (lastCommitSeenSeqNumMap.get(localmachineid) > lastCommitSeen)) { + // There is a commit that the other client has not seen yet + + Map cm = commitMap.get(localmachineid); + if (cm != null) { + + List commitKeys = new ArrayList(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>(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>(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>(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 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 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 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 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 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 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(true, seenliveslot, seqn); + } + } + } + } + + // Did not resize + return new ThreeTuple(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 speculativeTableTmp = new HashMap(); + List transSeqNums = new ArrayList(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 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 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(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 machineSet = new HashSet(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 commitSeqNums = new ArrayList(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 cm = commitMap.get(arb); + if (cm == null) { + cm = new HashMap(); + } + + 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 commitsToEditSet = new HashSet(); + + for (KeyValue kv : entry.getkeyValueUpdateSet()) { + commitsToEditSet.add(committedMapByKey.get(kv.getKey())); + } + + commitsToEditSet.remove(null); + + for (Commit prevCommit : commitsToEditSet) { + + Set deletedKV = prevCommit.updateLiveKeys(entry.getkeyValueUpdateSet()); + + if (!prevCommit.isLive()) { + Map 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 cm = commitMap.get(arb); + if (cm == null) { + cm = new HashMap(); + } + 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> i = transactionStatusMap.entrySet().iterator(); i.hasNext();) { + Map.Entry 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> 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 speculativeTableTmp = new HashMap(); + List utSeqNums = new ArrayList(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 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 watchset = new HashSet(); + for (Map.Entry > 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 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 arbMap = newCommitMap.get(entry.getTransArbitrator()); + + if (arbMap == null) { + arbMap = new HashMap(); + } + + 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 entries = watchlist.get(machineid); + if (entries == null) + watchlist.put(machineid, entries = new HashSet()); + entries.add(entry); + } + + private void updateLastMessage(long machineid, long seqnum, Liveness liveness, boolean acceptupdatestolocal, HashSet machineSet) { + machineSet.remove(machineid); + + HashSet watchset = watchlist.get(machineid); + if (watchset != null) { + for (Iterator 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> i = abortMap.entrySet().iterator(); i.hasNext();) { + Abort abort = i.next().getValue(); + + if ((abort.getMachineID() == machineid) && (abort.getTransSequenceNumber() <= seqnum)) { + abort.setDead(); + i.remove(); + } + } + + Pair lastmsgentry = lastmessagetable.put(machineid, new Pair(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 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 transStatusList = new ArrayList(); + + // 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 transStatusList = new ArrayList(); + + // 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 transStatusList = new ArrayList(); + + // 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 transStatusList = new ArrayList(); + + // 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 transStatusList = new ArrayList(); + + // 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 transStatusList = new ArrayList(); + + // 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 transStatusList = new ArrayList(); + + // 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 transStatusList = new ArrayList(); + + // 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 { + 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 keyValueUpdateSet = null; + private Set keyValueGuardSet = null; + private Long arbitrator; + + public Transaction(Slot slot, long _seqnum, long _machineid, Long _arbitrator, Set _keyValueUpdateSet, Set _keyValueGuardSet) { + super(slot); + seqnum = _seqnum; + machineid = _machineid; + arbitrator = _arbitrator; + // keyValueUpdateSet = new HashSet(); + // keyValueGuardSet = new HashSet(); + + // 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 getkeyValueUpdateSet() { + return keyValueUpdateSet; + } + + public Set getkeyValueGuardSet() { + return keyValueGuardSet; + } + + public boolean evaluateGuard(Map keyValTableCommitted, Map 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 kvSetUpdates = new HashSet(); + for (int i = 0; i < numberOfKeys; i++) { + KeyValue kv = KeyValue.decode(bb); + kvSetUpdates.add(kv); + } + + int numberOfGuards = bb.getInt(); + Set kvSetGuards = new HashSet(); + 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 +#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 +#include +#include +#include +#include +#include +#include +#include +#include + +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=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= 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 +#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 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(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(transactionMachineId, transactionClientLocalSequenceNumber); + } + + public Pair 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 parts = null; + private Set missingParts = null; + private boolean isComplete = false; + private boolean hasLastPart = false; + private Set keyValueUpdateSet = null; + private boolean isDead = false; + private long sequenceNumber = -1; + private long machineId = -1; + private long transactionSequenceNumber = -1; + + private Set liveKeys = null; + + public Commit() { + parts = new HashMap(); + keyValueUpdateSet = new HashSet(); + + liveKeys = new HashSet(); + } + + public Commit(long _sequenceNumber, long _machineId, long _transactionSequenceNumber) { + parts = new HashMap(); + keyValueUpdateSet = new HashSet(); + + liveKeys = new HashSet(); + + sequenceNumber = _sequenceNumber; + machineId = _machineId; + transactionSequenceNumber = _transactionSequenceNumber; + isComplete = true; + } + -/** - * This Entry records the commit of a transaction. - * @author Ali Younis - * @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 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(); + hasLastPart = true; - public Commit(Slot slot, long _seqnumtrans, long _seqnumcommit, long _transarbitrator, Set _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(); + 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 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 getParts() { + return parts; + } - Set kvSet = new HashSet(); - 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 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 updateLiveKeys(Set 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(); + // 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 toDelete = new HashSet(); + while (remaining > 0) { - for (KeyValue kv1 : kvSet) { - for (Iterator 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 partId = null; + private Pair 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(sequenceNumber, partNumber); + commitId = new Pair(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 getPartId() { + return partId; + } + + public Pair 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 { +class Pair { 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 { 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 keyValueUpdateSet = null; private Set 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(); keyValueGuardSet = new HashSet(); } @@ -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 getKVUpdates() { return keyValueUpdateSet; } - /** - * Get the key value update set - * - */ + * Get the key value update set + */ public Set 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 keyValTableCommitted, Map keyValTableSpeculative, Map 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 > lastmessagetable = new HashMap >(); - // machine id -> ... - private HashMap > watchlist = new HashMap >(); - private Vector rejectedmessagelist = new Vector(); - 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 pendingTransQueue = null; // Queue of pending transactions - private Map> commitMap = null; // List of all the most recent live commits - private Map abortMap = null; // Set of the live aborts - private Map committedMapByKey = null; // Table of committed KV - private Map commitedTable = null; // Table of committed KV - private Map speculativeTable = null; // Table of speculative KV - private Map uncommittedTransactionsMap = null; - private Map arbitratorTable = null; // Table of arbitrators - private Map newKeyTable = null; // Table of speculative KV - private Map> newCommitMap = null; // Map of all the new commits - private Map lastCommitSeenSeqNumMap = null; // sequence number of the last commit that was seen grouped by arbitrator - private Map lastCommitSeenTransSeqNumMap = null; // transaction sequence number of the last commit that was seen grouped by arbitrator - private Map lastAbortSeenSeqNumMap = null; // sequence number of the last abort that was seen grouped by arbitrator - private Map pendingTransSpeculativeTable = null; - private List pendingCommitsList = null; - private List pendingCommitsToDelete = null; - private Map localCommunicationChannels; - private Map transactionStatusMap = null; - private Map 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 committedKeyValueTable = null; // Table of committed key value pairs + private Map speculatedKeyValueTable = null; // Table of speculated key value pairs, if there is a speculative value + private Map pendingTransactionSpeculatedKeyValueTable = null; // Table of speculated key value pairs, if there is a speculative value from the pending transactions + + private Map liveNewKeyTable = null; // Table of live new keys + private HashMap> lastMessageTable = null; // Last message sent by a client machine id -> (Seq Num, Slot or LastMessage); + private HashMap> rejectedMessageWatchListTable = null; // Table of machine Ids and the set of rejected messages they have not seen yet + private Map arbitratorTable = null; // Table of keys and their arbitrators + private Map, Abort> liveAbortTable = null; // Table live abort messages + private Map, TransactionPart>> newTransactionParts = null; // transaction parts that are seen in this latest round of slots from the server + private Map, CommitPart>> newCommitParts = null; // commit parts that are seen in this latest round of slots from the server + private Map lastArbitratedTransactionNumberByArbitratorTable = null; // Last transaction sequence number that an arbitrator arbitrated on + private Map liveTransactionBySequenceNumberTable = null; // live transaction grouped by the sequence number + private Map, Transaction> liveTransactionByTransactionIdTable = null; // live transaction grouped by the transaction ID + private Map> liveCommitsTable = null; + private Map liveCommitsByKeyTable = null; + private Map lastCommitSeenSequenceNumberByArbitratorTable = null; + private Vector rejectedSlotList = null; // List of rejected slots that have yet to be sent to the server + + private List pendingTransactionQueue = null; + private List pendingSendArbitrationEntries = null; + private List pendingSendArbitrationEntriesToDelete = null; + private Map> transactionPartsSent = null; + private Map 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(); - commitMap = new HashMap>(); - abortMap = new HashMap(); - committedMapByKey = new HashMap(); - commitedTable = new HashMap(); - speculativeTable = new HashMap(); - uncommittedTransactionsMap = new HashMap(); + /** + * 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(); + speculatedKeyValueTable = new HashMap(); + pendingTransactionSpeculatedKeyValueTable = new HashMap(); + liveNewKeyTable = new HashMap(); + lastMessageTable = new HashMap>(); + rejectedMessageWatchListTable = new HashMap>(); arbitratorTable = new HashMap(); - newKeyTable = new HashMap(); - newCommitMap = new HashMap>(); - lastCommitSeenSeqNumMap = new HashMap(); - lastCommitSeenTransSeqNumMap = new HashMap(); - lastAbortSeenSeqNumMap = new HashMap(); - pendingTransSpeculativeTable = new HashMap(); - pendingCommitsList = new LinkedList(); - pendingCommitsToDelete = new LinkedList(); - localCommunicationChannels = new HashMap(); - transactionStatusMap = new HashMap(); - transactionStatusNotSentMap = new HashMap(); - mutex = new Semaphore(1); + liveAbortTable = new HashMap, Abort>(); + newTransactionParts = new HashMap, TransactionPart>>(); + newCommitParts = new HashMap, CommitPart>>(); + lastArbitratedTransactionNumberByArbitratorTable = new HashMap(); + liveTransactionBySequenceNumberTable = new HashMap(); + liveTransactionByTransactionIdTable = new HashMap, Transaction>(); + liveCommitsTable = new HashMap>(); + liveCommitsByKeyTable = new HashMap(); + lastCommitSeenSequenceNumberByArbitratorTable = new HashMap(); + rejectedSlotList = new Vector(); + pendingTransactionQueue = new ArrayList(); + pendingSendArbitrationEntries = new ArrayList(); + pendingSendArbitrationEntriesToDelete = new ArrayList(); + transactionPartsSent = new HashMap>(); + outstandingTransactionStatus = new HashMap(); + + // 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 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 strList = new ArrayList(); - // 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 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(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 newCommits = new LinkedList(); - 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> 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 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 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> 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 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 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> 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 newCommits = new LinkedList(); - for (int i = 0; i < numberOfCommites; i++ ) { - bbDecode.get(); - Commit com = (Commit)Commit.decode(null, bbDecode); - newCommits.add(com); - } - - return new Pair>(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> 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> doLocalUpdateAndArbitrate(Transaction ut, Long lastCommitSeen) { - - List returnCommits = new ArrayList(); - - if ((lastCommitSeenSeqNumMap.get(localmachineid) != null) && (lastCommitSeenSeqNumMap.get(localmachineid) > lastCommitSeen)) { - // There is a commit that the other client has not seen yet - - Map cm = commitMap.get(localmachineid); - if (cm != null) { + private Pair sendSlotsToServer(Slot slot, int newSize) throws ServerException { - List commitKeys = new ArrayList(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>(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>(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>(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(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 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 retTup = doMandatoryResuce(s, resize); - - // Resize was needed so redo call - if (retTup.getFirst()) { - return tryput(pendingTrans, true); - } + // Do mandatory rescue of entries + ThreeTuple 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(true, null, null); } - doOptionalRescue(s, seenliveslot, seqn, resize); - Pair 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 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 partsSent = transactionPartsSent.get(transaction); + if (partsSent == null) { + partsSent = new ArrayList(); + transactionPartsSent.put(transaction, partsSent); + } + + partsSent.add(part.getPartNumber()); + transactionPartsSent.put(transaction, partsSent); - doOptionalRescue(s, seenliveslot, seqn, resize); - Pair 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(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 doMandatoryResuce(Slot s, boolean resize) { - long newestseqnum = buffer.getNewestSeqNum(); - long oldestseqnum = buffer.getOldestSeqNum(); - if (lastliveslotseqn < oldestseqnum) - lastliveslotseqn = oldestseqnum; + private ThreeTuple 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 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(true, seenliveslot, seqn); - } - } } - } - // Did not resize - return new ThreeTuple(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 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(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 speculativeTableTmp = new HashMap(); - List transSeqNums = new ArrayList(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(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 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(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 machineSet = new HashSet(lastmessagetable.keySet()); // + // Set to keep track of messages from clients + HashSet machineSet = new HashSet(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 commitSeqNums = new ArrayList(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 cm = commitMap.get(arb); - if (cm == null) { - cm = new HashMap(); - } + // 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, TransactionPart> parts = newTransactionParts.get(machineId); + + // Iterate through all the parts for that machine Id + for (Pair 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 commitsToEditSet = new HashSet(); + // 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 deletedKV = prevCommit.updateLiveKeys(entry.getkeyValueUpdateSet()); + public void arbitrateFromServer() { - if (!prevCommit.isLive()) { - Map 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 transactionSequenceNumbers = new ArrayList(liveTransactionBySequenceNumberTable.keySet()); + Collections.sort(transactionSequenceNumbers); - // Add the new commit - Map cm = commitMap.get(arb); - if (cm == null) { - cm = new HashMap(); - } - cm.put(entry.getSequenceNumber(), entry); - commitMap.put(arb, cm); + // Collection of key value pairs that are + Map speculativeTableTmp = new HashMap(); - 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> i = transactionStatusMap.entrySet().iterator(); i.hasNext();) { - Map.Entry 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> 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 speculativeTableTmp = new HashMap(); - List utSeqNums = new ArrayList(uncommittedTransactionsMap.keySet()); + // Iterate through all the machine Ids that we received new parts for + for (Long machineId : newCommitParts.keySet()) { + Map, 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 partId : parts.keySet()) { + CommitPart part = parts.get(partId); - if (utSeqNums.get(0) > (lastUncommittedTransaction)) { + // Get the transaction object for that sequence number + Map commitForClientTable = liveCommitsTable.get(part.getMachineId()); - speculativeTable.clear(); - lastUncommittedTransaction = -1; + if (commitForClientTable == null) { + // This is the first commit from this device + commitForClientTable = new HashMap(); + 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 commitForClientTable = liveCommitsTable.get(arbitratorId); + + // Sort the commits in order + List commitSequenceNumbers = new ArrayList(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 commitsToEdit = new HashSet(); + 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 transactionSequenceNumbersSorted = new ArrayList(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 incompleteTransactionArbitrator = new HashSet(); + 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> 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> 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 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 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 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 watchset = new HashSet(); - for (Map.Entry > 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 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 deviceWatchSet = new HashSet(); + for (Map.Entry> 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 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 arbMap = newCommitMap.get(entry.getTransArbitrator()); - if (arbMap == null) { - arbMap = new HashMap(); + // Set dead a transaction if we can + Transaction transactionToSetDead = liveTransactionByTransactionIdTable.remove(new Pair(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, TransactionPart> transactionPart = newTransactionParts.get(entry.getMachineId()); + + if (transactionPart == null) { + // Dont have a table for this machine Id yet so make one + transactionPart = new HashMap, 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 entries = watchlist.get(machineid); - if (entries == null) - watchlist.put(machineid, entries = new HashSet()); - entries.add(entry); + /** + * Process new commit entries and save them for future use. Delete duplicates + */ + private void processEntry(CommitPart entry) { + Map, CommitPart> commitPart = newCommitParts.get(entry.getMachineId()); + + if (commitPart == null) { + // Dont have a table for this machine Id yet so make one + commitPart = new HashMap, 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 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 machineSet) { - HashSet 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 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 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, 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> 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 lastMessageEntry = lastMessageTable.put(machineId, new Pair(seqNum, liveness)); + if (lastMessageEntry == null) { + // If no last message then there is nothing else to process + return; } - Pair lastmsgentry = lastmessagetable.put(machineid, new Pair(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 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 entries = rejectedMessageWatchListTable.get(machineId); + if (entries == null) { + // There is no set for this machine ID yet so create one + entries = new HashSet(); + 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 transStatusList = new ArrayList(); - - // 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 transStatusList = new ArrayList(); - - // 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 transStatusList = new ArrayList(); - - // 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 transStatusList = new ArrayList(); - - // 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 transStatusList = new ArrayList(); - - // 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 transStatusList = new ArrayList(); - - // 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 transStatusList = new ArrayList(); - - // 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 transStatusList = new ArrayList(); - - // 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 transStatusList = new ArrayList(); + + // 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 transStatusList = new ArrayList(); + + // 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 transStatusList = new ArrayList(); + + // 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 transStatusList = new ArrayList(); + + // 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 transStatusList = new ArrayList(); + + // 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 transStatusList = new ArrayList(); + + // 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 keyValueUpdateSet = null; + private Map parts = null; + private Set missingParts = null; + private List partsPendingSend = null; + private boolean isComplete = false; private Set keyValueGuardSet = null; - private Long arbitrator; + private Set keyValueUpdateSet = null; + private boolean isDead = false; + private long sequenceNumber = -1; + private long clientLocalSequenceNumber = -1; + private long arbitratorId = -1; + private long machineId = -1; + private Pair transactionId = null; + + private int nextPartToSend = 0; + private boolean didSendAPartToServer = false; + + private TransactionStatus transactionStatus = null; + + public Transaction() { + parts = new HashMap(); + keyValueGuardSet = new HashSet(); + keyValueUpdateSet = new HashSet(); + partsPendingSend = new ArrayList(); + } + + 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(); - public Transaction(Slot slot, long _seqnum, long _machineid, Long _arbitrator, Set _keyValueUpdateSet, Set _keyValueGuardSet) { - super(slot); - seqnum = _seqnum; - machineid = _machineid; - arbitrator = _arbitrator; - // keyValueUpdateSet = new HashSet(); - // keyValueGuardSet = new HashSet(); + 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 getkeyValueUpdateSet() { - return keyValueUpdateSet; + for (Integer i : parts.keySet()) { + parts.get(i).setSequenceNumber(sequenceNumber); + } } - public Set getkeyValueGuardSet() { - return keyValueGuardSet; + public long getClientLocalSequenceNumber() { + return clientLocalSequenceNumber; } - public boolean evaluateGuard(Map keyValTableCommitted, Map keyValTableSpeculative) { - for (KeyValue kvGuard : keyValueGuardSet) { + public Map 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 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 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 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 kvSetUpdates = new HashSet(); - 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 kvSetGuards = new HashSet(); - 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 committedKeyValueTable, Map speculatedKeyValueTable, Map 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 transactionId = null; + private Pair 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(machineId, clientLocalSequenceNumber); + partId = new Pair(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 getTransactionId() { + return transactionId; + } + + public long getArbitratorId() { + return arbitratorId; + } + + public Pair 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