From c60cc2f55244690a93770068b109da50b1e9319c Mon Sep 17 00:00:00 2001 From: David Dias Date: Mon, 14 Sep 2015 08:55:07 +0100 Subject: [PATCH 01/86] Initial commit --- .gitignore | 27 +++++++++++++++++++++++++++ LICENSE | 22 ++++++++++++++++++++++ README.md | 2 ++ 3 files changed, 51 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..123ae94 --- /dev/null +++ b/.gitignore @@ -0,0 +1,27 @@ +# Logs +logs +*.log + +# Runtime data +pids +*.pid +*.seed + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (http://nodejs.org/api/addons.html) +build/Release + +# Dependency directory +# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git +node_modules diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..59a33ba --- /dev/null +++ b/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 David Dias + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/README.md b/README.md new file mode 100644 index 0000000..132222e --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +# abstract-socket +A test suite and interface you can use to implement a socket. From 549395cd56afb7fb4b5de1c227af1642114c10c1 Mon Sep 17 00:00:00 2001 From: David Dias Date: Tue, 15 Sep 2015 15:06:42 +0100 Subject: [PATCH 02/86] add badge --- img/badge.png | Bin 0 -> 5258 bytes img/badge.sketch | Bin 0 -> 65536 bytes img/badge.svg | 19 +++++++++++++++++++ 3 files changed, 19 insertions(+) create mode 100644 img/badge.png create mode 100644 img/badge.sketch create mode 100644 img/badge.svg diff --git a/img/badge.png b/img/badge.png new file mode 100644 index 0000000000000000000000000000000000000000..a79ca44967796cd1569661767d3bfbe09a1ad512 GIT binary patch literal 5258 zcmV;56m{!~P)Px}L`g(JRCodHT@O%HN1FeUKm;ogLD7vQ0YtOt#$kn6kmNKnnmf=GL$a``QrN`o z1+0|e&t=ucO}H#rVR8RNERqAP4W2=7H;{{vD8y1m#a*!yOdL$$)KS<362}b_m_)`f z$=ug(=FPk}^9J4k3X#`UGxNIp>#x85-tTpP{dM;nKnaQleqb8#vCC`yfyu3iJ9RbS zbIa6iyVCL>ss?<8f?KUhhbz>Fay@x8;N!y^1%T4iPM-UsNFq}M3TS02N+Fv(8c;wx zdG3WGiA)VBpp~g8g>3R@KmqOKxfhBgGBqIb9t>CJ^Ups=MMVV;9y|!SUIsSfTd`sV zo_p>&+;h)8!?9QL6b;DM0PjcOd|jTvXfPP?%rnn`0r&gwzn9IN5i;P;nl%frzWOTW z&70>{S%p~90Ivao%S+zkLV0=lr~u0@S2s<5}M2i7zDvA_8Y`gFBOv zv^$tHd3sqUqSv5xY1a=TYUw?U7wuhaL1P2f#?E!f9lSUV)wNl+c>NxncSWta8+F_B zp^ow4Mh%}BHMUkZ!|33^UzpHPoQ=%bh-%5WJvq{)U`W_x)1#qa&mI8v#yhGr1Mv{UI6dg>a1qCE9N~QD?mEL#l>0K=l=Wex9-}t z%Svu9>pSnfGwkQUfdke(d-hlp5)!y_#l^+a%x^$yUuzlHJ|N55F(gsp(4dt(BvwP; zfH-ro!Liga(TzVyCNqjU8OU3>@hiAd|uhL)%`cSX%uC zywcRm(lffh(CLdSrWdVvAd=5f?G)AK*Qr9yR^ft%=dH5S?O>fgi}K^JQ2 zZNTy^^^})Lrj3oZufuNE?9kx9+nvOg?)^wE(Q$mrj(2f8)OPK4dtL*jAUSB%@QpJiw?X5=Okgp8*BP ztK9~5xa|gp#;!$qk;cq%rOqX}QJ z1{ym@KLO{CL$$V8ViFe596)<}JNg2Cj8r>uqxQUmx}(V^v#OvGIm^Pibl=c;{7H8N z^|kfLjtOzlvJ3y|_D3yf8vN;j_LX|5MI}bB7ddSeRplWvB|sawpNkvxNE}F z-GofF8tTmL&^z*dG0on^ME_WPqwo9IB#w!;>69c8{r#MGJ|t|mtwQ4EOm)f-8E((F zC-MESlY*}Jhfcc%s)+eesZ^LBp+c?=pSWZm5|Oh=WO$e;Ep)LODQPK)wLg5KOcoh% zOYx8}dcDYMtA|>J$x?b0=HC?8f%nX_w_^wPo!p8Sm)UwYzH!~}zlR#FmZA$VRL!S4 zv10g&O{1J}`s|~>b3}=W_laCY&XNw~!vQBxpx^=NEbMkQbj?2eXH=tO+cIF#&UI)A zSFaA?j(Lk5Wq8_4hAWOVcG2BZhx$*x#*(C;VVM+gU2OnSX>o)X*=xP<1;y;Vj!lW4 z);Ii7@D75)GBN3CB$K$z}*lTZyQx~h1<}~o80D3yu3Ue zxf|Bw=s#Tj7g>&0u(vQ;r}KEpNt#^w1V?RWfSM2mFQZ1|{CIhRn# z!gR1c7Bp3kszMZ_2FK%#feuvU z*=H}>dTuq036q#r3SHkDh;>>3HW}w8;Y3FxvKU}=_1LkaiUYVxQ-oH-tB7`8JhPXd zHQm)sPvu>PU%zS7ZFM+Y4z{T6dk&@xXJVL(%_llgn!yQz9fgHt&Jbl{9(gr(xybEG zTLNxe;Ao2w-3~BQq=}BgiMq*_+ct<{&iC6@Bi~ zn2cAr1p;5PZG(eHrg!HsJ}G9Bz0YJpKp@F6g4{M*>NxxYF*nrpi4@CYv6x}8_`;vL z5WEOCN-u(r)6s2cFZof}*2E=CmbhjsaYX~N zG~hL88CTZRzWw&w7#MI)gBc^X*Y@1GbM%DzBQ`dc1Fuku+i$-eYu2p60}nhPOjfQH z4S1!2QGwP|4M&a~K|w(QxAW_*x88CCt*3HIx}pJZG#~`6tclog%>DdqGX~bvjP$pL1XitD#o4ZMD^}+|#2K zRw<`wfN8)x|AvIln9r8Ew!Z%QYe~<7L=jQa$&)8R+Zeg>qKpwDK|w)y?6JobkHiRi zx*4;_3|a%i3h9m*s)`H{q@%o6&2-)%st)Ii&KC5 zJ5HUuNO`kyZ+rq)#7AS|Jg@E|PDD!g=~J*o#vv+L3<;zDG|nLs@lnCD#PXJ27E>3Z zF`EYq7A(Mw88bL{7xTw@6Am9f%mJ1G{?=P>?-w5+u;)w zhJ@rAd|~#7x&I`R6BZ&sy&c^*xne|ES*>%mw>~U-IefRc^nenx+Gi2 zXa7v}`_9Rx9}S^*D3hpx0gQp@w%cy=+TCR~+cs~o1qcTIY15{0riMf{jwIH@GO#k= zFzcOJZ!IVEHWy=lav{o&TtJT6*)wn6l#i{nHlJIQg8ffL%VG6{q&ZVtg23c#X?xIj zKB_x95aPBjeFY!TUZS!N=Pt708*jwP5}>H{-r>o{m^r>~WrPsr*EZWK2urnVq0@^0mLtrGCSzkvRgVcDj2B;}bLH@pW7rBAnme;W3*j2&i zOFN0_BAV#3|IqIpGO4q7Lhn2%A?9ViMD!{=bgzUdVd=%b;!L|9e4x(Xjc!j(6LWQo zjTyA;W-{Q6#}RSHN6yEs6(c0{9^Zo+WMN}UxFlutQ~NP==$CFct$lYU7A9{%@(~@K zN;RN$%OY%88G(Iz32j??pe?CFF72`SQBDW?x{f2EW&~ZoK`& zR^s`8kAaRIbtx@JX8c`lI?y-fqPgEK8Rni2Zi7%^?jTCK4Cw1=L(=73%zw5{(#$Y- zy-)OO5bcK9UTlvG!@9b=(R#rELsu&jn%5(uph=RCyCmY2F@u(EpkaY6E{q3o#o~=m z?2EGp0NN|-Gz#4Ly4mB#7G$iiK}FjDvek=elT0Y$*A`(fZ4xUz@QE7(i{U(Xo~-n^ zfqL_31c(0=KVh4)G}U-xa|-5C`K1rCy{`U}L$smeG$Y3GzWqpvjY8<$D6B21Ca4|2 zr*1u)8JGJaX-KcO(T0X&RdTAEOk9G0FqSTkq|0C8XGzNt>o%dX4e9G1W;Ei^b@~V; z6yUHpU1`!bvc;t~syk1d(5WiuxVdn5lrN&y ziO}`Cn?XkX`^3Ho>m8>e%;=}>u0`b9BnHa$LTA8J(S-T=nc_babp7O-)Vs z_~VZ`b3ka@O2{}4Nnb*D%^heQW9m`WAyxRpfOtP`e=i|!O_s}zHsQpdwa6>I+fAm6 zC+i5IRd8&vHg=+p`pNS9oG83_lv37JyYOVJjQD^iFzebDi20hoA$WW#PNSH<$u_A> z%qzk#md0S!lKU}8puI|-5ceBuMtd{KyrIU84@=;O^cts_cDf$?jf%wnW{#+w`TPko z0TOVB+m>N6p9q`(lJDBj+K(+|Ee5xBm?qmSOq=yE_SR=3#!JAmvhHnUsa}L{zx|e@ zHX4nfcOl^D(W8!q0tQAlL1SJ5e)wfJD|2+SXs|`{Ps9M@gdtn|SUvDa znXAk;QMn;KL*q82!>9*4c)wI6lLbd#&m|L}5%XPQ^}%fsB4N2ou4(Cc{zDF( zI(91m^zN-VeA`2#@{hY#ZEJ}a-|_MBc=p+6VeB&^WM&AKE?vqk>0Y~bjq?bwEs0mI zTyeOl*!u<87Dl%0+tShkwpaIhhwzjP?6X3&5c1GTtlCMLNABpsejE zoedC&yO+?>@7!%@$XCYl;-ob3dKgZtn=(Mn*-n9h8#E^^Z}=sXSSnw7tC%Q0O#OAj6O{xAInk5oTJ zGSORWcIP5_2fgy4i}sl~DzIG`&wW4_sfctFAprO0(F66XomhKtGot)0&^P}HaqHzL~T9meE(IRed?!}92W$Jo|aJ<~7 z!;G8^Bt+0S>7!z_LDB0J>akF$Ao zn(==pw8%L!zMf#WvG~5irIea>>0KfbF6jqodzQ9l``b6 zRH4RBnpIRluq;7BVFQL{neUnxQGH z$Iribkz`sgk|`yeZ>O`^XQ#;7Ra7EzI+@Ot711c9p=0j>TO0+-8ZSVKkAq;C3mdME|w|Plk$Zu zWTG(-k2~0Sis63EbkxX1M{!1!X*ed7XQUCAG>^dGVD7~rBan--1tz4==vpCSH$D+2 zlt3_#Oy$yfV&F^Q8{xQ=s3-#~;rluU^6%0FCV8XH=9FEZZy>$ONhsKGx00Rx8hsP| z$z3;w;*N@g$ut~~FkDN=ga5ERgqv*G(k$CVE~lpzp7NCg^kIdV=uC?rmX=l^et{5x z6D`!_u75&7%Qo6D&@r%%W~T-G}$Cns}%5yk%h zK?ze!0~6{!m|Ch52kUgGy1lAXR!V0i+j!L;)29Y=DKPD82U%VnGDy zJyelS=)K+*@b^FG+;RS6-1qJscf2=77$PC;tiATP=bCHI@0)XLYAD;fS#nxAxu8wm zIE5ij2nK^toSYB@QG#y@@cqvh9K0a;=NIr3_Mcw=yO)#@|2WlA@K-no8saDf{}I24 z2oMnu5K=t zrf4^BXG;t0Bq9)o3JeUSP}S6QbFp=_h8?_CGqbZacY}fgVZ0Qw>gt+WYPaQrRmi|F zNDoUFS6e5?e>;q-JKD_B1quu#SJl)e2+QCl82vv+f-PWRL=?De zwC(?6b2u+{*+R_BLe$K{QcOtL-0ZTMkd>8?sOi;LK2WPbOVxu zlptkD9nykyp}UY7WC>YAXy_i~26;gK5EcrAf}wEeIrIvOf)b!aC<*F@hM`et44QI2oJ*P7OZ_ zN5X00jBsZ7DYzh92rdGbf=k2Y;PP-4xGG!=eg|$0H-X#11K@%1NAM@`r|@w2D|jS4 z3jP-U4ju!Kg@1xq!@t7o;PvoEcoV!6-UaW5{6D@fo5+G$NW2Er?D;526n-j2J=uK};Z~5Yvd|J7*l- z9UOYWQ@E)hYUl`b6gmdcKuCxdIu6l6Cm?!=0b+zsf^TMRQ&(3@H&m6tCuB-MLa|dv^F_OX3kRomRj!N~K|Sfpim_0b^lSj562LOF6*cqio}VKFmwgFXsDv8;$&gzaHxqNPD2zr zkD>TaXD3{pBqW8Q!W?f0FJ&MUBnw@Gu7kk^>@B?ogbcuo!!;-%grUVyV~(gHPkc`} zg00}+rJ}}6YpKb~A2Ix{$wK(JJfr|ALN}pXT7-UXEms@Uix?#4DCU?NtyjnsbybC< zRLoSs=N!GMc;hyBq&fzC`%;jBcU)g{Zh{{$*V_u$psXg=DG1Eu<_%dbFWBq=dCZBi z-MYon0g3QIUvicQ=Xo?PAHU+k*K_T1l+ESGXD@zF2`E1qc23t!RO6y-7`7+JTvltA z^xHMMNP)^P_lnEc_xFMaTrr5bD1Kpyc)!YUHjiY<=Iu8Fn{&dMKk7dx=H=xnhCb=} z5E2sdJ5{c4CcAP?KlzQY_1YUD(`)lLPPfjyRFj_4GBO$zIsW8w=l0Pnj;TqxQ{J4I z8hQBE)1HY`Wsc{OTm5{txFURop-1nSub+R_#*|vXmfWLbY|=x%^OcOg-0hdYZ3SFw zKU0w{{1k#ya3a~U8DzZFULW|nf|F{Mw*I_SW8e5nrxvsEjnSC3$hox^ACmY@^)j>j zty{NZYn+Jd3l$t$9_hJOZzsJExD`O#@Ehz@#fe&+-+Jv1hs#>NmnPL&McT^Sf#OR>-NA1n{BNns0QDPxs&E8 zmDL=x`-7*|(ad`B_qV3Jv4$8!U@?T}`IGwKmj<7mx-KArFK8bu`faPVEuEksq=x=x zMvH>5cd0D+AiGghDbJ$kxp5TMlP&Kj!cSiO?ST*H=@dAhY*gtJIUV+Rs@aG6Qc?e1 zvs^wBDpZXRB|kDsH+#4?h&q<+DD!>SrmGW2s+NB84&`j2ck~1>;+mx3njmZ#$))38 z9Ui_{o?ut^$Dxba{3l{PrHib7y&Y%Rxuv9(nVfyTRHN5safcB`vUDn;IT0tUoAF3G zy{W&{HI~hzXXHz$V3k)3C3lR_m!B4ildguH@8Tm|@{|vTpQUinggVG-lZua3etmt> zoH>Pj#5=at7nN5hXj->r%ep>0GPLe#|8>v z%&Xk*vi(F321}#!1=1lQY#YIQEmP{IlE_v9nkE8FY2<0WGkqPs=RM=1U%*OaYwe&Ik5RrxvW z?QBYTD%B$}f&)*^H)wL;SNliXmz##=ZfKPa z+==HpNQzKQ?5T{n8pY-DU-4Tj?P_WiN|mYH;G)%S;H|&z{Mc?Vvfi%Z2)p6g#-AuQ z?&NAx5ssoDnaz?ei}xIR$|UR)=RN*LgN<%-P+EZGPI3t0{>f50tevdsaE#qu!_9M) zbuUkmF2*J}!M;g~^h{owLwKdT#^X;EIQ$A0&qf>NRT$ztijeDy^JpVxVX$-`(6&eg zj+O;D7kvEqCsD9^$0hqg>PdQ8@cC<;IXAQ8f@IRyhk!wIGmF?vP9`{&%S@h=y+|UW zMGMZrY4RxWGP>__mDl3$_#;f0{;Z3n`J-H3|6pkJqK1(m7eZ^uU_z^7yV2D&B@kG&e;9k`2f$!HX)T2gNsdBCh=E}e`SZeWkQ}dAh%JxfVE;!j3F9$8D{n%z~#dH4*S4Ua#IfON{}BB^js6)vgONRbP+? zW@}7ZA%$^N8|u_C-hJdg(^Z(Elg&QToSi9Oid=Y(!BaT`m%F_AwK-t5$20*pFXkHz3jF!9ibpmLRDhEm1(C4yH9r{ z8vCtwHhG9qR$@NeUB49y!AFiiJ`<2ym(kbf=W2;7LgU{C+^HiA4t>~n8iOi>Zw;p8 zAUD9$+BbG#zaQ~2wBC(iY*C~cF@e$Ac)cUt6l&P&+> z?)N2N4UPgo62Qs*(1GM@0AcaRQH%%Ndh$>}pOr zg_vOFO53UWZ+R?Y3VVEHz}Qk!F}@57A8)=qRt(uNx@ArUJ|(PuE$)Q*=Qn_tja8G` zT`Bpqy#i^If3qiG$4+{>S#nu9S}v3koh%XHycsPiLJ}&UfFTKCzHBo*`Dn~vn|qZ) z`-J*H3KhXAAmPtRsTo!-gqL|@M>nb;*xcs9qWno`#cImjXT%$H_~xJUJc&K>f_aOI?5;gG=7iQaf~NrXlw@#AoQ_+y|AHQs547CJ%EthBCt7>XPUb_ zXBD0qmcJw?oCbnVQIVX3^e1@*1U#~;w(QCa0?TQY(a82p$@Wvo6EMT%cH6#!RAJLv zl8GjXdAGM$?5*m$Nk478q9dH5#u|>L#Ee@>VT!gJe04RRg+E%-!)eA~C_Z+1Zhtoa z!a$~(P5P1Q#J`TF!dO|e$l#FrCqI(6SliQmtAC5hTrX-$J{Btze^?uJsP%$%`QFCxB*!MH#}s8yNVYDwvOO5xfiGTCOlsI6xWR z^h^h5JHViZ7X_`@2_3}Kgu?<+80h2!`6R+-bnq#R4hEX+G&xDQ8p02S9O>z)5P5moMG9aWDh$ zUj>{5l}+~LpHqTOk^*ze#v<+U-+5Dbky2ocjt2j8N-UBB30-gbLD)wQXSG0ugew2j zm7YR!Lcs~oQ&0aht6_Rh$mhyQ`F~D%jS~eOk>%2q`)5`}AP$j45rH5AL0lRHUq|#2 zf88q)2qF-~?eicWBE}JaqckEA|KA1zdlVxlCwIz*{Xe2;3!?uX2`>w84+&x`|7f+~FiV64sgtrN zdWqThy6m&~nNG?@W;n%1F7;BYU(D9shMtD8F|VmTlM;Vi1~(lQMRHoa$mUMe_%8=q z6z4~{N5vdS;bV@miStojfOQgbjQjk^)RYw6XMCrmFjbB`RF6V02bU$0$I^J;{8$KED-78qCi^S&S*E&nFUe?yjV8-l8e&^T4Qj+9oa*du1__iyHF0~tqza`MP< zAZd*Vy05}oxNZJtN>DBO?FG7n)YC$Xv#r=^=nB5C&^MY+_R{pvMkGZh25vI8MBflQ z;5oD#bO!z?I?w~1@SB2jSO>Nqy7`zZ+{b~cX zQ;!;zn_@VknC@6vf!kP9MBVd*L$2>=MT%S~1KrDV>}FvVKC2OiRi~Qt`LOs&DO!Ac zzIs2AEMY$Sc=$f`SQ_dREvjY$nBB?3OUIZmPga2s_Bxu(^-e9zCwftAXmA#8dyb_= zHUXR8RL^_5ldxiLu5k_^U5Oz@ty6E6jE4KBR8;i0UV$afk{#Ri0m^+LL(X6|J+HQy z#Cp#rg0zN71hKkzZ?{@s=@Jaw|7U*K$N0EbuC##~dU2}>{~6K_MsL3DP%BDc8;XG0 zd)&8P2gIT+>-6Xj*P{N#*?jB9*RAyoiTgMm3T})=eM~PQgU6j7jw8(wwB243 zXzQ)X2h{y#7oZ@i-n)0PZq6Z;ul+RgG|bOf%yr_IyXw`kR}GncThbTK?F8A7CZqv7 z7sIBpOK-0_$}Rxj-obOwd*TGZY(d-E%y-CuH%=4pM$V;%*JvqSb zj@|Xe7giUhwQn=#(PO3o0mMm~<^yG}3>xFho^SK4!bMv2K2rvLNlQ5SL;to7AfZAq zu|i2Rd-OBgzk@u7{MJ)4_yXS?5EmAAO&*#gCnO)yoibuv=yz?F8Y`c#$~USx`n}$Z z^7Sb=1_vv3w<4RfhI2BRIvBB;6tAM982kVW+%)|8q1t3?V%;t30FU$a+UG{m2do4Kg+mUt z*d2Di=QF zBU%Yv1W)BmDl!EtNYmX=!v8F_?BU<$zgy5^lrIsuFe6=pR;j1`@fhRC_@;$T<^vF& zHZRfd%_^A7TQw;kI9!+0>S(CoEbl^ZAuKUD&S!ddCaZF;CY#$2-;t`x{h_-g$6obq za1iIP61K#ViK+F%kR~{dlEjh0r~{(z?Au--}*o|&XMlsMnjm45<%a{ z<5y3IY=Ux;Dcmzhl_eLy(n-YJ8)u65oNdXC6QFz!w7Q&bd#Ppzofn&27}PJ)*?jc+ zdZ7fY5RF@V*etVO5wD`MkzRVroM7<&j6KsgIazd2EaW&whv>&oV!VDoY1huu?M-kT zD08wgr_B`(#CyFDOCH$=PC5YQm&8lIlOZazG^S%L4m6?_W66<-e81hHz*k6cl_|NX zOoWMDQ$5m#CFmz$vS%miI0AO&XOD~ko?otzHdPDx^wak-Kz8r&o78Phxf)z{8musj zoy{Fz!#NI3d?x3pXuB0VyE*zOw))+(HbP`kc)VwPps+sdp|8lWeRgHn@86+<{yua4 zrMnVtlfx<2LhlCy1KC+QbknU&ElBbNO{Dx8c2;ygnD54%Rf-ZL$n|W1wQuBbm|E*C z;@*B9z1x;ULRf4-=5}y}3To}HYq;M{*xfhY-|BA8s#y9-Tjq8nx+~K_0_>h{C*ZuZ zH)zqym-3Vij?sXNti(uXQ?g~5RyW=Py3SP3nBmGpe5dJ`;N2~tw!~Yz7C59t@KpL2 zPFPYAHYy~X>8N>^c|w?cXW?hJqy=b|YA{RU@oZ0qo^Ub{io@BsQWkmW$r6wMoLoYj zQawfcBNyX0xKdj1<tl8ZIoVwNie&vae(at3`BaTZEqNDli+Cfj1fA?`LQXncS1+t8%R@^zhT zBXk{QSwPV{UGn$I*WRPQ_Q%zBRCr~TVk{xtfYI`WJej0E;rHghdOp1-wd?Jp;r7il za#vt<^yt`g?8!H(^5UDDyrk0Lvod*6-+E5wqwhvlEDzad!Ly$U6&gqZP!v*g;uERt z(en2Kt8b+u*rj~d?~Q(E6Y|?yd04eIoo;x=DEV6`W4Ub0pbky2=H1(~Clvf5Ei-JY z{C>2g>SV{Ym)tWSdZqFCt6sTBCX3>I%@p|g$A#{48MQ~fL>vOvY&z2-4Vx@~0(07{ zKPEA|Fb#l}oJ*G3ojT$adByR`9)}K2i!E=&FW&S3kXnWxHz8$OyF~Mb{L9ws$gCpK z$<*XHyZ3`30?frh9tKzZ=SnB0`}`F7k1X|6U?kBq9G!EOW8zim$yq}l*AwN#X8*kV zrS|HfC!fjr9Xb6Xhs{^;`iIl2=)`aO^Ny|pV77@-?6qSvi&75r9od7;ZP4Iyp{aUscN&Uz^e8}Aw$pPvZYMXtg!rKdf^#5 z0?{9I;aRa?dVQG2oyp}D3O$pX?(1V$DoAqb@vj>S#mO?BvmwT#AN6$!AO~B1CXQ_R z@N{#a?K$80eyojk-%P4C>YU(p4K-Cg$gF}mpA|zT7vqvWYeg(J2b@apjlh=zf_S@C zqN;EUzN%0zi~M%heVDCT2ti;1yPZ7AZ7DR<8z$lUa;VbI+X+qu<^=u#Y7i(edrn-p z{-q~aXknn&8md+VS@J;V*f0hv$G*siz{6h&gMrU5jad*PMdBcb5d3dolIBhcbhZ#X zDUCI56T^n8i%JJ#5-LF+?(oX~>xtLTk88I9$(uia;En@<$rTm%ZKWG( z^8N}x!wN9?a0Ek;(4XMS-@kuvf)tiVcGqeP`CyKVZ8zCcBa6PmKdcBy6TCWVLOE8vvvhxN+xIBQk?ajT*nLm$ zc1XS=q$ObP71V$Dkbf$ZPVt~XP;Yk!L;ef9|TZwFW`XTZAI};I&uP=^vMDZI;Z?)uR%+YAKgwANYwSt@dD^}Xi zhdljtvBITvddu;@_eh-!8?#E)nR5B*D-9U6k;s=^^SZbqkoU|t{QMb`=_!y%z1Z03 zv)R#pGFBX&=DIl+&0*z~dPHO9`O4GpoPYhqA3fkWCdht_|K|6a8Yx-~Vl*oG5_8qn zYPwBGBdFkY<@0*WnYRHK2bTCRErvd6+MI|TQY(+E?l`R#_#uO|NjWOsn-Iu=ed-*{ z1NJDPke`I-@!72Mxw79QUmmGxK(ko}-|l=Lz1M)nt#F~*x!r0X8O{rqxs2Tc)jD&N z>7#qnH~EE0D*kGp*cxC$h;EB1`W}`aYeND1zEwL*4a>Hb{2<6^$iY;VDjRNP7bqWE2nb$Jq%4xjybCfJETvr_%f~sE zqxZq&>_4h@*|e9r+bcwe)nwq1Eg}GKXz3TxDRPZ~v4R-k6qiDH zh2LC7Y2|4-S_Myws}_MW|4HkEy2^j91InALm{ z&haIU_+*04)*61>F<#nl{3Dh1a+~vMdLGT}<$omcy%Rl2bkL68*;{w6V~^GbB&)`D z+375ZieLR>5L6x6M_+LR>lit1DN1Mb;aPEpYOMG&i-?2R0L$L(hSNKr&UhVa&NcgR zmOyBqG5WY;I_5!4Vd;{-M(y|&K%~UFNe<`KYo6mUe3Rk^O89*2P+-Nd;B~|Pu zC^OGK*U(e?fZmxrZ$%B2DB@7j?GwmAVt&Atw2^76TOih*Wkd_&0oM6F!sBV#)2UHLc1)9?l}# zR2%;izhEs_{PN~>ceWGv;}tQG**U3dKjL6!veU6Tgb{U)q({j5TC4T)!di*@RGVeE z<9wG9qi2y)#UH)}d+(-?^)F)#f(P1mi$RU5XORQx?e)Hg(xV^54&7m(DyKD;(=Bf^ zGso8|+h^bZJ)LmhjK1I1qXF9Pt0dpua&4>ury)8X{ovEzDc5`l{@0Q3_`<%g?|zvF zUfj@gZ8C0VP=-tEYJaGFbDS{Fd-GE?Zc@6IdXn_W#!tD$gSAN|k72KQVEZ#Iq@*$+ z8^qsD4N-zKS)+^n)WSNI?-F6 z+M5;YtZz=l%OsgT4E7ir47qGdSDJYxPR+n)6B~1?vWe-r<+;OK@lg;3 zDGxP8XqJskXBH1m+XQKD{PYFobZF5WVUO9KW8Q*CNdE>5FdYn}tIPWU7uSc!eXWfT z5|o;dBGrdFav!6qLf`fsB8I#R!VzbjWB%E0_x+{vq2jkM^cXV7Esd`I4Id=Ix>?3F zoj&xPU{Age%4e2SoBxTkAngB{BmRuB=X$4bhaRj~MNLj`ChqcAnrZEr2Jug}^%n2x z6b+;J-@h&YQYPs%U07iTCh@cRARG&z^s0>;D8`9sE&d^E2u5>#2Q+?>EGqtDgV7q- z&+W~=;HycZ7hpnCqDh3SqLSxi+Ul=K&SBK~CPL|J{{5+KLiv`P057k7`;7=tE1$Rr z3T|)Bs>%Q@0fdnQVGXIEEOrMZJOaG@{DxW09WTZjqMOA)2{0p0cPn7FE_c;5J)@vG z&hMF7J%_Ta zHgKBf9E%4$vIz|cqh!Ze#bmR4BLudUf@CWTOce{(FYR(dR{F0t2{D8;qvG6yTQ*$g z2Hc+1tAK7;5BU9ryXmc@cV5v=#5?_(bOZfvTTtYunQbIxUB~9>X8*}{z_8Y*$mkk7 z1!oQ^Ovz;{r6(c-^lea4KZqry#;Wo6m+GH`f=g#{&$-zG`|i0&7=r9b^;)03g570N ziax$8hPivjt6vi2%H^C8V7PgD$OU0QOPsmw7#;S9flJ8K8(`ZYiK-3O#D&pS~k_TuZ@ zEBnW(gLt(w#8w7<=cNij3GV&?Xak7{#g#3q$*~@N6Fx(`F?|yvSMG+&Il61Xhqa%a zB+zRVMHsZN76N^}%SoLb;5uumv>RYt2 z67EF{I`8it@4I5j!b$B{^!yZ_Gy#3VOqSxrx6ws2zr7DWMT4J1z+U%RuheBMe1D@( z#t5`rd|3yCRJFg=mBQwnAcbAaKg%~EzQ?PNZV{F|3ssMNtq4P(O-bDeHs9IY08p&j zK4BN#e#%2K-iM9H;bG!YwI||f`cfN!yu$<(gAaRWOHNBfi0lx*RxV=t5h zyj_9q`6_W+73OHHBu5na!n;KxpVRD$EM6p39S+N2Efzr5!0t7S^`Dm0-3687Mm>f0 zIuhRnt?JnHoZ`{WJ32nMfI)2CXP9wiZoT;MVyx8NRCDL;nVuTC>%m7#YuxxKrd_L4 z>PC-4dd-A<0De3t>;alJs?&61aH)n6ZSlg8FAl*-RN1lY~+w>$<`ZW&{ zq>JSM3-=V4H9}ycv!wmDo_0?3`&WO%*BO;T_h~=!))DHOG0h|n<|Ztpyip!ZmHU9Y z$5$$48_u@Tc|47ey*ZhGT$2@0cTX@65`3SU6gv*^xbMti!$ceIF}-N;QNm+3)3AyXFWzP_wSN3h(h48QZi>z& zV01omxX?;GH7}pTb#4#L2Z*kwrDuuD3nqtCXLJfm!iT%QdUF6PT_{EI>_~ysXP1nI zT~fz-t*B+7uORdBJ^AcrT=?%(>IN{0r`IEHYmk(lpN)pa|Wm>#k`ROWrI=6Ekn;HJ>{iRDRgpC(_1Zc43 zRewflWOkefG*zX>z5rev=e}Ds^#aMn!<;V*VOgm^UP~|>_WR1kHCG0DwT`ve8&s-t z!i4IMe)~-r15K;Gkw5RdE*=2Nwzn0@ABbjXCWr6F4^-jc4Fwb=x;0fN3M9l@T_@t2 z+^=W*jieGq3ch%orKNrR2r6B%}Pe8oTBr<4WcT={-V-wxLtwv8zAjhR+@aveJMs zaHkH8-boY9)hhD!$RC9$t##V)ZvIT1%>DBwBhCK5 zK=(#ztIYmpGdI4f3MhB!i)T6R>z)k(D@+gBayH=uy073Kp=3TO3n|pX@P0l*#sB!DaHnVwqxq^zbf`?izdk zi<$J=aPt1)?FZ1w43SR-;DBo|zkB8DXr1Pv&zecfSMvb(e2mfO*hmN}k~ZZ%25$V- z|BRid^Ful{$;GBW79=&yTNiev-MZ zZ7QB-+*jhMJ=|TM^C)bFREr`5Y*hnSCqw@5^_#;sPv1mz%)h*`vS7-zt9y>)lExVl z{}Va$j@v`L*%dDCfIz{2jo65ozeLy?9Smz!Pv*}@=otIC{{Rxm-4FBh>rYKYei{^0 zx1F5Bj@Zq1YTGF7b#>1?qDoP=^gmB-Ntko$z-2=FRrJl5-1tlhe_g2mkG_#?D$N*> z%62Gp8Pu!Q@7B>s5E>Ko>Tlnm#BAC|WC*x}xRZLvE#}#=yPjaHT=3K8{nbc&UJ@oc zdjX2;Oflt3Hw^N7&S-Qcl@=6C7@&$nGYX7q0NEA$?yhzW^cua|-!z2Bvw&@u2RD?h zS>sy1G9AsJ@|7-p&Olsnvq;eAOu8UX_UU07AiL^yxysZ4N_af=*V`+J>&+Z0*DoL` zK4S1v>IzTuL1&L)Axp`pDr5f&c4bcc8}CJVTR>jg3kveX%lwuZN*vbnweu9cERK^PH#BPeZ}bg*jyCd{BJqucHWF3dA1Q^U ztHnzvcFur8es6no(zcDIv4+^nwZ56FUpljCB(@(zy65egSAQg7aGKJlwSipkE<0YE zHx~KyMihREw#9MyF`lf;D98_>;Xha_t5~tDp|XC7mXK?QQWp-mVRAGr!twd`)i{}eJ!wKe67<8l1%y!-ShH6>u20_8QEO1k&@cRH+nLr`9Bu5sS@LJ8 zG*~Z-<-r)KMvYY0{OVP(y-gR}m$~-|xIQM%?^zY{ewPmW*ZG%X2uZ49N zGi@2q<>zs{UeM9zz)#aF_B=OrooG4>Ii`Q)Nbde#Td8+{e{VN>Z76$Rh}w(_dsSjv zyCK4Ul-_o*VzZkONJ#sQirHfojBOF<>u-=LSPTv)3S^);Ia<$Wam&mJ7eS*9SZ-Sn zGRU2QY>I0~l(=UngpaHhRc`zajZVu=&NM9V@}Rc%yZ#|E{dTp$2Vtuw=ip;(J|9PV zm1N~U_EY>(LdAEpfwtjIeaQwn5S^S*jHS3_+xBtI;Wc#*HuF}WSBYL@Fd!ghXkPE( z-6XhrR4MB9)e`vCMbiZd?RYWN0Rbb!0Ip5nN{i3J_6-GFi-eA(mC~uC>T4Oy?$-#s zZ;3IFIMc@~Frk33XRBuo7H&hKlTDmMcl|m$$(7$+`qp|;jkcGrk5*!z*Mg{@Ux{gP&nm~gxbJ`FtQDN37n1J58`MSgu1!`r78 zLU^;hmVfeQOAdz$RyzwGO&qLw8ibBPJ8Y@o( z-9Q&R2l-QeboaVD!OKtcA*&6a;&?@f@|K%=vHwiJZhvs5$Xm~LN}yxEpI z^+#z10(b4@cPt=n6f*D2+1RV|k&_GZt@!YB-v3cx8ExwQNfL*5wzyW#T^lYa*zrnc zpH%v)o1ZSr6+5kTAj|K(BBHHHPp%f|d@T~1A=eAou=q9b8Qwb95NQnBybY=qt ziHQ-O3y^q$x}2f!hwespBC@2(pdHA1rQPd*K5i%;^7mCRlyNq$>;{!eJCy`sr4&r4 z;yv9>qor^sS&e-`wV<2xzM!oLg(rQ$0A)^mSvxYDTgR>nB9vI*n@W9=S`mFZabPD% z!1$5PDzpGz(cHH-ZQ$W}on0$pvQE3+<;`uD*Bxe#rDIWaAnxxCwQ7#5%+CC=#yhxo zV!B_$A*g7eTDn3M&YZ(pL-yYh3LFLb!Y}f5#R|TjvaQ@rz%?T?|pJy-| zLtDA0Q!!%;++?~uajnprisS*eNjywqZ+ zHR!-4TV1$s=C(#inY zP1uR2ubC(BeBlTxHnBR}8uG?#XR)r_v3hrHNCq%`o-;=VlYp3#?a*IxFQ_tk(c$vU zuk@{ZCSQDd3~%(~LMLxL*9BzcD4T?vo7-lz46AG7MSHjAq0yJi-8OgO-}wklaM(Yd ziYL=mV`(W~fi^AmUy=T%^uzrYqFxKbLp0LX3!dUtYL8akbK|N4J?8aucw2$~PcCL) z$iYY1$Modwxin+R$V~mh5O;iEC&--@;w1k(yDqOsiPrORa5tLj4!-c0GXlMHJ&I1D zMm0%Nph_p(8&Dndlcla|3NZCeen%-k+iU~_*_8-0(%Ks!!}?uTx*|x>l;UzH`Dauz zrsa;Dr2i%dc5~3InLye$xWQO(3#L5>x~Qv-xP6XvW*IAV&*ZU-uOAnDladysI72(z z^8WhWy8V?IP$%W}F59Sh3<(#;HvTOQ{gD7J_u*^*cXxRH82WFHDM;-$w(HF2d^zU0 zp()gG>wG>ktyn6S*>QhDfbn3E_E?Nx&xwoo@Xyn!|i2^4!L}8 zdSk&f9D}aY1*wnv_}|W2gx$T<^B~EQg|Xe8bY?PRl=nr$uihosJ8DsXDPVQLHp-$) z`J-`_e;R;2`ni@D;rVvH6-oMx4|UFsKNg@JtpHJSG_IeX3g1ZQ0~1#J#d>)EQVJnh zPM5`A9A{}m;Zo^Doc&c$9g*mcwWh{gl_6{Pfz5QJ(ixY!Snj3+&UWRaepAE9eAfp; zdQAwl6@U!JvF;$yc~lSdm$D{Ec=Wi;9^>$y@@^l4@#4GGvq9-hf<@3kT)l&KocBKz zau=Agfhbkq$+>!tvbuS}jCHkBe1ap*Egj1zbCDoN>W|0mM_$J_ae4DbME;{cP>3Ca zy#7aA=(TQY z#8uaP^TWCa;Ks9nF&kb@`m6SWhHD46e+D5d9tA5E4+(aIz&h zvFGqtFu5{d)ox?2kNrIvk|G5Rns8Jh_g|#18@L%$KQFTV9fA|74hBWBgq!`nTZGKA>vAv^6bcLqLyLtR!V2E|2pUY4b;73(44!}m z1`LYmb3VcH&&(8cfH7sUwjb)Ez`S5!P_lxN^CJJuj0nj8A|UA09$KlQ73ulESb|KN zf-0zYk6t_TwA9XRDf&-sf|l=D4~-jO-~G?UV?M`C06A!Bsc1$RELv2D}JXH)@Az@J*3ZFsScdh0|N6>(?= zRlp239kG1;)gX+q|M~MLE8DTx^EG$w8fiF=C6Eg#p$WC~1fxpH1fHnKOqzWtJindG zPHuLva@(Mf-r+N^(*Q>MwJzUy2Y)7u3@DyB$34|G|86apOYU%CB(#8&1a;RszFV6c z*iMb))#-b}={*DE6A%a&1fp>-pv;V~SWzc$@~MqnxpL)D{os@Wm*V5-ANLb)DzW+J zrLopi)fIuLa^^77npn@l%_nD0&b*87X8n0kAB1FviToh#>XZ3OG`^;60ko{g_}w)+D7J&ZI`0Yvm7oQf6xTI z^`1UYoy=0G7!B%62F)1-?%I@N6B!!wa#B>R^z`-hFT0K_Ppnt(?+R|O;SJt@b6Ki8 zcdLTn zPy8UTAFnE-8U_^%*g1R}SQ+hs>?nvV>oN}V*$b%xk6j_OThygR-xF=mDW(Y6GZH+1;383k zCiGJ_zSL$uCFN!ufK6a=GpsUne-cG6u92ua5q#ss@NX&?2D0Kl1MAVr=Q*~Q(XAkE zyp^OJ`}+vHsAc(~vPx~r!}!q1jk$bKMXaD4g&=Py||u@b63sR{KZ21cNgGdR0?Z|f$* z-sP%tf!<4bx4o=F4h#l@GtnuchlNG1Tb(!pn@QEYIdeWIb2(}wn8k52oxN%l@D5}6 z)rxG7RT2Gi4}5u9Oyeuye8S47lHSEFhG%c~6k3|SrK$8cIvGr>s9EE$+&f_lSQ4h} zxb^tyvbVW6PLJR!uka)Sm&KgyIX|ch$hKIpSJxShVo|yz4u0K6!V`%R(k|V8e*Opz z_aXmOl52tqLCV20nTa!BhFF{`G(2Z}a=BIFBsV7j!B%Ue{|0E*RqpOjjSa)>i(g1M z|Ni>KcdCiKj^?D$utU)2Zr|1E)kZ=MckH-^3qacwB(xk`4pdKQvFT}wRvMdWR7-zim4o4;(^cBbgdF$pbwq?X_?P!USUC&SK-R{yhpuY<=w z(6F3sm&;?9lKQ>g@2VDW9wFh87l;3D^lCQ1))V&$G3j7)_ zPHH5OZci}Q`y!$8{vhg91%ZOZOapmv@5KDq!(>JQw}LIeO*2K{{T#hEY0zYDvK>|I+q1h>@7?a@qk zx?zof;PmIX#4mo#^Ie~7nlU%tp8+LaUTMFKEr-S0m9Gg49*%PfJU*fk`oLlRydOVf z^lrXlxyiTGw|GvwF(a^AYd2Ftqdbxh^aYGat^qoFQh+OS<;8$m_$pt}*MrB%5y%^p zFS8Hjf83_+FY4^A>Yfo`_nYoakM3NEPJ6gY0%-LLP|FtnV1jzB%xLs<>jln#N}aHW zb&6A`&v@>BkN2G`-O8@q>+6gacXx5iQ)uebZ-@|a>@RTvOz{6`@4dsKXqvs@nORUU zVh$KEq9QqiNCv$nNR}K|a&Q+=@{n_0GAN3Y1wk?r%oz|6R7A`q0Ruq+<*nIOZoQx9 zIq&yf-#OR!&tWfMr)Q=^b@i{RtE#)DYrnKe##T=cR!ZoWJe+;{J)}co#jZqm-H^9^ z@9(9Jb@M-)m7v+NOJ6)vAn8 z-z84Z^UH!u7foFIeu~_$!cAWMMT66f*m5Y6a0^P?SL9WAPsMCAOucc>mi2shWn2Wbv@{U)BL zQyEDeJyrU9ORH5K8dN1N*uR}j9j?nLD_)&UU!DWQZtW90Gxmd0&zIh=Iq`<4%!@jd z?sLA7BgD=?W!%}rJ}ZT1|zkkTjsHrZTojy->=w(Z%SHNws>yesdeJUgW4 zIG(_MLej8ys8U5+?n~vwi)3r|XG`7HdjwMU>$e}W7Rjss)S|FLG&OX?;FEjDKt^^y zVP{VIn&}Nq`vsJ0nN)gMRnHtVY*H!ER)>jODXppY70!7ET$OF=sdY3 zrM*>jfODj)sU~{vS=~APC)W3U&2*}Hl5C6%^$c@}6%i0}U8S7}jq;J1sfA03|A zwfsW<_w9ii_nvigRcP5pDdH}Y_1u4)F)^hBxOcRyG*F(O|O;1sB(gJ+63 z8oVhgsT-O;;T%)Xr@MkWw!%?KBJIv5t;o;sF8i#=c=W8&e)B}G_%64Bch`mN@!1uK z{pAJxPTPoiYMBm6mvijaVfMPQ3QhONA$!dIInL-j`dlQEYPd&P?j3i#sga+1o42+U zCB{jxe-DdqHmtY<~K+M^^T!UA_Bgv&x6=bcsVF ze+egPIJ#WS^r(EMX*_On?~c?h6*6zx>6BV(zd@LB-szI7i+1jC*ixu|@mhJew*QPj zVRm-toqDPfo0B7X`-z0rR0NM^jlfnvP8pr1nwlrV?GY<=yp$cJ1h_T_e5~;tQtKFY z*Y%F>&WToa>$&b9d*JgOztw!h_T5)$Ud<{fBmU?c&Ph?jBoyF%+VO8{+qv z>N0;2lv!@38=m&9i|Gn`MTv2}uAOKhGkTJ%y3+RhWMfj#GXHkdvX~sod*0NsOv$&; zsh!j*buRmI1H&cB8}s%tWw~|f%e(203lFWR+_KT}%qrF%;Vrc25AUBTyQ$cSnqTm& zKL-mJPt>^%e!d4TKJS{L3)Q~GbMJGf41T=XI``zwv$NeX+%c;~%Z}!F+~7X22G_VC zSG;+L{yF2zU6FNa=xBf7!sWgvkMO*hx{@5a)%WY%3y-O+Rn>h?Ucu{QRGgH7uM1JPNnoAQ!xi{UsmJq8vn>|}A9u*C`HnO+Mg$fvyVPUW~ zJMn9al)L+A?bK<%O%NNRZfHJH#hm$YGVYY<6WF24YwTu<$rYuuPeh(L9_W-jhz#cm)f^**``x5YMtNC z8)vwyx^Bwt;WA)B#cNAe7x3&_WH*_!CwGX zx-K+1a5Lq0?Fw#OtGwB*vsz+7c>#~}`RCT0KSHXzoh=Q2z%2LbswW>pww=;Bdt)!k zoLeX2V0*htv!FY0)?wmkbJ=~%^Kvi!iVu2vB=$BJbQ5cpOk}@uO1w}{+x@lP%f8)r zt;=}W@ct)vw(?A0ckNwR7kofOe3^DpNRObH)txx47v94Uy+*dQJQK7lQn~8Kd2I8= z65W1c`7CdpVawJB?r-XP<{XXNc_vnlOlFn5u>4+SXEChlQr~4?zLI|#__XD+Up9S^ ze-y#4qY0)y>w8W@i~D|byPw?01RH{OCb|{Xc3jMUv9vm*x7n-xFsJdwz#o2vTlj~+ zab=FSZLEk|vj6$bI=2yB??;O38mtnJv8bq~FA+J>TU6Ui`1|vF>-GKbsHN`-+q$^G z(`oSOkli*Hz}1ttVm5otfCm0di`S8jHZmsyJLP_gZ5>#DX7}B#E6*CFR9W5e0-uiq z%)>AB@X{{msb^pAh>;YB@bB5`>QJr?4*LjWYp>~+FP|r#>7;ZRzWi3eU(9)Q-D|b| z$7)`3LNNCavaZONM`y@eMW5`EX|kDe=&Eto_|WFFZheqj|KJ0&e(-q}*Ev^i7;GKc z1}kEztzDyisDwwpru0D;KVO5pdl!SNFi~cpalHC)WVp{ZGj^xN-0qL`H$Rv=RDN_+ z_M3?J@WtvM_3|^z)}H$CptWt>&hfd}JrPBT$w!YABb?Qk$&hv;#?c^h2F(!p;rSXV0H2- zm|efJ9S$fg2wq#C-1ppr8hZ+ql{&k?ku8f)LA=k0Jp0B8Q02;uQ@A^&MDBhxH1C$2 z9X6dC3Sn<}{~}S}-^B)OzQv(J6{z@x|It{A#_eUBHwQ~i6~Cm#W3P5?abUt881w+)T{y zw)ghtbnYiy6mQ(BlVaSz%LHe&`7KQK&l1~_9zw)xz3iO+{9wSkq~bbD#jxnO(%w~| zrDnS@eYN1qtXg8MO8a!qz0AJXZ(>Q&mC>sP>V7kWPedh4xGU;~8@K|lsmAkot*kcT z=`H_Y<)wc6kV)GW4{DF1)7j8arcOg6+ue1f2rs3Nuq*P%FA<+3LKcO%Qa=w#FziBv0!z3H#KOfD z?jO#W6xF>wJRnn8aOs+=LUj`r{8hb#*<{Rpy2<$C(*S{O;)CS1RWAk%{9eyl`69GoeU7?-XpC8Nkan6!rVOiOHw=5UYd3?fR!B=H(A#hePC4 z;$x<$u&AYLbb7c{x~t`Ma{NTXN+(|{it~G?;dc4uQm(}gbq2B7Ps?~b;?@(ZrdNO} ztfCcCwcQ5eFG)T=Fe*C&DGh5M=3TIYYUn$mo1(S_9#}@?g4LKayG)%sxpNr8;@c&8@vc zUcTzmPZ3Ew+a&(X#V~2H#CTVgs@7E{P9a(;L_S=Lei$HlK{VL4JEPLR+qa~3*}9c| zouPW*g2G`WGIyrFC=_yRcTF?isxEbqd2>ibz2wX_;WcP`gcbNH{%m>N2-j-zi#ZEVbWqSmv0X->AZ^eab4I|xkzA{i{K z(5PPNg3Ga;tD^o9sdqpdg)^q zTCF-WKKO+;5mm78(nq`0Vhw@j)umjf{e&H=_ssVX)?+2y)a$B;uj)!fN9gljns69_ zy(ugmDHBc)m&lK~IOadNqvG0dV~vRM)M#&C1>dm&sewv<3nN&Azu*6w>cZg1YLx}i zJ#VIpU{%Vl-WxC6zbCD>Q;&7byqIzs!fpCCwyFDmPIO-5r28bq$St>jcWu|50;|VE zI`qvkG8-yxZ}B=%<~pc3+`Dh!#Ve}``*AC&cWV;}V@s5(%lISZCW}^s*hQ0HEGLM_ z{#W1>SbMC;UOu@^spt@U`7VhKR_8~yJih6;m?H07?+2F6flVi$pXkm@|1{pA)^yXI zRKyvI>{%V$^n?i3;mN93Ihuc>6*>4OO7Mq8y(p__LFKKo)39tmb1dHQKR`*S$L$EIR3n?m{T? zW1rFGKf7U%d~yM=9X)~5e--jb?b%a@Wr``XZAas*lntP3AJ3O zZrN+?k@r!%P5V~oH=Al08je|aE<QSCUiwO_P1tDy!FRnt;@ynGJpQQh{qd z7geN*8gg$Y4XS`G8}=>HY9Z`R$Dh>2Q4UGP!v6L#Fmr1xGQP2b`E1+BqAJUo)uZR8 z%RUBww>e}{XZ|G0p#h?&`yz;q0ys0s+q`{6>aTr-LrH?bN_DJRwIn)q!fN$iq|v)G zUitec2(Tzi+_8Vd0s%ICA6Ey{@r=Pfv8m@ittPPN5xYb2UM+Sf<}ojN*+$`=AuEvCl@6_vNTjY~-!{QTqm{RQ=W!s$KovvA76to9pqJvPk+OVn8^ zKEnA0Ya`YX3u0!{<5e7_3WkZaLF{ihLD!}HeJVt{>gEj>U6`Gk7h=*EOS-T+I3n=ZL4dU z-)*ne8))F3))iH6YCP^%CZXS398);VN%)WTvN$dqI6Jx-mwWC#YDxve#_}J46q{%G z&v~StDV3J?bzFWA7F~XWwRP{CmQr*v$KnXrqcb_61v?ZhePy+|d-dbH+g3=ja@1aJ zoq@@8MM-K>f8FUrpSAC{+`NSduDsBz8I<3zKbiiEmAvm zbgXMzpfA5om?zuFq^AM*UlBrnf)RtfF6M3bij^M>Wl!>yLnD2~2U1e zp6}sY6C>&K^0e3JV1DmyL4bsf;WsTNzn$pTd&m_xtSK?PwF7)fADKmZB-v&QH3Em2 zD&h+zwv0lMBl|emgPxzW&vI+`vDlo&X11`d=Y%W&h`{6g+Mf4ZanC3tOJXhR>qCvN zJUQ(}%%V8?jZVRgq;?&@Zd!45u5Nwbm5%4&J)0p`hn$?_n+xrAUAQ6-HhhRt!(4b% zGlwPgx(^z9s!mkdw?A#{v8cZv**nzAV^ZvL*{Ghgl-T@SN~Gbjp8DD)_RsP3T)T$0 z(Ux23-KI^j9%kF3yL1l&EKN^~XU+@o>Lr=h_b0n*bIG67s2!@JS?4bK6t$dpN*rvBO|}f>UR|`*XKu6OaF~xS z*qBF3xFEi$s-%-f`^3g_1;02peMN#{z*_jyayWhF$NgO&?L(Gxz3dIs{{&tI_m5!i zY|$vab5=s6u*_2>^L2tno=LCg+I!_DB**;TrLGW7TVD(-&Rd^3)sL7+fWa$tP;*z` z`9;~3o=LuoAInZ;RZ3nN1LdJLi_iR;#v;v&j!)MzQFZ#Yv+SlF{F_IqA6{Iq-eckK zkX52F^&a+ZnteIuJ5@h-;r-9rQHaMs3OI?s1-e-x4$v$11!0tnzZ13@OYM@nr8SrdHPr-~$D$?Qnw zie4S|xhe^z!pPB!JqmNJLnYikr``{H22)*)9v|Qylj@DW+VIh!&}d&#lHiWpBRZm` z{Fb*|+j$y#3C-O^wk9LM?6O0*IE?HA_c_m*B&`{$EjPO&<3Dj<(Dk9jSVf`UMX&Ey zI=n)Kc^4nrK3A4E0dZtJ-#*{xozkcoEPnZ1u>l_$Z*;EncyNeZWy?-YrFJE}# z;xfZrjjxZY+od+^tDL!>=N}8!U8uADA>wSPr2D?< z0@-><>haa3cIl?NUAtOm9aFrNq@}@w(7%3UNLo9Td}H-?A1_H|%q+J{_x&nu|Lg@a zgage@k;S66@)gCaJAqxW$INp|l6>Hpwx!kmics$RBIhToWn0hBI4zhf$Zc8ce&yYr zm+b-qMcPjtj=#6^zlA9)$%j5XFmcJf?NX0n@{&MVK}z?o$J^|O6QYW_pMT?&mY#h2 zwC3WtX7pAOX~~vKqxu>4XCcAF;^VlbU?dRHCP}ouz4c(JIeJmD~- zKLbw$_J9wEWB28st0G~7M(hbL$EBe(|4tyh^ZzxImvcS!b8Y$MT=*Mjfy5gu{JM!@ zi)3eEmFgFf%)kkC&eO`l5+}sZ8TE$t&v@Kc6)`}n#t0k*IU=Da4^_A8Cx>^ z{)Y zE`u|*7Xt z$zo!kTdt4@_zssTG))GdNtW)e6%T@9`r(u<(G_tWBA$nlrtsCRL~!I(2&*jsdv4?2X%W?DEGItl39&?-4}Z#QGpzcsRzrlTy;6$MsNR zUy20?gp(YUpvbTPVaC*0W8=Z4@$c%eXGepAabZ;I z^oOyr?n~?&(26e48?fE%Q=T{jjoOluQC$rU4Lg7G+>H^3Qn3kn#6NybT;tB-{~Q4=|^9;gO%>4Rw9~G6C*85ID72C`=pc< zzZMpbtxG@%ahPH48vXW%E?c+$P(FFG`rsy&MHB0s2o}i2%ByTNd3e73#hKy@3x)5^ z?8MU+efEa8%-%9CcuicV9vbw3I|wGG$eC}b)^g` zS~(1gxeVRzKRi6_SEM{9ur$+P%WiAod@)!dx1Zy?Qh86p?ul1Tka zAondZ^h5Ii>mh4yAGbZVuyU&DuJ{3II4I`5Mckt|{aI{$+}SyPpC3DWP$r!9s8nj1 z?yWJc^{7y91rv}_oFh)+XdmoCG<<4f~VzT*rar=T`p9k!)7`t!ZzSrdkmgL~5v*v;9o2H<1nBB55?5w>zwh zKWVat#QL&G`zsNA$Y$mbCMu)ERrP-R?@REl+4RzVyAabYIhkc^CpI<3ofh=|Iu&|E z#Ni2BlJvVlnv3TF6*SNOBXP(K%O%|X^~M)h0~hv%yDK&L-#pvYRj^RZTMovR1{fdr zFS#r3_hLq}&-B||g7nfrCYRM41FMPH9wU~46<2op$3rkhrG39|{a}^e(W5fbqgaXn z+Z_zm|HAvIz5TV0uCDJD2ppptTsXVK&sR7No7`1H#A!#dI`lPu7hSq^>DC9C;(~j% zu8sHKdKT-l!euYVT~njAQOniSg$9Qp%yg~YQ%dumZ+&|A8<+U-N<^LAK1-}VUB5EF zq~su%-hw&rd$hf*wR?gd$;dk@=d;wTYZuTfbEsMU2W(>aR(Q7H6*sr;^g`p$ z?<)HjCL$7cNCX{2wPf<###v1Jw30pYi9Ii9JNYutoY~6$Q0x>0EUlOHAAdV`BYaho zo0}Uq>24qU@&#$wmU{O$S;UYuUu?5-VC!IaXXo>TogYIX`mtk>dhb~`b-IDq3D#SCUcgzHeDn4X+nGy6=n%>u_>W|s@X8Z0Eo#d1h(;Ba-k{G7k z5EjxViL-zO)vM#)@}6Di{k-Ai)F7OdnYVnWpL6D%gAdAOWZV}N_!_Ug_BMF&y4=Uz-E&$inoT zL;!L#E;Ait4!g5$^`-ns?Cr=?oq9VH_|Ek3_hsfQWl1B4!fLs)!_Vv=c{?#a{`M}d zR8K?W#2~D98M~L%*jwil8CCLm_M-CSVGvT66$v4ApV^A@td@MimjEk+ul33?n`~|D z>4_v9!R3mP6=#$ z!Ygg=v86wFVGTDcw->XOr0P8(XBFS=0_oo8Hk(R{dwS8c!T%($Z1_Ly>_igktsy`#$~{mH@Cd34ju^Jo~M51g&S;J{m|IhxNq;ieSbwS8fu7A8fY_DG0DjZ^7x!{ zcgJECb~|Ue&4%bx_gU(A0_C8oTotF(okjQ6^Q)`7hd+E6hmG*_`lkUwL>yqf;YUEt zk{ONrUp~J(p<>dX@@CHd$<$8#`tI`x(dno0562osw&x4SIj>6f8En5$DGE_%db!yz zhP{7O3 zu6I*@Vc~R&(W9AKZ!hJ@S-+2Af0?{?R+rEe`#QBvQF3OY>A3ZPEzcR{lVCbV4lAlD zl*pL{w8Qbms)!n*PMmyf;o(ZA;>-WTNym)ymNgJf(|>v6-yITd35P_l{_T)x;)rS+#Fn0?EOAoz z?*-Z+_K3sJ0{?N7QQ$uk|5>0D5Z)Pa(ffJOJH8Izz&NU!0<~EGk6N5WPMNZ?t7>W4 zm{Z7B^z*#wXMD>$JGsJv@?=Ub;(>S~UWhm1L)7WNdeoF{6OiLS=48sY?$=$$0owCr zn6j;(fAJr;{$1nz5FBU6H!~U)0FB!8k4A+eVQ@tGufph!nlCFC2}dH3NF)l0Cff5K zo`Sm0*XYmp|Jzy-i^Snu@okJ&BtR=T{?UpQB-NB{>mRL{KZ1R}Ir&H$l8$5`nMf9L z8aYEW>HqeMDI3S1ZTr_;|Nrl2;)M6JK$IQ$0s=^6kbTGnoEhKQ$F>2T|FGg$0*LO=EPa`I>#w+-aMRPZ(~lCcd)gz*S4WJyII@7%Qo}xXu3I&DFk{M zlTW!gQ(Vc9%PD&E!9==l#98O71>JL!-cHx>Fn&NYwqAiHneg!WsGW~ zIxF1C6jxhIbBFo-AZ@;ix<79kIzfdFQF zchjG=p20Y_MHo*jfNAbxNz7W_KCYlrm5pV#;C2lyQp7MvUBW5p5T;oHO?Y*n?okkq9Q<9#QWQZN zQ53~c*iQ|}*@P|t?p_7FNOd)*xGIn>ohfv#CAiGM(bkpVJ@}{ZNE(MHlG-L=l7FJX|bBmmvJd2zVh<{;U_hOVDLNetvubQM(oBN<;=- zg|0^Sp=;4~=z4Smy3r7{1qP4v@c;H*=(q2JzkL?~P>{z9Z~;63ckN?a0mHozAfX7L zpc=_TH=&!MAMybTN&y62SmtrAUSegZwV*!qq#Si|$#wZWU%V1+%1Q+_nm8c+) zK=?Nf1g=EIAWd{0>(B$xEU_;1ARX5#QE5~LxsA%Aa;QAA3RTdd!l(z7VRL7UU@hX3 z=tSM)&J;&;S6mW5fJ@-wT2y!Iqo!;-D*ZpAV_|M(O@0j2&_(soW2ioA060zmXn}H~WA5N$W3Fuh zK&WF)A(NeO8AhdzP-EahdPki{jZhQRl);1als{_*#1Iu%65!`kP~a93R^a0n5)~2U z7UL6wZvrB63jE?CO2TsdjC?InOK7qdz&M$XI=QG7N=A>P)~F4g7=Pa(i1GIoREGXM zF%IZo{;WeE+uA=wSSeD1+R;1uGHMT83gQcOpmS>7FX4qc1JAku6)7kceqB+wpZt0X zc(@t$q6@8Nq{;B4t&^3llQkg`fITb;X$6-AP^&sy{VkW~QwiWbf*)pKLvaP1f>VKs z^;uc)zaafjL`MD5fS;_aj4RHwatN@k(mz-^64DOMr~SbT3?xJo&_v`K9pT7k^se6_ zqrzYTLjumsova-Iz@DJVbO0j(L!(z&T%4)4 zbRlj@jCp-m8<5{lWGeL-kw0A$+fo&s%t2&Y;Trf+Tz!WOqt*!PpG`UHK7KBLb`>Et4A&XbwZC;$E3 z^c?M_)0xQsUpnap+6SHVlISGpihuLS|C=s)4P)EjZ!z^Ay2!APZ8d>s=m1?MF9S(i zf(}Br4gFNd31u9R3>`*4&~a)VVA)6X6O7}}fL-J01YlV+`jxJN6LR<(`rVXmGoe5` zI}s`_=&jCb)ik%VbtCjd3uk8svbmGG3z?zY z0Ftrd=C})P#Dbr|EdkanKxDJ9teX4z^PAd<&S41f6R4fIDQ^4=Ub#kSGrY-N_Oa(BpV#S>q*{+9d$@l^S zPF5f?7(1aGVVkin*j9|=H?2ny=x&Q2#~pF|C)jpu2jsjH+lB4MII%s1l)(03To@N} z4St)A2uiw9+{sE5XGeWY;u|3gbm)~}ToZSUKp6zmGU%vBe@xri;Mcp_&MdeMZcThx zbiMfYbMX1l#{tHV34n~G=YN!F)p<-16T*b)^6~FfLO%X>rK&~L;OEThKQ%xc6Nd(f zBh5s~cEkrt<|j)2PjAR#vhap1;|&KKK8!c`|Kl5nvBU7jVa6LyID8nbBSae$1r`&05imlFk?dZz)Xmd3>fm~a}22eXWB}F z>{M50M|xg#T?Dj$dT0DIl|ZFmt@!yMU3mPF_Akv7RutzG6IKN6iJwoNP^JjLl$YmL zQj(XG6yjH6G|wEfAe1R0J7rL;T?#Qv%nBo8$1!WnhTbq+hEg^E1YN0`zk-+P z)uO*l-1_b6r-Wh54d=xo$V zL>LGD!rYN&>=gWZ0z@=p-k1;4bj+48L~0$91*$rMwT!L<%9BCrP>8ND`dtBI!F_Ny z+><_GScdzv;C^uO3JZ?o-ha#)FdXy$i9x4u_jwEo24FZjk2egkh=4SY`83dNV6s^T z6Hj8=Ie|=KaacT-0Du#RCE)va$S5$B72Iow3>5>XK4 zmJ<;W=2nzflvfZ{R1_AM7txiob#*j%`HwS7VoHc3z?EJMXxPs|3{-v}pFfi5r~yek zg%yQ1CkS{GJ3YIHd-Ez7S5p6EoWE>4ihFO!W?DF zw$0oPMmbF8VJ7I{p+GS|sqUl$1|*7u2NXtN7oq7#;$bX!CLRbvFb~g%zY%m%$nu*A z#G0|IKiMw?51MDco4{(p^Q^{Tzk84tI-mBh?AL}p!rFlpooFJ#euD081lQr=J7hL7 z*bkbF2!DAuRy#?gMfXFy}{mM@38k+ zKQ=%(D(uM~JOaPI@Z%|XI-ZOt;xs%4kJVFSOfoyMVeA7o0vU~hs2#&T>ZoaHD$9XW zfj;9TdKji!E8&uky?-Yv@o~Th!Q=3RcI-2}{{P18k!nii<>=GGK*7aKa)cVIuDh&iN*gpx1=H7#v(3oTb?7b@rx zvJSS^PL6<6Fm99BnCMxOm`KbpUjLcPIT91y1)~J)*hyj`v62=cOG%4KOGrye%fK~5 z%>IF1$CVt+t*LOW1}4TGGJK34W@E!s@iee_9U?oplL_urBfF8gRY84ob7Yjdg0vFe zB62@RT7fWT-iRS-^*O>%!+2yZX&ngMc`NmK(t6Sc(nh)zo=+sC@chM}nE#lxiL{xt zg|rpw%|Y5m+Kz4@?Sv|80b@8>!)O9}{9+Qq3<}hqzor7+6vz%_YjamJo`qk)&w!mN zmzbfbl6I3g@za0(&614D?j`M`XQ}GuO3V})h2)U-lDNPq@vG1dU_UTwlem$6BwjoR zw7Kl3=xXdG^cS5oN&IkA0J!rl@F#F4_KqZuy(dZjt^)pIQbAzSWMETZP+-(^z^Z3~ zphsXbv_l3>OUs6y_%mEAse{vwL3uikohKb7X^=Dlng2)zK$~~R%~RLvR~Nuo`Pamq!t$a5V)BA= z-10)gBA{U?C~?aPi-~Y6h{(&y3&|@AD2g#=Vk_Al5> zIzgiR#O8~5(JyQ!xdRRt&*Ly1n@L`fRx+RVFW3y@*Pj&d3!CvvJ7lC71C|Alm|svp zSVUAK25Us4(ae18Mg z9Wu*~A2)-R{0&s60jSQ9mhF()&uAcCL*UVdKk-O~u(=-wZ;q|y-=KM(bOF$@j*Y}% z^gL2NSUKi-_)n|P;`8(1Sl8Q*h=sMC){0e>*ZvYeZJZzNFVdEG5eqq67z=BG= z39z8?Pcl{iO(wvBBp_8XL9Bm6rbHmoSs)S+>(4B%{hKWA{g+uZ<1xPme9O-P53C_4 zF3-ourzFk|_ERus34s+=PE4F16o23gSp|N6L4Ljt(jy?*Qe+jWgVd>QVNNCMTD#d= zf&9IJ-(qlQ7jWmzdG2J;w}pZEK^n%n!AG-62#L%6;_!Hy}yaf3{=M)qa z^!d92uSuQw-Cu>igF^4k7YdCLSKwFRmlcxZ78I2OtGJ@50JoeFn8t+^MdSs9MC66} z6&aQ}(lF@*xLydiN+En7A&rv8NFPa`=;-p9;f5h@{`X8#7~~iyO_-=^DL7lY5n`Hf z7M>?flBP(Y4g4b=qz#be_b2~B8C5M=ilvP$m{J~*W=ONz6lZ5w97R}=4ajzc3lT;n z5NY%U`W9ow*svWKFD8r~z;s|*hGWrKEX*Miu_P=7OU2T$3@i&P$F5-4vB%hR>;))N zgot_%I?z{a8k+@0g9PJk9f^a)OA;X+Bq@V1I7TuhIg;E-r%0Y8KY*PGQWPnMltDUA zxNb&NtjQ$G6enEKh;0jKf}Mk|Em9e|8DEP_(g5aXyi@{aFHNkbk9l=k7KLmdXo(P@_ z{uVqFJQsq5aE9=ONQEecXoZ-EIERFV#D^q?&_c38azlzku7=zUxgXLN(iQR~4Dr2-_C6BWza~XV~7b zePLW-++jRnykY!df?+~o!eOFeDq(72N5eG3w8D(SOv22<$YIuDE@6}~*D#MTuP|Cz za#%`Oc344JWms3(i?Fw0@52VdhQdCCjfPEyeG8ijX9`~s&Jw;WoF`l;{9w3pxJI~E zxK6lUxLLS)xO@1i@H650;Z@HM17C?5j7W$Mw6nMqZdT8L@$b7 z9K9rZS@iPg711lBS4D4%=8qPP7LFE;7K@gRmXB79{wrD~S|?gBT0hz(+AP{5+AG>S z+CMrbIw|^ebawRF=$z={=#uD5(Kn+XMR!C$j_!_r8r>5;8^ap2DrQ5>ju^feu^9On zg&3unLot8FsKltoXvY}G*u_v|@R*pG%$SCl#+c@qYcV%sZpGY=X^9z&`55y#W;|vx z=4;HiSR{5^?Dp85vAbjU#O{mbiWP{Jj8%?38fz458+$VLbZlO1U2JRY>)6S-#c|u> zgyIgx>BjlQg~TPs<;PXVU5{&vdlB~`ZYG{J{#d+u{K@#B_~7`^`0)71_^9~k_}KXP z_{4Zxd~$qBd|G^Zd}jRV`0V(~`0DuD`1<%O@i*gd$KQ=_iEoR48s8J&8~-}~Z33D= zN?4MxEMZ;3`UL3&r3BLi^90KTa>A*E$b|BQ`h>d)&k_a_rV^PGS0`>y^hwM}%t?3ur8~MYP4VrL^TVHrfi>D%u*_TG~3=2HHm2Cfa7&7TQ*t5KV+8Mw6gP z(iCV)v_mvinmX+m&46Y^GpAY7a9RK@k`_%%rlrubX}4+Zv|-u^ZH)GbHl4(rv@nS^ zX?GHDl30>N(t)IdNis=tNeW3yNoq-^N#rC-QfN|4Qe09(QbtmC(z&GbNf(pqlA4kp zBt1^*O?sU)k&GoTP2P~qp1e7EYx16C$z+*i<>X_@rpY$R&dH~eJ(InYeUp=tPbXhU zE=<0hT$6k?`C)Qb^6TWWR1|*wj^ys8fThFnnK!{w6kgF z($1&lrsbs-q!pzVr(H}dO)E<)PrIB}nO2onlUAEnmsX$Fme!uunbwuoo%S;Ab=ups z!L;GD@wCabuW8@Y=F&H%Z%OA!-}!-e$bd7|0mP_>eK0@iF6b=DN%snY%JM zGxuh~4rQ6Vnf#f8nZlU|Gi5RrGaWKLGJP|HGea}OGZQjtnaPY0kt`6dd^qukl*Wc5t{ts0DKtKQh literal 0 HcmV?d00001 diff --git a/img/badge.svg b/img/badge.svg new file mode 100644 index 0000000..a240ced --- /dev/null +++ b/img/badge.svg @@ -0,0 +1,19 @@ + + + + badge + Created with Sketch. + + + + + + Connectio + n + + + Compatibl + e + + + \ No newline at end of file From c255edbca36f3ae901984e5a610a53b9b9e963e1 Mon Sep 17 00:00:00 2001 From: David Dias Date: Tue, 15 Sep 2015 16:50:58 +0100 Subject: [PATCH 03/86] readme till API --- README.md | 58 ++++++++++++++++++++++++++++++++++++++++++++-- package.json | 25 ++++++++++++++++++++ tests/base-test.js | 9 +++++++ tests/index.js | 6 +++++ 4 files changed, 96 insertions(+), 2 deletions(-) create mode 100644 package.json create mode 100644 tests/base-test.js create mode 100644 tests/index.js diff --git a/README.md b/README.md index 132222e..43f8dcf 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,56 @@ -# abstract-socket -A test suite and interface you can use to implement a socket. +abstract-connection +=================== + +[![](https://img.shields.io/badge/made%20by-Protocol%20Labs-blue.svg?style=flat-square)](http://ipn.io) [![](https://img.shields.io/badge/freenode-%23ipfs-blue.svg?style=flat-square)](http://webchat.freenode.net/?channels=%23ipfs) + +> A test suite and interface you can use to implement a connection. A connection is understood as something that offers a dial+listen interface + +The primary goal of this module is to enable developers to pick and swap their Record Store module as they see fit for their libp2p installation, without having to go through shims or compatibility issues. This module and test suite were heavily inspired by abstract-blob-store and abstract-stream-muxer. + +Publishing a test suite as a module lets multiple modules all ensure compatibility since they use the same test suite. + +The API is presented with both Node.js and Go primitives, however, there is not actual limitations for it to be extended for any other language, pushing forward the cross compatibility and interop through diferent stacks. + + +# Modules that implement the interface + +- [node-libp2p-tcp](https://github.com/diasdavid/node-libp2p-tcp) + +# Badge + +Include this badge in your readme if you make a module that is compatible with the abstract-connection API. You can validate this by running the tests. + +![](https://raw.githubusercontent.com/diasdavid/abstract-connection/master/img/badge.svg) + +# How to use the battery of tests + +## Node.js + +``` +var tape = require('tape') +var tests = require('abstract-connection/tests') +var YourConnectionHandler = require('../src') + +var common = { + setup: function (t, cb) { + cb(null, YourConnectionHandler) + }, + teardown: function (t, cb) { + cb() + } +} + +tests(tape, common) +``` + +## Go + +> WIP + +# API + +A valid (read: that follows this abstraction) connection, must implement the following API. + +note: a `stream` should be a stream that implements the [abstract-stream](https://github.com/diasdavid/abstract-stream) interface. + + diff --git a/package.json b/package.json new file mode 100644 index 0000000..5455457 --- /dev/null +++ b/package.json @@ -0,0 +1,25 @@ +{ + "name": "abstract-connection", + "version": "0.0.0", + "description": "A test suite and interface you can use to implement a connection.", + "main": "index.js", + "directories": { + "test": "tests" + }, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "https://github.com/diasdavid/abstract-connection.git" + }, + "keywords": [ + "IPFS" + ], + "author": "David Dias ", + "license": "MIT", + "bugs": { + "url": "https://github.com/diasdavid/abstract-connection/issues" + }, + "homepage": "https://github.com/diasdavid/abstract-connection" +} diff --git a/tests/base-test.js b/tests/base-test.js new file mode 100644 index 0000000..8d5f5c2 --- /dev/null +++ b/tests/base-test.js @@ -0,0 +1,9 @@ +module.exports.all = function (test, common) { + test('a test', function (t) { + common.setup(test, function (err, Connection) { + t.ifError(err) + t.pass('woot!') + t.end() + }) + }) +} diff --git a/tests/index.js b/tests/index.js new file mode 100644 index 0000000..b232406 --- /dev/null +++ b/tests/index.js @@ -0,0 +1,6 @@ +var timed = require('timed-tape') + +module.exports = function (test, common) { + test = timed(test) + require('./base-test.js').all(test, common) +} From a9164f0ca43bcef746287dc8f3388fca81b7118f Mon Sep 17 00:00:00 2001 From: David Dias Date: Tue, 15 Sep 2015 16:51:41 +0100 Subject: [PATCH 04/86] fix badge link --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 43f8dcf..966e08e 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ The API is presented with both Node.js and Go primitives, however, there is not Include this badge in your readme if you make a module that is compatible with the abstract-connection API. You can validate this by running the tests. -![](https://raw.githubusercontent.com/diasdavid/abstract-connection/master/img/badge.svg) +![](https://raw.githubusercontent.com/diasdavid/abstract-connection/master/img/badge.png) # How to use the battery of tests From 225ec3cf557f4effb8e7172fc84789a337a255cb Mon Sep 17 00:00:00 2001 From: David Dias Date: Tue, 15 Sep 2015 18:23:09 +0100 Subject: [PATCH 05/86] add interface --- README.md | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 966e08e..9fcef64 100644 --- a/README.md +++ b/README.md @@ -11,11 +11,12 @@ Publishing a test suite as a module lets multiple modules all ensure compatibili The API is presented with both Node.js and Go primitives, however, there is not actual limitations for it to be extended for any other language, pushing forward the cross compatibility and interop through diferent stacks. - # Modules that implement the interface - [node-libp2p-tcp](https://github.com/diasdavid/node-libp2p-tcp) +note: for any new given implementation that adds one more option to the multiaddr space that was not expected yet, the respective multiaddr should be added to the PeerInfo objects available on the tests, so that implementation can be properly tested. + # Badge Include this badge in your readme if you make a module that is compatible with the abstract-connection API. You can validate this by running the tests. @@ -51,6 +52,24 @@ tests(tape, common) A valid (read: that follows this abstraction) connection, must implement the following API. -note: a `stream` should be a stream that implements the [abstract-stream](https://github.com/diasdavid/abstract-stream) interface. +### Dialing to another Peer +- `Node.js` stream = conn.dial(peerInfo, []) +This method dials a connection to the Peer referenced by the peerInfo object. + +peerInfo must be of the type `PeerInfo`. + +stream must be a stream that implements the [abstract-stream](https://github.com/diasdavid/abstract-stream) interface. + +[] are not mandatory fields for all the implementations that might be passed for certain implementations for them to work (e.g. a Signalling Server for a WebRTC transport/connection implementation) + +### Listening for incoming connections from other Peers + +- `Node.js` conn.listen(options, function (stream) {}) + +This method waits and listens for incoming connections by other peers. + +stream must be a stream that implements the [abstract-stream](https://github.com/diasdavid/abstract-stream) interface. + +Options are the properties this listener must have access in order to properly listen on a given transport/socket From f1f7d9b5ce4b2c9757cabf9e2f3fe0af6c2adec1 Mon Sep 17 00:00:00 2001 From: David Dias Date: Tue, 15 Sep 2015 19:36:44 +0100 Subject: [PATCH 06/86] spec update --- README.md | 29 +++++++++++++++++++++++------ tests/base-test.js | 4 ++++ 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 9fcef64..1d20d38 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,8 @@ The primary goal of this module is to enable developers to pick and swap their R Publishing a test suite as a module lets multiple modules all ensure compatibility since they use the same test suite. +The purpose of this abstraction is not to reinvent any wheels when it comes to dialing and listening to connections, instead, it tries to uniform several transports through a shimmed interface. + The API is presented with both Node.js and Go primitives, however, there is not actual limitations for it to be extended for any other language, pushing forward the cross compatibility and interop through diferent stacks. # Modules that implement the interface @@ -54,22 +56,37 @@ A valid (read: that follows this abstraction) connection, must implement the fol ### Dialing to another Peer -- `Node.js` stream = conn.dial(peerInfo, []) +- `Node.js` var stream = conn.dial(multiaddr, [options]) This method dials a connection to the Peer referenced by the peerInfo object. -peerInfo must be of the type `PeerInfo`. +multiaddr must be of the type [`multiaddr`](http://npmjs.org/multiaddr). -stream must be a stream that implements the [abstract-stream](https://github.com/diasdavid/abstract-stream) interface. +`stream` must implements the [abstract-stream](https://github.com/diasdavid/abstract-stream) interface. -[] are not mandatory fields for all the implementations that might be passed for certain implementations for them to work (e.g. a Signalling Server for a WebRTC transport/connection implementation) +`[options]` are not mandatory fields for all the implementations that might be passed for certain implementations for them to work (e.g. a Signalling Server for a WebRTC transport/connection implementation) ### Listening for incoming connections from other Peers -- `Node.js` conn.listen(options, function (stream) {}) +- `Node.js` var listener = conn.createListener(options, function (stream) {}) This method waits and listens for incoming connections by other peers. -stream must be a stream that implements the [abstract-stream](https://github.com/diasdavid/abstract-stream) interface. +`stream` must be a stream that implements the [abstract-stream](https://github.com/diasdavid/abstract-stream) interface. Options are the properties this listener must have access in order to properly listen on a given transport/socket + +### Start listening + +- `Node.js` listener.listen(options, [callback]) + +This method opens the listener to start listening for incoming connections + +### Close an active listener + +- `Node.js` listener.close([callback]) + +This method closes the listener so that no more connections can be open + +`callback` is function that gets called when the listener is closed. It is optional + diff --git a/tests/base-test.js b/tests/base-test.js index 8d5f5c2..e0f2b15 100644 --- a/tests/base-test.js +++ b/tests/base-test.js @@ -6,4 +6,8 @@ module.exports.all = function (test, common) { t.end() }) }) + + // test for: + // 1. dial and listen + // 2. close } From e79d664d242486c9c06134cf5c92c57f4701830a Mon Sep 17 00:00:00 2001 From: David Dias Date: Wed, 16 Sep 2015 11:52:42 +0100 Subject: [PATCH 07/86] simple, gets it done --- README.md | 4 ++-- package.json | 8 +++++++- tests/base-test.js | 32 +++++++++++++++++++++++++------- 3 files changed, 34 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 1d20d38..9a2176f 100644 --- a/README.md +++ b/README.md @@ -62,7 +62,7 @@ This method dials a connection to the Peer referenced by the peerInfo object. multiaddr must be of the type [`multiaddr`](http://npmjs.org/multiaddr). -`stream` must implements the [abstract-stream](https://github.com/diasdavid/abstract-stream) interface. +`stream` must implements the [abstract-transport](https://github.com/diasdavid/abstract-transport) interface. `[options]` are not mandatory fields for all the implementations that might be passed for certain implementations for them to work (e.g. a Signalling Server for a WebRTC transport/connection implementation) @@ -72,7 +72,7 @@ multiaddr must be of the type [`multiaddr`](http://npmjs.org/multiaddr). This method waits and listens for incoming connections by other peers. -`stream` must be a stream that implements the [abstract-stream](https://github.com/diasdavid/abstract-stream) interface. +`stream` must be a stream that implements the [abstract-transport](https://github.com/diasdavid/abstract-transport) interface. Options are the properties this listener must have access in order to properly listen on a given transport/socket diff --git a/package.json b/package.json index 5455457..9ac97c0 100644 --- a/package.json +++ b/package.json @@ -21,5 +21,11 @@ "bugs": { "url": "https://github.com/diasdavid/abstract-connection/issues" }, - "homepage": "https://github.com/diasdavid/abstract-connection" + "homepage": "https://github.com/diasdavid/abstract-connection", + "devDependencies": { + }, + "dependencies": { + "multiaddr": "^1.0.0", + "timed-tape": "^0.1.0" + } } diff --git a/tests/base-test.js b/tests/base-test.js index e0f2b15..e136ef0 100644 --- a/tests/base-test.js +++ b/tests/base-test.js @@ -1,13 +1,31 @@ +var multiaddr = require('multiaddr') + module.exports.all = function (test, common) { test('a test', function (t) { - common.setup(test, function (err, Connection) { + common.setup(test, function (err, conn) { + t.plan(5) t.ifError(err) - t.pass('woot!') - t.end() + + var maddr = multiaddr('/ip4/127.0.0.1/tcp/9050') + + var listener = conn.createListener(function (stream) { + t.pass('received incoming connection') + stream.end() + listener.close(function () { + t.pass('listener closed successfully') + t.end() + }) + }) + + listener.listen(maddr.nodeAddress().port, function () { + t.pass('started listening') + var stream = conn.dial(maddr, { + ready: function () { + t.pass('dialed successfuly') + stream.end() + } + }) + }) }) }) - - // test for: - // 1. dial and listen - // 2. close } From cb6642e678b4a9cf65b079945a9df4870c0e6dbe Mon Sep 17 00:00:00 2001 From: David Dias Date: Wed, 16 Sep 2015 11:55:33 +0100 Subject: [PATCH 08/86] clean package.json --- package.json | 7 ------- 1 file changed, 7 deletions(-) diff --git a/package.json b/package.json index 9ac97c0..b7d9418 100644 --- a/package.json +++ b/package.json @@ -2,13 +2,6 @@ "name": "abstract-connection", "version": "0.0.0", "description": "A test suite and interface you can use to implement a connection.", - "main": "index.js", - "directories": { - "test": "tests" - }, - "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" - }, "repository": { "type": "git", "url": "https://github.com/diasdavid/abstract-connection.git" From 4a571f1ac1bfa94af52a196b1b4660d7315a2cf3 Mon Sep 17 00:00:00 2001 From: David Dias Date: Wed, 16 Sep 2015 11:55:44 +0100 Subject: [PATCH 09/86] Release v0.1.0. --- package.json | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index b7d9418..70ef40e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "abstract-connection", - "version": "0.0.0", + "version": "0.1.0", "description": "A test suite and interface you can use to implement a connection.", "repository": { "type": "git", @@ -15,8 +15,7 @@ "url": "https://github.com/diasdavid/abstract-connection/issues" }, "homepage": "https://github.com/diasdavid/abstract-connection", - "devDependencies": { - }, + "devDependencies": {}, "dependencies": { "multiaddr": "^1.0.0", "timed-tape": "^0.1.0" From 97f3bf5f81330e03b641b2cf2f14613b9d96e3aa Mon Sep 17 00:00:00 2001 From: David Dias Date: Thu, 17 Sep 2015 02:49:43 +0100 Subject: [PATCH 10/86] rename, following https://github.com/diasdavid/node-ipfs-swarm/issues/8#issuecomment-140929746 --- README.md | 38 +++++++++++++++++++------------------- img/badge.png | Bin 5258 -> 5226 bytes img/badge.sketch | Bin 65536 -> 40960 bytes img/badge.svg | 8 ++++---- package.json | 12 ++++++------ tests/base-test.js | 6 +++--- 6 files changed, 32 insertions(+), 32 deletions(-) diff --git a/README.md b/README.md index 9a2176f..ec31bef 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,15 @@ -abstract-connection +abstract-transport =================== [![](https://img.shields.io/badge/made%20by-Protocol%20Labs-blue.svg?style=flat-square)](http://ipn.io) [![](https://img.shields.io/badge/freenode-%23ipfs-blue.svg?style=flat-square)](http://webchat.freenode.net/?channels=%23ipfs) -> A test suite and interface you can use to implement a connection. A connection is understood as something that offers a dial+listen interface +> A test suite and interface you can use to implement a transport. A transport is understood as something that offers a dial+listen interface The primary goal of this module is to enable developers to pick and swap their Record Store module as they see fit for their libp2p installation, without having to go through shims or compatibility issues. This module and test suite were heavily inspired by abstract-blob-store and abstract-stream-muxer. Publishing a test suite as a module lets multiple modules all ensure compatibility since they use the same test suite. -The purpose of this abstraction is not to reinvent any wheels when it comes to dialing and listening to connections, instead, it tries to uniform several transports through a shimmed interface. +The purpose of this abstraction is not to reinvent any wheels when it comes to dialing and listening to transports, instead, it tries to uniform several transports through a shimmed interface. The API is presented with both Node.js and Go primitives, however, there is not actual limitations for it to be extended for any other language, pushing forward the cross compatibility and interop through diferent stacks. @@ -21,9 +21,9 @@ note: for any new given implementation that adds one more option to the multiadd # Badge -Include this badge in your readme if you make a module that is compatible with the abstract-connection API. You can validate this by running the tests. +Include this badge in your readme if you make a module that is compatible with the abstract-transport API. You can validate this by running the tests. -![](https://raw.githubusercontent.com/diasdavid/abstract-connection/master/img/badge.png) +![](https://raw.githubusercontent.com/diasdavid/abstract-transport/master/img/badge.png) # How to use the battery of tests @@ -31,12 +31,12 @@ Include this badge in your readme if you make a module that is compatible with t ``` var tape = require('tape') -var tests = require('abstract-connection/tests') -var YourConnectionHandler = require('../src') +var tests = require('abstract-transport/tests') +var YourTransportHandler = require('../src') var common = { setup: function (t, cb) { - cb(null, YourConnectionHandler) + cb(null, YourTransportHandler) }, teardown: function (t, cb) { cb() @@ -52,27 +52,27 @@ tests(tape, common) # API -A valid (read: that follows this abstraction) connection, must implement the following API. +A valid (read: that follows this abstraction) transport, must implement the following API. ### Dialing to another Peer -- `Node.js` var stream = conn.dial(multiaddr, [options]) +- `Node.js` var stream = transport.dial(multiaddr, [options]) -This method dials a connection to the Peer referenced by the peerInfo object. +This method dials a transport to the Peer referenced by the peerInfo object. multiaddr must be of the type [`multiaddr`](http://npmjs.org/multiaddr). -`stream` must implements the [abstract-transport](https://github.com/diasdavid/abstract-transport) interface. +`stream` must implements the [abstract-connection](https://github.com/diasdavid/abstract-connection) interface. -`[options]` are not mandatory fields for all the implementations that might be passed for certain implementations for them to work (e.g. a Signalling Server for a WebRTC transport/connection implementation) +`[options]` are not mandatory fields for all the implementations that might be passed for certain implementations for them to work (e.g. a Signalling Server for a WebRTC transport implementation) -### Listening for incoming connections from other Peers +### Listening for incoming transports from other Peers -- `Node.js` var listener = conn.createListener(options, function (stream) {}) +- `Node.js` var listener = transport.createListener(options, function (stream) {}) -This method waits and listens for incoming connections by other peers. +This method waits and listens for incoming transports by other peers. -`stream` must be a stream that implements the [abstract-transport](https://github.com/diasdavid/abstract-transport) interface. +`stream` must be a stream that implements the [abstract-connection](https://github.com/diasdavid/abstract-connection) interface. Options are the properties this listener must have access in order to properly listen on a given transport/socket @@ -80,13 +80,13 @@ Options are the properties this listener must have access in order to properly l - `Node.js` listener.listen(options, [callback]) -This method opens the listener to start listening for incoming connections +This method opens the listener to start listening for incoming transports ### Close an active listener - `Node.js` listener.close([callback]) -This method closes the listener so that no more connections can be open +This method closes the listener so that no more connections can be open on this transport instance `callback` is function that gets called when the listener is closed. It is optional diff --git a/img/badge.png b/img/badge.png index a79ca44967796cd1569661767d3bfbe09a1ad512..1c8fe6972b5957adcd6088961701fda1ff5cf368 100644 GIT binary patch delta 5224 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 JfB6_B{2wM#je`IH literal 65536 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 diff --git a/img/badge.svg b/img/badge.svg index a240ced..aa56dac 100644 --- a/img/badge.svg +++ b/img/badge.svg @@ -6,10 +6,10 @@ - - - Connectio - n + + + Transpor + t Compatibl diff --git a/package.json b/package.json index 70ef40e..f8c2d11 100644 --- a/package.json +++ b/package.json @@ -1,10 +1,10 @@ { - "name": "abstract-connection", - "version": "0.1.0", - "description": "A test suite and interface you can use to implement a connection.", + "name": "abstract-transport", + "version": "0.0.0", + "description": "A test suite and interface you can use to implement a transport.", "repository": { "type": "git", - "url": "https://github.com/diasdavid/abstract-connection.git" + "url": "https://github.com/diasdavid/abstract-transport.git" }, "keywords": [ "IPFS" @@ -12,9 +12,9 @@ "author": "David Dias ", "license": "MIT", "bugs": { - "url": "https://github.com/diasdavid/abstract-connection/issues" + "url": "https://github.com/diasdavid/abstract-transport/issues" }, - "homepage": "https://github.com/diasdavid/abstract-connection", + "homepage": "https://github.com/diasdavid/abstract-transport", "devDependencies": {}, "dependencies": { "multiaddr": "^1.0.0", diff --git a/tests/base-test.js b/tests/base-test.js index e136ef0..bd67f93 100644 --- a/tests/base-test.js +++ b/tests/base-test.js @@ -2,13 +2,13 @@ var multiaddr = require('multiaddr') module.exports.all = function (test, common) { test('a test', function (t) { - common.setup(test, function (err, conn) { + common.setup(test, function (err, transport) { t.plan(5) t.ifError(err) var maddr = multiaddr('/ip4/127.0.0.1/tcp/9050') - var listener = conn.createListener(function (stream) { + var listener = transport.createListener(function (stream) { t.pass('received incoming connection') stream.end() listener.close(function () { @@ -19,7 +19,7 @@ module.exports.all = function (test, common) { listener.listen(maddr.nodeAddress().port, function () { t.pass('started listening') - var stream = conn.dial(maddr, { + var stream = transport.dial(maddr, { ready: function () { t.pass('dialed successfuly') stream.end() From c48504ecbd8c6c93688cfe4cbbb5962b30e2c392 Mon Sep 17 00:00:00 2001 From: David Dias Date: Thu, 17 Sep 2015 02:51:47 +0100 Subject: [PATCH 11/86] Release v0.1.0. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f8c2d11..e2c0cd8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "abstract-transport", - "version": "0.0.0", + "version": "0.1.0", "description": "A test suite and interface you can use to implement a transport.", "repository": { "type": "git", From c121fd83fac0f7757bbee228301b297f5a69d8a7 Mon Sep 17 00:00:00 2001 From: David Dias Date: Fri, 11 Dec 2015 20:44:47 -0800 Subject: [PATCH 12/86] update name --- README.md | 16 ++++++++-------- package.json | 8 ++++---- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index ec31bef..1ba1fb5 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,15 @@ -abstract-transport +interface-transport =================== [![](https://img.shields.io/badge/made%20by-Protocol%20Labs-blue.svg?style=flat-square)](http://ipn.io) [![](https://img.shields.io/badge/freenode-%23ipfs-blue.svg?style=flat-square)](http://webchat.freenode.net/?channels=%23ipfs) > A test suite and interface you can use to implement a transport. A transport is understood as something that offers a dial+listen interface -The primary goal of this module is to enable developers to pick and swap their Record Store module as they see fit for their libp2p installation, without having to go through shims or compatibility issues. This module and test suite were heavily inspired by abstract-blob-store and abstract-stream-muxer. +The primary goal of this module is to enable developers to pick and swap their Record Store module as they see fit for their libp2p installation, without having to go through shims or compatibility issues. This module and test suite were heavily inspired by abstract-blob-store and interface-stream-muxer. Publishing a test suite as a module lets multiple modules all ensure compatibility since they use the same test suite. -The purpose of this abstraction is not to reinvent any wheels when it comes to dialing and listening to transports, instead, it tries to uniform several transports through a shimmed interface. +The purpose of this interfaceion is not to reinvent any wheels when it comes to dialing and listening to transports, instead, it tries to uniform several transports through a shimmed interface. The API is presented with both Node.js and Go primitives, however, there is not actual limitations for it to be extended for any other language, pushing forward the cross compatibility and interop through diferent stacks. @@ -21,9 +21,9 @@ note: for any new given implementation that adds one more option to the multiadd # Badge -Include this badge in your readme if you make a module that is compatible with the abstract-transport API. You can validate this by running the tests. +Include this badge in your readme if you make a module that is compatible with the interface-transport API. You can validate this by running the tests. -![](https://raw.githubusercontent.com/diasdavid/abstract-transport/master/img/badge.png) +![](https://raw.githubusercontent.com/diasdavid/interface-transport/master/img/badge.png) # How to use the battery of tests @@ -31,7 +31,7 @@ Include this badge in your readme if you make a module that is compatible with t ``` var tape = require('tape') -var tests = require('abstract-transport/tests') +var tests = require('interface-transport/tests') var YourTransportHandler = require('../src') var common = { @@ -62,7 +62,7 @@ This method dials a transport to the Peer referenced by the peerInfo object. multiaddr must be of the type [`multiaddr`](http://npmjs.org/multiaddr). -`stream` must implements the [abstract-connection](https://github.com/diasdavid/abstract-connection) interface. +`stream` must implements the [interface-connection](https://github.com/diasdavid/interface-connection) interface. `[options]` are not mandatory fields for all the implementations that might be passed for certain implementations for them to work (e.g. a Signalling Server for a WebRTC transport implementation) @@ -72,7 +72,7 @@ multiaddr must be of the type [`multiaddr`](http://npmjs.org/multiaddr). This method waits and listens for incoming transports by other peers. -`stream` must be a stream that implements the [abstract-connection](https://github.com/diasdavid/abstract-connection) interface. +`stream` must be a stream that implements the [interface-connection](https://github.com/diasdavid/interface-connection) interface. Options are the properties this listener must have access in order to properly listen on a given transport/socket diff --git a/package.json b/package.json index e2c0cd8..f0a1e62 100644 --- a/package.json +++ b/package.json @@ -1,10 +1,10 @@ { - "name": "abstract-transport", + "name": "interface-transport", "version": "0.1.0", "description": "A test suite and interface you can use to implement a transport.", "repository": { "type": "git", - "url": "https://github.com/diasdavid/abstract-transport.git" + "url": "https://github.com/diasdavid/interface-transport.git" }, "keywords": [ "IPFS" @@ -12,9 +12,9 @@ "author": "David Dias ", "license": "MIT", "bugs": { - "url": "https://github.com/diasdavid/abstract-transport/issues" + "url": "https://github.com/diasdavid/interface-transport/issues" }, - "homepage": "https://github.com/diasdavid/abstract-transport", + "homepage": "https://github.com/diasdavid/interface-transport", "devDependencies": {}, "dependencies": { "multiaddr": "^1.0.0", From 6848fa9819ba88763f58eae04f4dc2eb5b80549e Mon Sep 17 00:00:00 2001 From: David Dias Date: Fri, 11 Dec 2015 20:45:01 -0800 Subject: [PATCH 13/86] Release v0.1.1. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f0a1e62..72989bb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "interface-transport", - "version": "0.1.0", + "version": "0.1.1", "description": "A test suite and interface you can use to implement a transport.", "repository": { "type": "git", From 1add6a71353abe62a666fe24b39d2cad485ae6b5 Mon Sep 17 00:00:00 2001 From: David Dias Date: Sun, 12 Jun 2016 10:34:01 -0700 Subject: [PATCH 14/86] Revamp the spec, now that there is some battle earned understanding of the needs across transports --- README.md | 105 +++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 81 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index 1ba1fb5..e165568 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,12 @@ interface-transport =================== -[![](https://img.shields.io/badge/made%20by-Protocol%20Labs-blue.svg?style=flat-square)](http://ipn.io) [![](https://img.shields.io/badge/freenode-%23ipfs-blue.svg?style=flat-square)](http://webchat.freenode.net/?channels=%23ipfs) +[![](https://img.shields.io/badge/made%20by-Protocol%20Labs-blue.svg?style=flat-square)](http://ipn.io) +[![](https://img.shields.io/badge/freenode-%23ipfs-blue.svg?style=flat-square)](http://webchat.freenode.net/?channels=%23ipfs) -> A test suite and interface you can use to implement a transport. A transport is understood as something that offers a dial+listen interface +> A test suite and interface you can use to implement a libp2p transport. A libp2p transport is understood as something that offers a dial and listen interface. -The primary goal of this module is to enable developers to pick and swap their Record Store module as they see fit for their libp2p installation, without having to go through shims or compatibility issues. This module and test suite were heavily inspired by abstract-blob-store and interface-stream-muxer. +The primary goal of this module is to enable developers to pick and swap their transport module as they see fit for their libp2p installation, without having to go through shims or compatibility issues. This module and test suite were heavily inspired by abstract-blob-store, interface-stream-muxer and others. Publishing a test suite as a module lets multiple modules all ensure compatibility since they use the same test suite. @@ -15,9 +16,11 @@ The API is presented with both Node.js and Go primitives, however, there is not # Modules that implement the interface -- [node-libp2p-tcp](https://github.com/diasdavid/node-libp2p-tcp) - -note: for any new given implementation that adds one more option to the multiaddr space that was not expected yet, the respective multiaddr should be added to the PeerInfo objects available on the tests, so that implementation can be properly tested. +- [js-libp2p-tcp](https://github.com/diasdavid/node-libp2p-tcp) +- [js-libp2p-webrtc-star](https://github.com/diasdavid/js-libp2p-webrtc-star) +- [js-libp2p-websockets](https://github.com/diasdavid/js-libp2p-websockets) +- [js-libp2p-utp](https://github.com/diasdavid/js-libp2p-utp) +- [webrtc-explorer](https://github.com/diasdavid/webrtc-explorer) # Badge @@ -52,41 +55,95 @@ tests(tape, common) # API -A valid (read: that follows this abstraction) transport, must implement the following API. +A valid (read: that follows the interface defined) transport, must implement the following API. -### Dialing to another Peer +**Table of contents:** -- `Node.js` var stream = transport.dial(multiaddr, [options]) +- type: `Transport` + - `new Transport([options])` + - `transport.dial(multiaddr, [options, callback])` + - event: 'connect' + - event: 'error' + - `transport.createListener([options], handlerFunction)` + - type: `transport.Listener` + - event: 'listening' + - event: 'close' + - event: 'connection' + - event: 'error' + - `listener.listen(multiaddr, [callback])` + - `listener.getAddrs(callback)` + - `listener.close([options])` -This method dials a transport to the Peer referenced by the peerInfo object. +### Creating a transport instance -multiaddr must be of the type [`multiaddr`](http://npmjs.org/multiaddr). +- `JavaScript` - `var transport = new Transport([options])` + +Creates a new Transport instance. `options` is a optional JavaScript object, might include the necessary parameters for the transport instance. + +**Note: Why is it important to instantiate a transport -** Some transports have state that can be shared between the dialing and listening parts. One example is a libp2p-webrtc-star (or pretty much any other WebRTC flavour transport), where that, in order to dial, a peer needs to be part of some signalling network that is shared also with the listener. + +### Dial to another peer + +- `JavaScript` - `var conn = transport.dial(multiaddr, [options, callback])` + +This method dials a transport to the Peer listening on `multiaddr`. + +`multiaddr` must be of the type [`multiaddr`](http://npmjs.org/multiaddr). `stream` must implements the [interface-connection](https://github.com/diasdavid/interface-connection) interface. -`[options]` are not mandatory fields for all the implementations that might be passed for certain implementations for them to work (e.g. a Signalling Server for a WebRTC transport implementation) +`[options]` is an optional argument, which can be used by some implementations -### Listening for incoming transports from other Peers +`callback` should follow the `function (err, conn)` signature. -- `Node.js` var listener = transport.createListener(options, function (stream) {}) +`conn` is the same `conn` that gets returned by call, which should follow [`interface-connection`](https://github.com/diasdavid/interface-connection). This `conn` object can emit 3 extra events: -This method waits and listens for incoming transports by other peers. +- `connect` - +- `timeout` - +- `error` - -`stream` must be a stream that implements the [interface-connection](https://github.com/diasdavid/interface-connection) interface. -Options are the properties this listener must have access in order to properly listen on a given transport/socket +### Create a listener -### Start listening +- `JavaScript` - `var listener = transport.createListener([options], handlerFunction)` -- `Node.js` listener.listen(options, [callback]) +This method creates a listener on the transport. -This method opens the listener to start listening for incoming transports +`options` is an optional object that contains the properties the listener must have, in order to properly listen on a given transport/socket. -### Close an active listener +`handlerFunction` is a function called each time a new connection is received. It must follow the following signature: `function (conn) {}`, where `conn` is a connection that follows the [`interface-connection`](https://github.com/diasdavid/interface-connection). -- `Node.js` listener.close([callback]) +The listener object created, can emit the following events: -This method closes the listener so that no more connections can be open on this transport instance +- `listening` - +- `close` - +- `connection` - +- `error` - -`callback` is function that gets called when the listener is closed. It is optional +### Start a listener +- `JavaScript` - `listener.listen(multiaddr, [callback])` + +This method puts the listener in `listening` mode, waiting for incoming connections. + +`multiaddr` is the address where the listener should bind to. + +`callback` is a function called once the listener is ready. + +### Get listener addrs + +- `JavaScript` - `listener.getAddrs(callback)` + +This method retrieves the addresses in which this listener is listening. Useful for when listening on port 0 or any interface (0.0.0.0). + +### Stop a listener + +- `JavaScript` - `listener.close([options, callback])` + +This method closes the listener so that no more connections can be open on this transport instance. + +`options` is an optional object that might contain the following properties: + + - `timeout` - A timeout value (in ms) that fires and destroys all the connections on this transport if the transport is not able to close graciously. (e.g { timeout: 1000 }) + +`callback` is function that gets called when the listener is closed. It is optional. From 0ac4c1a938d8d35c17c9795b8bc350e3a3501a94 Mon Sep 17 00:00:00 2001 From: David Dias Date: Thu, 16 Jun 2016 11:19:48 +0100 Subject: [PATCH 15/86] Release v0.2.0. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 72989bb..6c96e7e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "interface-transport", - "version": "0.1.1", + "version": "0.2.0", "description": "A test suite and interface you can use to implement a transport.", "repository": { "type": "git", From d9b20bea63826362a769418847a132e31317485c Mon Sep 17 00:00:00 2001 From: greenkeeperio-bot Date: Thu, 4 Aug 2016 19:12:33 +0200 Subject: [PATCH 16/86] chore(package): update dependencies https://greenkeeper.io/ --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 6c96e7e..7d18556 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ "homepage": "https://github.com/diasdavid/interface-transport", "devDependencies": {}, "dependencies": { - "multiaddr": "^1.0.0", + "multiaddr": "^2.0.2", "timed-tape": "^0.1.0" } } From 1881c590ed0dfc8c883300a8aaf15c4661a81e46 Mon Sep 17 00:00:00 2001 From: Richard Littauer Date: Fri, 5 Aug 2016 09:05:56 -0400 Subject: [PATCH 17/86] Fix spelling error Also slight syntax improvement. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e165568..2dd0479 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ The primary goal of this module is to enable developers to pick and swap their t Publishing a test suite as a module lets multiple modules all ensure compatibility since they use the same test suite. -The purpose of this interfaceion is not to reinvent any wheels when it comes to dialing and listening to transports, instead, it tries to uniform several transports through a shimmed interface. +The purpose of this interface is not to reinvent any wheels when it comes to dialing and listening to transports. Instead, it tries to uniform several transports through a shimmed interface. The API is presented with both Node.js and Go primitives, however, there is not actual limitations for it to be extended for any other language, pushing forward the cross compatibility and interop through diferent stacks. From f9266e4036b727731c36100526817f2e5b4f76ed Mon Sep 17 00:00:00 2001 From: greenkeeperio-bot Date: Thu, 1 Sep 2016 22:07:15 +0200 Subject: [PATCH 18/86] chore(package): update timed-tape to version 0.1.1 https://greenkeeper.io/ --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 7d18556..249aa8d 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,6 @@ "devDependencies": {}, "dependencies": { "multiaddr": "^2.0.2", - "timed-tape": "^0.1.0" + "timed-tape": "^0.1.1" } } From 2e12166ba84b661b4f16ae3fd9e581615e2b018a Mon Sep 17 00:00:00 2001 From: David Dias Date: Fri, 19 Aug 2016 14:37:40 +0100 Subject: [PATCH 19/86] feat(spec): update the dial interface to cope with new pull additions --- README.md | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/README.md b/README.md index 2dd0479..cd43b50 100644 --- a/README.md +++ b/README.md @@ -62,8 +62,6 @@ A valid (read: that follows the interface defined) transport, must implement the - type: `Transport` - `new Transport([options])` - `transport.dial(multiaddr, [options, callback])` - - event: 'connect' - - event: 'error' - `transport.createListener([options], handlerFunction)` - type: `transport.Listener` - event: 'listening' @@ -96,12 +94,7 @@ This method dials a transport to the Peer listening on `multiaddr`. `callback` should follow the `function (err, conn)` signature. -`conn` is the same `conn` that gets returned by call, which should follow [`interface-connection`](https://github.com/diasdavid/interface-connection). This `conn` object can emit 3 extra events: - -- `connect` - -- `timeout` - -- `error` - - +`conn` is the same `conn` that gets returned by call, which should follow [`interface-connection`](https://github.com/diasdavid/interface-connection). `err` is an `Error` instance to signal that the dial was unsuccessful, this error can be a 'timeout' or simply 'error'. ### Create a listener From 1bd20d94d9d8f7ec2b3d79035002e641126f40bf Mon Sep 17 00:00:00 2001 From: David Dias Date: Thu, 1 Sep 2016 17:34:52 -0400 Subject: [PATCH 20/86] feat(dialer): remove conn from on connect callback --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index cd43b50..4097239 100644 --- a/README.md +++ b/README.md @@ -92,9 +92,9 @@ This method dials a transport to the Peer listening on `multiaddr`. `[options]` is an optional argument, which can be used by some implementations -`callback` should follow the `function (err, conn)` signature. +`callback` should follow the `function (err)` signature. -`conn` is the same `conn` that gets returned by call, which should follow [`interface-connection`](https://github.com/diasdavid/interface-connection). `err` is an `Error` instance to signal that the dial was unsuccessful, this error can be a 'timeout' or simply 'error'. +`err` is an `Error` instance to signal that the dial was unsuccessful, this error can be a 'timeout' or simply 'error'. ### Create a listener From d50224dc5d19f237a2ff1a4627a7b58bcc9a2fce Mon Sep 17 00:00:00 2001 From: Friedel Ziegelmayer Date: Fri, 2 Sep 2016 13:31:28 +0200 Subject: [PATCH 21/86] feat(tests): add dial and listen tests --- .gitignore | 12 ++++- .npmignore | 34 +++++++++++++ .travis.yml | 10 ++++ README.md | 34 ++++++++----- package.json | 23 ++++++++- src/dial-test.js | 74 ++++++++++++++++++++++++++++ src/index.js | 12 +++++ src/listen-test.js | 118 +++++++++++++++++++++++++++++++++++++++++++++ tests/base-test.js | 31 ------------ tests/index.js | 6 --- 10 files changed, 300 insertions(+), 54 deletions(-) create mode 100644 .npmignore create mode 100644 .travis.yml create mode 100644 src/dial-test.js create mode 100644 src/index.js create mode 100644 src/listen-test.js delete mode 100644 tests/base-test.js delete mode 100644 tests/index.js diff --git a/.gitignore b/.gitignore index 123ae94..254988d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,13 @@ +**/node_modules/ +**/*.log +test/repo-tests* + # Logs logs *.log +coverage + # Runtime data pids *.pid @@ -19,9 +25,11 @@ coverage # node-waf configuration .lock-wscript -# Compiled binary addons (http://nodejs.org/api/addons.html) -build/Release +build # Dependency directory # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git node_modules + +lib +dist diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..59335fd --- /dev/null +++ b/.npmignore @@ -0,0 +1,34 @@ +**/node_modules/ +**/*.log +test/repo-tests* + +# Logs +logs +*.log + +coverage + +# Runtime data +pids +*.pid +*.seed + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# node-waf configuration +.lock-wscript + +build + +# Dependency directory +# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git +node_modules + +test diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..52cd14d --- /dev/null +++ b/.travis.yml @@ -0,0 +1,10 @@ +sudo: false +language: node_js +node_js: + - "stable" + +before_install: + - npm install -g npm + +script: + - npm run lint diff --git a/README.md b/README.md index 4097239..b61e09c 100644 --- a/README.md +++ b/README.md @@ -32,21 +32,29 @@ Include this badge in your readme if you make a module that is compatible with t ## Node.js -``` -var tape = require('tape') -var tests = require('interface-transport/tests') -var YourTransportHandler = require('../src') +```js +/* eslint-env mocha */ +'use strict' -var common = { - setup: function (t, cb) { - cb(null, YourTransportHandler) - }, - teardown: function (t, cb) { - cb() - } -} +const tests = require('interface-transport') +const multiaddr = require('multiaddr') +const YourTransport = require('../src') -tests(tape, common) +describe('compliance', () => { + tests({ + setup (cb) { + let t = new YourTransport() + const addrs = [ + multiaddr('valid-multiaddr-for-your-transport'), + multiaddr('valid-multiaddr2-for-your-transport') + ] + cb(null, t, addrs) + }, + teardown (cb) { + cb() + } + }) +}) ``` ## Go diff --git a/package.json b/package.json index 249aa8d..539c2da 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,21 @@ "type": "git", "url": "https://github.com/diasdavid/interface-transport.git" }, + "main": "src/index.js", + "jsnext:main": "src/index.js", + "scripts": { + "lint": "aegir-lint", + "build": "aegir-build", + "release": "aegir-release", + "release-minor": "aegir-release --type minor", + "release-major": "aegir-release --type major", + "coverage": "exit(0)", + "coverage-publish": "exit(0)" + }, + "pre-commit": [ + "lint", + "test" + ], "keywords": [ "IPFS" ], @@ -15,9 +30,13 @@ "url": "https://github.com/diasdavid/interface-transport/issues" }, "homepage": "https://github.com/diasdavid/interface-transport", - "devDependencies": {}, + "devDependencies": { + "aegir": "^6.0.1" + }, "dependencies": { + "chai": "^3.5.0", "multiaddr": "^2.0.2", - "timed-tape": "^0.1.1" + "pull-goodbye": "0.0.1", + "pull-stream": "^3.4.4" } } diff --git a/src/dial-test.js b/src/dial-test.js new file mode 100644 index 0000000..c4d1d73 --- /dev/null +++ b/src/dial-test.js @@ -0,0 +1,74 @@ +/* eslint-env mocha */ +'use strict' + +const expect = require('chai').expect +const pull = require('pull-stream') +const goodbye = require('pull-goodbye') + +module.exports = (common) => { + describe('dial', () => { + let addrs + let transport + let listener + + before((done) => { + common.setup((err, _transport, _addrs) => { + if (err) return done(err) + transport = _transport + addrs = _addrs + done() + }) + }) + + after((done) => { + common.teardown(done) + }) + + beforeEach((done) => { + listener = transport.createListener((conn) => { + pull( + conn, + pull.map((x) => { + if (x.toString() !== 'GOODBYE') { + return new Buffer(x.toString() + '!') + } + return x + }), + conn + ) + }) + listener.listen(addrs[0], done) + }) + + afterEach((done) => { + listener.close(done) + }) + + it('simple', (done) => { + const s = goodbye({ + source: pull.values([new Buffer('hey')]), + sink: pull.collect((err, values) => { + expect(err).to.not.exist + expect( + values + ).to.be.eql( + [new Buffer('hey!')] + ) + done() + }) + }) + + pull(s, transport.dial(addrs[0]), s) + }) + + it('to non existent listener', (done) => { + pull( + transport.dial(addrs[1]), + pull.onEnd((err) => { + expect(err).to.exist + done() + }) + ) + }) + }) +} diff --git a/src/index.js b/src/index.js new file mode 100644 index 0000000..e8173e2 --- /dev/null +++ b/src/index.js @@ -0,0 +1,12 @@ +/* eslint-env mocha */ +'use strict' + +const dial = require('./dial-test') +const listen = require('./listen-test') + +module.exports = (common) => { + describe('interface-transport', () => { + dial(common) + listen(common) + }) +} diff --git a/src/listen-test.js b/src/listen-test.js new file mode 100644 index 0000000..5398c59 --- /dev/null +++ b/src/listen-test.js @@ -0,0 +1,118 @@ +/* eslint max-nested-callbacks: ["error", 8] */ +/* eslint-env mocha */ +'use strict' + +const expect = require('chai').expect +const pull = require('pull-stream') + +module.exports = (common) => { + describe('listen', () => { + let addrs + let transport + + before((done) => { + common.setup((err, _transport, _addrs) => { + if (err) return done(err) + transport = _transport + addrs = _addrs + done() + }) + }) + + after((done) => { + common.teardown(done) + }) + + it('simple', (done) => { + const listener = transport.createListener((conn) => {}) + listener.listen(addrs[0], () => { + listener.close(done) + }) + }) + + it('close listener with connections, through timeout', (done) => { + const finish = plan(3, done) + const listener = transport.createListener((conn) => { + pull(conn, conn) + }) + + listener.listen(addrs[0], () => { + const socket1 = transport.dial(addrs[0], () => { + listener.close(finish) + }) + + pull( + transport.dial(addrs[0]), + pull.onEnd(() => { + finish() + }) + ) + + pull( + pull.values([Buffer('Some data that is never handled')]), + socket1, + pull.onEnd(() => { + finish() + }) + ) + }) + }) + + describe('events', () => { + // TODO: figure out why it fails in the full test suite + it.skip('connection', (done) => { + const finish = plan(2, done) + + const listener = transport.createListener() + + listener.on('connection', (conn) => { + expect(conn).to.exist + finish() + }) + + listener.listen(addrs[0], () => { + transport.dial(addrs[0], () => { + listener.close(finish) + }) + }) + }) + + it('listening', (done) => { + const listener = transport.createListener() + listener.on('listening', () => { + listener.close(done) + }) + listener.listen(addrs[0]) + }) + + // TODO: how to get the listener to emit an error? + it.skip('error', (done) => { + const listener = transport.createListener() + listener.on('error', (err) => { + expect(err).to.exist + listener.close(done) + }) + }) + + it('close', (done) => { + const finish = plan(2, done) + const listener = transport.createListener() + listener.on('close', finish) + + listener.listen(addrs[0], () => { + listener.close(finish) + }) + }) + }) + }) +} + +function plan (n, done) { + let i = 0 + return (err) => { + if (err) return done(err) + i++ + + if (i === n) done() + } +} diff --git a/tests/base-test.js b/tests/base-test.js deleted file mode 100644 index bd67f93..0000000 --- a/tests/base-test.js +++ /dev/null @@ -1,31 +0,0 @@ -var multiaddr = require('multiaddr') - -module.exports.all = function (test, common) { - test('a test', function (t) { - common.setup(test, function (err, transport) { - t.plan(5) - t.ifError(err) - - var maddr = multiaddr('/ip4/127.0.0.1/tcp/9050') - - var listener = transport.createListener(function (stream) { - t.pass('received incoming connection') - stream.end() - listener.close(function () { - t.pass('listener closed successfully') - t.end() - }) - }) - - listener.listen(maddr.nodeAddress().port, function () { - t.pass('started listening') - var stream = transport.dial(maddr, { - ready: function () { - t.pass('dialed successfuly') - stream.end() - } - }) - }) - }) - }) -} diff --git a/tests/index.js b/tests/index.js deleted file mode 100644 index b232406..0000000 --- a/tests/index.js +++ /dev/null @@ -1,6 +0,0 @@ -var timed = require('timed-tape') - -module.exports = function (test, common) { - test = timed(test) - require('./base-test.js').all(test, common) -} From 8e9f7cf8c53b48c17082dd65ef8c500dbcc71cb1 Mon Sep 17 00:00:00 2001 From: David Dias Date: Mon, 5 Sep 2016 17:38:41 -0400 Subject: [PATCH 22/86] fix(tests): add place holder test script for releases --- package.json | 1 + test/transport.spec.js | 2 ++ 2 files changed, 3 insertions(+) create mode 100644 test/transport.spec.js diff --git a/package.json b/package.json index 539c2da..7160a0d 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "jsnext:main": "src/index.js", "scripts": { "lint": "aegir-lint", + "test": "aegir-test", "build": "aegir-build", "release": "aegir-release", "release-minor": "aegir-release --type minor", diff --git a/test/transport.spec.js b/test/transport.spec.js new file mode 100644 index 0000000..b7ff1bc --- /dev/null +++ b/test/transport.spec.js @@ -0,0 +1,2 @@ +'use strict' + From 899f392c319702000a7bc592f7c98279ab8844dc Mon Sep 17 00:00:00 2001 From: David Dias Date: Mon, 5 Sep 2016 17:39:40 -0400 Subject: [PATCH 23/86] chore: update contributors --- package.json | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 7160a0d..89a3450 100644 --- a/package.json +++ b/package.json @@ -39,5 +39,11 @@ "multiaddr": "^2.0.2", "pull-goodbye": "0.0.1", "pull-stream": "^3.4.4" - } -} + }, + "contributors": [ + "David Dias ", + "Friedel Ziegelmayer ", + "Richard Littauer ", + "greenkeeperio-bot " + ] +} \ No newline at end of file From 8f2a77c3331e73201df36d55ae4f013d0739780a Mon Sep 17 00:00:00 2001 From: David Dias Date: Mon, 5 Sep 2016 17:39:41 -0400 Subject: [PATCH 24/86] chore: release version v0.3.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 89a3450..dde3a2e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "interface-transport", - "version": "0.2.0", + "version": "0.3.0", "description": "A test suite and interface you can use to implement a transport.", "repository": { "type": "git", From ace615053a3c58cfb7ca79fa159188bb661eb975 Mon Sep 17 00:00:00 2001 From: David Dias Date: Mon, 5 Sep 2016 18:18:33 -0400 Subject: [PATCH 25/86] fix(package.json): point to right main --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index dde3a2e..f80c0b9 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "type": "git", "url": "https://github.com/diasdavid/interface-transport.git" }, - "main": "src/index.js", + "main": "lib/index.js", "jsnext:main": "src/index.js", "scripts": { "lint": "aegir-lint", @@ -46,4 +46,4 @@ "Richard Littauer ", "greenkeeperio-bot " ] -} \ No newline at end of file +} From 8551ddda3c5da604f1858f5af27b4311d4aeedf7 Mon Sep 17 00:00:00 2001 From: David Dias Date: Mon, 5 Sep 2016 18:18:56 -0400 Subject: [PATCH 26/86] chore: update contributors --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f80c0b9..cfe22fa 100644 --- a/package.json +++ b/package.json @@ -46,4 +46,4 @@ "Richard Littauer ", "greenkeeperio-bot " ] -} +} \ No newline at end of file From 95d2a2c4d16d7d6536a826c70554092cec984bcf Mon Sep 17 00:00:00 2001 From: David Dias Date: Mon, 5 Sep 2016 18:18:56 -0400 Subject: [PATCH 27/86] chore: release version v0.3.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index cfe22fa..6730920 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "interface-transport", - "version": "0.3.0", + "version": "0.3.1", "description": "A test suite and interface you can use to implement a transport.", "repository": { "type": "git", From e1346da0f150c592a9425ecd8a9ea0e94c595eff Mon Sep 17 00:00:00 2001 From: Friedel Ziegelmayer Date: Tue, 6 Sep 2016 10:16:25 +0200 Subject: [PATCH 28/86] fix(dial-test): ensure goodbye works over tcp --- package.json | 3 ++- src/dial-test.js | 26 +++++++++++--------------- 2 files changed, 13 insertions(+), 16 deletions(-) diff --git a/package.json b/package.json index 6730920..8e09a1d 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,7 @@ "chai": "^3.5.0", "multiaddr": "^2.0.2", "pull-goodbye": "0.0.1", + "pull-serializer": "^0.3.2", "pull-stream": "^3.4.4" }, "contributors": [ @@ -46,4 +47,4 @@ "Richard Littauer ", "greenkeeperio-bot " ] -} \ No newline at end of file +} diff --git a/src/dial-test.js b/src/dial-test.js index c4d1d73..8f9d13b 100644 --- a/src/dial-test.js +++ b/src/dial-test.js @@ -4,6 +4,7 @@ const expect = require('chai').expect const pull = require('pull-stream') const goodbye = require('pull-goodbye') +const serializer = require('pull-serializer') module.exports = (common) => { describe('dial', () => { @@ -26,16 +27,7 @@ module.exports = (common) => { beforeEach((done) => { listener = transport.createListener((conn) => { - pull( - conn, - pull.map((x) => { - if (x.toString() !== 'GOODBYE') { - return new Buffer(x.toString() + '!') - } - return x - }), - conn - ) + pull(conn, conn) }) listener.listen(addrs[0], done) }) @@ -45,20 +37,24 @@ module.exports = (common) => { }) it('simple', (done) => { - const s = goodbye({ - source: pull.values([new Buffer('hey')]), + const s = serializer(goodbye({ + source: pull.values(['hey']), sink: pull.collect((err, values) => { expect(err).to.not.exist expect( values ).to.be.eql( - [new Buffer('hey!')] + ['hey'] ) done() }) - }) + })) - pull(s, transport.dial(addrs[0]), s) + pull( + s, + transport.dial(addrs[0]), + s + ) }) it('to non existent listener', (done) => { From 18fa3d09b19bda60e437837f6d66b1f5e9db5648 Mon Sep 17 00:00:00 2001 From: David Dias Date: Tue, 6 Sep 2016 08:29:57 -0400 Subject: [PATCH 29/86] chore: update contributors --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8e09a1d..808d403 100644 --- a/package.json +++ b/package.json @@ -47,4 +47,4 @@ "Richard Littauer ", "greenkeeperio-bot " ] -} +} \ No newline at end of file From a979d85ff647b3a8d682047d902891827b95e6f9 Mon Sep 17 00:00:00 2001 From: David Dias Date: Tue, 6 Sep 2016 08:29:57 -0400 Subject: [PATCH 30/86] chore: release version v0.3.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 808d403..de418eb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "interface-transport", - "version": "0.3.1", + "version": "0.3.2", "description": "A test suite and interface you can use to implement a transport.", "repository": { "type": "git", From cef3796b944c9594f45a198c537f6b8ac26ecfea Mon Sep 17 00:00:00 2001 From: David Dias Date: Tue, 6 Sep 2016 08:30:36 -0400 Subject: [PATCH 32/86] chore: release version v0.3.3 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index de418eb..c42db04 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "interface-transport", - "version": "0.3.2", + "version": "0.3.3", "description": "A test suite and interface you can use to implement a transport.", "repository": { "type": "git", From f2d36283e590c3cfdc8bf55a6b7c342e7b7a0cc5 Mon Sep 17 00:00:00 2001 From: greenkeeperio-bot Date: Tue, 6 Sep 2016 15:11:47 +0200 Subject: [PATCH 33/86] chore(package): update aegir to version 8.0.0 https://greenkeeper.io/ --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c42db04..873effd 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,7 @@ }, "homepage": "https://github.com/diasdavid/interface-transport", "devDependencies": { - "aegir": "^6.0.1" + "aegir": "^8.0.0" }, "dependencies": { "chai": "^3.5.0", From 02823042dc7c0e38b97da553e2e8274b03e038a2 Mon Sep 17 00:00:00 2001 From: greenkeeperio-bot Date: Thu, 8 Sep 2016 01:20:22 +0200 Subject: [PATCH 34/86] chore(package): update multiaddr to version 2.0.3 https://greenkeeper.io/ --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 873effd..165587b 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,7 @@ }, "dependencies": { "chai": "^3.5.0", - "multiaddr": "^2.0.2", + "multiaddr": "^2.0.3", "pull-goodbye": "0.0.1", "pull-serializer": "^0.3.2", "pull-stream": "^3.4.4" From de9f12d395f053c7919bd8eba5f9bddca503937d Mon Sep 17 00:00:00 2001 From: greenkeeperio-bot Date: Fri, 9 Sep 2016 01:34:26 +0200 Subject: [PATCH 35/86] chore(package): update aegir to version 8.0.1 https://greenkeeper.io/ --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 165587b..0a2e0b2 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,7 @@ }, "homepage": "https://github.com/diasdavid/interface-transport", "devDependencies": { - "aegir": "^8.0.0" + "aegir": "^8.0.1" }, "dependencies": { "chai": "^3.5.0", From 9df6689175a0d11553a1a359b5c1f9a3297dc42e Mon Sep 17 00:00:00 2001 From: Richard Littauer Date: Fri, 30 Sep 2016 14:42:49 -0400 Subject: [PATCH 36/86] Update README URLs based on HTTP redirects --- README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index b61e09c..f5eede4 100644 --- a/README.md +++ b/README.md @@ -16,10 +16,10 @@ The API is presented with both Node.js and Go primitives, however, there is not # Modules that implement the interface -- [js-libp2p-tcp](https://github.com/diasdavid/node-libp2p-tcp) -- [js-libp2p-webrtc-star](https://github.com/diasdavid/js-libp2p-webrtc-star) -- [js-libp2p-websockets](https://github.com/diasdavid/js-libp2p-websockets) -- [js-libp2p-utp](https://github.com/diasdavid/js-libp2p-utp) +- [js-libp2p-tcp](https://github.com/libp2p/js-libp2p-tcp) +- [js-libp2p-webrtc-star](https://github.com/libp2p/js-libp2p-webrtc-star) +- [js-libp2p-websockets](https://github.com/libp2p/js-libp2p-websockets) +- [js-libp2p-utp](https://github.com/libp2p/js-libp2p-utp) - [webrtc-explorer](https://github.com/diasdavid/webrtc-explorer) # Badge @@ -94,9 +94,9 @@ Creates a new Transport instance. `options` is a optional JavaScript object, mig This method dials a transport to the Peer listening on `multiaddr`. -`multiaddr` must be of the type [`multiaddr`](http://npmjs.org/multiaddr). +`multiaddr` must be of the type [`multiaddr`](https://www.npmjs.com/multiaddr). -`stream` must implements the [interface-connection](https://github.com/diasdavid/interface-connection) interface. +`stream` must implements the [interface-connection](https://github.com/libp2p/interface-connection) interface. `[options]` is an optional argument, which can be used by some implementations From aa6fae9b75c9f05b5aca9c87485c38f1a11e2d97 Mon Sep 17 00:00:00 2001 From: greenkeeperio-bot Date: Fri, 4 Nov 2016 16:07:37 +0100 Subject: [PATCH 37/86] chore(package): update aegir to version 9.1.1 https://greenkeeper.io/ --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 0a2e0b2..a455a95 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,7 @@ }, "homepage": "https://github.com/diasdavid/interface-transport", "devDependencies": { - "aegir": "^8.0.1" + "aegir": "^9.1.1" }, "dependencies": { "chai": "^3.5.0", From d7f31a18e2dedcd60462b422f7b33a842a7c00c0 Mon Sep 17 00:00:00 2001 From: greenkeeperio-bot Date: Fri, 9 Dec 2016 13:25:13 +0100 Subject: [PATCH 38/86] chore(package): update aegir to version 9.2.1 https://greenkeeper.io/ --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a455a95..76e9214 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,7 @@ }, "homepage": "https://github.com/diasdavid/interface-transport", "devDependencies": { - "aegir": "^9.1.1" + "aegir": "^9.2.1" }, "dependencies": { "chai": "^3.5.0", From 5cb8c44b95cae1c5289afcd11af7f10cec32ffb0 Mon Sep 17 00:00:00 2001 From: greenkeeperio-bot Date: Tue, 7 Feb 2017 18:45:39 +0100 Subject: [PATCH 39/86] chore(package): update aegir to version 10.0.0 https://greenkeeper.io/ --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 76e9214..0bea438 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,7 @@ }, "homepage": "https://github.com/diasdavid/interface-transport", "devDependencies": { - "aegir": "^9.2.1" + "aegir": "^10.0.0" }, "dependencies": { "chai": "^3.5.0", From d30d54cf62cdb59f61499a2c1c11e6da9e4a1cdd Mon Sep 17 00:00:00 2001 From: David Dias Date: Thu, 9 Feb 2017 08:52:46 -0800 Subject: [PATCH 40/86] chore: ^ to ~ --- package.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 76e9214..a669709 100644 --- a/package.json +++ b/package.json @@ -32,14 +32,14 @@ }, "homepage": "https://github.com/diasdavid/interface-transport", "devDependencies": { - "aegir": "^9.2.1" + "aegir": "^10.0.0" }, "dependencies": { "chai": "^3.5.0", - "multiaddr": "^2.0.3", + "multiaddr": "^2.2.1", "pull-goodbye": "0.0.1", "pull-serializer": "^0.3.2", - "pull-stream": "^3.4.4" + "pull-stream": "^3.5.0" }, "contributors": [ "David Dias ", @@ -47,4 +47,4 @@ "Richard Littauer ", "greenkeeperio-bot " ] -} \ No newline at end of file +} From 2377b1c00bb93b2c45706dfd7fc715f1d2a83425 Mon Sep 17 00:00:00 2001 From: David Dias Date: Thu, 9 Feb 2017 08:52:56 -0800 Subject: [PATCH 41/86] chore: update contributors --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a669709..e822073 100644 --- a/package.json +++ b/package.json @@ -47,4 +47,4 @@ "Richard Littauer ", "greenkeeperio-bot " ] -} +} \ No newline at end of file From 47fa5ed823063c47cfc7e3d75ebcdcc5ae0b8a11 Mon Sep 17 00:00:00 2001 From: David Dias Date: Thu, 9 Feb 2017 08:52:56 -0800 Subject: [PATCH 42/86] chore: release version v0.3.4 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e822073..027f82b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "interface-transport", - "version": "0.3.3", + "version": "0.3.4", "description": "A test suite and interface you can use to implement a transport.", "repository": { "type": "git", From 54b83a781305bc27a3cf6f87b9fdbe607683190d Mon Sep 17 00:00:00 2001 From: dmitriy ryajov Date: Sat, 18 Feb 2017 16:55:23 -0800 Subject: [PATCH 43/86] fix: wrong main path in package.json --- package.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/package.json b/package.json index 027f82b..aa3fd15 100644 --- a/package.json +++ b/package.json @@ -6,8 +6,7 @@ "type": "git", "url": "https://github.com/diasdavid/interface-transport.git" }, - "main": "lib/index.js", - "jsnext:main": "src/index.js", + "main": "src/index.js", "scripts": { "lint": "aegir-lint", "test": "aegir-test", From 3f83a4afc7835dbd68eea8cf2d986285636a2d4e Mon Sep 17 00:00:00 2001 From: David Dias Date: Tue, 21 Mar 2017 14:23:00 +0000 Subject: [PATCH 44/86] chore: update aegir --- package.json | 7 ++++--- src/dial-test.js | 9 ++++++--- src/listen-test.js | 10 +++++++--- test/transport.spec.js | 1 - 4 files changed, 17 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index aa3fd15..58b0ffe 100644 --- a/package.json +++ b/package.json @@ -31,11 +31,12 @@ }, "homepage": "https://github.com/diasdavid/interface-transport", "devDependencies": { - "aegir": "^10.0.0" + "aegir": "^11.0.0", + "dirty-chai": "^1.2.2" }, "dependencies": { "chai": "^3.5.0", - "multiaddr": "^2.2.1", + "multiaddr": "^2.2.2", "pull-goodbye": "0.0.1", "pull-serializer": "^0.3.2", "pull-stream": "^3.5.0" @@ -46,4 +47,4 @@ "Richard Littauer ", "greenkeeperio-bot " ] -} \ No newline at end of file +} diff --git a/src/dial-test.js b/src/dial-test.js index 8f9d13b..85c1a6c 100644 --- a/src/dial-test.js +++ b/src/dial-test.js @@ -1,7 +1,10 @@ /* eslint-env mocha */ 'use strict' -const expect = require('chai').expect +const chai = require('chai') +const dirtyChai = require('dirty-chai') +const expect = chai.expect +chai.use(dirtyChai) const pull = require('pull-stream') const goodbye = require('pull-goodbye') const serializer = require('pull-serializer') @@ -40,7 +43,7 @@ module.exports = (common) => { const s = serializer(goodbye({ source: pull.values(['hey']), sink: pull.collect((err, values) => { - expect(err).to.not.exist + expect(err).to.not.exist() expect( values ).to.be.eql( @@ -61,7 +64,7 @@ module.exports = (common) => { pull( transport.dial(addrs[1]), pull.onEnd((err) => { - expect(err).to.exist + expect(err).to.exist() done() }) ) diff --git a/src/listen-test.js b/src/listen-test.js index 5398c59..e43e393 100644 --- a/src/listen-test.js +++ b/src/listen-test.js @@ -2,7 +2,11 @@ /* eslint-env mocha */ 'use strict' -const expect = require('chai').expect +const chai = require('chai') +const dirtyChai = require('dirty-chai') +const expect = chai.expect +chai.use(dirtyChai) + const pull = require('pull-stream') module.exports = (common) => { @@ -66,7 +70,7 @@ module.exports = (common) => { const listener = transport.createListener() listener.on('connection', (conn) => { - expect(conn).to.exist + expect(conn).to.exist() finish() }) @@ -89,7 +93,7 @@ module.exports = (common) => { it.skip('error', (done) => { const listener = transport.createListener() listener.on('error', (err) => { - expect(err).to.exist + expect(err).to.exist() listener.close(done) }) }) diff --git a/test/transport.spec.js b/test/transport.spec.js index b7ff1bc..ccacec3 100644 --- a/test/transport.spec.js +++ b/test/transport.spec.js @@ -1,2 +1 @@ 'use strict' - From 9a993eaa0f75f1cbaa6c52bd256a262b2af9cfdd Mon Sep 17 00:00:00 2001 From: David Dias Date: Tue, 21 Mar 2017 14:24:31 +0000 Subject: [PATCH 45/86] chore: update package.json --- package.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 58b0ffe..d69afc2 100644 --- a/package.json +++ b/package.json @@ -4,16 +4,16 @@ "description": "A test suite and interface you can use to implement a transport.", "repository": { "type": "git", - "url": "https://github.com/diasdavid/interface-transport.git" + "url": "https://github.com/libp2p/interface-transport.git" }, "main": "src/index.js", "scripts": { "lint": "aegir-lint", "test": "aegir-test", "build": "aegir-build", - "release": "aegir-release", - "release-minor": "aegir-release --type minor", - "release-major": "aegir-release --type major", + "release": "aegir-release --env node", + "release-minor": "aegir-release --type minor --env node", + "release-major": "aegir-release --type major --env node", "coverage": "exit(0)", "coverage-publish": "exit(0)" }, @@ -27,9 +27,9 @@ "author": "David Dias ", "license": "MIT", "bugs": { - "url": "https://github.com/diasdavid/interface-transport/issues" + "url": "https://github.com/libp2p/interface-transport/issues" }, - "homepage": "https://github.com/diasdavid/interface-transport", + "homepage": "https://github.com/libp2p/interface-transport", "devDependencies": { "aegir": "^11.0.0", "dirty-chai": "^1.2.2" From 131f06c47b613193555a793ed6b0ca3fb8275111 Mon Sep 17 00:00:00 2001 From: David Dias Date: Tue, 21 Mar 2017 14:25:02 +0000 Subject: [PATCH 46/86] chore: update contributors --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index d69afc2..8abb611 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,7 @@ "David Dias ", "Friedel Ziegelmayer ", "Richard Littauer ", + "dmitriy ryajov ", "greenkeeperio-bot " ] -} +} \ No newline at end of file From d4ee2a31e0e3c97bbb27a60876368457bb9f5520 Mon Sep 17 00:00:00 2001 From: David Dias Date: Tue, 21 Mar 2017 14:25:02 +0000 Subject: [PATCH 47/86] chore: release version v0.3.5 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8abb611..2509d1a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "interface-transport", - "version": "0.3.4", + "version": "0.3.5", "description": "A test suite and interface you can use to implement a transport.", "repository": { "type": "git", From 2d35afd8de449058304ec0ac9ccd8d1b6ae3aff1 Mon Sep 17 00:00:00 2001 From: David Dias Date: Wed, 13 Dec 2017 17:13:39 +0000 Subject: [PATCH 48/86] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index f5eede4..2f6f16a 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,7 @@ The API is presented with both Node.js and Go primitives, however, there is not - [js-libp2p-tcp](https://github.com/libp2p/js-libp2p-tcp) - [js-libp2p-webrtc-star](https://github.com/libp2p/js-libp2p-webrtc-star) +- [js-libp2p-websocket-star](https://github.com/libp2p/js-libp2p-websocket-star) - [js-libp2p-websockets](https://github.com/libp2p/js-libp2p-websockets) - [js-libp2p-utp](https://github.com/libp2p/js-libp2p-utp) - [webrtc-explorer](https://github.com/diasdavid/webrtc-explorer) From f3e86748c3d98c7135c499940f397e4ba2113178 Mon Sep 17 00:00:00 2001 From: David Dias Date: Wed, 13 Dec 2017 17:13:59 +0000 Subject: [PATCH 49/86] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 2f6f16a..e995384 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,7 @@ The API is presented with both Node.js and Go primitives, however, there is not - [js-libp2p-tcp](https://github.com/libp2p/js-libp2p-tcp) - [js-libp2p-webrtc-star](https://github.com/libp2p/js-libp2p-webrtc-star) +- [js-libp2p-webrtc-direct](https://github.com/libp2p/js-libp2p-webrtc-direct) - [js-libp2p-websocket-star](https://github.com/libp2p/js-libp2p-websocket-star) - [js-libp2p-websockets](https://github.com/libp2p/js-libp2p-websockets) - [js-libp2p-utp](https://github.com/libp2p/js-libp2p-utp) From 5b47e193f8868fa9610c13b7c97128d50b239977 Mon Sep 17 00:00:00 2001 From: David Dias Date: Thu, 5 Apr 2018 16:53:39 +0100 Subject: [PATCH 50/86] chore: update all the deps --- package.json | 24 ++++++++++++------------ src/listen-test.js | 2 +- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/package.json b/package.json index 2509d1a..dd9192c 100644 --- a/package.json +++ b/package.json @@ -8,12 +8,12 @@ }, "main": "src/index.js", "scripts": { - "lint": "aegir-lint", - "test": "aegir-test", + "lint": "aegir lint", + "test": "aegir test", "build": "aegir-build", - "release": "aegir-release --env node", - "release-minor": "aegir-release --type minor --env node", - "release-major": "aegir-release --type major --env node", + "release": "aegir release -t node", + "release-minor": "aegir release --type minor -t node", + "release-major": "aegir release --type major -t node", "coverage": "exit(0)", "coverage-publish": "exit(0)" }, @@ -31,15 +31,15 @@ }, "homepage": "https://github.com/libp2p/interface-transport", "devDependencies": { - "aegir": "^11.0.0", - "dirty-chai": "^1.2.2" + "aegir": "^13.0.6", + "dirty-chai": "^2.0.1" }, "dependencies": { - "chai": "^3.5.0", - "multiaddr": "^2.2.2", - "pull-goodbye": "0.0.1", + "chai": "^4.1.2", + "multiaddr": "^4.0.0", + "pull-goodbye": "0.0.2", "pull-serializer": "^0.3.2", - "pull-stream": "^3.5.0" + "pull-stream": "^3.6.7" }, "contributors": [ "David Dias ", @@ -48,4 +48,4 @@ "dmitriy ryajov ", "greenkeeperio-bot " ] -} \ No newline at end of file +} diff --git a/src/listen-test.js b/src/listen-test.js index e43e393..847a89d 100644 --- a/src/listen-test.js +++ b/src/listen-test.js @@ -53,7 +53,7 @@ module.exports = (common) => { ) pull( - pull.values([Buffer('Some data that is never handled')]), + pull.values([Buffer.from('Some data that is never handled')]), socket1, pull.onEnd(() => { finish() From f580b42cf22521ee854ed01e10a534a1cd2de0c9 Mon Sep 17 00:00:00 2001 From: David Dias Date: Thu, 5 Apr 2018 16:54:44 +0100 Subject: [PATCH 52/86] chore: release version v0.3.6 --- CHANGELOG.md | 77 ++++++++++++++++++++++++++++++++++++++++++++++++++++ package.json | 2 +- 2 files changed, 78 insertions(+), 1 deletion(-) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..ea120b0 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,77 @@ + +## [0.3.6](https://github.com/libp2p/interface-transport/compare/v0.3.5...v0.3.6) (2018-04-05) + + + + +## [0.3.5](https://github.com/libp2p/interface-transport/compare/v0.3.4...v0.3.5) (2017-03-21) + + +### Bug Fixes + +* wrong main path in package.json ([54b83a7](https://github.com/libp2p/interface-transport/commit/54b83a7)) + + + + +## [0.3.4](https://github.com/libp2p/interface-transport/compare/v0.3.3...v0.3.4) (2017-02-09) + + + + +## [0.3.3](https://github.com/libp2p/interface-transport/compare/v0.3.2...v0.3.3) (2016-09-06) + + + + +## [0.3.2](https://github.com/libp2p/interface-transport/compare/v0.3.1...v0.3.2) (2016-09-06) + + +### Bug Fixes + +* **dial-test:** ensure goodbye works over tcp ([e1346da](https://github.com/libp2p/interface-transport/commit/e1346da)) + + + + +## [0.3.1](https://github.com/libp2p/interface-transport/compare/v0.3.0...v0.3.1) (2016-09-05) + + +### Bug Fixes + +* **package.json:** point to right main ([ace6150](https://github.com/libp2p/interface-transport/commit/ace6150)) + + + + +# [0.3.0](https://github.com/libp2p/interface-transport/compare/v0.2.0...v0.3.0) (2016-09-05) + + +### Bug Fixes + +* **tests:** add place holder test script for releases ([8e9f7cf](https://github.com/libp2p/interface-transport/commit/8e9f7cf)) + + +### Features + +* **dialer:** remove conn from on connect callback ([1bd20d9](https://github.com/libp2p/interface-transport/commit/1bd20d9)) +* **spec:** update the dial interface to cope with new pull additions ([2e12166](https://github.com/libp2p/interface-transport/commit/2e12166)) +* **tests:** add dial and listen tests ([d50224d](https://github.com/libp2p/interface-transport/commit/d50224d)) + + + + +# [0.2.0](https://github.com/libp2p/interface-transport/compare/v0.1.1...v0.2.0) (2016-06-16) + + + + +## [0.1.1](https://github.com/libp2p/interface-transport/compare/v0.1.0...v0.1.1) (2015-12-12) + + + + +# 0.1.0 (2015-09-17) + + + diff --git a/package.json b/package.json index dd9192c..743138a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "interface-transport", - "version": "0.3.5", + "version": "0.3.6", "description": "A test suite and interface you can use to implement a transport.", "repository": { "type": "git", From 6ac58f7745fe2ae35c412686e3d650c8867a9aa1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Santos?= Date: Tue, 29 May 2018 23:19:00 +0100 Subject: [PATCH 53/86] docs: minor typo fix minor typo fix --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e995384..cf1a15a 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ Publishing a test suite as a module lets multiple modules all ensure compatibili The purpose of this interface is not to reinvent any wheels when it comes to dialing and listening to transports. Instead, it tries to uniform several transports through a shimmed interface. -The API is presented with both Node.js and Go primitives, however, there is not actual limitations for it to be extended for any other language, pushing forward the cross compatibility and interop through diferent stacks. +The API is presented with both Node.js and Go primitives, however, there are no actual limitations for it to be extended for any other language, pushing forward the cross compatibility and interop through diferent stacks. # Modules that implement the interface From a82b230511822158f13b628e8166042d32b6cb03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Kr=C3=BCger?= Date: Thu, 14 Jun 2018 09:56:13 +0200 Subject: [PATCH 54/86] misc: fix url for protocol labs badge --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e995384..a79eedb 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ interface-transport =================== -[![](https://img.shields.io/badge/made%20by-Protocol%20Labs-blue.svg?style=flat-square)](http://ipn.io) +[![](https://img.shields.io/badge/made%20by-Protocol%20Labs-blue.svg?style=flat-square)](https://protocol.ai) [![](https://img.shields.io/badge/freenode-%23ipfs-blue.svg?style=flat-square)](http://webchat.freenode.net/?channels=%23ipfs) > A test suite and interface you can use to implement a libp2p transport. A libp2p transport is understood as something that offers a dial and listen interface. From ecdaf40107bbf95881bd5d8f4bdfe39be7248e24 Mon Sep 17 00:00:00 2001 From: Jacob Heun Date: Fri, 9 Nov 2018 16:14:14 +0100 Subject: [PATCH 55/86] chore: add lead maintainer --- README.md | 4 ++++ package.json | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 53100e2..cb3fd3b 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,10 @@ The purpose of this interface is not to reinvent any wheels when it comes to dia The API is presented with both Node.js and Go primitives, however, there are no actual limitations for it to be extended for any other language, pushing forward the cross compatibility and interop through diferent stacks. +## Lead Maintainer + +[Jacob Heun](https://github.com/jacobheun/) + # Modules that implement the interface - [js-libp2p-tcp](https://github.com/libp2p/js-libp2p-tcp) diff --git a/package.json b/package.json index 743138a..e6a6067 100644 --- a/package.json +++ b/package.json @@ -2,6 +2,7 @@ "name": "interface-transport", "version": "0.3.6", "description": "A test suite and interface you can use to implement a transport.", + "leadMaintainer": "Jacob Heun ", "repository": { "type": "git", "url": "https://github.com/libp2p/interface-transport.git" @@ -24,7 +25,6 @@ "keywords": [ "IPFS" ], - "author": "David Dias ", "license": "MIT", "bugs": { "url": "https://github.com/libp2p/interface-transport/issues" From 84166fa0ea166f121277986bbe17d00f59b53826 Mon Sep 17 00:00:00 2001 From: Jacob Heun Date: Fri, 9 Nov 2018 16:19:18 +0100 Subject: [PATCH 56/86] chore: update deps and fix linting --- .gitignore | 1 + package.json | 18 ++++++++++-------- src/listen-test.js | 2 ++ 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/.gitignore b/.gitignore index 254988d..e1a7614 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ **/node_modules/ **/*.log test/repo-tests* +package-lock.json # Logs logs diff --git a/package.json b/package.json index e6a6067..013148f 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,9 @@ "url": "https://github.com/libp2p/interface-transport.git" }, "main": "src/index.js", + "files": [ + "src" + ], "scripts": { "lint": "aegir lint", "test": "aegir test", @@ -19,8 +22,7 @@ "coverage-publish": "exit(0)" }, "pre-commit": [ - "lint", - "test" + "lint" ], "keywords": [ "IPFS" @@ -31,15 +33,15 @@ }, "homepage": "https://github.com/libp2p/interface-transport", "devDependencies": { - "aegir": "^13.0.6", + "aegir": "^17.0.1", "dirty-chai": "^2.0.1" }, "dependencies": { - "chai": "^4.1.2", - "multiaddr": "^4.0.0", - "pull-goodbye": "0.0.2", - "pull-serializer": "^0.3.2", - "pull-stream": "^3.6.7" + "chai": "^4.2.0", + "multiaddr": "^5.0.2", + "pull-goodbye": "~0.0.2", + "pull-serializer": "~0.3.2", + "pull-stream": "^3.6.9" }, "contributors": [ "David Dias ", diff --git a/src/listen-test.js b/src/listen-test.js index 847a89d..082361a 100644 --- a/src/listen-test.js +++ b/src/listen-test.js @@ -63,6 +63,7 @@ module.exports = (common) => { }) describe('events', () => { + // eslint-disable-next-line // TODO: figure out why it fails in the full test suite it.skip('connection', (done) => { const finish = plan(2, done) @@ -89,6 +90,7 @@ module.exports = (common) => { listener.listen(addrs[0]) }) + // eslint-disable-next-line // TODO: how to get the listener to emit an error? it.skip('error', (done) => { const listener = transport.createListener() From f2fb04a5d3ae92358d4ba9f3161fdde49f42965c Mon Sep 17 00:00:00 2001 From: Jacob Heun Date: Thu, 29 Nov 2018 14:31:06 +0100 Subject: [PATCH 57/86] chore: update aegir commands --- package.json | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 013148f..28408b9 100644 --- a/package.json +++ b/package.json @@ -9,15 +9,16 @@ }, "main": "src/index.js", "files": [ + "dist", "src" ], "scripts": { "lint": "aegir lint", "test": "aegir test", - "build": "aegir-build", - "release": "aegir release -t node", - "release-minor": "aegir release --type minor -t node", - "release-major": "aegir release --type major -t node", + "build": "aegir build", + "release": "aegir release --no-test", + "release-minor": "aegir release --type minor --no-test", + "release-major": "aegir release --type major --no-test", "coverage": "exit(0)", "coverage-publish": "exit(0)" }, From ad468f47ed879df44c2e861036d8e1f26a324390 Mon Sep 17 00:00:00 2001 From: Jacob Heun Date: Thu, 29 Nov 2018 14:31:24 +0100 Subject: [PATCH 58/86] chore: update contributors --- package.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 28408b9..cbc48b2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "interface-transport", - "version": "0.3.6", + "version": "0.3.7", "description": "A test suite and interface you can use to implement a transport.", "leadMaintainer": "Jacob Heun ", "repository": { @@ -47,6 +47,9 @@ "contributors": [ "David Dias ", "Friedel Ziegelmayer ", + "Jacob Heun ", + "João Santos ", + "Maciej Krüger ", "Richard Littauer ", "dmitriy ryajov ", "greenkeeperio-bot " From 6e99899b3d12437ca700cbcde0f9946869f14c8b Mon Sep 17 00:00:00 2001 From: Jacob Heun Date: Thu, 29 Nov 2018 14:31:24 +0100 Subject: [PATCH 59/86] chore: release version v0.3.7 --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ea120b0..516a437 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ + +## [0.3.7](https://github.com/libp2p/interface-transport/compare/v0.3.6...v0.3.7) (2018-11-29) + + + ## [0.3.6](https://github.com/libp2p/interface-transport/compare/v0.3.5...v0.3.6) (2018-04-05) From b30ee5f6afade1126ef3bc85c5242ba22d00cc71 Mon Sep 17 00:00:00 2001 From: dirkmc Date: Thu, 18 Apr 2019 01:35:06 +0800 Subject: [PATCH 60/86] feat: callbacks -> async / await (#44) * feat: callbacks -> async / await BREAKING CHANGE: All places in the API that used callbacks are now replaced with async/await * test: add tests for canceling dials * feat: Adapter class --- README.md | 111 +++++++++++++++++++++++++++-------------- package.json | 10 ++-- src/adapter.js | 80 ++++++++++++++++++++++++++++++ src/dial-test.js | 107 +++++++++++++++++++++++----------------- src/errors.js | 16 ++++++ src/index.js | 3 ++ src/listen-test.js | 120 +++++++++++++++++++-------------------------- 7 files changed, 291 insertions(+), 156 deletions(-) create mode 100644 src/adapter.js create mode 100644 src/errors.js diff --git a/README.md b/README.md index cb3fd3b..bbe4f05 100644 --- a/README.md +++ b/README.md @@ -10,9 +10,9 @@ The primary goal of this module is to enable developers to pick and swap their t Publishing a test suite as a module lets multiple modules all ensure compatibility since they use the same test suite. -The purpose of this interface is not to reinvent any wheels when it comes to dialing and listening to transports. Instead, it tries to uniform several transports through a shimmed interface. +The purpose of this interface is not to reinvent any wheels when it comes to dialing and listening to transports. Instead, it tries to provide a uniform API for several transports through a shimmed interface. -The API is presented with both Node.js and Go primitives, however, there are no actual limitations for it to be extended for any other language, pushing forward the cross compatibility and interop through diferent stacks. +The API is presented with both Node.js and Go primitives, however there are no actual limitations for it to be extended for any other language, pushing forward the cross compatibility and interop through diferent stacks. ## Lead Maintainer @@ -48,16 +48,32 @@ const YourTransport = require('../src') describe('compliance', () => { tests({ - setup (cb) { - let t = new YourTransport() + setup () { + let transport = new YourTransport() + const addrs = [ multiaddr('valid-multiaddr-for-your-transport'), multiaddr('valid-multiaddr2-for-your-transport') ] - cb(null, t, addrs) + + const network = require('my-network-lib') + const connect = network.connect + const connector = { + delay (delayMs) { + // Add a delay in the connection mechanism for the transport + // (this is used by the dial tests) + network.connect = (...args) => setTimeout(() => connect(...args), 100) + }, + restore () { + // Restore the connection mechanism to normal + network.connect = connect + } + } + + return { transport, addrs, connector } }, - teardown (cb) { - cb() + teardown () { + // Clean up any resources created by setup() } }) }) @@ -69,50 +85,73 @@ describe('compliance', () => { # API -A valid (read: that follows the interface defined) transport, must implement the following API. +A valid transport (one that follows the interface defined) must implement the following API: **Table of contents:** - type: `Transport` - `new Transport([options])` - - `transport.dial(multiaddr, [options, callback])` + - ` transport.dial(multiaddr, [options])` - `transport.createListener([options], handlerFunction)` - type: `transport.Listener` - event: 'listening' - event: 'close' - event: 'connection' - event: 'error' - - `listener.listen(multiaddr, [callback])` - - `listener.getAddrs(callback)` - - `listener.close([options])` + - ` listener.listen(multiaddr)` + - `listener.getAddrs()` + - ` listener.close([options])` ### Creating a transport instance - `JavaScript` - `var transport = new Transport([options])` -Creates a new Transport instance. `options` is a optional JavaScript object, might include the necessary parameters for the transport instance. +Creates a new Transport instance. `options` is an optional JavaScript object that should include the necessary parameters for the transport instance. -**Note: Why is it important to instantiate a transport -** Some transports have state that can be shared between the dialing and listening parts. One example is a libp2p-webrtc-star (or pretty much any other WebRTC flavour transport), where that, in order to dial, a peer needs to be part of some signalling network that is shared also with the listener. +**Note: Why is it important to instantiate a transport -** Some transports have state that can be shared between the dialing and listening parts. For example with libp2p-webrtc-star, in order to dial a peer, the peer must be part of some signaling network that is shared with the listener. ### Dial to another peer -- `JavaScript` - `var conn = transport.dial(multiaddr, [options, callback])` +- `JavaScript` - `const conn = await transport.dial(multiaddr, [options])` -This method dials a transport to the Peer listening on `multiaddr`. +This method uses a transport to dial a Peer listening on `multiaddr`. `multiaddr` must be of the type [`multiaddr`](https://www.npmjs.com/multiaddr). -`stream` must implements the [interface-connection](https://github.com/libp2p/interface-connection) interface. +`[options]` the options that may be passed to the dial. Must support the `signal` option (see below) -`[options]` is an optional argument, which can be used by some implementations +`conn` must implement the [interface-connection](https://github.com/libp2p/interface-connection) interface. -`callback` should follow the `function (err)` signature. +The dial may throw an `Error` instance if there was a problem connecting to the `multiaddr`. -`err` is an `Error` instance to signal that the dial was unsuccessful, this error can be a 'timeout' or simply 'error'. +### Canceling a dial + +Dials may be cancelled using an `AbortController`: + +```Javascript +const AbortController = require('abort-controller') +const { AbortError } = require('interface-transport') +const controller = new AbortController() +try { + const conn = await mytransport.dial(ma, { signal: controller.signal }) + // Do stuff with conn here ... +} catch (err) { + if(err.code === AbortError.code) { + // Dial was aborted, just bail out + return + } + throw err +} + +// ---- +// In some other part of the code: + controller.abort() +// ---- +``` ### Create a listener -- `JavaScript` - `var listener = transport.createListener([options], handlerFunction)` +- `JavaScript` - `const listener = transport.createListener([options], handlerFunction)` This method creates a listener on the transport. @@ -120,37 +159,33 @@ This method creates a listener on the transport. `handlerFunction` is a function called each time a new connection is received. It must follow the following signature: `function (conn) {}`, where `conn` is a connection that follows the [`interface-connection`](https://github.com/diasdavid/interface-connection). -The listener object created, can emit the following events: +The listener object created may emit the following events: -- `listening` - -- `close` - -- `connection` - -- `error` - +- `listening` - when the listener is ready for incoming connections +- `close` - when the listener is closed +- `connection` - (`conn`) each time an incoming connection is received +- `error` - (`err`) each time there is an error on the connection ### Start a listener -- `JavaScript` - `listener.listen(multiaddr, [callback])` +- `JavaScript` - `await listener.listen(multiaddr)` This method puts the listener in `listening` mode, waiting for incoming connections. -`multiaddr` is the address where the listener should bind to. - -`callback` is a function called once the listener is ready. +`multiaddr` is the address that the listener should bind to. ### Get listener addrs -- `JavaScript` - `listener.getAddrs(callback)` +- `JavaScript` - `listener.getAddrs()` -This method retrieves the addresses in which this listener is listening. Useful for when listening on port 0 or any interface (0.0.0.0). +This method returns the addresses on which this listener is listening. Useful when listening on port 0 or any interface (0.0.0.0). ### Stop a listener -- `JavaScript` - `listener.close([options, callback])` +- `JavaScript` - `await listener.close([options])` -This method closes the listener so that no more connections can be open on this transport instance. +This method closes the listener so that no more connections can be opened on this transport instance. -`options` is an optional object that might contain the following properties: +`options` is an optional object that may contain the following properties: - - `timeout` - A timeout value (in ms) that fires and destroys all the connections on this transport if the transport is not able to close graciously. (e.g { timeout: 1000 }) - -`callback` is function that gets called when the listener is closed. It is optional. + - `timeout` - A timeout value (in ms) after which all connections on this transport will be destroyed if the transport is not able to close gracefully. (e.g { timeout: 1000 }) diff --git a/package.json b/package.json index cbc48b2..723903c 100644 --- a/package.json +++ b/package.json @@ -38,11 +38,15 @@ "dirty-chai": "^2.0.1" }, "dependencies": { + "abort-controller": "^3.0.0", + "async-iterator-to-pull-stream": "^1.3.0", "chai": "^4.2.0", + "interface-connection": "~0.3.3", + "it-goodbye": "^2.0.0", + "it-pipe": "^1.0.0", "multiaddr": "^5.0.2", - "pull-goodbye": "~0.0.2", - "pull-serializer": "~0.3.2", - "pull-stream": "^3.6.9" + "pull-stream": "^3.6.9", + "streaming-iterables": "^4.0.2" }, "contributors": [ "David Dias ", diff --git a/src/adapter.js b/src/adapter.js new file mode 100644 index 0000000..e7c87db --- /dev/null +++ b/src/adapter.js @@ -0,0 +1,80 @@ +'use strict' + +const { Connection } = require('interface-connection') +const toPull = require('async-iterator-to-pull-stream') +const error = require('pull-stream/sources/error') +const drain = require('pull-stream/sinks/drain') +const noop = () => {} + +function callbackify (fn) { + return async function (...args) { + let cb = args.pop() + if (typeof cb !== 'function') { + args.push(cb) + cb = noop + } + let res + try { + res = await fn(...args) + } catch (err) { + return cb(err) + } + cb(null, res) + } +} + +// Legacy adapter to old transport & connection interface +class Adapter { + constructor (transport) { + this.transport = transport + } + + dial (ma, options, callback) { + if (typeof options === 'function') { + callback = options + options = {} + } + + callback = callback || noop + + const conn = new Connection() + + this.transport.dial(ma, options) + .then(socket => { + conn.setInnerConn(toPull.duplex(socket)) + conn.getObservedAddrs = callbackify(socket.getObservedAddrs.bind(socket)) + conn.close = callbackify(socket.close.bind(socket)) + callback(null, conn) + }) + .catch(err => { + conn.setInnerConn({ sink: drain(), source: error(err) }) + callback(err) + }) + + return conn + } + + createListener (options, handler) { + if (typeof options === 'function') { + handler = options + options = {} + } + + const server = this.transport.createListener(options, socket => { + const conn = new Connection(toPull.duplex(socket)) + conn.getObservedAddrs = callbackify(socket.getObservedAddrs.bind(socket)) + handler(conn) + }) + + const proxy = { + listen: callbackify(server.listen.bind(server)), + close: callbackify(server.close.bind(server)), + getAddrs: callbackify(server.getAddrs.bind(server)), + getObservedAddrs: callbackify(() => server.getObservedAddrs()) + } + + return new Proxy(server, { get: (_, prop) => proxy[prop] || server[prop] }) + } +} + +module.exports = Adapter diff --git a/src/dial-test.js b/src/dial-test.js index 85c1a6c..35d383b 100644 --- a/src/dial-test.js +++ b/src/dial-test.js @@ -5,69 +5,86 @@ const chai = require('chai') const dirtyChai = require('dirty-chai') const expect = chai.expect chai.use(dirtyChai) -const pull = require('pull-stream') -const goodbye = require('pull-goodbye') -const serializer = require('pull-serializer') + +const goodbye = require('it-goodbye') +const { collect } = require('streaming-iterables') +const pipe = require('it-pipe') +const AbortController = require('abort-controller') +const AbortError = require('./errors').AbortError module.exports = (common) => { describe('dial', () => { let addrs let transport + let connector let listener - before((done) => { - common.setup((err, _transport, _addrs) => { - if (err) return done(err) - transport = _transport - addrs = _addrs - done() - }) + before(async () => { + ({ addrs, transport, connector } = await common.setup()) }) - after((done) => { - common.teardown(done) + after(() => common.teardown && common.teardown()) + + beforeEach(() => { + listener = transport.createListener((conn) => pipe(conn, conn)) + return listener.listen(addrs[0]) }) - beforeEach((done) => { - listener = transport.createListener((conn) => { - pull(conn, conn) - }) - listener.listen(addrs[0], done) + afterEach(() => listener.close()) + + it('simple', async () => { + const conn = await transport.dial(addrs[0]) + + const s = goodbye({ source: ['hey'], sink: collect }) + + const result = await pipe(s, conn, s) + + expect(result.length).to.equal(1) + expect(result[0].toString()).to.equal('hey') }) - afterEach((done) => { - listener.close(done) + it('to non existent listener', async () => { + try { + await transport.dial(addrs[1]) + } catch (_) { + // Success: expected an error to be throw + return + } + expect.fail('Did not throw error attempting to connect to non-existent listener') }) - it('simple', (done) => { - const s = serializer(goodbye({ - source: pull.values(['hey']), - sink: pull.collect((err, values) => { - expect(err).to.not.exist() - expect( - values - ).to.be.eql( - ['hey'] - ) - done() - }) - })) + it('cancel before dialing', async () => { + const controller = new AbortController() + controller.abort() + const socket = transport.dial(addrs[0], { signal: controller.signal }) - pull( - s, - transport.dial(addrs[0]), - s - ) + try { + await socket + } catch (err) { + expect(err.code).to.eql(AbortError.code) + return + } + expect.fail('Did not throw error with code ' + AbortError.code) }) - it('to non existent listener', (done) => { - pull( - transport.dial(addrs[1]), - pull.onEnd((err) => { - expect(err).to.exist() - done() - }) - ) + it('cancel while dialing', async () => { + // Add a delay to connect() so that we can cancel while the dial is in + // progress + connector.delay(100) + + const controller = new AbortController() + const socket = transport.dial(addrs[0], { signal: controller.signal }) + setTimeout(() => controller.abort(), 50) + + try { + await socket + } catch (err) { + expect(err.code).to.eql(AbortError.code) + return + } finally { + connector.restore() + } + expect.fail('Did not throw error with code ' + AbortError.code) }) }) } diff --git a/src/errors.js b/src/errors.js new file mode 100644 index 0000000..32289a5 --- /dev/null +++ b/src/errors.js @@ -0,0 +1,16 @@ +'use strict' + +class AbortError extends Error { + constructor () { + super('AbortError') + this.code = AbortError.code + } + + static get code () { + return 'ABORT_ERR' + } +} + +module.exports = { + AbortError +} diff --git a/src/index.js b/src/index.js index e8173e2..73f1373 100644 --- a/src/index.js +++ b/src/index.js @@ -10,3 +10,6 @@ module.exports = (common) => { listen(common) }) } + +module.exports.AbortError = require('./errors').AbortError +module.exports.Adapter = require('./adapter') diff --git a/src/listen-test.js b/src/listen-test.js index 082361a..8abdbca 100644 --- a/src/listen-test.js +++ b/src/listen-test.js @@ -7,118 +7,98 @@ const dirtyChai = require('dirty-chai') const expect = chai.expect chai.use(dirtyChai) -const pull = require('pull-stream') +const pipe = require('it-pipe') module.exports = (common) => { describe('listen', () => { let addrs let transport - before((done) => { - common.setup((err, _transport, _addrs) => { - if (err) return done(err) - transport = _transport - addrs = _addrs - done() - }) + before(async () => { + ({ transport, addrs } = await common.setup()) }) - after((done) => { - common.teardown(done) - }) + after(() => common.teardown && common.teardown()) - it('simple', (done) => { + it('simple', async () => { const listener = transport.createListener((conn) => {}) - listener.listen(addrs[0], () => { - listener.close(done) - }) + await listener.listen(addrs[0]) + await listener.close() }) - it('close listener with connections, through timeout', (done) => { - const finish = plan(3, done) - const listener = transport.createListener((conn) => { - pull(conn, conn) + it('close listener with connections, through timeout', async () => { + let finish + let done = new Promise((resolve) => { + finish = resolve }) - listener.listen(addrs[0], () => { - const socket1 = transport.dial(addrs[0], () => { - listener.close(finish) - }) + const listener = transport.createListener((conn) => pipe(conn, conn)) - pull( - transport.dial(addrs[0]), - pull.onEnd(() => { - finish() - }) - ) + // Listen + await listener.listen(addrs[0]) - pull( - pull.values([Buffer.from('Some data that is never handled')]), - socket1, - pull.onEnd(() => { - finish() - }) - ) + // Create two connections to the listener + const socket1 = await transport.dial(addrs[0]) + await transport.dial(addrs[0]) + + pipe( + [Buffer.from('Some data that is never handled')], + socket1 + ).then(() => { + finish() }) + + // Closer the listener (will take a couple of seconds to time out) + await listener.close() + + // Pipe should have completed + await done }) describe('events', () => { - // eslint-disable-next-line - // TODO: figure out why it fails in the full test suite - it.skip('connection', (done) => { - const finish = plan(2, done) - + it('connection', (done) => { const listener = transport.createListener() - listener.on('connection', (conn) => { + listener.on('connection', async (conn) => { expect(conn).to.exist() - finish() + await listener.close() + done() }) - listener.listen(addrs[0], () => { - transport.dial(addrs[0], () => { - listener.close(finish) - }) - }) + ;(async () => { + await listener.listen(addrs[0]) + await transport.dial(addrs[0]) + })() }) it('listening', (done) => { const listener = transport.createListener() - listener.on('listening', () => { - listener.close(done) + listener.on('listening', async () => { + await listener.close() + done() }) listener.listen(addrs[0]) }) - // eslint-disable-next-line - // TODO: how to get the listener to emit an error? - it.skip('error', (done) => { + it('error', (done) => { const listener = transport.createListener() - listener.on('error', (err) => { + listener.on('error', async (err) => { expect(err).to.exist() - listener.close(done) + await listener.close() + done() }) + listener.emit('error', new Error('my err')) }) it('close', (done) => { - const finish = plan(2, done) const listener = transport.createListener() - listener.on('close', finish) + listener.on('close', done) - listener.listen(addrs[0], () => { - listener.close(finish) - }) + ;(async () => { + await listener.listen(addrs[0]) + await listener.close() + })() }) }) }) } - -function plan (n, done) { - let i = 0 - return (err) => { - if (err) return done(err) - i++ - - if (i === n) done() - } -} From 4fd37bb616a7e2e72d68af196bfbd1a96fe11e0b Mon Sep 17 00:00:00 2001 From: dirkmc Date: Thu, 18 Apr 2019 18:44:17 +0800 Subject: [PATCH 61/86] feat: add type to AbortError (#45) --- src/dial-test.js | 2 ++ src/errors.js | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/src/dial-test.js b/src/dial-test.js index 35d383b..11b9dea 100644 --- a/src/dial-test.js +++ b/src/dial-test.js @@ -62,6 +62,7 @@ module.exports = (common) => { await socket } catch (err) { expect(err.code).to.eql(AbortError.code) + expect(err.type).to.eql(AbortError.type) return } expect.fail('Did not throw error with code ' + AbortError.code) @@ -80,6 +81,7 @@ module.exports = (common) => { await socket } catch (err) { expect(err.code).to.eql(AbortError.code) + expect(err.type).to.eql(AbortError.type) return } finally { connector.restore() diff --git a/src/errors.js b/src/errors.js index 32289a5..40dd027 100644 --- a/src/errors.js +++ b/src/errors.js @@ -4,11 +4,16 @@ class AbortError extends Error { constructor () { super('AbortError') this.code = AbortError.code + this.type = AbortError.type } static get code () { return 'ABORT_ERR' } + + static get type () { + return 'aborted' + } } module.exports = { From 06ed59dc392fd738c9838c0118e279e3d9c3a164 Mon Sep 17 00:00:00 2001 From: Alan Shaw Date: Thu, 18 Apr 2019 15:47:47 +0100 Subject: [PATCH 62/86] refactor: a better abort error message (#47) Change to message as described here: https://heycam.github.io/webidl/#aborterror --- src/errors.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/errors.js b/src/errors.js index 40dd027..38609c1 100644 --- a/src/errors.js +++ b/src/errors.js @@ -2,7 +2,7 @@ class AbortError extends Error { constructor () { - super('AbortError') + super('The operation was aborted') this.code = AbortError.code this.type = AbortError.type } From 1dc5baab0b3efdf5ec07be646b8692700eb4c91c Mon Sep 17 00:00:00 2001 From: Jacob Heun Date: Fri, 19 Apr 2019 10:20:56 +0200 Subject: [PATCH 63/86] feat: make listen take an array of addrs (#46) * feat: make listen take an array of addrs * fix: make error more user friendly --- README.md | 6 +++--- package.json | 6 +++--- src/errors.js | 16 ++++++++++++++-- src/listen-test.js | 33 +++++++++++++++++++++++++++++---- 4 files changed, 49 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index bbe4f05..9e0e44f 100644 --- a/README.md +++ b/README.md @@ -98,7 +98,7 @@ A valid transport (one that follows the interface defined) must implement the fo - event: 'close' - event: 'connection' - event: 'error' - - ` listener.listen(multiaddr)` + - ` listener.listen(Array)` - `listener.getAddrs()` - ` listener.close([options])` @@ -168,11 +168,11 @@ The listener object created may emit the following events: ### Start a listener -- `JavaScript` - `await listener.listen(multiaddr)` +- `JavaScript` - `await listener.listen(Array)` This method puts the listener in `listening` mode, waiting for incoming connections. -`multiaddr` is the address that the listener should bind to. +`Array` is an array of the addresses that the listener should bind to. ### Get listener addrs diff --git a/package.json b/package.json index 723903c..e6be5fa 100644 --- a/package.json +++ b/package.json @@ -34,17 +34,17 @@ }, "homepage": "https://github.com/libp2p/interface-transport", "devDependencies": { - "aegir": "^17.0.1", - "dirty-chai": "^2.0.1" + "aegir": "^18.2.2" }, "dependencies": { "abort-controller": "^3.0.0", "async-iterator-to-pull-stream": "^1.3.0", "chai": "^4.2.0", + "dirty-chai": "^2.0.1", "interface-connection": "~0.3.3", "it-goodbye": "^2.0.0", "it-pipe": "^1.0.0", - "multiaddr": "^5.0.2", + "multiaddr": "^6.0.6", "pull-stream": "^3.6.9", "streaming-iterables": "^4.0.2" }, diff --git a/src/errors.js b/src/errors.js index 38609c1..74168a9 100644 --- a/src/errors.js +++ b/src/errors.js @@ -16,6 +16,18 @@ class AbortError extends Error { } } -module.exports = { - AbortError +class AllListenersFailedError extends Error { + constructor () { + super('All listeners failed to listen on any addresses, please verify the addresses you provided are correct') + this.code = AllListenersFailedError.code + } + + static get code () { + return 'ERR_ALL_LISTENERS_FAILED' + } +} + +module.exports = { + AbortError, + AllListenersFailedError } diff --git a/src/listen-test.js b/src/listen-test.js index 8abdbca..167abfc 100644 --- a/src/listen-test.js +++ b/src/listen-test.js @@ -8,6 +8,7 @@ const expect = chai.expect chai.use(dirtyChai) const pipe = require('it-pipe') +const { collect } = require('streaming-iterables') module.exports = (common) => { describe('listen', () => { @@ -22,7 +23,31 @@ module.exports = (common) => { it('simple', async () => { const listener = transport.createListener((conn) => {}) - await listener.listen(addrs[0]) + await listener.listen([addrs[0]]) + await listener.close() + }) + + it('listen on multiple addresses', async () => { + // create an echo listener + const listener = transport.createListener((conn) => pipe(conn, conn)) + await listener.listen(addrs.slice(0, 2)) + + // Connect on both addresses + const [socket1, socket2] = await Promise.all([ + transport.dial(addrs[0]), + transport.dial(addrs[1]) + ]) + + const data = Buffer.from('hi there') + const results = await pipe( + [data], // [data] -> socket1 + socket1, // socket1 -> server (echo) -> socket1 -> socket2 + socket2, // socket2 -> server (echo) -> socket2 -> collect + collect + ) + + expect(results).to.eql([data]) + await listener.close() }) @@ -35,7 +60,7 @@ module.exports = (common) => { const listener = transport.createListener((conn) => pipe(conn, conn)) // Listen - await listener.listen(addrs[0]) + await listener.listen([addrs[0]]) // Create two connections to the listener const socket1 = await transport.dial(addrs[0]) @@ -66,7 +91,7 @@ module.exports = (common) => { }) ;(async () => { - await listener.listen(addrs[0]) + await listener.listen([addrs[0]]) await transport.dial(addrs[0]) })() }) @@ -95,7 +120,7 @@ module.exports = (common) => { listener.on('close', done) ;(async () => { - await listener.listen(addrs[0]) + await listener.listen([addrs[0]]) await listener.close() })() }) From 00396d028cfdec4416a26edea3d5767543a56257 Mon Sep 17 00:00:00 2001 From: Jacob Heun Date: Fri, 19 Apr 2019 10:37:43 +0200 Subject: [PATCH 64/86] chore: update contributors --- package.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index e6be5fa..9bb93b0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "interface-transport", - "version": "0.3.7", + "version": "0.4.0", "description": "A test suite and interface you can use to implement a transport.", "leadMaintainer": "Jacob Heun ", "repository": { @@ -49,12 +49,14 @@ "streaming-iterables": "^4.0.2" }, "contributors": [ + "Alan Shaw ", "David Dias ", "Friedel Ziegelmayer ", "Jacob Heun ", "João Santos ", "Maciej Krüger ", "Richard Littauer ", + "dirkmc ", "dmitriy ryajov ", "greenkeeperio-bot " ] From 16cba53674ed50ee0eceba267739a9e8de726438 Mon Sep 17 00:00:00 2001 From: Jacob Heun Date: Fri, 19 Apr 2019 10:37:44 +0200 Subject: [PATCH 65/86] chore: release version v0.4.0 --- CHANGELOG.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 516a437..3b7dc02 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,24 @@ + +# [0.4.0](https://github.com/libp2p/interface-transport/compare/v0.3.7...v0.4.0) (2019-04-19) + + +### Features + +* add type to AbortError ([#45](https://github.com/libp2p/interface-transport/issues/45)) ([4fd37bb](https://github.com/libp2p/interface-transport/commit/4fd37bb)) +* callbacks -> async / await ([#44](https://github.com/libp2p/interface-transport/issues/44)) ([b30ee5f](https://github.com/libp2p/interface-transport/commit/b30ee5f)) +* make listen take an array of addrs ([#46](https://github.com/libp2p/interface-transport/issues/46)) ([1dc5baa](https://github.com/libp2p/interface-transport/commit/1dc5baa)) + + +### BREAKING CHANGES + +* All places in the API that used callbacks are now replaced with async/await + +* test: add tests for canceling dials + +* feat: Adapter class + + + ## [0.3.7](https://github.com/libp2p/interface-transport/compare/v0.3.6...v0.3.7) (2018-11-29) From 8101be9d4f28498edae0437c516a8a4b9db89ac2 Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Tue, 23 Apr 2019 10:57:43 +0100 Subject: [PATCH 66/86] chore: add discourse badge (#48) --- README.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 9e0e44f..43dbb2d 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,13 @@ interface-transport =================== -[![](https://img.shields.io/badge/made%20by-Protocol%20Labs-blue.svg?style=flat-square)](https://protocol.ai) -[![](https://img.shields.io/badge/freenode-%23ipfs-blue.svg?style=flat-square)](http://webchat.freenode.net/?channels=%23ipfs) +[![](https://img.shields.io/badge/made%20by-Protocol%20Labs-blue.svg?style=flat-square)](http://protocol.ai) +[![](https://img.shields.io/badge/project-libp2p-yellow.svg?style=flat-square)](http://libp2p.io/) +[![](https://img.shields.io/badge/freenode-%23libp2p-yellow.svg?style=flat-square)](http://webchat.freenode.net/?channels=%23libp2p) +[![Discourse posts](https://img.shields.io/discourse/https/discuss.libp2p.io/posts.svg)](https://discuss.libp2p.io) +[![](https://img.shields.io/travis/libp2p/interface-transport.svg?style=flat-square)](https://travis-ci.com/libp2p/interface-transport) +[![Dependency Status](https://david-dm.org/libp2p/interface-transport.svg?style=flat-square)](https://david-dm.org/libp2p/interface-transport) +[![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat-square)](https://github.com/feross/standard) > A test suite and interface you can use to implement a libp2p transport. A libp2p transport is understood as something that offers a dial and listen interface. From e4cc841ebda0a1c5d3ccea25eb6b889f90a21e06 Mon Sep 17 00:00:00 2001 From: dirkmc Date: Mon, 29 Apr 2019 17:32:51 +0800 Subject: [PATCH 67/86] chore: fix typos in README (#50) --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 43dbb2d..f8f6448 100644 --- a/README.md +++ b/README.md @@ -67,7 +67,7 @@ describe('compliance', () => { delay (delayMs) { // Add a delay in the connection mechanism for the transport // (this is used by the dial tests) - network.connect = (...args) => setTimeout(() => connect(...args), 100) + network.connect = (...args) => setTimeout(() => connect(...args), delayMs) }, restore () { // Restore the connection mechanism to normal @@ -109,7 +109,7 @@ A valid transport (one that follows the interface defined) must implement the fo ### Creating a transport instance -- `JavaScript` - `var transport = new Transport([options])` +- `JavaScript` - `const transport = new Transport([options])` Creates a new Transport instance. `options` is an optional JavaScript object that should include the necessary parameters for the transport instance. From 030195e3155b3a0515b99c2fa9a81c2418d47a2a Mon Sep 17 00:00:00 2001 From: dirkmc Date: Mon, 29 Apr 2019 23:04:17 +0800 Subject: [PATCH 68/86] revert: "feat: make listen take an array of addrs (#46)" (#51) This reverts commit 1dc5baab0b3efdf5ec07be646b8692700eb4c91c. --- README.md | 6 +++--- package.json | 6 +++--- src/errors.js | 14 +------------- src/listen-test.js | 33 ++++----------------------------- 4 files changed, 11 insertions(+), 48 deletions(-) diff --git a/README.md b/README.md index f8f6448..a5821fc 100644 --- a/README.md +++ b/README.md @@ -103,7 +103,7 @@ A valid transport (one that follows the interface defined) must implement the fo - event: 'close' - event: 'connection' - event: 'error' - - ` listener.listen(Array)` + - ` listener.listen(multiaddr)` - `listener.getAddrs()` - ` listener.close([options])` @@ -173,11 +173,11 @@ The listener object created may emit the following events: ### Start a listener -- `JavaScript` - `await listener.listen(Array)` +- `JavaScript` - `await listener.listen(multiaddr)` This method puts the listener in `listening` mode, waiting for incoming connections. -`Array` is an array of the addresses that the listener should bind to. +`multiaddr` is the address that the listener should bind to. ### Get listener addrs diff --git a/package.json b/package.json index 9bb93b0..4baf103 100644 --- a/package.json +++ b/package.json @@ -34,17 +34,17 @@ }, "homepage": "https://github.com/libp2p/interface-transport", "devDependencies": { - "aegir": "^18.2.2" + "aegir": "^17.0.1", + "dirty-chai": "^2.0.1" }, "dependencies": { "abort-controller": "^3.0.0", "async-iterator-to-pull-stream": "^1.3.0", "chai": "^4.2.0", - "dirty-chai": "^2.0.1", "interface-connection": "~0.3.3", "it-goodbye": "^2.0.0", "it-pipe": "^1.0.0", - "multiaddr": "^6.0.6", + "multiaddr": "^5.0.2", "pull-stream": "^3.6.9", "streaming-iterables": "^4.0.2" }, diff --git a/src/errors.js b/src/errors.js index 74168a9..38609c1 100644 --- a/src/errors.js +++ b/src/errors.js @@ -16,18 +16,6 @@ class AbortError extends Error { } } -class AllListenersFailedError extends Error { - constructor () { - super('All listeners failed to listen on any addresses, please verify the addresses you provided are correct') - this.code = AllListenersFailedError.code - } - - static get code () { - return 'ERR_ALL_LISTENERS_FAILED' - } -} - module.exports = { - AbortError, - AllListenersFailedError + AbortError } diff --git a/src/listen-test.js b/src/listen-test.js index 167abfc..8abdbca 100644 --- a/src/listen-test.js +++ b/src/listen-test.js @@ -8,7 +8,6 @@ const expect = chai.expect chai.use(dirtyChai) const pipe = require('it-pipe') -const { collect } = require('streaming-iterables') module.exports = (common) => { describe('listen', () => { @@ -23,31 +22,7 @@ module.exports = (common) => { it('simple', async () => { const listener = transport.createListener((conn) => {}) - await listener.listen([addrs[0]]) - await listener.close() - }) - - it('listen on multiple addresses', async () => { - // create an echo listener - const listener = transport.createListener((conn) => pipe(conn, conn)) - await listener.listen(addrs.slice(0, 2)) - - // Connect on both addresses - const [socket1, socket2] = await Promise.all([ - transport.dial(addrs[0]), - transport.dial(addrs[1]) - ]) - - const data = Buffer.from('hi there') - const results = await pipe( - [data], // [data] -> socket1 - socket1, // socket1 -> server (echo) -> socket1 -> socket2 - socket2, // socket2 -> server (echo) -> socket2 -> collect - collect - ) - - expect(results).to.eql([data]) - + await listener.listen(addrs[0]) await listener.close() }) @@ -60,7 +35,7 @@ module.exports = (common) => { const listener = transport.createListener((conn) => pipe(conn, conn)) // Listen - await listener.listen([addrs[0]]) + await listener.listen(addrs[0]) // Create two connections to the listener const socket1 = await transport.dial(addrs[0]) @@ -91,7 +66,7 @@ module.exports = (common) => { }) ;(async () => { - await listener.listen([addrs[0]]) + await listener.listen(addrs[0]) await transport.dial(addrs[0]) })() }) @@ -120,7 +95,7 @@ module.exports = (common) => { listener.on('close', done) ;(async () => { - await listener.listen([addrs[0]]) + await listener.listen(addrs[0]) await listener.close() })() }) From ad04ee932ed4ad55bf66a1874ba7bc8b6e7f7137 Mon Sep 17 00:00:00 2001 From: Jacob Heun Date: Mon, 29 Apr 2019 17:09:53 +0200 Subject: [PATCH 69/86] chore: update deps --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 4baf103..0638af5 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ }, "homepage": "https://github.com/libp2p/interface-transport", "devDependencies": { - "aegir": "^17.0.1", + "aegir": "^18.2.2", "dirty-chai": "^2.0.1" }, "dependencies": { @@ -44,7 +44,7 @@ "interface-connection": "~0.3.3", "it-goodbye": "^2.0.0", "it-pipe": "^1.0.0", - "multiaddr": "^5.0.2", + "multiaddr": "^6.0.6", "pull-stream": "^3.6.9", "streaming-iterables": "^4.0.2" }, From cfbfbe48b6a00cc3e1983673cef6468475e30dba Mon Sep 17 00:00:00 2001 From: Jacob Heun Date: Mon, 29 Apr 2019 17:11:40 +0200 Subject: [PATCH 70/86] chore: update contributors --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 0638af5..d451234 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "interface-transport", - "version": "0.4.0", + "version": "0.5.0", "description": "A test suite and interface you can use to implement a transport.", "leadMaintainer": "Jacob Heun ", "repository": { @@ -56,6 +56,7 @@ "João Santos ", "Maciej Krüger ", "Richard Littauer ", + "Vasco Santos ", "dirkmc ", "dmitriy ryajov ", "greenkeeperio-bot " From 4175c66d4269ee7b7c12f4571976b086cd6d66b8 Mon Sep 17 00:00:00 2001 From: Jacob Heun Date: Mon, 29 Apr 2019 17:11:41 +0200 Subject: [PATCH 71/86] chore: release version v0.5.0 --- CHANGELOG.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b7dc02..248b4f9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,13 @@ + +# [0.5.0](https://github.com/libp2p/interface-transport/compare/v0.4.0...v0.5.0) (2019-04-29) + + +### Reverts + +* "feat: make listen take an array of addrs ([#46](https://github.com/libp2p/interface-transport/issues/46))" ([#51](https://github.com/libp2p/interface-transport/issues/51)) ([030195e](https://github.com/libp2p/interface-transport/commit/030195e)) + + + # [0.4.0](https://github.com/libp2p/interface-transport/compare/v0.3.7...v0.4.0) (2019-04-19) From f9a7908f753a1fc61271776c876da3a507a63dfa Mon Sep 17 00:00:00 2001 From: Alan Shaw Date: Wed, 1 May 2019 13:11:16 +0100 Subject: [PATCH 72/86] fix: move dirty-chai to dependencies (#52) * fix: move dirty-chai to dependencies * chore: test on node 10 License: MIT Signed-off-by: Alan Shaw --- .travis.yml | 2 +- package.json | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 52cd14d..89d4354 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,7 @@ sudo: false language: node_js node_js: - - "stable" + - "10" before_install: - npm install -g npm diff --git a/package.json b/package.json index d451234..34efde4 100644 --- a/package.json +++ b/package.json @@ -34,13 +34,13 @@ }, "homepage": "https://github.com/libp2p/interface-transport", "devDependencies": { - "aegir": "^18.2.2", - "dirty-chai": "^2.0.1" + "aegir": "^18.2.2" }, "dependencies": { "abort-controller": "^3.0.0", "async-iterator-to-pull-stream": "^1.3.0", "chai": "^4.2.0", + "dirty-chai": "^2.0.1", "interface-connection": "~0.3.3", "it-goodbye": "^2.0.0", "it-pipe": "^1.0.0", From 1e8ea9c2cf85d208bb8da6aaf3f2253f8254c49e Mon Sep 17 00:00:00 2001 From: Jacob Heun Date: Wed, 1 May 2019 14:13:15 +0200 Subject: [PATCH 73/86] chore: update contributors --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 34efde4..1c6cda6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "interface-transport", - "version": "0.5.0", + "version": "0.5.1", "description": "A test suite and interface you can use to implement a transport.", "leadMaintainer": "Jacob Heun ", "repository": { From 68e2791beabf28b2899dc49494f019af05afda49 Mon Sep 17 00:00:00 2001 From: Jacob Heun Date: Wed, 1 May 2019 14:13:16 +0200 Subject: [PATCH 74/86] chore: release version v0.5.1 --- CHANGELOG.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 248b4f9..3c03601 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,13 @@ + +## [0.5.1](https://github.com/libp2p/interface-transport/compare/v0.5.0...v0.5.1) (2019-05-01) + + +### Bug Fixes + +* move dirty-chai to dependencies ([#52](https://github.com/libp2p/interface-transport/issues/52)) ([f9a7908](https://github.com/libp2p/interface-transport/commit/f9a7908)) + + + # [0.5.0](https://github.com/libp2p/interface-transport/compare/v0.4.0...v0.5.0) (2019-04-29) From a6d6098ec73f620a8d8422be1832264f69d7ed4b Mon Sep 17 00:00:00 2001 From: dirkmc Date: Tue, 11 Jun 2019 13:16:55 -0400 Subject: [PATCH 75/86] test: add tests for abort after connect (#49) --- src/dial-test.js | 77 ++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 74 insertions(+), 3 deletions(-) diff --git a/src/dial-test.js b/src/dial-test.js index 11b9dea..d79438d 100644 --- a/src/dial-test.js +++ b/src/dial-test.js @@ -53,7 +53,7 @@ module.exports = (common) => { expect.fail('Did not throw error attempting to connect to non-existent listener') }) - it('cancel before dialing', async () => { + it('abort before dialing throws AbortError', async () => { const controller = new AbortController() controller.abort() const socket = transport.dial(addrs[0], { signal: controller.signal }) @@ -68,8 +68,8 @@ module.exports = (common) => { expect.fail('Did not throw error with code ' + AbortError.code) }) - it('cancel while dialing', async () => { - // Add a delay to connect() so that we can cancel while the dial is in + it('abort while dialing throws AbortError', async () => { + // Add a delay to connect() so that we can abort while the dial is in // progress connector.delay(100) @@ -88,5 +88,76 @@ module.exports = (common) => { } expect.fail('Did not throw error with code ' + AbortError.code) }) + + it('abort while reading throws AbortError', async () => { + // Add a delay to the response from the server + async function * delayedResponse (source) { + for await (const val of source) { + await new Promise((resolve) => setTimeout(resolve, 1000)) + yield val + } + } + const delayedListener = transport.createListener(async (conn) => { + await pipe(conn, delayedResponse, conn) + }) + await delayedListener.listen(addrs[1]) + + // Create an abort signal and dial the socket + const controller = new AbortController() + const socket = await transport.dial(addrs[1], { signal: controller.signal }) + + try { + // Set a timeout to abort before the server responds + setTimeout(() => controller.abort(), 100) + + // An AbortError should be thrown before the pipe completes + const s = goodbye({ source: ['hey'], sink: collect }) + await pipe(s, socket, s) + } catch (err) { + expect(err.code).to.eql(AbortError.code) + expect(err.type).to.eql(AbortError.type) + return + } finally { + await delayedListener.close() + } + expect.fail('Did not throw error with code ' + AbortError.code) + }) + + it('abort while writing does not throw AbortError', async () => { + // Record values received by the listener + const recorded = [] + async function * recorderTransform (source) { + for await (const val of source) { + recorded.push(val) + yield val + } + } + const recordListener = transport.createListener(async (conn) => { + await pipe(conn, recorderTransform, conn) + }) + await recordListener.listen(addrs[1]) + + // Create an abort signal and dial the socket + const controller = new AbortController() + const socket = await transport.dial(addrs[1], { signal: controller.signal }) + + // Set a timeout to abort before writing has completed + setTimeout(() => controller.abort(), 100) + + try { + // The pipe should write to the socket until aborted + await pipe( + async function * () { + yield 'hey' + await new Promise((resolve) => setTimeout(resolve, 200)) + yield 'there' + }, + socket) + expect(recorded.length).to.eql(1) + expect(recorded[0].toString()).to.eql('hey') + } finally { + await recordListener.close() + } + }) }) } From 543125914026d22ce7536162c67d162510d8d405 Mon Sep 17 00:00:00 2001 From: Jacob Heun Date: Tue, 11 Jun 2019 19:22:13 +0200 Subject: [PATCH 76/86] chore: update contributors --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 1c6cda6..14671cf 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "interface-transport", - "version": "0.5.1", + "version": "0.5.2", "description": "A test suite and interface you can use to implement a transport.", "leadMaintainer": "Jacob Heun ", "repository": { From 259fc58622d2206c729bcf78846b9164286c9be4 Mon Sep 17 00:00:00 2001 From: Jacob Heun Date: Tue, 11 Jun 2019 19:22:13 +0200 Subject: [PATCH 77/86] chore: release version v0.5.2 --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3c03601..2adcded 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ + +## [0.5.2](https://github.com/libp2p/interface-transport/compare/v0.5.1...v0.5.2) (2019-06-11) + + + ## [0.5.1](https://github.com/libp2p/interface-transport/compare/v0.5.0...v0.5.1) (2019-05-01) From a5ad120b606356c392c301b05cdbff2a88350bb2 Mon Sep 17 00:00:00 2001 From: Jacob Heun Date: Fri, 6 Sep 2019 09:44:17 +0200 Subject: [PATCH 78/86] feat: add upgrader support to transports (#53) BREAKING CHANGE: Transports must now be passed and use an `Upgrader` instance. See the Readme for usage. Compliance test suites will now need to pass `options` from `common.setup(options)` to their Transport constructor. * docs: update readme to include upgrader * docs: update readme to include MultiaddrConnection ref * feat: add upgrader spy to test suite * test: validate returned value of spy --- README.md | 42 ++++++++++++++++++++++++++++++------------ package.json | 7 ++++--- src/dial-test.js | 34 ++++++++++++++++++++++++++++++++-- src/listen-test.js | 38 +++++++++++++++++++++++++++++++++++--- 4 files changed, 101 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index a5821fc..2c44362 100644 --- a/README.md +++ b/README.md @@ -53,8 +53,8 @@ const YourTransport = require('../src') describe('compliance', () => { tests({ - setup () { - let transport = new YourTransport() + setup (options) { + let transport = new YourTransport(options) const addrs = [ multiaddr('valid-multiaddr-for-your-transport'), @@ -84,10 +84,6 @@ describe('compliance', () => { }) ``` -## Go - -> WIP - # API A valid transport (one that follows the interface defined) must implement the following API: @@ -95,7 +91,7 @@ A valid transport (one that follows the interface defined) must implement the fo **Table of contents:** - type: `Transport` - - `new Transport([options])` + - `new Transport({ upgrader, ...[options] })` - ` transport.dial(multiaddr, [options])` - `transport.createListener([options], handlerFunction)` - type: `transport.Listener` @@ -107,17 +103,39 @@ A valid transport (one that follows the interface defined) must implement the fo - `listener.getAddrs()` - ` listener.close([options])` +### Types + +#### Upgrader +Upgraders have 2 methods: `upgradeOutbound` and `upgradeInbound`. +- `upgradeOutbound` must be called and returned by `transport.dial`. +- `upgradeInbound` must be called and the results must be passed to the `createListener` `handlerFunction` and the `connection` event handler, anytime a new connection is created. + +```js +const connection = await upgrader.upgradeOutbound(multiaddrConnection) +const connection = await upgrader.upgradeInbound(multiaddrConnection) +``` + +The `Upgrader` methods take a [MultiaddrConnection](#multiaddrconnection) and will return an `interface-connection` instance. + +#### MultiaddrConnection + +- `MultiaddrConnection` + - `sink`: A [streaming iterable sink](https://gist.github.com/alanshaw/591dc7dd54e4f99338a347ef568d6ee9#sink-it) + - `source`: A [streaming iterable source](https://gist.github.com/alanshaw/591dc7dd54e4f99338a347ef568d6ee9#source-it) + - `conn`: The raw connection of the transport, such as a TCP socket. + - `remoteAddr`: The remote `Multiaddr` of the connection. + ### Creating a transport instance -- `JavaScript` - `const transport = new Transport([options])` +- `JavaScript` - `const transport = new Transport({ upgrader, ...[options] })` -Creates a new Transport instance. `options` is an optional JavaScript object that should include the necessary parameters for the transport instance. +Creates a new Transport instance. `options` is an JavaScript object that should include the necessary parameters for the transport instance. Options **MUST** include an `Upgrader` instance, as Transports will use this to return `interface-connection` instances from `transport.dial` and the listener `handlerFunction`. **Note: Why is it important to instantiate a transport -** Some transports have state that can be shared between the dialing and listening parts. For example with libp2p-webrtc-star, in order to dial a peer, the peer must be part of some signaling network that is shared with the listener. ### Dial to another peer -- `JavaScript` - `const conn = await transport.dial(multiaddr, [options])` +- `JavaScript` - `const connection = await transport.dial(multiaddr, [options])` This method uses a transport to dial a Peer listening on `multiaddr`. @@ -125,7 +143,7 @@ This method uses a transport to dial a Peer listening on `multiaddr`. `[options]` the options that may be passed to the dial. Must support the `signal` option (see below) -`conn` must implement the [interface-connection](https://github.com/libp2p/interface-connection) interface. +Dial **MUST** call and return `upgrader.upgradeOutbound(multiaddrConnection)`. The upgrader will return an [interface-connection](https://github.com/libp2p/interface-connection) instance. The dial may throw an `Error` instance if there was a problem connecting to the `multiaddr`. @@ -158,7 +176,7 @@ try { - `JavaScript` - `const listener = transport.createListener([options], handlerFunction)` -This method creates a listener on the transport. +This method creates a listener on the transport. Implementations **MUST** call `upgrader.upgradeInbound(multiaddrConnection)` and pass its results to the `handlerFunction` and any emitted `connection` events. `options` is an optional object that contains the properties the listener must have, in order to properly listen on a given transport/socket. diff --git a/package.json b/package.json index 14671cf..cfc10f0 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ }, "homepage": "https://github.com/libp2p/interface-transport", "devDependencies": { - "aegir": "^18.2.2" + "aegir": "^20.0.0" }, "dependencies": { "abort-controller": "^3.0.0", @@ -44,9 +44,10 @@ "interface-connection": "~0.3.3", "it-goodbye": "^2.0.0", "it-pipe": "^1.0.0", - "multiaddr": "^6.0.6", + "multiaddr": "^7.0.0", "pull-stream": "^3.6.9", - "streaming-iterables": "^4.0.2" + "sinon": "^7.4.2", + "streaming-iterables": "^4.1.0" }, "contributors": [ "Alan Shaw ", diff --git a/src/dial-test.js b/src/dial-test.js index d79438d..80d29b6 100644 --- a/src/dial-test.js +++ b/src/dial-test.js @@ -11,8 +11,26 @@ const { collect } = require('streaming-iterables') const pipe = require('it-pipe') const AbortController = require('abort-controller') const AbortError = require('./errors').AbortError +const sinon = require('sinon') module.exports = (common) => { + const upgrader = { + upgradeOutbound (multiaddrConnection) { + ['sink', 'source', 'remoteAddr', 'conn'].forEach(prop => { + expect(multiaddrConnection).to.have.property(prop) + }) + + return { sink: multiaddrConnection.sink, source: multiaddrConnection.source } + }, + upgradeInbound (multiaddrConnection) { + ['sink', 'source', 'remoteAddr', 'conn'].forEach(prop => { + expect(multiaddrConnection).to.have.property(prop) + }) + + return { sink: multiaddrConnection.sink, source: multiaddrConnection.source } + } + } + describe('dial', () => { let addrs let transport @@ -20,7 +38,7 @@ module.exports = (common) => { let listener before(async () => { - ({ addrs, transport, connector } = await common.setup()) + ({ addrs, transport, connector } = await common.setup({ upgrader })) }) after(() => common.teardown && common.teardown()) @@ -30,23 +48,31 @@ module.exports = (common) => { return listener.listen(addrs[0]) }) - afterEach(() => listener.close()) + afterEach(() => { + sinon.restore() + return listener.close() + }) it('simple', async () => { + const upgradeSpy = sinon.spy(upgrader, 'upgradeOutbound') const conn = await transport.dial(addrs[0]) const s = goodbye({ source: ['hey'], sink: collect }) const result = await pipe(s, conn, s) + expect(upgradeSpy.callCount).to.equal(1) + expect(upgradeSpy.returned(conn)).to.equal(true) expect(result.length).to.equal(1) expect(result[0].toString()).to.equal('hey') }) it('to non existent listener', async () => { + const upgradeSpy = sinon.spy(upgrader, 'upgradeOutbound') try { await transport.dial(addrs[1]) } catch (_) { + expect(upgradeSpy.callCount).to.equal(0) // Success: expected an error to be throw return } @@ -54,6 +80,7 @@ module.exports = (common) => { }) it('abort before dialing throws AbortError', async () => { + const upgradeSpy = sinon.spy(upgrader, 'upgradeOutbound') const controller = new AbortController() controller.abort() const socket = transport.dial(addrs[0], { signal: controller.signal }) @@ -61,6 +88,7 @@ module.exports = (common) => { try { await socket } catch (err) { + expect(upgradeSpy.callCount).to.equal(0) expect(err.code).to.eql(AbortError.code) expect(err.type).to.eql(AbortError.type) return @@ -69,6 +97,7 @@ module.exports = (common) => { }) it('abort while dialing throws AbortError', async () => { + const upgradeSpy = sinon.spy(upgrader, 'upgradeOutbound') // Add a delay to connect() so that we can abort while the dial is in // progress connector.delay(100) @@ -80,6 +109,7 @@ module.exports = (common) => { try { await socket } catch (err) { + expect(upgradeSpy.callCount).to.equal(0) expect(err.code).to.eql(AbortError.code) expect(err.type).to.eql(AbortError.type) return diff --git a/src/listen-test.js b/src/listen-test.js index 8abdbca..d90bb00 100644 --- a/src/listen-test.js +++ b/src/listen-test.js @@ -6,20 +6,42 @@ const chai = require('chai') const dirtyChai = require('dirty-chai') const expect = chai.expect chai.use(dirtyChai) +const sinon = require('sinon') const pipe = require('it-pipe') module.exports = (common) => { + const upgrader = { + upgradeOutbound (multiaddrConnection) { + ['sink', 'source', 'remoteAddr', 'conn'].forEach(prop => { + expect(multiaddrConnection).to.have.property(prop) + }) + + return { sink: multiaddrConnection.sink, source: multiaddrConnection.source } + }, + upgradeInbound (multiaddrConnection) { + ['sink', 'source', 'remoteAddr', 'conn'].forEach(prop => { + expect(multiaddrConnection).to.have.property(prop) + }) + + return { sink: multiaddrConnection.sink, source: multiaddrConnection.source } + } + } + describe('listen', () => { let addrs let transport before(async () => { - ({ transport, addrs } = await common.setup()) + ({ transport, addrs } = await common.setup({ upgrader })) }) after(() => common.teardown && common.teardown()) + afterEach(() => { + sinon.restore() + }) + it('simple', async () => { const listener = transport.createListener((conn) => {}) await listener.listen(addrs[0]) @@ -27,12 +49,16 @@ module.exports = (common) => { }) it('close listener with connections, through timeout', async () => { + const upgradeSpy = sinon.spy(upgrader, 'upgradeInbound') let finish - let done = new Promise((resolve) => { + const done = new Promise((resolve) => { finish = resolve }) - const listener = transport.createListener((conn) => pipe(conn, conn)) + const listener = transport.createListener((conn) => { + expect(upgradeSpy.returned(conn)).to.equal(true) + pipe(conn, conn) + }) // Listen await listener.listen(addrs[0]) @@ -53,13 +79,19 @@ module.exports = (common) => { // Pipe should have completed await done + + // 2 dials = 2 connections upgraded + expect(upgradeSpy.callCount).to.equal(2) }) describe('events', () => { it('connection', (done) => { + const upgradeSpy = sinon.spy(upgrader, 'upgradeInbound') const listener = transport.createListener() listener.on('connection', async (conn) => { + expect(upgradeSpy.returned(conn)).to.equal(true) + expect(upgradeSpy.callCount).to.equal(1) expect(conn).to.exist() await listener.close() done() From 2616ae2e109cc7d53be531efd2bdc9c118cb945d Mon Sep 17 00:00:00 2001 From: Jacob Heun Date: Fri, 6 Sep 2019 10:00:52 +0200 Subject: [PATCH 79/86] chore: update contributors --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index cfc10f0..892cab4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "interface-transport", - "version": "0.5.2", + "version": "0.6.0", "description": "A test suite and interface you can use to implement a transport.", "leadMaintainer": "Jacob Heun ", "repository": { From d143f79ceba771d1b7ec6aa18c0564858c6fac2f Mon Sep 17 00:00:00 2001 From: Jacob Heun Date: Fri, 6 Sep 2019 10:00:53 +0200 Subject: [PATCH 80/86] chore: release version v0.6.0 --- CHANGELOG.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2adcded..f647929 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,26 @@ + +# [0.6.0](https://github.com/libp2p/interface-transport/compare/v0.5.2...v0.6.0) (2019-09-06) + + +### Features + +* add upgrader support to transports ([#53](https://github.com/libp2p/interface-transport/issues/53)) ([a5ad120](https://github.com/libp2p/interface-transport/commit/a5ad120)) + + +### BREAKING CHANGES + +* Transports must now be passed and use an `Upgrader` instance. See the Readme for usage. Compliance test suites will now need to pass `options` from `common.setup(options)` to their Transport constructor. + +* docs: update readme to include upgrader + +* docs: update readme to include MultiaddrConnection ref + +* feat: add upgrader spy to test suite + +* test: validate returned value of spy + + + ## [0.5.2](https://github.com/libp2p/interface-transport/compare/v0.5.1...v0.5.2) (2019-06-11) From 583f02d47ac319dcb303b3a9e3224d9cabcad0f9 Mon Sep 17 00:00:00 2001 From: Jacob Heun Date: Mon, 16 Sep 2019 15:58:25 +0200 Subject: [PATCH 81/86] fix(test): close with timeout (#54) * fix(test): allow time for the listener to finish * chore: pre push instead of pre commit chore: clean up git ignore and add docs to list --- .gitignore | 30 +----------------------------- package.json | 2 +- src/listen-test.js | 32 +++++++++++++++----------------- 3 files changed, 17 insertions(+), 47 deletions(-) diff --git a/.gitignore b/.gitignore index e1a7614..fd34c26 100644 --- a/.gitignore +++ b/.gitignore @@ -1,36 +1,8 @@ **/node_modules/ **/*.log -test/repo-tests* package-lock.json -# Logs -logs -*.log - -coverage - -# Runtime data -pids -*.pid -*.seed - -# Directory for instrumented libs generated by jscoverage/JSCover -lib-cov - # Coverage directory used by tools like istanbul coverage - -# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) -.grunt - -# node-waf configuration -.lock-wscript - -build - -# Dependency directory -# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git -node_modules - -lib +docs dist diff --git a/package.json b/package.json index 892cab4..fda7fa4 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "coverage": "exit(0)", "coverage-publish": "exit(0)" }, - "pre-commit": [ + "pre-push": [ "lint" ], "keywords": [ diff --git a/src/listen-test.js b/src/listen-test.js index d90bb00..9754cbd 100644 --- a/src/listen-test.js +++ b/src/listen-test.js @@ -50,10 +50,6 @@ module.exports = (common) => { it('close listener with connections, through timeout', async () => { const upgradeSpy = sinon.spy(upgrader, 'upgradeInbound') - let finish - const done = new Promise((resolve) => { - finish = resolve - }) const listener = transport.createListener((conn) => { expect(upgradeSpy.returned(conn)).to.equal(true) @@ -64,21 +60,23 @@ module.exports = (common) => { await listener.listen(addrs[0]) // Create two connections to the listener - const socket1 = await transport.dial(addrs[0]) - await transport.dial(addrs[0]) + const [socket1] = await Promise.all([ + transport.dial(addrs[0]), + transport.dial(addrs[0]) + ]) - pipe( - [Buffer.from('Some data that is never handled')], - socket1 - ).then(() => { - finish() - }) + // Give the listener a chance to finish its upgrade + await new Promise(resolve => setTimeout(resolve, 0)) - // Closer the listener (will take a couple of seconds to time out) - await listener.close() - - // Pipe should have completed - await done + // Wait for the data send and close to finish + await Promise.all([ + pipe( + [Buffer.from('Some data that is never handled')], + socket1 + ), + // Closer the listener (will take a couple of seconds to time out) + listener.close() + ]) // 2 dials = 2 connections upgraded expect(upgradeSpy.callCount).to.equal(2) From 675037f4bcd4152f081a369fd15d42ed5ce306c6 Mon Sep 17 00:00:00 2001 From: Jacob Heun Date: Mon, 16 Sep 2019 16:00:18 +0200 Subject: [PATCH 82/86] chore: update contributors --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index fda7fa4..e556028 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "interface-transport", - "version": "0.6.0", + "version": "0.6.1", "description": "A test suite and interface you can use to implement a transport.", "leadMaintainer": "Jacob Heun ", "repository": { From 02fe6d9040a6dbfc496c59cfb5134ea198ca3ab5 Mon Sep 17 00:00:00 2001 From: Jacob Heun Date: Mon, 16 Sep 2019 16:00:19 +0200 Subject: [PATCH 83/86] chore: release version v0.6.1 --- CHANGELOG.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f647929..0d3c8ee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,13 @@ + +## [0.6.1](https://github.com/libp2p/interface-transport/compare/v0.6.0...v0.6.1) (2019-09-16) + + +### Bug Fixes + +* **test:** close with timeout ([#54](https://github.com/libp2p/interface-transport/issues/54)) ([583f02d](https://github.com/libp2p/interface-transport/commit/583f02d)) + + + # [0.6.0](https://github.com/libp2p/interface-transport/compare/v0.5.2...v0.6.0) (2019-09-06) From 993ca1cb8520f6c2ca8fa95ba7e6c9458fbfdfe5 Mon Sep 17 00:00:00 2001 From: Jacob Heun Date: Thu, 19 Sep 2019 13:34:25 +0200 Subject: [PATCH 84/86] feat: timeline and close checking (#55) * feat: add test to validate timeline presence * feat: add test and docs for close and timeline * feat: add filter test * docs: dont reference go, it's not relevant --- README.md | 29 +++++++++++++++++++++-------- src/dial-test.js | 28 +++++++++++++++++++--------- src/filter-test.js | 37 +++++++++++++++++++++++++++++++++++++ src/index.js | 2 ++ src/listen-test.js | 26 ++++++++++++++++++-------- src/utils/index.js | 16 ++++++++++++++++ 6 files changed, 113 insertions(+), 25 deletions(-) create mode 100644 src/filter-test.js create mode 100644 src/utils/index.js diff --git a/README.md b/README.md index 2c44362..5c7fe49 100644 --- a/README.md +++ b/README.md @@ -17,8 +17,6 @@ Publishing a test suite as a module lets multiple modules all ensure compatibili The purpose of this interface is not to reinvent any wheels when it comes to dialing and listening to transports. Instead, it tries to provide a uniform API for several transports through a shimmed interface. -The API is presented with both Node.js and Go primitives, however there are no actual limitations for it to be extended for any other language, pushing forward the cross compatibility and interop through diferent stacks. - ## Lead Maintainer [Jacob Heun](https://github.com/jacobheun/) @@ -93,6 +91,7 @@ A valid transport (one that follows the interface defined) must implement the fo - type: `Transport` - `new Transport({ upgrader, ...[options] })` - ` transport.dial(multiaddr, [options])` + - ` transport.filter(multiaddrs)` - `transport.createListener([options], handlerFunction)` - type: `transport.Listener` - event: 'listening' @@ -122,12 +121,17 @@ The `Upgrader` methods take a [MultiaddrConnection](#multiaddrconnection) and wi - `MultiaddrConnection` - `sink`: A [streaming iterable sink](https://gist.github.com/alanshaw/591dc7dd54e4f99338a347ef568d6ee9#sink-it) - `source`: A [streaming iterable source](https://gist.github.com/alanshaw/591dc7dd54e4f99338a347ef568d6ee9#source-it) + - `close`: A method for closing the connection - `conn`: The raw connection of the transport, such as a TCP socket. - `remoteAddr`: The remote `Multiaddr` of the connection. + - `[localAddr]`: An optional local `Multiaddr` of the connection. + - `timeline`: A hash map of connection time events + - `open`: The time in ticks the connection was opened + - `close`: The time in ticks the connection was closed ### Creating a transport instance -- `JavaScript` - `const transport = new Transport({ upgrader, ...[options] })` +- `const transport = new Transport({ upgrader, ...[options] })` Creates a new Transport instance. `options` is an JavaScript object that should include the necessary parameters for the transport instance. Options **MUST** include an `Upgrader` instance, as Transports will use this to return `interface-connection` instances from `transport.dial` and the listener `handlerFunction`. @@ -135,7 +139,7 @@ Creates a new Transport instance. `options` is an JavaScript object that should ### Dial to another peer -- `JavaScript` - `const connection = await transport.dial(multiaddr, [options])` +- `const connection = await transport.dial(multiaddr, [options])` This method uses a transport to dial a Peer listening on `multiaddr`. @@ -172,9 +176,18 @@ try { // ---- ``` +### Filtering Addresses + +- `const supportedAddrs = await transport.filter(multiaddrs)` + +When using a transport its important to be able to filter out `multiaddr`s that the transport doesn't support. A transport instance provides a filter method to return only the valid addresses it supports. + +`multiaddrs` must be an array of type [`multiaddr`](https://www.npmjs.com/multiaddr). +Filter returns an array of `multiaddr`. + ### Create a listener -- `JavaScript` - `const listener = transport.createListener([options], handlerFunction)` +- `const listener = transport.createListener([options], handlerFunction)` This method creates a listener on the transport. Implementations **MUST** call `upgrader.upgradeInbound(multiaddrConnection)` and pass its results to the `handlerFunction` and any emitted `connection` events. @@ -191,7 +204,7 @@ The listener object created may emit the following events: ### Start a listener -- `JavaScript` - `await listener.listen(multiaddr)` +- `await listener.listen(multiaddr)` This method puts the listener in `listening` mode, waiting for incoming connections. @@ -199,13 +212,13 @@ This method puts the listener in `listening` mode, waiting for incoming connecti ### Get listener addrs -- `JavaScript` - `listener.getAddrs()` +- `listener.getAddrs()` This method returns the addresses on which this listener is listening. Useful when listening on port 0 or any interface (0.0.0.0). ### Stop a listener -- `JavaScript` - `await listener.close([options])` +- `await listener.close([options])` This method closes the listener so that no more connections can be opened on this transport instance. diff --git a/src/dial-test.js b/src/dial-test.js index 80d29b6..d6f919c 100644 --- a/src/dial-test.js +++ b/src/dial-test.js @@ -6,6 +6,7 @@ const dirtyChai = require('dirty-chai') const expect = chai.expect chai.use(dirtyChai) +const { isValidTick } = require('./utils') const goodbye = require('it-goodbye') const { collect } = require('streaming-iterables') const pipe = require('it-pipe') @@ -15,19 +16,18 @@ const sinon = require('sinon') module.exports = (common) => { const upgrader = { - upgradeOutbound (multiaddrConnection) { - ['sink', 'source', 'remoteAddr', 'conn'].forEach(prop => { + _upgrade (multiaddrConnection) { + ['sink', 'source', 'remoteAddr', 'conn', 'timeline', 'close'].forEach(prop => { expect(multiaddrConnection).to.have.property(prop) }) - - return { sink: multiaddrConnection.sink, source: multiaddrConnection.source } + expect(isValidTick(multiaddrConnection.timeline.open)).to.equal(true) + return multiaddrConnection + }, + upgradeOutbound (multiaddrConnection) { + return upgrader._upgrade(multiaddrConnection) }, upgradeInbound (multiaddrConnection) { - ['sink', 'source', 'remoteAddr', 'conn'].forEach(prop => { - expect(multiaddrConnection).to.have.property(prop) - }) - - return { sink: multiaddrConnection.sink, source: multiaddrConnection.source } + return upgrader._upgrade(multiaddrConnection) } } @@ -67,6 +67,16 @@ module.exports = (common) => { expect(result[0].toString()).to.equal('hey') }) + it('can close connections', async () => { + const upgradeSpy = sinon.spy(upgrader, 'upgradeOutbound') + const conn = await transport.dial(addrs[0]) + + expect(upgradeSpy.callCount).to.equal(1) + expect(upgradeSpy.returned(conn)).to.equal(true) + await conn.close() + expect(isValidTick(conn.timeline.close)).to.equal(true) + }) + it('to non existent listener', async () => { const upgradeSpy = sinon.spy(upgrader, 'upgradeOutbound') try { diff --git a/src/filter-test.js b/src/filter-test.js new file mode 100644 index 0000000..09c53a3 --- /dev/null +++ b/src/filter-test.js @@ -0,0 +1,37 @@ +/* eslint-env mocha */ +'use strict' + +const chai = require('chai') +const dirtyChai = require('dirty-chai') +const expect = chai.expect +chai.use(dirtyChai) + +module.exports = (common) => { + const upgrader = { + _upgrade (multiaddrConnection) { + return multiaddrConnection + }, + upgradeOutbound (multiaddrConnection) { + return upgrader._upgrade(multiaddrConnection) + }, + upgradeInbound (multiaddrConnection) { + return upgrader._upgrade(multiaddrConnection) + } + } + + describe('filter', () => { + let addrs + let transport + + before(async () => { + ({ addrs, transport } = await common.setup({ upgrader })) + }) + + after(() => common.teardown && common.teardown()) + + it('filters addresses', () => { + const filteredAddrs = transport.filter(addrs) + expect(filteredAddrs).to.eql(addrs) + }) + }) +} diff --git a/src/index.js b/src/index.js index 73f1373..ba9ee98 100644 --- a/src/index.js +++ b/src/index.js @@ -3,11 +3,13 @@ const dial = require('./dial-test') const listen = require('./listen-test') +const filter = require('./filter-test') module.exports = (common) => { describe('interface-transport', () => { dial(common) listen(common) + filter(common) }) } diff --git a/src/listen-test.js b/src/listen-test.js index 9754cbd..a455195 100644 --- a/src/listen-test.js +++ b/src/listen-test.js @@ -9,22 +9,23 @@ chai.use(dirtyChai) const sinon = require('sinon') const pipe = require('it-pipe') +const { isValidTick } = require('./utils') module.exports = (common) => { const upgrader = { - upgradeOutbound (multiaddrConnection) { - ['sink', 'source', 'remoteAddr', 'conn'].forEach(prop => { + _upgrade (multiaddrConnection) { + ['sink', 'source', 'remoteAddr', 'conn', 'timeline', 'close'].forEach(prop => { expect(multiaddrConnection).to.have.property(prop) }) + expect(isValidTick(multiaddrConnection.timeline.open)).to.equal(true) - return { sink: multiaddrConnection.sink, source: multiaddrConnection.source } + return multiaddrConnection + }, + upgradeOutbound (multiaddrConnection) { + return upgrader._upgrade(multiaddrConnection) }, upgradeInbound (multiaddrConnection) { - ['sink', 'source', 'remoteAddr', 'conn'].forEach(prop => { - expect(multiaddrConnection).to.have.property(prop) - }) - - return { sink: multiaddrConnection.sink, source: multiaddrConnection.source } + return upgrader._upgrade(multiaddrConnection) } } @@ -50,8 +51,10 @@ module.exports = (common) => { it('close listener with connections, through timeout', async () => { const upgradeSpy = sinon.spy(upgrader, 'upgradeInbound') + const listenerConns = [] const listener = transport.createListener((conn) => { + listenerConns.push(conn) expect(upgradeSpy.returned(conn)).to.equal(true) pipe(conn, conn) }) @@ -78,6 +81,13 @@ module.exports = (common) => { listener.close() ]) + await socket1.close() + + expect(isValidTick(socket1.timeline.close)).to.equal(true) + listenerConns.forEach(conn => { + expect(isValidTick(conn.timeline.close)).to.equal(true) + }) + // 2 dials = 2 connections upgraded expect(upgradeSpy.callCount).to.equal(2) }) diff --git a/src/utils/index.js b/src/utils/index.js new file mode 100644 index 0000000..b0f3bf1 --- /dev/null +++ b/src/utils/index.js @@ -0,0 +1,16 @@ +'use strict' + +module.exports = { + /** + * A tick is considered valid if it happened between now + * and `ms` milliseconds ago + * @param {number} date Time in ticks + * @param {number} ms max milliseconds that should have expired + * @returns {boolean} + */ + isValidTick: function isValidTick (date, ms = 5000) { + const now = Date.now() + if (date > now - ms && date <= now) return true + return false + } +} From c783da898609eed0002d68418a665dc641977964 Mon Sep 17 00:00:00 2001 From: Jacob Heun Date: Thu, 19 Sep 2019 13:36:33 +0200 Subject: [PATCH 85/86] chore: update contributors --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e556028..fc4779c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "interface-transport", - "version": "0.6.1", + "version": "0.7.0", "description": "A test suite and interface you can use to implement a transport.", "leadMaintainer": "Jacob Heun ", "repository": { From c5bcbcf1bb10865404f4f4d27e9ed45c48ec6055 Mon Sep 17 00:00:00 2001 From: Jacob Heun Date: Thu, 19 Sep 2019 13:36:34 +0200 Subject: [PATCH 86/86] chore: release version v0.7.0 --- CHANGELOG.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d3c8ee..ec01b39 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,13 @@ + +# [0.7.0](https://github.com/libp2p/interface-transport/compare/v0.6.1...v0.7.0) (2019-09-19) + + +### Features + +* timeline and close checking ([#55](https://github.com/libp2p/interface-transport/issues/55)) ([993ca1c](https://github.com/libp2p/interface-transport/commit/993ca1c)) + + + ## [0.6.1](https://github.com/libp2p/interface-transport/compare/v0.6.0...v0.6.1) (2019-09-16)