From a97063f9b731cf73ffddc4c81826935f6fd1b978 Mon Sep 17 00:00:00 2001 From: "Kartikey S. Chauhan" Date: Sat, 2 Sep 2023 23:18:25 +0530 Subject: [PATCH 01/15] Initial website setup - Created project structure with necessary directories and files - Set up Next.js with Tailwind CSS and Font Awesome - Added base HTML structure and layout components - Configured routing and created the homepage - Styled the homepage with basic styling - Added FontAwesome icons - Configured font imports and styles - Integrated HackClub branding elements This commit establishes the foundation for our website, including the project structure, styling, and initial content. --- site/.eslintrc.json | 3 + site/.gitignore | 5 ++ site/.prettierignore | 6 ++ site/assets/Bold.woff2 | Bin 0 -> 25000 bytes site/assets/Italic.woff2 | Bin 0 -> 21744 bytes site/assets/Regular.woff2 | Bin 0 -> 22408 bytes site/layout/layout.tsx | 47 ++++++++++++ site/next.config.js | 6 ++ site/package.json | 36 +++++++++ site/pages/_app.tsx | 14 ++++ site/pages/_document.tsx | 13 ++++ site/pages/index.tsx | 154 ++++++++++++++++++++++++++++++++++++++ site/postcss.config.js | 6 ++ site/prettier.config.js | 3 + site/public/hackclub.svg | 66 ++++++++++++++++ site/static/globals.css | 3 + site/tailwind.config.ts | 28 +++++++ site/tsconfig.json | 27 +++++++ 18 files changed, 417 insertions(+) create mode 100644 site/.eslintrc.json create mode 100644 site/.gitignore create mode 100644 site/.prettierignore create mode 100644 site/assets/Bold.woff2 create mode 100644 site/assets/Italic.woff2 create mode 100644 site/assets/Regular.woff2 create mode 100644 site/layout/layout.tsx create mode 100644 site/next.config.js create mode 100644 site/package.json create mode 100644 site/pages/_app.tsx create mode 100644 site/pages/_document.tsx create mode 100644 site/pages/index.tsx create mode 100644 site/postcss.config.js create mode 100644 site/prettier.config.js create mode 100644 site/public/hackclub.svg create mode 100644 site/static/globals.css create mode 100644 site/tailwind.config.ts create mode 100644 site/tsconfig.json diff --git a/site/.eslintrc.json b/site/.eslintrc.json new file mode 100644 index 0000000..bffb357 --- /dev/null +++ b/site/.eslintrc.json @@ -0,0 +1,3 @@ +{ + "extends": "next/core-web-vitals" +} diff --git a/site/.gitignore b/site/.gitignore new file mode 100644 index 0000000..71b863e --- /dev/null +++ b/site/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/out/ + +/.next/ +next-env.d.ts diff --git a/site/.prettierignore b/site/.prettierignore new file mode 100644 index 0000000..fcac576 --- /dev/null +++ b/site/.prettierignore @@ -0,0 +1,6 @@ +# Ignore artifacts: +build +coverage + +# Ignore all HTML files: +**/*.html \ No newline at end of file diff --git a/site/assets/Bold.woff2 b/site/assets/Bold.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..8c00084a985116cb2ef26d1b79acd67af692ba53 GIT binary patch literal 25000 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 z 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 VarXY^{9pFMN%PJW>}6qK001pXiIM;S literal 0 HcmV?d00001 diff --git a/site/assets/Italic.woff2 b/site/assets/Italic.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..056909c32a24bdfed5c6149656be0e42cc6b47ec GIT binary patch literal 21744 zcmXT-cQayOWME)m2>HMu2%^v3U|?qlI@XJKGbVC^hopO(SX$SeJNoB52{R!WQN?s6Ut$=d(*dDw>B znP1+8RZeU!x|Fi(toyq#CFdCN)c^ngS9+%M;>>AHLEhRo+L`)2 zR0B00^u(<(aXcrm^67*%O$o1-M{k|pVze>R*zi`t-pcYdzW(1I@|d-+6_$FoVO~hx z#mmJH*?;}b?r!lBaM8Huw(-gJGS*ghZ~x?-KbO?L`<8#OS@og!6Fm*33vPew^6m?r z)czA}qdGa@f7pZI$+q45bZ6!qt2^erHSX$X+ZQveSN&Nq>Gz!+!+^ZghAB^*fRs;lB`2_PM=9klU+7m5oye;+$R;GeaJ!mw2=OSf*Jq!huk=Dwr=t+sU{~y8>U2$4;DSjE5le=t|@$B zJ@9HyiMU+D-32$Rmdw9D-~Rj2UrtiScUCU@^6AgS6)c*(HuBFO@?AP{_VgZw%7l{a z?vP&j(p#s`S~esl`!L;B)Vg4Ogwu}a@SROFSMeR1>FUq1@#VGXO+1n@*^1%&>cZI1 zbk~RJhj-Tabhe!2n0KM~;{9798c*35AN%}e`ucaCR&@v5XC7@Z`*ODJ zoy~$NZTU_!GgdERfZTajjcUvlV zG?T1IZwOqMZS5b zZet{W9h%?6NL~B2F2TGF+6~ zOYZDh`}4jxOHyWotaRIpKab~q`%+q)U%<6A>*zLtOB-dk`^a4We)sPF$i{aYXBA)V z4_{Jx(9&T-_5DoSrDctqcC)J*oRC?Vd$H$?K-Td@&@$Q&X##e63!+-e9u@p|fzcc4m z@@9>+8{``xEQ#*IOtr zNEC165?Iij%>O&W=f$JPX39x6hbM{2f3kL~W3#fA;5C{Nx^&?ix7Cl0u4)B_N@-7? z@Q34@;2h~kYU@m99FzHK+4k7a;^mafcW1`=+@BwDL-sj~sA|q1ZEsIb&j=PpEj7K1 z*B6HxXRWkRShh%WoAR2g_qvbqbp#7FszlDevQ_QurK%@iU!9gRVoUBdzQ+=}>U6uH z{qLje>pt(cfAuuA(!QZLc*nl*j~}cL?%)c3E$A1!Y|q}Hq99e@T|6^4rJO$bVAq+f z8?{|#%XGQ_3Ma0WyOE+Q8aVl$wR5VESaBa~*f*gP1q($E-v}W+r;u*DMUG29o_Zv- z|E5rx_QokGOGP=|JYAzM`L_8)ZhAW94)0Qv%xTw@*N4Y*ndW}!*cy?(m)Go8%BDM; zoc;!6@QAgm3v`#v^;vl71&>dBzW#Cf{y%sB7PIjmtmvGr6xzG3M7&^!OZjg$_SB*~ulW3x zCWtRAdd}Q&=*X6L%TpFyv!5Zrz_7v6;I@n&zd)BzJc=z^k>>SMKVD2$J8Sp*QtjUS zhI=oN`@xZsuivh;$*p61yQj-GZ^ciZYr9<8 z-rikoX2v8Ewd2-@!)F>zc6?7+qF-j7m+QIx&>=3}4dQ;Yr>)H2C-fz3{xVy$&04c> z9P*Y^+ab8()Ao3=M~l~OQufl>9Ck)AMeUO1K(dkfQ4|}V@0-?zps+Xp`5Mh}7 z-%-p|_glZn$0d{e*k)co*vFK+cTEoa<94>D`=Y`(7*?oU@F*`ddYEL9#=PKy!_r!* z$*To|mPjn?^H8!rbuTN#^worwX?yH0E0uKX9^P>HW;7EJbP zd%W)O`TNp&k9WP+>lBZhSn=3)BL6wdYXa!hm-VKsCR5H4&G)e}l^moPMB z;StWiYR~&Cvp~-*N?UZEZAME=Q(Ir*m+pgd9e4E%7kFE$)E}!|aN75_EAz?7q=Nm8 z0lzpM6s%u(FUjXHm?@I9#=I}^*$&x0rND#!KbnI$r?j!WWaoUY@YQICN&aiE9&fI% z!ih@)Uv$4!TDPZ&S9ME}Fqb5+q(w{D#O6!qcdUNV*=Yi(|oYZ`puTpdDU;Wp0As6w2JeNf^1fe%bIh$pKRjR+x2Kw_PYNYqa*fxQGNcZ zc*14h_tFZd=Y44R*Jsh3KU<7-=T-lMn?CTzZ@j7cF4ZJ;12g~YeAW`NKgApRwVkJX zuepDoA)K-3YY5A;0}bb9n>Y*lmiLt^=uW6+sej!k*nfz%=ZT17+{ATllBv_1TbFLV zTGqd)b!%H?$r-C_^6h=e(^oO=Tj6r~VTsLL|Ld0@zkK^;>8klzE3zg>Sug|Z)`}yya_%qM74JLJYFJU>lOEPb!ZJ*dgk=^dM z4&9o$Q+v&BL&o``hcfGTna=%Zc%LUX;NQl-yAqC++`ND0q+4X^oT{qoRlj-v)&H1( z^~<*}^S`|RzyCU;{9dLb5BvQ;%@gpSH1EWltLr`%JYIBc$FahDq4&1lt7MSBv-j&g z&U=aXYVy~Z|9W_d{rjza$K&0N0tW;S{5&xAz*@5%?-IrL^4=Erx7+sHZlzn7oMM$W9f;v}U8+4p{LzZAte28AXGD4l zFwN|nbEASs%1~hiul3xgt+H3#)E(6#N}~R12Tl@N^kaEkxghVQr5;l?ec6`Aq&s-nEd z^i;L--r%>Zd+lyN;cj#>-M8}~e{{|EtNOS9ZakYGT(@hY|A&wF{r<~zGJals@Q%g? z1FJLLT-=8eJ#``^?{j4fMb5tQs%6=a?DB>*ciH_J%KiSFE z1eb|34-{QI@8i_4``e*o=MJ7cs{7&iVMAU)EuS466BoI33u^oAN%np)OI;r-%V+U!%5L47^UlX^*kvT0VK}SUvE`fDJSpdGCo|TD&YfGnqEpE`=+c%) z*MA$G{>oRhoA+8&`q!iKF~52|*C}qCnLf4Xc*e9qMa+8jx9HAv zSJNsbm+aNCmWxt4XX}JkYkEzc8x!=ib7`Zozb4zUJXz&WEAAx8|58`Dbz;rYSko-t z?Y)-UHtTYDRUQdjw(98O4Rur2U+pQ7eQI|id|!CNclnd)ySMD0Z*%_n9m^KhOX2fQ zJ*w9asqjrrTBFKasd%^HzymEVi@o|ht%CW?$O= zV7Ys1)appz>sPO{yXIMFo!ikI)Ai8j_?I1mztmZ{mwYdDZj{g~iSlXIzpT1ThwIjF z-lOiJCqieKFRylGf8m&0$R;IMDOI}VRnJzho#9smw8b{(%;Rfau;w;L`)mu?qt z-e#Zb@vY+jwl_a_>{#S?+)id@#iFVV8|t>;;vtdTDI<=_5%^X89N9UVDkXHIQDxR(9fyNyR>e{L)*V3#>{RQ1{N zZ3QblyV=tsPEM&XN_;ZM)Tu|Xr?mWyx~k2V8K?Or?lQP8@ICGvaXS5;)W%;oR&9`o zd}{PhMIzMDQIVlWu$-?{Zq_YiQ=1#CtdZ}^v z-OTg9?!LTRa@)O*<7ev8~6>w$$%JkX#*0-hGy`tT}wO*QYORJNnHWTKZR|I&H(GN4f{)}H_A zvF=&^f?rvseja##PU7L$jsI6(TODl7cKKDn-N-GclrJq>sI}N3vr_9)ftvF69zT)8 zJ}(T;c{%zn;xw6a^pTmpPawl9mBl;KTIAeArfp=3;^kP;pb;olwct*~m%{8HD<&IG z@=Vz3aBgy75=$<3o}qxCrXgoY=Cyaf?`nQo)gtK7ov^d%cT$)z-`#?JUrePoK0D+; zBcJW^-=o*~G;f=iT`Jz?JZrO1=ytt|h>a(Ti>226FZ^2ib?q;Xi409W^K0GeQ~a(h zzGx+XtnA>E`d^oXi>K^d=Dp+Nwz{xSx1zag<31g`yoKp{R`RZO;q9|b`H#M6b-J!w zSi~E*e*t6VHH)@AI-A2c6z#e+_fK3;tQb$j=iuLN*Lq6(gjT;=7_PiS&p;qve#WXJ zUMD0EX7}9RVeP)%ra+22Jn7o!zd@C^Kg7>=`sqfmm;FyN4oW@D{ zX@|GZn7#I#`_qI?Eva#Q<|ncn<7zRX3V-WWv#=zkUJ_4nXAGU zlr0u)?-CQ0)vCH8v8phsSpAk+g811$)fb#qZE*{}UtoWs{Drgb{E4Dhdn-L{S3a$D zmEX>H-(BMP-*1gswp?{$U#d>dRDN&s`Q+z6hxR?WKK+;Y7O`_vB<|^5>^YI)mS(cp zXx2qplVuZ*iCG;#D0KKoPomSYfT@Ndk8{>XWKR45nk5}zK^O?VlJhn2ZNWE9HfW*zB_|CsLMs zhKg)H;>7;<2#1G<#rJI6@Lz`099}oSK9N4v#k?%!)Hk({uhyNIt{M>fe&+2o!;nw) z=BqC}i`BWQyfMK{zs+btDTn&+M#<0DjB3^zbiS(Fv-R`lfTi2?u1O}AX>M9ko$U4d z>cQR3CF0S-aqEpH{`KJaVDGY|`NWJ_+=;gp7Dk+j)Iayc<%~!Al;({>D$aTg_JJuv zF+9RC9tG!~l`K1CDWI~X%5TCHmgCk-=M-HiTXtv~E7!GamYT(rRGMlEbgEDN@!WrO za>3;1E9btRrL<(KO_Z|IQll5~T0)gfd(`IgdTP%&EXQt`#dmyWxc1u2w_7UsJ$bKj z*4I8XTcvRA*l~ey$-{Cfv?#NX;GBZj=L8N(N5wlkUzqmNZ zERpMM^Hxf%^A!1I7Ww6vdh~Lp-5O_k_Z0!+UfE?X@Up58oT zM&k*Y8Pgx<%#ix>JaO*!=2|s#m)j4nem>D4e)MJhZ!_M78p_LVeO<uN+?3G~s}k+p9PGZXH(o zbF|0H&tGq^)l)9xRqtaAWy48X3dV|$k!4RzsoS z(>Y$B+s1vrW!;Plo0%J!_o&L$?d!EpUU8)+^pfLOi{J?H0R9~ZkBjwLJuBKEFM4q1 z1AFd$H%>}$=maW+Nj|Xo5ZZdFAuYk0b4h;ij;DHWkF{FQ^faH^{XXaWn|n?dkA+U# zD!$WqYcuM(E$JmaBy?SpURS>uIp&HMfvojnlqm-LjQ=i?}yM5}_LDoJ`=W8K_ zMf1@7^i?XA($MjSg&o5ld!X7<4xQfGMLg}K_dAmx(s_dh@*4%kfY9J_i?XOm4 z>azpSkD9TzSloN@DX;655`)iytK#WWcQ5BHc%AxKsAX4K_Jz>W((_qm*M6;4x9SUP znYzEyye_A%<<`Hod66@PPZgAD3P!fwez`pN%p_DRel(jmFmjAj6XE_ zM7BbSN$@CCO9Ga3; zb;0mniLzSHlow2wyqS+}R5w{_p}LHFi}tz7v-fuxDZOgDS20mKYMtzojAu8xJfr%a z&%Qi^;|}NR2P_wtGQLsVS|QRFA?qcwFs}BWoL7d`x49yZ?j1a?SbywS+P5cH+kWhN zdN+;p<^5*Qt6$|616=vWfry zhXYT9C%sPV_!xWc{dM+Kf%!`Bmw%IN+T=WE?!xk!4|A`HzI^MrTKV)z?ftyZi-ow} za(;-YopbO0$-h{y+ik& zMV4uj`=ez~9lB&O@yCn0ggaZz57_;1@Zr<{BV{IkE61oK|KXP34NsPr*!B7=7Y7Bu z+cUv)`J=PTzm*9$U48pm@a@6bw;s&-z2oxq(5C5=e^{Liy*b+-nIMXRU2nzh8YUFQ#{TV%N;qe z&`tPSjnb;6{~T>M?0&sx%TG-fwR@9|rb=AR@-r+h7R_;c~b&^ajfa-ykmLuaQ=t76*)&`uRCQdF@C(#=k&{hZ!6ykm>s+pEGMSB zz{6nul4eeWi~MmDHl3Z@v!y`L%I(b}?s*JXMK3bCUz}53Qto?Ly4Fbl2;-5Yeaq(- z%=8sY_t5;sf5ElQy}I$%G>HRKS9&N}XPtX__W8VnXT^nsZeNJ5U&7bJUh+NHx;^A- zxv7kfi*Bf?UCt?v+%Jzgwm3EDsjau@ZGEB0dQ?D5t97Q91W)mUhKQ86Y(bYK7w_Kq zk99$Y(CZ-)!$?6ziNV{;S81Q^Yl-jzb;}pfA;6T$`w}@ zT?;!pvqC55IJeAL^& z?TP>PZph*^?qRICUY0jc$0b6$c)6^a?IknseSzOz1;2dT`Q`eDC&B%Bsar36%6fXN zS#5_ypYjw{&#?B8^L=;4w|TA?vGGZoxx`c^qdD9BlJ}aUmi;DYi_89nl>W8~-QaeC zcW+Y$=kc0unK?1;;*U2vnfnz7KHEBLvERL<;8U{$O&K^>9Sf9wAIq4LvTD~ogND?P zQMqDIo>U!Fk9cBoSoQW@vA~NFNmIYAJ@Z#5c4x}-z7N5A_nb90-g~Rv<6?cXB3MwP zW5KI{X^KK;-m{pjyydY>gIQ9TJLA8g_r9lGiiY1 zn|npdIN+?Sa)#F$sW+QfUF(>4S7hzytShC4zwA!FW}g@__jC!f&QGFO_4d73HFx%NUiIgXVsqd2##*W9NannV*|bG_qfuG{k9b~Z-^oL9 zk;{dztUvwy{4VYgosDiY4}|Ppx6gOyQQgk-ZWlj>F%);ryL`c*_eW~Fz}Iw>O3N*s z^QJJ*xN{=#+s`cloX=urarK?J`_3^_Wr6IccHPN0zwv(Cuyb;2bm_@oPh_esL|S*1 z21J-VcqK2LmwES6?6pq|I}KwLctSm$MGL!EJhQCx2zUGdKLV zLFM|*%MC70KGS^q9?QBbMs3{v-@lisro4G@T-K;$@uSz_A^S5sNhc+zod`ThF2+y7q;{(JrTD$_1~^VFst zXFH@`+g0fV@zg#kaVT4Cxmfy2N{yqY{9f~;+qd#R?_mAII*~OlUYV=)j#>0EixuY% z)NzW($UKy~{Pv_l<+`^4?XN@`els43dl|r4%v!J}?edi;A08Cchqs=2^savwyY4k3 zk-|#hm)E)kzb`1LR$Q|DRWWbt#BWUn(Tt{N3jgL9Pm!4QnoGItmHw59)0$+oQ`qama zgwrxFoO?YbNU(m3?yVJkHxd^sS?6B*zLsHGg3cSO!zZJDr!HG3$5|M>X#3x|yxnJC zxhr0pFx_pwdC}6;2l1iI-+#}%75S3=&AnNB_sA^r<_5&h|8X~QOPxY(2$nup!)OL?PUG<9Ih+>fAJsvViGB|d{Oe%$6q3p z9;IHHx9rgOn1>$e*TVYOFI|*9xA2hUcH@xRhqwMM6>LcltbG6Kqu(BLJ^_u-SH*SC zEt${tT}rO^wQg*E-cPOLd1_Ld=Bsa&l5VTW$eQ(UZ=MM(+LN^KhRLJPOZ+ZfzBY}6X~ql5x9ne@c+?+T;;}ek^_4j?FTJBhC68Rz z3jBQ`=G^->N9jvu`^^;syyu?%&8hV(c(af0j{l-!OA0-1Nj+HMldbY)e^%n}?~e{! zw;jDExcSA=>FG!P`cJ>TcH-0IZsAJ%h5PC>*L?AH&v-b0vX%2mRsYOq-_ujSDtI^_ z(a%u(5%}0`?XOU`qq^VIvyVU5mX+8FO zkH?*)T92|{uaDlsy?l24Z24C+{2F<4u9k#c+p*WMsBTwe&W+5Bi~hoMsur$(e6W1? z{2V8zaER#d+}8qt~`X^`fu5`WJ3v@V;=4pxq}Dmz!#_=zW7t}K-o zKJ(%dNAgC?oZru?ZtmYCza}U#`BF5aeuU%CLq+9=-#0Ffv_Ja7E~0E=AW5!^@L9+V|>sX2hQ$Qu0CJ4cI1i)N`8zG~7qP><@9iD!2%ZF=6CEoE?2Nq^qdnYEkt*V(9VIym9*?W>Q< zcRX}^rL$Dq^~&*oXWgHO9u1N|#>f*NzBA+ECAEB;H-!x^*_oEk>xn+D&L`-^%zfL7 zd)uEA1wT1Cj>tONHH2|`Tz#68IsecbCx-ez&Hsguy(~?gbU^D{$3mt(k0*IQ4t|sM zbk1bUso{#hSm$@075gDy5V794^08m+jqSgD)z8ZE$nDEov}sCQT++?W41bdJed{KB z{P?qb4Z}x0j(zh=kGQ>f&ELaPl^S|>yZ)3%slT}z7I>~{mbEJ7^q8KvZrxKOZQ-d; z-S_QwQF~u%E$-kM@kaLhmfd^W3xYm5<<7~onfzd_r*rkDER&Zi)rFGJukG_*r8(u^ zwqGs$9m^&fq)s|f=O^90-6usYUVc-?wLsg~Ed9SM&M=*yDZT1@$+xhhhqi5v_-Z^S z@vYUI^W9?SB!B;z?`s@z$rUBC{&_wMF*&$6%muG%*% zsQP&D?~I_f4d1R8GVffWc~^LwR&m^x(9C1eQHN50dMr3(lMvYWDE{&PyH4-#C@q+N zNn2x?^vrz8!tN(tf-61tsyQXz{dqKQ)|bq>vwVTpy;Cw?`oCC`dE@q~P3=*}4>!e? zC)h1LVUrlVVA65<%1tgbLW|I zM@*mb-}sTI-sUaSiZ|Ezd2tn%*)`WT#0UNLF`u)`OJw4_OD)Sor zE51G7y12g>9pEeD)5@~lZ$2$tEA)X$)}A6`p<}WqEwWx_mUr;>U6tX|TN;(H&yon_n3vu^JG1sy--{J(gL>{WeOXx3EvYtf{ijpp-% zeY7RM_4drqwp=W8tlqTCGssYAv%2hc6;+!T+YY?U@>eb28usmXq-T@nnj*FLX>#5N zp4gmO6@No0^>6-y1DliXS1=e#xWsn9?lz5=~;(j6ZhN4 zN1W0>-&}sV-SkP`M8T`t#(BAkhi2>!+xubj6{*nsXG>-soBeuv;rll6sn1?~`P8{^ zK9}QX-mEjLw=K;%Zv6Uq*R~v`pH;>F&B20N8ijjvgS42I&dy%-@9FPibB!nZA6M1Q z`ptV@@<>zEyQx!TQVR86>ThcK{NK2_K&_qkK~~<=$uY-ISe>el-Tq_cb;g{xM}BT) z__piCkzVW43G2FkHe5fy>|4n6^m~~Hg5x9?y3PyPnPQrrA;|l*He%k9rgQ4tF0vjU z-mAV*cz8xm+G}ZZfJJ`or+EcA_iR&hH=j1|5np=f>BRNx=3Xhz{;^}xh4AC0yi3Jm zzZ`X#+aM6S<*rBn_0H-H?~Ju3D#Zt;ua(u1>3Yj}XrY>x{LC^|XTFC!{p$quHG1;) zZ~2^G{LucXsoO{S@N*|s`1k)ikk1?A(edr+p?4g26z)4JD0 z{$W3{^1)8mRj2;!c#&>uey*6otoX_@p_0^l4+RXC2zF~do{Igqa z{adTGeoeTe98%%(N9&S<9>@7-FF)FdstN?Or?;0IFzr}Se&+#$c+o-hl1?j4czYCjgLYCJvYADrI#VS|JBp|tJim5 zdsF%OQNFm?YQ31JC%%|#>en1iExPFQ!**49(SxY>Cr$@!J-~c5q1S;DRxeY_#H#4R03d|2Ony(hND?cu~8T;gYeAsUbHNHg~G;alL z@y+w+;}tTi)Qj;H5zOr{SL1kffQ4^sU*EQdjTipQs;{;`z25g;{)^2kK0o?YbG+Rz z>cWBQ^_z0u$@Uq2Z{Ru}QmOv%&aK(^-}UVJefj3ZUu%EfJM3j~!O%JQ(vCCfLW1X2 zE7b1K(3^N+WqDNVMfS&kM34VG{5s24$=gl8dFq;owfP!tM{nO=Tou@Tdv@6E4)h(@HoR zxa_|8|7)3Q;q%i9p9@qvEPU*0pE-3$hwQq4HIK#a9O=&QWrKGonAr)A=*N%qI?shK&P zd;M#l>glq{_WyNnTot?OsU&~QWmiJDS=I!X_#W<*dBgGl_pXz){?;BhmKl34xaiby z{@zwF=a1nxAI@98E|o6}y;3$!H{Md)bj^0_>;+YU3X|IEkJv9bw76K{V5XMDGNlAg zspfZ2`=`ucKB|1EODMxM`^BGghpo@|=O6vQ)u5zr|EoWJhpqQS8H$ANuP}Y|Klg2v z-iMW}w_n=zos<9EEYcvNucbNZ%c`EVt$sazxx140EZz5;kx|svg>`Y2&g|klJP#B^ z&;L66$46Equ-4NxVbyoV2{YR7sQzb6_+iDlg5!gEU*v&$w&07LZqd0`;@uZkmjz$c z|8}7yfB&)AJ(IfDG5;4!-|q6k>xrD!r{kWxzA=Z_NdN!!k$u$?o03;qzph^Nwy@d! ze9OOE>wd3Z67&9{c4~ZYNkL`D^76)nKNpPFy%SuVaC9M)`*)*%>gsz-uAUS7+xu{x z+-lP=H@`LpMDy%8vU)>X%izm;rMwZ0;{G$i2B(JM(J z=F+vkJAB`lyVOp7vv}2Qae1W+xs?p3H}}{5eamX=8y>!E!*byRH@30A`)+)-bJ@2E zuJfGtzSVM1Qt=cC?w&02Sr}oIRTFkdH(+^ZMkpmwZo@xKKQf2NBrdX<35+3 z?Eje*yl%smy$81}+g-5ZdT`&9Nh^3iT~S`Ed8E-q;*geBnek2GB0heWuq=ND<+Z8) zvf9xzpVa%lmpt=p&P=`dr<$^BCUGqYHk@a8dn%i5#p~O}Z{rp_J^Fsb=uhQo#r5IK zJFEiumxSDYp6M*O{@#!6)>AoVU46gMwqzz>>o@CNmyWybY`MBtn%DIC+EUph*;^e) z7wfiM+8d_3?xPm3!Di2P(R|Zyd}zg~kiM{x?qBw%qXMbuDd^^harz zRiZp5XRLq6zGw9#lZKy%Zms9H==!qe_QTJuy-j7-au>8;{r$Hg@M(J>)*a0W_l_s8zgXzFCjT;@p>`tqQGVse z%1+~^gF$%`U%ppA;qOouS~pSuW5>?GqR(F{@2wSo$fkSjrOI!oFR6FU7qeZuooB-I za_U+6ochkpj^+u{WiOYOTgv}+*R-!X{%g_dwiBmb-L?oyO+6cOv3Zv8)HC(1LRF6# zT0aJ#?1(Oq=;ulJ9oep|_g#>|`p~StfZDJK_RWf_{_oE4tbMcJrf5TI#q)gs=})`E zzD!)l_dj!ejO2xkSN-xTQY-peUp=hdvT{SN`u{_c;dof#^$DvMnaD&Rj%9sd zoBqw)Lzkg!LE7EA&Y428c4gOYx^VqVIj)`?e=O(F&XX%`cxPWp+|F==d*xcg=jWMk zq;0t8mHduhc;!j$vU`Oyj^5H`ED(CqZFwYThVXA)#!eId`SCXQAM?l>cVB!@t`$NQ{ zIPTZ~_sq+=aE;x*@8}}~iADSCY(4J>{#o7dt8(Itu&J+}eKfH1UUB6;mn;8@5dO}V z_tWpFRfRX`3Oxxwe|q_n56;%}ndH^Zi^#F=$^NoSV}_U4x{1ehoR{aMcz3_w*f(A1 z68D!U2Li6FIr-y2*vf+)AKX^WPTp;ku)yoegllDn;U4*!uN@OuB}q}k$Y z_@8W^!Q6E6+q$LJMhwde{~CLjyIkCTR<-ho(aNOaCjYuEpLeG}oxki;`R}eX+pjFU zG|g=i-@eKF_PovJ-pzf%yHSi$HdpJ2`q^1$C+R%l*SGSE zUMI=DjqSvf2WtCH&QDuAW98LtAD9@(F|A7Np7~_nfo7Q*hb>c0BjmTpa_%zsUnQ9{ znc;F#&Gb2}#SB)ug5||&Crej^d=B^^x2sq7O+?b|;BT^QhG7QZ(~4aWg!S!U^m*R= ze3hc5Sj0S;oRaP4Yy57tF4ioYBX^^-@3y-1%#XhoDwe4Dw|LC;yZ*tyz%qH3@cZI} zX0IJ5gf7l{$saeL`(TC2inI-~JPB5uHQ6`IA6jPzZ{M*bB}VV+C3%}x;e?;uA&X5G z-^l#_mPaf;!25d9GxOUfEo%I8 z>+A2$pMT(+?vH0H?u*C;Y$}=PHYZ(d-F-Dl-i8vUeTN!;NQKzPpA%C(Ipfv(wDcRM zueqDeW#SVZYFHkKSFGE^zd_@v5c9k6zZZJcd}r)oRFGy>kY)V7f}LUIPsWcc>=K!F zrp{i%-cTa(ql=@1-!Zf)hsDEGZ_ZzZH6cn0`x+Ee&&vgE{dFyNb=cKYZ*NZtzBr*o z%=4td+A~*ICO_==%3eHwi>c%r?feJ#)@b~n-Fa!*sfxAy=X@t6uQJjXn`yIwWBw|q z>C^ArUH`j-#jI$@UGBe6_`Xm4cmBYHim32!2|kxU?|LAa;jgkns4r(ztUm&ZpKl^9EX4$)`LcmH=7ynx)^fm z_RRk;r-l1HH&qW5V4chRE91fRS*-{nyk3o|Q;f%Rm-h%bNpMUkOzw>hb-YVN)FOuIsn*DF@3HxUs6m#CW zF|f(A-FREe)9{FIw~Z*LWA*J{8Fdz)Rv+(p@4QxSOYU>V{MA3~uFbxnxbF4NO2?Hl z%r74lW(}M z7v-~B@AKMroXZ=7k4QSwfmzj4Zoo98a3Xm5FYpkhy> zK4-pP<_&{dxxHqZorhzC75&4vJziM$_Q9IqDvMVedgTsvUrzs((m5@zoTKWL2i@=6%N~}$nbg#M zHfQNu_ev|L=(?n9Z)0x0`pp?#E0|&6(4ADr>!ro6D+XHdokfPEuc9vihiSs;TS*=?yVbds4g(|CgPz z_ULM@Tf9Gv6ZBUfVa|0jd-L()SCd)0tW-CzxpC{(*3*w)eSNy2#&`OMj`QEY*6980 zFOGN|A}YC%|4zsG{j*ubC!5WGd;93oZM#p$&e~~vc17wnlg|A6sP{=FSdh+x}@sjPgmxU#*9Vp5Km1 z+sipUrF)VQo5G6&Y{xnFJD!OsSK4~@%$cJ?=NN69CI^2xFLvr}@98P=E(Q0F?V0@b zrk?&`OT)>>ln=S?dOxA6(Biy8+aFiItDIJA(wOwBebd4gi`{&n;Leh@(;;w9q3T@` zY5SM+Pd+@? zTK`;>);pU#>w?)8>0G-StNH#W^BiJ%`l0E0TB7XLpbNFel6RSZH+1={tV_svTXcSE zjSjg3( z#Nk}FtGP^4p7BLVZtq_%{r95uR1ZC!H2Fm88vnzKcwBcn-H$Xko>nVd>~~(O-Smm{ zZ`U(Afq#-dA6~ia&PxXVuL_>K6C~Ux|;>Ly`sLi z$CKa4mHoTcZuk7|V;$LzE%KeMXW5H)S%)$FnsasjE+wy@$9G$I%-vkInmZ)u-G(l% z{S{d!Rpvw}8#7zqeV*sdKjqK9!@*BFK5Fzi&0k{TD*4j*uyL>8q%RBN69l9k2HaI*(^6`E37G_H~EP($$mi zI?P;mbNyTc{ga93IZt;xOZr)L80^+nI-<>eLA&8=@zXbS#&yrSLfxQ&OfpisHGr#vu6gMK+#mL7rLX=jZQ{ z-+%BjOG;>RH;?ppv#DDh<_K)86uZaJUDh%8p4qM2XC03F`K4c(+xa@+j4A&vr->gG zKD;bU5P8D;&yq#FV_Rr`%9Hng;(a_BBwoxVimFd<;Ti37n|EBGXXpE-t z=Azv^lSLEr)^eNmg)NzUJYdrK$%Pl68BdvMdVZIzwS`ik`o+!jGsV}k`FTdjWiHj7 zb*@O*cg@{%u}OPWw_I7_TkU@K$10W`r`58~O}-Py{_K?Hei79*^3x};7m@t@nfLEU z>5$)^19!#t ze5Rlrn}XQJi${V!EZZ8=zUJMsLo>F9eMs77Ewgn~nYV6a*12tE?XlZZ=U$mqllmh5 z@;sk4OQuWsw*UGi&K#AO(!A^3?~<$ob8=<={CN5B&xZSRa{Q-t-(0dOQs`HZ!6mLc zQ+I2|IsbpT^5+~84|nY|x;5Udu3JklXl|ZX(;OA@<(Aoxg*KLYYn$_9)||WkeDW>5 z)2Z|N9><8*sH~b^;TY#J$8hWAh;Mvc7iHhgF|X}2|JEzADQ7YRn_AZ0IOla1?>wK_ zESdPZ=mEE%TlLiaYa(A48QyqZowYPP{#|6{x6_>Krk}I?z{$Q@mXD?Xh~eZ{X8Ow0 zT<;ya|AjOE^YP6q9TivJ$ylAT@#p2fsNceJo(_j6t?>Hvi=+Scp>-Nd`QpCL6`!=$ zTx8^zZ$7ir6V2tXV`k6f3b<2#;l!k8+nvP%GCWQugvy;x+FIfE zI76KKSrn&96GwPY;<5=%-2K1rt$dx{Y$J6xy->qw=G>c{FJ)|Edgm;vHOzXaRPP$u zx5USC&EGX&-@c2`KM>a0#^WoLUF^@U!&B`y>(kxGsf*3b?%yf7|69yYVCRcyn+*=( z?s`(%(@jigv4^CbJwNe}L&jX=N(CmHQzc%zD;JyG-gEY=WbEwMq4z>Rv$rkJUTnHN zpd##U&7xhRc2%FBy~&e59D2L+iOi?I`QH9%>-Bv5-!d7b?djTFHZ{Lx-h#h}?};u{ z)yoLnyf)kSmUVB|)ueqgi>`~xY|WS`f9znzs)b#i>}M6_435TB9AS<8v}#LwK-}L? zg)`N@|M+Lfyv}pN!QGdp{Cw=Z*i6q#u7drDqmhYQ^2ceOLf)q*KUvqH;=}Ipze_#x z{dE(j)+@?)51yIO>d~~`E}H3$x6PhjHkW07;;Ux}JU0uU>2&562g|eA@CMs&*MeqP z&DyYd`yze$`xp4WhJ9?AozC&z$Uic#(`n%;_gh{ccYe?7?2&%9^)&mNx@z0`w{mxe z9*SVw>6Mq6`{wv+U)u{O=N$I0Q{EToJMHShO=c3W4(zi1-LWV{x%GYT-z%A`&1_5( zZznp~dq1^2VrQ*Jm&EPE{$z94?#d7;(Qs`;u$O(WyZ#-+aD*>XZ0> zays9yPLW&xmF6dXogk{==`}TH!UTT*lkLmZf8YI;QydnoniE+vy}nT>?f+?A>#n*` z;Y}**`S&?Yta`9O*fHkCsmUL5F0TyniWf_ryLiRxq(=o!#%bA>hE5K_AOD-oh*)oA zRl3P2&DlwBdvx0Dw=Bsk4)PWz+~0U0&U@!ks|W2zcJ&mVvU25mbW!(;)Q5(tAJ)WH zig@b|R9?yDF%sp4o`A2S}<$7j|2M2g` zX6A<9)Q>wJBZdB(E)LwK77 zug8b7Q}fsVG}^{$b^JWf<(}CEe@?7)D%hi4cwVZtH{Ht4qyGK2Oiqx^P*A zspxvtwTRssGq2ffC_LCc=i`?o!z*)Z*B_kh6@0p(dCwI4-@oQ<^!jx9*-!TgTMuTa zl<=3Yn=Ns5&IT<@#}GlK+RtSh-rGHKRMp8{ls0Sq*_9G9 zwoPNzS;r6^uxb5*_kvPilPC#x7-oEEy8v>B+&Y;ogd%qPfO&!$#fO-=zZp8a=sJ6IKxy{FmTP+FqP;60 z&R)c#^!@M`rvAXMt*3j$HtkS){a=k?;iC&40veX#PTSRaOcGz8^{S33;gyKz>WTd# zdS6pfi&gCT=Z_nPc-z{N!asGoZ6=sgcY3-|KUKZ_? zUcN|mO52I$*PnHs_2@O*FWC5wiEsA|zdO81LWkb&ICOdq|3T&lj#3Xkap>qOl>ar2 zSBHxWvCphNowZ$g9^?%WUfVvX$1Zuul7@N>#Zt3%ye zv{xlL%=XMMyT0~%`qRQ_=iAS;F0a=#`;f3I0jRh*wHrBo2V@Kc=| z>z=~I9~Zm5YhURot!(+bV{_k?QWy5$WsE+oOLo;BKF~kYb#AhexP;#$vyMNmnOdg~ zyY?GTzwt6e`by~}v29Ba<|JHxy1~)+^16)Mh8}7b>w{+Ql)Sz*$%`-B;m|a{RZ-$C zf>~cQc-J#0dpaNFXtXuaw@5lg?DHVyH4ob`+aEVnb1N|(u= z*(-iOR`+@EUent)b9YznIJr~hvb@FXhf|g`aN1ARI(29Mt>>yYq@$(?WLYg2 zQhWY%s-O9{O@Zf+Zd%b%5F?ncJ$A+-S5T}Z4@#- zo2$qE+GLu>zl%Ku2breLyOq4*m8)#j7NO;9X9woTE?xWn?Q@<<#S%n7*?9nFc>g7O}jk_@JM~ z*n0l_bn{Y$f~Xx&WQz^z_vhBeJdT-grnF;PRK?+%$c)p{YU~e7x1VYh5Kppx@GHyg zp3U9IM$237mNQm~7RsCL{ZilG8FgAK>{P~Gk7DyF4$VHAyYg8MoSmZe`%LV6rK;0k zAKbWdeJ!(I;@8WQL+5e!eLS`@YWhshcsE9W*RGjggAU2u5k7XOPu#9}dhG1GGb=OB zomo7g%%t#^n0Z)5bMdu5!c|V0nhHt~59AIGk4?eV6CR)y@aXGcCU_dMaJ}=Haw+r}tiy|Mcs{;nv6J)@pY4 z9p;|AUH$x2-@|fmlv&dKZ8{#Ie{?rJoD)GCXax8=#j>n+uD z7z>{CO8?87_q9CD|BQKwkMD&=O%FITCoQ&_evVvU^2CwjVjDK@ zJa=Nd`56x@QOz~|oV(KYJN?|e*qK{Cg?aPT7~Y>dYG=1P=N|D_*PHs;?P=@sgD2k1 zxU{Ld(u?W*g!4P!M}IEA!~R!2Fg{^kxBT58m2C&}*U!Fx=*yC=B|pBORhiiI^7-cs zojGqXg;TPy6P-jk+@X~Urw~B-?MU+xoY6+jwdC%`F+ZM zWlMMm)%-N|49qS1a_Ngi^+d^yM|*Df)ZEQ_r*t=M`t_yG8(zJMT07fdmPzFCqzBTq zn~vEoPG7)m%RMbcPv^a7|Apd9vhNPBNna6A{oLhDsQ!}B;_KWNlf{hZr?~}AnOs-Swp*r~N99R4=o=HqSCD^NHo0 z^FFdm5>DP)`^|j6a{HU?34fntpPrEG>mF@0?c7gK52dX3Rllz9XxqO@e$SnMIWA`c zE^Og&c-LPc{9wNc%h&mm`5O)$6PptK@J8S1lTVklTsqfswe;$`?p(=3lV+Wtwc3N< zNL1YI`Kx`avb;T~_o_ZwP_unf(azdwv$hz;iWlkU{{FoB*^(EPlNYVesGPd6z51U0 zGS#&A2~Gdjn~Of=(eQ1IVAyXrweHCgZh_55c&^nKsbu@E`D$Ok@!5&P!sjCD=AJv5 zbpLThTwb&Bfh$qZgrc_hgo=fhHuh@m`zYiUc7r!;@>OZgk1dTqPt4dVeC9}!%>lPF z;*tgXi?(RoIlCZdV#?{R84`PYHEK*6|K0I_v@X_vW6r!_=DaT~FJCQOQ5Afyvgy$# z>DN*}r8ZC3J$GZ?ADiPo&KGv>dt-JaC)3a>(okW-bS;z6eC-PE;tLlqZqMyl?7!xeVZ!zUhAi&Q7F%+5K9%r$x3-Yw*u>nn zKq>8niW952Iv(#kD?OVpVQz_qK`R_1(Js@#{9l%7rtxFFiV`+xz`7#pm4`DN*mg?>s7Z zq=c(2UWsA9k&C~lo#^T|_VzjNx4%ny^!CQr1MNZg%s)B(V0+(sF!|EP_p7kNR`nX!ub)8FkPIgJxDjJg0G$tq+eqi-e7G$~3o>{Rb zvS+>5ve6540 zC>uTFSaUQbkOkl+{+6lpS8Lz)c2x;G4{{r{TrNhj=0IZ zc(vxU-VxV5ACx;ToOcm)7g7omk1e`6HKk6y|IqGr|F3H-?y{PdQR*#J%d>9Rvfa|# z(t0$XMI`l}{ru+NlMdr`JA`K_nBTp0_-&K$qzf&oHiDf#A&Z0`&uu=ccJB0}#;;eR z<@Zkc%+jbB5NB7%C=nB|V&6aZ40gemofpnCFn`t8Qqxn^TbuP0t#?Z3a3jYrBTXNjWjrW=#wa_)EuhebSnee>sq@O}R_Mr9t-TD>-N z>(-4P*@@*Av3dR1+5g|Oez0=>oz*`gb2q7!+ZLV|>9=|I^*}BE+$%3`-~TU`J}Y&b z<+EAo`&h-IQZ}kiJ9hl3{sA|s6zw%fjs&f0j`+y(NafMiHL_(@YtK|k%M@PGT>f_B z>A2m70aq5RHa*9A^@iF?^Nmk4b+@sYYA>3(_!LXF%(w8hf!b>~-I6+gzUbQRgyiza z56@X#NsF#4_-FWI@!D-S^V;W@9sTGznSJG&OA{yE^5I+-l|Aid+Kv3JuFoP97Vdl& zeJigz>U!MoH{}N$+hxj*1Rj?uKjgW5PMKTt=KmX9duAVzHTlxBGj09-{V%q6G9_MY z%Vj-1{gGf*_@{|p^BKMrxXt}%uN(75&MkJ%`^gR$;tSlrx*j_BsVeDAhJRbRLUUt6 zQCBE0-%3yM^ck0*owX|NIq5m8%U1uEe(K>Px%cwwmQQn56rK~M`$S7M_Tj(mC-dSm zCbqrilyg7Y|Jt1?X&Nj4siX<}_lg#r>hy5QnzDGu(Z@?7rfm$)*Iz8)qN|p-ztu=; zqU$D;^ZJ}SZEie0&ddHe%rUq)#N_y!R|!@+aXTGSqc0k1_ll-&j{TK4XYr*cVymJ9 zU&$Anem}9gsZ0Kypk#>GZI`!of7;4b_q}#^-xVh0STyg-rFVfr9Ncr~P4D2aJzse! zM1Qu-#B)y;w6ta1t1s4?pq{xZPTS}54{Jyult)1f4sWn>*-rkZ;E4X_!ZASIK`pP?&Xwfi!5358|}-? z=IgFmb(s133q`YY7w%`zX*E7})x|BAlKHeG#_0@)v@iZk<_z69KGo3`rv4YSKh z9*Zxt{8ZJ8ywb8WZRHtXt;_QTINtty@W%aMS<5n;TWxat-{{5PZrpa#T9WDXrw`R) zeIE)Ooc0Rbf5NP3*D_CVSIc~L$4}e&uHMtSAr>F)u!P%Krkp`u_|?4WR?kJIT(|!n ffBWP?!PB2qK85a8^sno_JLiw{%c66hb<7L^n`vTV literal 0 HcmV?d00001 diff --git a/site/assets/Regular.woff2 b/site/assets/Regular.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..d7c3d52c693bb4dfd0786625f21ea2f4726125d5 GIT binary patch literal 22408 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

Ra^9H{`RJM&`B<@1)md zLRJkoy5+7c%es5=-ASuwTSKm|e!cab<%RGWslUGcHo7(M`@Y0b_SdW5HwZ4v+j8oJ zRC@Tzjh0uQN1px0H2G^NyV}>6pH07JPQU+m@2pi(rCI)S*Unb^q^T^yzD%$DK#b?= zpWIzLErWTuPPTKeVXuqQ72Z&za^GXM*3K&F#Sfw-Umchjz-cER%I9*Rb~>Z1f%0?* zjn)>1#}RC%6Jj0ASffv-hc9bBbUfoRvl@??a@{5gR;x1??--q%C(V|+>E$BP$rp~d zez4S7t#D)6#k9^tzZR|S-?U+kZf;V$W%smlNn`tT`HZyHGZ)L8Pwlrozgdo5rh>6} zzV*4LR`m$KX?wLb{CEC+@Z9V7t<2?~o1e|SGqrrrWOlm`8*8qdDpI|7NuJ4mW=gXB z)PHh$i`btxi24{y^6k1}(O{O#qr()n^*o7 zbE(>N@N9J2eT#;J*=5X$!oeTE} z-k7)UtKHW6^9-h8vE5x-^&dDl-Gm1>*yn3=9nu8LQYBSmzk6 z@78D#WJqY3$LPn%z|g~U!I(kg!I!52oHqY1{nh=jPT{%DzQy);z8KiQYm)hGrhB0+ z`~FT1*K6IA7Ul*f@3$&*?2u}iw(@Pm$Nf@M|7v_V_u|b_wxeyQBF`(eH?!P({JO@M ktLMMn{5N$>s@vbz<)-Xu6W!CO^u65RTbIE + {children} + + ); +} diff --git a/site/next.config.js b/site/next.config.js new file mode 100644 index 0000000..a35bfad --- /dev/null +++ b/site/next.config.js @@ -0,0 +1,6 @@ +/** @type {import('next').NextConfig} */ +const nextConfig = { + output: "export", +}; + +module.exports = nextConfig; diff --git a/site/package.json b/site/package.json new file mode 100644 index 0000000..5deb167 --- /dev/null +++ b/site/package.json @@ -0,0 +1,36 @@ +{ + "name": "site", + "version": "0.1.0", + "private": true, + "scripts": { + "dev": "next dev", + "build": "next build", + "start": "next start", + "lint": "next lint" + }, + "dependencies": { + "@fortawesome/fontawesome-free": "^6.4.2", + "@fortawesome/fontawesome-svg-core": "^6.4.2", + "@fortawesome/free-brands-svg-icons": "^6.4.2", + "@fortawesome/free-solid-svg-icons": "^6.4.2", + "@fortawesome/react-fontawesome": "^0.2.0", + "@headlessui/react": "^1.7.17", + "@headlessui/tailwindcss": "^0.2.0", + "@types/node": "20.5.8", + "@types/react": "18.2.21", + "@types/react-dom": "18.2.7", + "autoprefixer": "10.4.15", + "eslint": "8.48.0", + "eslint-config-next": "13.4.19", + "next": "13.4.19", + "postcss": "8.4.29", + "react": "18.2.0", + "react-dom": "18.2.0", + "tailwindcss": "3.3.3", + "typescript": "5.2.2" + }, + "devDependencies": { + "prettier": "^3.0.3", + "prettier-plugin-tailwindcss": "^0.5.4" + } +} diff --git a/site/pages/_app.tsx b/site/pages/_app.tsx new file mode 100644 index 0000000..c0572f6 --- /dev/null +++ b/site/pages/_app.tsx @@ -0,0 +1,14 @@ +import Layout from "@/layout/layout"; +import type { AppProps } from "next/app"; +import { config } from "@fortawesome/fontawesome-svg-core"; +import "@fortawesome/fontawesome-svg-core/styles.css"; +config.autoAddCss = false; +import "static/globals.css"; + +export default function App({ Component, pageProps }: AppProps) { + return ( + + + + ); +} diff --git a/site/pages/_document.tsx b/site/pages/_document.tsx new file mode 100644 index 0000000..ce4b5e1 --- /dev/null +++ b/site/pages/_document.tsx @@ -0,0 +1,13 @@ +import { Html, Head, Main, NextScript } from "next/document"; + +export default function Document() { + return ( + + + +

+ + + + ); +} diff --git a/site/pages/index.tsx b/site/pages/index.tsx new file mode 100644 index 0000000..73fbc33 --- /dev/null +++ b/site/pages/index.tsx @@ -0,0 +1,154 @@ +import { faGithub } from "@fortawesome/free-brands-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import Head from "next/head"; +import { + faChevronDown, + faChevronUp, + faUpRightFromSquare, +} from "@fortawesome/free-solid-svg-icons"; +import { Menu, Transition } from "@headlessui/react"; +import { useState, useRef, useEffect } from "react"; +export default function Page() { + const [chevron, setChevron] = useState(false); + const menuButtonRef = useRef(null); + const toggleDropdown = () => { + setChevron(!chevron); + }; + const handleClickOutside = (event: MouseEvent) => { + if ( + menuButtonRef.current && + !menuButtonRef.current.contains(event.target as Node) + ) { + setChevron(false); + } + }; + useEffect(() => { + document.addEventListener("click", handleClickOutside); + + return () => { + document.removeEventListener("click", handleClickOutside); + }; + }, []); + return ( + <> + + Burrow + + + +
+
+

+ Burrow Through{" "} + Firewalls +

+
+

+ Burrow is an open source tool for burrowing through firewalls, + built by teenagers at{" "} + + + Hack Club. + + {" "} + + burrow + {" "} + is a Rust-based VPN for getting around restrictive Internet + censors. +

+
+
+
+ +
+ toggleDropdown()} + ref={menuButtonRef} + className="w-50 h-12 rounded-2xl bg-hackClubRed px-3 font-SpaceMono hover:scale-105 md:h-12 md:w-auto md:rounded-3xl md:text-xl 2xl:h-16 2xl:text-2xl " + > + Install for Linux + {chevron ? ( + + ) : ( + + )} + +
+ + +
+ + {({ active }) => ( + + Install for Windows + + )} + + + + Install for MacOS + + +
+
+
+
+ + + +
+ +
+ {/* Footer */} + {/* */} +
+
+ + ); +} diff --git a/site/postcss.config.js b/site/postcss.config.js new file mode 100644 index 0000000..12a703d --- /dev/null +++ b/site/postcss.config.js @@ -0,0 +1,6 @@ +module.exports = { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +}; diff --git a/site/prettier.config.js b/site/prettier.config.js new file mode 100644 index 0000000..d573118 --- /dev/null +++ b/site/prettier.config.js @@ -0,0 +1,3 @@ +module.exports = { + plugins: ["prettier-plugin-tailwindcss"], +}; diff --git a/site/public/hackclub.svg b/site/public/hackclub.svg new file mode 100644 index 0000000..38c2a68 --- /dev/null +++ b/site/public/hackclub.svg @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/site/static/globals.css b/site/static/globals.css new file mode 100644 index 0000000..b5c61c9 --- /dev/null +++ b/site/static/globals.css @@ -0,0 +1,3 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; diff --git a/site/tailwind.config.ts b/site/tailwind.config.ts new file mode 100644 index 0000000..3df6f5a --- /dev/null +++ b/site/tailwind.config.ts @@ -0,0 +1,28 @@ +import type { Config } from "tailwindcss"; + +const config: Config = { + content: [ + "./pages/**/*.{js,ts,jsx,tsx,mdx}", + "./components/**/*.{js,ts,jsx,tsx,mdx}", + "./app/**/*.{js,ts,jsx,tsx,mdx}", + ], + theme: { + extend: { + colors: { + backgroundBlack: "#17171D", + hackClubRed: "#EC3750", + hackClubBlueShade: "#32323D", + hackClubBlue: "#338EDA", + burrowStroke: "#595959", + burrowHover: "#3D3D3D", + }, + fontFamily: { + SpaceMono: ["var(--font-space-mono)"], + Poppins: ["var(--font-poppins)"], + PhantomSans: ["var(--font-phantom-sans)"], + }, + }, + }, + plugins: [require("@headlessui/tailwindcss")({ prefix: "ui" })], +}; +export default config; diff --git a/site/tsconfig.json b/site/tsconfig.json new file mode 100644 index 0000000..c714696 --- /dev/null +++ b/site/tsconfig.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + "target": "es5", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "incremental": true, + "plugins": [ + { + "name": "next" + } + ], + "paths": { + "@/*": ["./*"] + } + }, + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], + "exclude": ["node_modules"] +} From ec8cc533abf7502cb09ea6f8033124786afe5fb7 Mon Sep 17 00:00:00 2001 From: Conrad Kramer Date: Sat, 30 Mar 2024 17:17:52 -0700 Subject: [PATCH 02/15] Add apple-app-site-association file --- site/bun.lockb | Bin 0 -> 140507 bytes site/next.config.js | 9 +++++++- site/package.json | 2 +- .../.well-known/apple-app-site-association | 21 ++++++++++++++++++ 4 files changed, 30 insertions(+), 2 deletions(-) create mode 100755 site/bun.lockb create mode 100644 site/public/.well-known/apple-app-site-association diff --git a/site/bun.lockb b/site/bun.lockb new file mode 100755 index 0000000000000000000000000000000000000000..ea2d13747933513bfd419def1a5e0e9b8fb67bdc GIT binary patch literal 140507 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 z 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 literal 0 HcmV?d00001 diff --git a/site/next.config.js b/site/next.config.js index a35bfad..afbb70f 100644 --- a/site/next.config.js +++ b/site/next.config.js @@ -1,6 +1,13 @@ /** @type {import('next').NextConfig} */ const nextConfig = { - output: "export", + headers() { + return [ + { + source: "/.well-known/apple-app-site-association", + headers: [{ key: "Content-Type", value: "application/json" }], + } + ]; + } }; module.exports = nextConfig; diff --git a/site/package.json b/site/package.json index 5deb167..4fcacc8 100644 --- a/site/package.json +++ b/site/package.json @@ -1,5 +1,5 @@ { - "name": "site", + "name": "burrow", "version": "0.1.0", "private": true, "scripts": { diff --git a/site/public/.well-known/apple-app-site-association b/site/public/.well-known/apple-app-site-association new file mode 100644 index 0000000..63262fb --- /dev/null +++ b/site/public/.well-known/apple-app-site-association @@ -0,0 +1,21 @@ +{ + "applinks": { + "details": [ + { + "appIDs": [ + "P6PV2R9443.com.hackclub.burrow" + ], + "components": [ + { + "/": "/callback/*" + } + ] + } + ] + }, + "webcredentials": { + "apps": [ + "P6PV2R9443.com.hackclub.burrow" + ] + } +} From df549d48e6d995f0efacd028f06f0d2e51595a7e Mon Sep 17 00:00:00 2001 From: Conrad Kramer Date: Sat, 30 Mar 2024 16:47:59 -0700 Subject: [PATCH 03/15] Implement Slack authentication on iOS --- .github/actions/archive/action.yml | 1 - .github/actions/notarize/action.yml | 37 ++-- Apple/App/App-iOS.entitlements | 5 + Apple/App/App-macOS.entitlements | 5 + Apple/App/BurrowView.swift | 49 ++++- Apple/App/NetworkCarouselView.swift | 39 ++++ Apple/App/NetworkView.swift | 50 ----- Apple/App/OAuth2.swift | 280 +++++++++++++++++++++++++ Apple/App/TunnelButton.swift | 26 ++- Apple/Burrow.xcodeproj/project.pbxproj | 8 + 10 files changed, 419 insertions(+), 81 deletions(-) create mode 100644 Apple/App/NetworkCarouselView.swift create mode 100644 Apple/App/OAuth2.swift diff --git a/.github/actions/archive/action.yml b/.github/actions/archive/action.yml index 37282e1..e49eb0d 100644 --- a/.github/actions/archive/action.yml +++ b/.github/actions/archive/action.yml @@ -35,7 +35,6 @@ runs: -authenticationKeyID ${{ inputs.app-store-key-id }} \ -authenticationKeyIssuerID ${{ inputs.app-store-key-issuer-id }} \ -authenticationKeyPath "${PWD}/AuthKey_${{ inputs.app-store-key-id }}.p8" \ - -onlyUsePackageVersionsFromResolvedFile \ -scheme '${{ inputs.scheme }}' \ -destination '${{ inputs.destination }}' \ -archivePath '${{ inputs.archive-path }}' \ diff --git a/.github/actions/notarize/action.yml b/.github/actions/notarize/action.yml index 290ed86..f3f98f2 100644 --- a/.github/actions/notarize/action.yml +++ b/.github/actions/notarize/action.yml @@ -15,10 +15,6 @@ inputs: export-path: description: The path to export the archive to required: true -outputs: - notarized-app: - description: The compressed and notarized app - value: ${{ steps.notarize.outputs.notarized-app }} runs: using: composite steps: @@ -28,31 +24,28 @@ runs: run: | echo "${{ inputs.app-store-key }}" > AuthKey_${{ inputs.app-store-key-id }}.p8 - echo '{"destination":"upload","method":"developer-id"}' \ + echo '{"destination":"export","method":"developer-id"}' \ | plutil -convert xml1 -o ExportOptions.plist - - xcodebuild \ - -exportArchive \ + xcodebuild -exportArchive \ -allowProvisioningUpdates \ -allowProvisioningDeviceRegistration \ + -skipPackagePluginValidation \ + -skipMacroValidation \ + -onlyUsePackageVersionsFromResolvedFile \ -authenticationKeyID ${{ inputs.app-store-key-id }} \ -authenticationKeyIssuerID ${{ inputs.app-store-key-issuer-id }} \ -authenticationKeyPath "${PWD}/AuthKey_${{ inputs.app-store-key-id }}.p8" \ - -archivePath '${{ inputs.archive-path }}' \ + -archivePath Wallet.xcarchive \ + -exportPath Release \ -exportOptionsPlist ExportOptions.plist - until xcodebuild \ - -exportNotarizedApp \ - -allowProvisioningUpdates \ - -allowProvisioningDeviceRegistration \ - -authenticationKeyID ${{ inputs.app-store-key-id }} \ - -authenticationKeyIssuerID ${{ inputs.app-store-key-issuer-id }} \ - -authenticationKeyPath "${PWD}/AuthKey_${{ inputs.app-store-key-id }}.p8" \ - -archivePath '${{ inputs.archive-path }}' \ - -exportPath ${{ inputs.export-path }} - do - echo "Failed to export app, trying again in 10s..." - sleep 10 - done + ditto -c -k --keepParent Release/Wallet.app Upload.zip + SUBMISSION_ID=$(xcrun notarytool submit --issuer ${{ inputs.app-store-key-issuer-id }} --key-id ${{ inputs.app-store-key-id }} --key "${PWD}/AuthKey_${{ inputs.app-store-key-id }}.p8" Upload.zip | awk '/ id:/ { print $2; exit }') - rm -rf AuthKey_${{ inputs.app-store-key-id }}.p8 ExportOptions.plist + xcrun notarytool wait $SUBMISSION_ID --issuer ${{ inputs.app-store-key-issuer-id }} --key-id ${{ inputs.app-store-key-id }} --key "${PWD}/AuthKey_${{ inputs.app-store-key-id }}.p8" + xcrun stapler staple Release/Wallet.app + + aa archive -a lzma -b 8m -d Release -subdir Wallet.app -o Wallet.app.aar + + rm -rf Upload.zip Release AuthKey_${{ inputs.app-store-key-id }}.p8 ExportOptions.plist diff --git a/Apple/App/App-iOS.entitlements b/Apple/App/App-iOS.entitlements index 02ee960..53fcbb7 100644 --- a/Apple/App/App-iOS.entitlements +++ b/Apple/App/App-iOS.entitlements @@ -2,6 +2,11 @@ + com.apple.developer.associated-domains + + applinks:burrow.rs?mode=developer + webcredentials:burrow.rs?mode=developer + com.apple.developer.networking.networkextension packet-tunnel-provider diff --git a/Apple/App/App-macOS.entitlements b/Apple/App/App-macOS.entitlements index 02ee960..53fcbb7 100644 --- a/Apple/App/App-macOS.entitlements +++ b/Apple/App/App-macOS.entitlements @@ -2,6 +2,11 @@ + com.apple.developer.associated-domains + + applinks:burrow.rs?mode=developer + webcredentials:burrow.rs?mode=developer + com.apple.developer.networking.networkextension packet-tunnel-provider diff --git a/Apple/App/BurrowView.swift b/Apple/App/BurrowView.swift index b78b1e1..8447592 100644 --- a/Apple/App/BurrowView.swift +++ b/Apple/App/BurrowView.swift @@ -1,9 +1,29 @@ +import AuthenticationServices import SwiftUI +#if !os(macOS) struct BurrowView: View { + @Environment(\.webAuthenticationSession) + private var webAuthenticationSession + var body: some View { NavigationStack { VStack { + HStack { + Text("Networks") + .font(.largeTitle) + .fontWeight(.bold) + Spacer() + Menu { + Button("Hack Club", action: addHackClubNetwork) + Button("WireGuard", action: addWireGuardNetwork) + } label: { + Image(systemName: "plus.circle.fill") + .font(.title) + .accessibilityLabel("Add") + } + } + .padding(.top) NetworkCarouselView() Spacer() TunnelStatusView() @@ -11,9 +31,35 @@ struct BurrowView: View { .padding(.bottom) } .padding() - .navigationTitle("Networks") + .handleOAuth2Callback() } } + + private func addHackClubNetwork() { + Task { + try await authenticateWithSlack() + } + } + + private func addWireGuardNetwork() { + + } + + private func authenticateWithSlack() async throws { + guard + let authorizationEndpoint = URL(string: "https://slack.com/openid/connect/authorize"), + let tokenEndpoint = URL(string: "https://slack.com/api/openid.connect.token"), + let redirectURI = URL(string: "https://burrow.rs/callback/oauth2") else { return } + let session = OAuth2.Session( + authorizationEndpoint: authorizationEndpoint, + tokenEndpoint: tokenEndpoint, + redirectURI: redirectURI, + scopes: ["openid", "profile"], + clientID: "2210535565.6884042183125", + clientSecret: "2793c8a5255cae38830934c664eeb62d" + ) + let response = try await session.authorize(webAuthenticationSession) + } } #if DEBUG @@ -24,3 +70,4 @@ struct NetworkView_Previews: PreviewProvider { } } #endif +#endif diff --git a/Apple/App/NetworkCarouselView.swift b/Apple/App/NetworkCarouselView.swift new file mode 100644 index 0000000..b120c60 --- /dev/null +++ b/Apple/App/NetworkCarouselView.swift @@ -0,0 +1,39 @@ +import SwiftUI + +struct NetworkCarouselView: View { + var networks: [any Network] = [ + HackClub(id: "1"), + HackClub(id: "2"), + WireGuard(id: "4"), + HackClub(id: "5"), + ] + + var body: some View { + ScrollView(.horizontal) { + LazyHStack { + ForEach(networks, id: \.id) { network in + NetworkView(network: network) + .containerRelativeFrame(.horizontal, count: 10, span: 7, spacing: 0, alignment: .center) + .scrollTransition(.interactive, axis: .horizontal) { content, phase in + content + .scaleEffect(1.0 - abs(phase.value) * 0.1) + } + } + } + } + .scrollTargetLayout() + .scrollClipDisabled() + .scrollIndicators(.hidden) + .defaultScrollAnchor(.center) + .scrollTargetBehavior(.viewAligned) + .containerRelativeFrame(.horizontal) + } +} + +#if DEBUG +struct NetworkCarouselView_Previews: PreviewProvider { + static var previews: some View { + NetworkCarouselView() + } +} +#endif diff --git a/Apple/App/NetworkView.swift b/Apple/App/NetworkView.swift index 290254c..b839d65 100644 --- a/Apple/App/NetworkView.swift +++ b/Apple/App/NetworkView.swift @@ -30,59 +30,9 @@ struct NetworkView: View { } } -struct AddNetworkView: View { - var body: some View { - Text("Add Network") - .frame(maxWidth: .infinity, minHeight: 175, maxHeight: 175) - .background( - RoundedRectangle(cornerRadius: 10) - .stroke(style: .init(lineWidth: 2, dash: [6])) - ) - } -} - extension NetworkView where Content == AnyView { init(network: any Network) { color = network.backgroundColor content = { AnyView(network.label) } } } - -struct NetworkCarouselView: View { - var networks: [any Network] = [ - HackClub(id: "1"), - HackClub(id: "2"), - WireGuard(id: "4"), - HackClub(id: "5"), - ] - - var body: some View { - ScrollView(.horizontal) { - LazyHStack { - ForEach(networks, id: \.id) { network in - NetworkView(network: network) - .containerRelativeFrame(.horizontal, count: 10, span: 7, spacing: 0, alignment: .center) - .scrollTransition(.interactive, axis: .horizontal) { content, phase in - content - .scaleEffect(1.0 - abs(phase.value) * 0.1) - } - } - AddNetworkView() - } - .scrollTargetLayout() - } - .scrollClipDisabled() - .scrollIndicators(.hidden) - .defaultScrollAnchor(.center) - .scrollTargetBehavior(.viewAligned) - .containerRelativeFrame(.horizontal) - } -} - -#if DEBUG -struct NetworkCarouselView_Previews: PreviewProvider { - static var previews: some View { - NetworkCarouselView() - } -} -#endif diff --git a/Apple/App/OAuth2.swift b/Apple/App/OAuth2.swift new file mode 100644 index 0000000..dc8c62b --- /dev/null +++ b/Apple/App/OAuth2.swift @@ -0,0 +1,280 @@ +import AuthenticationServices +import SwiftUI +import Foundation + +enum OAuth2 { + enum Error: Swift.Error { + case unknown + case invalidAuthorizationURL + case invalidCallbackURL + case invalidRedirectURI + } + + struct Credential { + var accessToken: String + var refreshToken: String? + var expirationDate: Date? + } + + struct Session { + var authorizationEndpoint: URL + var tokenEndpoint: URL + var redirectURI: URL + var responseType = OAuth2.ResponseType.code + var scopes: Set + var clientID: String + var clientSecret: String + + fileprivate static var queue: [Int: CheckedContinuation] = [:] + + fileprivate static func handle(url: URL) { + let continuations = queue + queue.removeAll() + for (_, continuation) in continuations { + continuation.resume(returning: url) + } + } + + public init( + authorizationEndpoint: URL, + tokenEndpoint: URL, + redirectURI: URL, + scopes: Set, + clientID: String, + clientSecret: String + ) { + self.authorizationEndpoint = authorizationEndpoint + self.tokenEndpoint = tokenEndpoint + self.redirectURI = redirectURI + self.scopes = scopes + self.clientID = clientID + self.clientSecret = clientSecret + } + + private var authorizationURL: URL { + get throws { + var queryItems: [URLQueryItem] = [ + .init(name: "client_id", value: clientID), + .init(name: "response_type", value: responseType.rawValue), + .init(name: "redirect_uri", value: redirectURI.absoluteString), + ] + if !scopes.isEmpty { + queryItems.append(.init(name: "scope", value: scopes.joined(separator: ","))) + } + guard var components = URLComponents(url: authorizationEndpoint, resolvingAgainstBaseURL: false) else { + throw OAuth2.Error.invalidAuthorizationURL + } + components.queryItems = queryItems + guard let authorizationURL = components.url else { throw OAuth2.Error.invalidAuthorizationURL } + return authorizationURL + } + } + + private func handle(callbackURL: URL) async throws -> OAuth2.AccessTokenResponse { + switch responseType { + case .code: + guard let components = URLComponents(url: callbackURL, resolvingAgainstBaseURL: false) else { + throw OAuth2.Error.invalidCallbackURL + } + return try await handle(response: try components.decode(OAuth2.CodeResponse.self)) + default: + throw OAuth2.Error.invalidCallbackURL + } + } + + private func handle(response: OAuth2.CodeResponse) async throws -> OAuth2.AccessTokenResponse { + var components = URLComponents() + components.queryItems = [ + .init(name: "client_id", value: clientID), + .init(name: "client_secret", value: clientSecret), + .init(name: "grant_type", value: GrantType.authorizationCode.rawValue), + .init(name: "code", value: response.code), + .init(name: "redirect_uri", value: redirectURI.absoluteString) + ] + let httpBody = Data(components.percentEncodedQuery!.utf8) + + var request = URLRequest(url: tokenEndpoint) + request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type") + request.httpMethod = "POST" + request.httpBody = httpBody + + let session = URLSession(configuration: .ephemeral) + let (data, _) = try await session.data(for: request) + return try OAuth2.decoder.decode(OAuth2.AccessTokenResponse.self, from: data) + } + + func authorize(_ session: WebAuthenticationSession) async throws -> Credential { + let authorizationURL = try authorizationURL + let callbackURL = try await session.start( + url: authorizationURL, + redirectURI: redirectURI + ) + return try await handle(callbackURL: callbackURL).credential + } + } + + private struct CodeResponse: Codable { + var code: String + var state: String? + } + + private struct AccessTokenResponse: Codable { + var accessToken: String + var tokenType: TokenType + var expiresIn: Double? + var refreshToken: String? + + var credential: Credential { + .init(accessToken: accessToken, refreshToken: refreshToken, expirationDate: expiresIn.map { Date.init(timeIntervalSinceNow: $0) }) + } + } + + enum TokenType: Codable, RawRepresentable { + case bearer + case unknown(String) + + init(rawValue: String) { + self = switch rawValue.lowercased() { + case "bearer": .bearer + default: .unknown(rawValue) + } + } + + var rawValue: String { + switch self { + case .bearer: "bearer" + case .unknown(let type): type + } + } + } + + enum GrantType: Codable, RawRepresentable { + case authorizationCode + case unknown(String) + + init(rawValue: String) { + self = switch rawValue.lowercased() { + case "authorization_code": .authorizationCode + default: .unknown(rawValue) + } + } + + var rawValue: String { + switch self { + case .authorizationCode: "authorization_code" + case .unknown(let type): type + } + } + } + + enum ResponseType: Codable, RawRepresentable { + case code + case idToken + case unknown(String) + + init(rawValue: String) { + self = switch rawValue.lowercased() { + case "code": .code + case "id_token": .idToken + default: .unknown(rawValue) + } + } + + var rawValue: String { + switch self { + case .code: "code" + case .idToken: "id_token" + case .unknown(let type): type + } + } + } + + fileprivate static var decoder: JSONDecoder { + let decoder = JSONDecoder() + decoder.keyDecodingStrategy = .convertFromSnakeCase + return decoder + } + + fileprivate static var encoder: JSONEncoder { + let encoder = JSONEncoder() + encoder.keyEncodingStrategy = .convertToSnakeCase + return encoder + } +} + +extension WebAuthenticationSession { + func start(url: URL, redirectURI: URL) async throws -> URL { + #if canImport(BrowserEngineKit) + if #available(iOS 17.4, macOS 14.4, tvOS 17.4, watchOS 10.4, *) { + return try await authenticate( + using: url, + callback: try Self.callback(for: redirectURI), + additionalHeaderFields: [:] + ) + } + #endif + + return try await withThrowingTaskGroup(of: URL.self) { group in + group.addTask { + return try await authenticate(using: url, callbackURLScheme: redirectURI.scheme ?? "") + } + + let id = Int.random(in: 0.. ASWebAuthenticationSession.Callback { + switch redirectURI.scheme { + case "https": + guard let host = redirectURI.host else { throw OAuth2.Error.invalidRedirectURI } + return .https(host: host, path: redirectURI.path) + case "http": + throw OAuth2.Error.invalidRedirectURI + case .some(let scheme): + return .customScheme(scheme) + case .none: + throw OAuth2.Error.invalidRedirectURI + } + } + #endif +} + +extension View { + func handleOAuth2Callback() -> some View { + onOpenURL { url in OAuth2.Session.handle(url: url) } + } +} + +extension URLComponents { + fileprivate func decode(_ type: T.Type) throws -> T { + guard let queryItems else { + throw DecodingError.valueNotFound( + T.self, + .init(codingPath: [], debugDescription: "Missing query items") + ) + } + let data = try OAuth2.encoder.encode(try queryItems.values) + return try OAuth2.decoder.decode(T.self, from: data) + } +} + +extension Sequence where Element == URLQueryItem { + fileprivate var values: [String: String?] { + get throws { + try Dictionary(map { ($0.name, $0.value) }) { _, _ in + throw DecodingError.dataCorrupted(.init(codingPath: [], debugDescription: "Duplicate query items")) + } + } + } +} diff --git a/Apple/App/TunnelButton.swift b/Apple/App/TunnelButton.swift index df8d7e6..1f5693e 100644 --- a/Apple/App/TunnelButton.swift +++ b/Apple/App/TunnelButton.swift @@ -4,16 +4,19 @@ struct TunnelButton: View { @Environment(\.tunnel) var tunnel: any Tunnel + private var action: Action? { tunnel.action } + var body: some View { - if let action = tunnel.action { - Button { + Button { + if let action { tunnel.perform(action) - } label: { - Text(action.description) } - .padding(.horizontal) - .buttonStyle(.floating) + } label: { + Text(action.description) } + .disabled(action.isDisabled) + .padding(.horizontal) + .buttonStyle(.floating) } } @@ -40,12 +43,21 @@ extension TunnelButton { } } -extension TunnelButton.Action { +extension TunnelButton.Action? { var description: LocalizedStringKey { switch self { case .enable: "Enable" case .start: "Start" case .stop: "Stop" + case .none: "Start" + } + } + + var isDisabled: Bool { + if case .none = self { + true + } else { + false } } } diff --git a/Apple/Burrow.xcodeproj/project.pbxproj b/Apple/Burrow.xcodeproj/project.pbxproj index b08c4b0..3ea58b3 100644 --- a/Apple/Burrow.xcodeproj/project.pbxproj +++ b/Apple/Burrow.xcodeproj/project.pbxproj @@ -10,6 +10,8 @@ 0B28F1562ABF463A000D44B0 /* DataTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0B28F1552ABF463A000D44B0 /* DataTypes.swift */; }; 0B46E8E02AC918CA00BA2A3C /* Client.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0B46E8DF2AC918CA00BA2A3C /* Client.swift */; }; 43AA26D82A10004900F14CE6 /* MenuItemToggleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43AA26D72A10004900F14CE6 /* MenuItemToggleView.swift */; }; + D000363D2BB8928E00E582EC /* NetworkCarouselView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D000363C2BB8928E00E582EC /* NetworkCarouselView.swift */; }; + D000363F2BB895FB00E582EC /* OAuth2.swift in Sources */ = {isa = PBXBuildFile; fileRef = D000363E2BB895FB00E582EC /* OAuth2.swift */; }; D00117312B2FFFC900D87C25 /* NWConnection+Async.swift in Sources */ = {isa = PBXBuildFile; fileRef = D00117302B2FFFC900D87C25 /* NWConnection+Async.swift */; }; D00117332B3001A400D87C25 /* NewlineProtocolFramer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D00117322B3001A400D87C25 /* NewlineProtocolFramer.swift */; }; D001173B2B30341C00D87C25 /* Logging.swift in Sources */ = {isa = PBXBuildFile; fileRef = D001173A2B30341C00D87C25 /* Logging.swift */; }; @@ -78,6 +80,8 @@ 0B28F1552ABF463A000D44B0 /* DataTypes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataTypes.swift; sourceTree = ""; }; 0B46E8DF2AC918CA00BA2A3C /* Client.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Client.swift; sourceTree = ""; }; 43AA26D72A10004900F14CE6 /* MenuItemToggleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuItemToggleView.swift; sourceTree = ""; }; + D000363C2BB8928E00E582EC /* NetworkCarouselView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkCarouselView.swift; sourceTree = ""; }; + D000363E2BB895FB00E582EC /* OAuth2.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OAuth2.swift; sourceTree = ""; }; D00117302B2FFFC900D87C25 /* NWConnection+Async.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NWConnection+Async.swift"; sourceTree = ""; }; D00117322B3001A400D87C25 /* NewlineProtocolFramer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewlineProtocolFramer.swift; sourceTree = ""; }; D00117382B30341C00D87C25 /* libBurrowShared.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libBurrowShared.a; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -247,7 +251,9 @@ D00AA8962A4669BC005C8102 /* AppDelegate.swift */, 43AA26D72A10004900F14CE6 /* MenuItemToggleView.swift */, D05B9F7729E39EEC008CB1F9 /* BurrowView.swift */, + D000363C2BB8928E00E582EC /* NetworkCarouselView.swift */, D01A79302B81630D0024EC91 /* NetworkView.swift */, + D000363E2BB895FB00E582EC /* OAuth2.swift */, D032E64D2B8A69C90006B8AD /* Networks */, D0FAB5972B818B8200F6A84B /* TunnelStatusView.swift */, D0FAB5952B818B2900F6A84B /* TunnelButton.swift */, @@ -476,6 +482,7 @@ 43AA26D82A10004900F14CE6 /* MenuItemToggleView.swift in Sources */, D05B9F7829E39EEC008CB1F9 /* BurrowView.swift in Sources */, D0FAB5922B818A5900F6A84B /* NetworkExtensionTunnel.swift in Sources */, + D000363F2BB895FB00E582EC /* OAuth2.swift in Sources */, D0FAB5962B818B2900F6A84B /* TunnelButton.swift in Sources */, D00AA8972A4669BC005C8102 /* AppDelegate.swift in Sources */, D05EF8C82B81818D0017AB4F /* FloatingButtonStyle.swift in Sources */, @@ -484,6 +491,7 @@ D01A79312B81630D0024EC91 /* NetworkView.swift in Sources */, D032E6542B8A79DA0006B8AD /* WireGuard.swift in Sources */, D0BCC5FD2A086D4700AD070D /* NetworkExtension+Async.swift in Sources */, + D000363D2BB8928E00E582EC /* NetworkCarouselView.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; From abf1101484166ff434287056e8c0f5af727ef39e Mon Sep 17 00:00:00 2001 From: Jett Chen Date: Mon, 22 Apr 2024 06:01:47 +0800 Subject: [PATCH 04/15] Wireguard Configuration in SQLite (#263) #241 --- .github/workflows/release-linux.yml | 34 ++++ .vscode/settings.json | 35 +++-- Apple/Burrow.xcodeproj/project.pbxproj | 24 +-- Apple/NetworkExtension/Client.swift | 60 -------- Apple/NetworkExtension/DataTypes.swift | 61 -------- .../PacketTunnelProvider.swift | 70 +++++---- Apple/NetworkExtension/libburrow/libburrow.h | 4 +- Apple/Shared/Client.swift | 106 +++++++++++++ Apple/Shared/Constants.swift | 10 ++ Apple/Shared/DataTypes.swift | 139 +++++++++++++++++ .../NWConnection+Async.swift | 0 .../NewlineProtocolFramer.swift | 0 Cargo.lock | 89 +++++++++++ Dockerfile | 26 +++- burrow-gtk/build-aux/Dockerfile | 4 +- burrow-gtk/build-aux/build_appimage.sh | 2 + burrow/Cargo.toml | 22 ++- burrow/burrow.db | Bin 0 -> 20480 bytes burrow/src/daemon/apple.rs | 14 +- burrow/src/daemon/instance.rs | 49 ++++-- burrow/src/daemon/mod.rs | 39 +++-- burrow/src/daemon/net/mod.rs | 11 +- burrow/src/daemon/net/unix.rs | 103 +++++++++---- burrow/src/daemon/rpc/mod.rs | 40 +++++ burrow/src/daemon/rpc/notification.rs | 11 ++ .../src/daemon/{command.rs => rpc/request.rs} | 9 ++ burrow/src/daemon/{ => rpc}/response.rs | 15 ++ ...equest__daemoncommand_serialization-2.snap | 5 + ...equest__daemoncommand_serialization-3.snap | 5 + ...equest__daemoncommand_serialization-4.snap | 5 + ...equest__daemoncommand_serialization-5.snap | 5 + ..._request__daemoncommand_serialization.snap | 5 + ...c__response__response_serialization-2.snap | 5 + ...c__response__response_serialization-3.snap | 5 + ...c__response__response_serialization-4.snap | 5 + ...rpc__response__response_serialization.snap | 5 + burrow/src/database.rs | 145 ++++++++++++++++++ burrow/src/lib.rs | 6 +- burrow/src/main.rs | 79 +++++----- burrow/src/wireguard/config.rs | 3 + burrow/src/wireguard/iface.rs | 36 +++-- burrow/src/wireguard/mod.rs | 2 +- tun/src/unix/apple/mod.rs | 20 +-- 43 files changed, 988 insertions(+), 325 deletions(-) create mode 100644 .github/workflows/release-linux.yml delete mode 100644 Apple/NetworkExtension/Client.swift delete mode 100644 Apple/NetworkExtension/DataTypes.swift create mode 100644 Apple/Shared/Client.swift create mode 100644 Apple/Shared/DataTypes.swift rename Apple/{NetworkExtension => Shared}/NWConnection+Async.swift (100%) rename Apple/{NetworkExtension => Shared}/NewlineProtocolFramer.swift (100%) create mode 100644 burrow/burrow.db create mode 100644 burrow/src/daemon/rpc/mod.rs create mode 100644 burrow/src/daemon/rpc/notification.rs rename burrow/src/daemon/{command.rs => rpc/request.rs} (82%) rename burrow/src/daemon/{ => rpc}/response.rs (89%) create mode 100644 burrow/src/daemon/rpc/snapshots/burrow__daemon__rpc__request__daemoncommand_serialization-2.snap create mode 100644 burrow/src/daemon/rpc/snapshots/burrow__daemon__rpc__request__daemoncommand_serialization-3.snap create mode 100644 burrow/src/daemon/rpc/snapshots/burrow__daemon__rpc__request__daemoncommand_serialization-4.snap create mode 100644 burrow/src/daemon/rpc/snapshots/burrow__daemon__rpc__request__daemoncommand_serialization-5.snap create mode 100644 burrow/src/daemon/rpc/snapshots/burrow__daemon__rpc__request__daemoncommand_serialization.snap create mode 100644 burrow/src/daemon/rpc/snapshots/burrow__daemon__rpc__response__response_serialization-2.snap create mode 100644 burrow/src/daemon/rpc/snapshots/burrow__daemon__rpc__response__response_serialization-3.snap create mode 100644 burrow/src/daemon/rpc/snapshots/burrow__daemon__rpc__response__response_serialization-4.snap create mode 100644 burrow/src/daemon/rpc/snapshots/burrow__daemon__rpc__response__response_serialization.snap create mode 100644 burrow/src/database.rs diff --git a/.github/workflows/release-linux.yml b/.github/workflows/release-linux.yml new file mode 100644 index 0000000..6709edb --- /dev/null +++ b/.github/workflows/release-linux.yml @@ -0,0 +1,34 @@ +name: Release (Linux) +on: + release: + types: + - created +jobs: + appimage: + name: Build AppImage + runs-on: ubuntu-latest + container: docker + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Build AppImage + run: | + docker build -t appimage-builder . -f burrow-gtk/build-aux/Dockerfile + docker create --name temp appimage-builder + docker cp temp:/app/burrow-gtk/build-appimage/Burrow-x86_64.AppImage . + docker rm temp + - name: Get Build Number + id: version + shell: bash + run: | + echo "BUILD_NUMBER=$(Tools/version.sh)" >> $GITHUB_OUTPUT + - name: Attach Artifacts + uses: SierraSoftworks/gh-releases@v1.0.7 + with: + token: ${{ secrets.GITHUB_TOKEN }} + release_tag: builds/${{ steps.version.outputs.BUILD_NUMBER }} + overwrite: "true" + files: | + Burrow-x86_64.AppImage diff --git a/.vscode/settings.json b/.vscode/settings.json index 3c714be..a760137 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,18 +1,19 @@ { - "files.autoSave": "onFocusChange", - "files.defaultLanguage": "rust", - "editor.formatOnPaste": true, - "editor.formatOnSave": true, - "files.trimTrailingWhitespace": true, - "editor.suggest.preview": true, - "editor.acceptSuggestionOnEnter": "on", - "rust-analyzer.restartServerOnConfigChange": true, - "rust-analyzer.cargo.features": "all", - "rust-analyzer.rustfmt.extraArgs": [ - "+nightly" - ], - "[rust]": { - "editor.defaultFormatter": "rust-lang.rust-analyzer", - }, - "rust-analyzer.inlayHints.typeHints.enable": false -} \ No newline at end of file + "files.autoSave": "onFocusChange", + "files.defaultLanguage": "rust", + "editor.formatOnPaste": true, + "editor.formatOnSave": true, + "files.trimTrailingWhitespace": true, + "editor.suggest.preview": true, + "editor.acceptSuggestionOnEnter": "on", + "rust-analyzer.restartServerOnConfigChange": true, + "rust-analyzer.cargo.features": "all", + "rust-analyzer.rustfmt.extraArgs": ["+nightly"], + "[rust]": { + "editor.defaultFormatter": "rust-lang.rust-analyzer" + }, + "rust-analyzer.inlayHints.typeHints.enable": false, + "rust-analyzer.linkedProjects": [ + "./burrow/Cargo.toml" + ] +} diff --git a/Apple/Burrow.xcodeproj/project.pbxproj b/Apple/Burrow.xcodeproj/project.pbxproj index 3ea58b3..a3be02d 100644 --- a/Apple/Burrow.xcodeproj/project.pbxproj +++ b/Apple/Burrow.xcodeproj/project.pbxproj @@ -7,13 +7,13 @@ objects = { /* Begin PBXBuildFile section */ - 0B28F1562ABF463A000D44B0 /* DataTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0B28F1552ABF463A000D44B0 /* DataTypes.swift */; }; - 0B46E8E02AC918CA00BA2A3C /* Client.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0B46E8DF2AC918CA00BA2A3C /* Client.swift */; }; + 0BA6D73B2BA638D900BD4B55 /* Client.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0B46E8DF2AC918CA00BA2A3C /* Client.swift */; }; + 0BA6D73C2BA6393200BD4B55 /* NWConnection+Async.swift in Sources */ = {isa = PBXBuildFile; fileRef = D00117302B2FFFC900D87C25 /* NWConnection+Async.swift */; }; + 0BA6D73D2BA6393B00BD4B55 /* NewlineProtocolFramer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D00117322B3001A400D87C25 /* NewlineProtocolFramer.swift */; }; + 0BA6D73E2BA6394B00BD4B55 /* DataTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0B28F1552ABF463A000D44B0 /* DataTypes.swift */; }; 43AA26D82A10004900F14CE6 /* MenuItemToggleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43AA26D72A10004900F14CE6 /* MenuItemToggleView.swift */; }; D000363D2BB8928E00E582EC /* NetworkCarouselView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D000363C2BB8928E00E582EC /* NetworkCarouselView.swift */; }; D000363F2BB895FB00E582EC /* OAuth2.swift in Sources */ = {isa = PBXBuildFile; fileRef = D000363E2BB895FB00E582EC /* OAuth2.swift */; }; - D00117312B2FFFC900D87C25 /* NWConnection+Async.swift in Sources */ = {isa = PBXBuildFile; fileRef = D00117302B2FFFC900D87C25 /* NWConnection+Async.swift */; }; - D00117332B3001A400D87C25 /* NewlineProtocolFramer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D00117322B3001A400D87C25 /* NewlineProtocolFramer.swift */; }; D001173B2B30341C00D87C25 /* Logging.swift in Sources */ = {isa = PBXBuildFile; fileRef = D001173A2B30341C00D87C25 /* Logging.swift */; }; D00117442B30372900D87C25 /* libBurrowShared.a in Frameworks */ = {isa = PBXBuildFile; fileRef = D00117382B30341C00D87C25 /* libBurrowShared.a */; }; D00117452B30372C00D87C25 /* libBurrowShared.a in Frameworks */ = {isa = PBXBuildFile; fileRef = D00117382B30341C00D87C25 /* libBurrowShared.a */; }; @@ -158,6 +158,10 @@ D00117392B30341C00D87C25 /* Shared */ = { isa = PBXGroup; children = ( + 0B28F1552ABF463A000D44B0 /* DataTypes.swift */, + D00117322B3001A400D87C25 /* NewlineProtocolFramer.swift */, + D00117302B2FFFC900D87C25 /* NWConnection+Async.swift */, + 0B46E8DF2AC918CA00BA2A3C /* Client.swift */, D001173A2B30341C00D87C25 /* Logging.swift */, D08252752B5C9FC4005DA378 /* Constants.swift */, D00117422B30348D00D87C25 /* Shared.xcconfig */, @@ -199,10 +203,6 @@ isa = PBXGroup; children = ( D020F65729E4A697002790F6 /* PacketTunnelProvider.swift */, - 0B46E8DF2AC918CA00BA2A3C /* Client.swift */, - 0B28F1552ABF463A000D44B0 /* DataTypes.swift */, - D00117322B3001A400D87C25 /* NewlineProtocolFramer.swift */, - D00117302B2FFFC900D87C25 /* NWConnection+Async.swift */, D020F65929E4A697002790F6 /* Info.plist */, D020F66729E4A95D002790F6 /* NetworkExtension-iOS.entitlements */, D020F66629E4A95D002790F6 /* NetworkExtension-macOS.entitlements */, @@ -456,7 +456,11 @@ buildActionMask = 2147483647; files = ( D001173B2B30341C00D87C25 /* Logging.swift in Sources */, + 0BA6D73C2BA6393200BD4B55 /* NWConnection+Async.swift in Sources */, D08252762B5C9FC4005DA378 /* Constants.swift in Sources */, + 0BA6D73E2BA6394B00BD4B55 /* DataTypes.swift in Sources */, + 0BA6D73B2BA638D900BD4B55 /* Client.swift in Sources */, + 0BA6D73D2BA6393B00BD4B55 /* NewlineProtocolFramer.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -464,10 +468,6 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - D00117332B3001A400D87C25 /* NewlineProtocolFramer.swift in Sources */, - 0B28F1562ABF463A000D44B0 /* DataTypes.swift in Sources */, - D00117312B2FFFC900D87C25 /* NWConnection+Async.swift in Sources */, - 0B46E8E02AC918CA00BA2A3C /* Client.swift in Sources */, D020F65829E4A697002790F6 /* PacketTunnelProvider.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/Apple/NetworkExtension/Client.swift b/Apple/NetworkExtension/Client.swift deleted file mode 100644 index e7c1bc8..0000000 --- a/Apple/NetworkExtension/Client.swift +++ /dev/null @@ -1,60 +0,0 @@ -import BurrowShared -import Foundation -import Network - -final class Client { - let connection: NWConnection - - private let logger = Logger.logger(for: Client.self) - private var generator = SystemRandomNumberGenerator() - - convenience init() throws { - self.init(url: try Constants.socketURL) - } - - init(url: URL) { - let endpoint: NWEndpoint - if url.isFileURL { - endpoint = .unix(path: url.path(percentEncoded: false)) - } else { - endpoint = .url(url) - } - - let parameters = NWParameters.tcp - parameters.defaultProtocolStack - .applicationProtocols - .insert(NWProtocolFramer.Options(definition: NewlineProtocolFramer.definition), at: 0) - connection = NWConnection(to: endpoint, using: parameters) - connection.start(queue: .global()) - } - - func request(_ request: any Request, type: U.Type = U.self) async throws -> U { - do { - var copy = request - copy.id = generator.next(upperBound: UInt.max) - let content = try JSONEncoder().encode(copy) - logger.debug("> \(String(decoding: content, as: UTF8.self))") - - try await self.connection.send(content: content) - let (response, _, _) = try await connection.receiveMessage() - - logger.debug("< \(String(decoding: response, as: UTF8.self))") - return try JSONDecoder().decode(U.self, from: response) - } catch { - logger.error("\(error, privacy: .public)") - throw error - } - } - - deinit { - connection.cancel() - } -} - -extension Constants { - static var socketURL: URL { - get throws { - try groupContainerURL.appending(component: "burrow.sock", directoryHint: .notDirectory) - } - } -} diff --git a/Apple/NetworkExtension/DataTypes.swift b/Apple/NetworkExtension/DataTypes.swift deleted file mode 100644 index 1409fde..0000000 --- a/Apple/NetworkExtension/DataTypes.swift +++ /dev/null @@ -1,61 +0,0 @@ -import Foundation - -// swiftlint:disable identifier_name -enum BurrowError: Error { - case addrDoesntExist - case resultIsError - case cantParseResult - case resultIsNone -} - -protocol Request: Codable where Command: Codable { - associatedtype Command - - var id: UInt { get set } - var command: Command { get set } -} - -struct BurrowSingleCommand: Request { - var id: UInt - var command: String -} - -struct BurrowRequest: Request where T: Codable { - var id: UInt - var command: T -} - -struct BurrowStartRequest: Codable { - struct TunOptions: Codable { - let name: String? - let no_pi: Bool - let tun_excl: Bool - let tun_retrieve: Bool - let address: [String] - } - struct StartOptions: Codable { - let tun: TunOptions - } - let Start: StartOptions -} - -struct Response: Decodable where T: Decodable { - var id: UInt - var result: T -} - -struct BurrowResult: Codable where T: Codable { - var Ok: T? - var Err: String? -} - -struct ServerConfigData: Codable { - struct InternalConfig: Codable { - let address: [String] - let name: String? - let mtu: Int32? - } - let ServerConfig: InternalConfig -} - -// swiftlint:enable identifier_name diff --git a/Apple/NetworkExtension/PacketTunnelProvider.swift b/Apple/NetworkExtension/PacketTunnelProvider.swift index a07daa3..89e0de6 100644 --- a/Apple/NetworkExtension/PacketTunnelProvider.swift +++ b/Apple/NetworkExtension/PacketTunnelProvider.swift @@ -5,10 +5,14 @@ import os class PacketTunnelProvider: NEPacketTunnelProvider { private let logger = Logger.logger(for: PacketTunnelProvider.self) + private var client: Client? override init() { do { - libburrow.spawnInProcess(socketPath: try Constants.socketURL.path) + libburrow.spawnInProcess( + socketPath: try Constants.socketURL.path(percentEncoded: false), + dbPath: try Constants.dbURL.path(percentEncoded: false) + ) } catch { logger.error("Failed to spawn: \(error)") } @@ -17,33 +21,17 @@ class PacketTunnelProvider: NEPacketTunnelProvider { override func startTunnel(options: [String: NSObject]? = nil) async throws { do { let client = try Client() + self.client = client + register_events(client) - let command = BurrowRequest(id: 0, command: "ServerConfig") - let data = try await client.request(command, type: Response>.self) - - let encoded = try JSONEncoder().encode(data.result) - self.logger.log("Received final data: \(String(decoding: encoded, as: UTF8.self))") - guard let serverconfig = data.result.Ok else { - throw BurrowError.resultIsError - } - guard let tunNs = generateTunSettings(from: serverconfig) else { - throw BurrowError.addrDoesntExist - } - try await self.setTunnelNetworkSettings(tunNs) - self.logger.info("Set remote tunnel address to \(tunNs.tunnelRemoteAddress)") - - let startRequest = BurrowRequest( - id: .random(in: (.min)..<(.max)), - command: BurrowStartRequest( - Start: BurrowStartRequest.StartOptions( - tun: BurrowStartRequest.TunOptions( - name: nil, no_pi: false, tun_excl: false, tun_retrieve: true, address: [] - ) - ) + _ = try await self.loadTunSettings() + let startRequest = Start( + tun: Start.TunOptions( + name: nil, no_pi: false, tun_excl: false, tun_retrieve: true, address: [] ) ) - let response = try await client.request(startRequest, type: Response>.self) - self.logger.log("Received start server response: \(String(describing: response.result))") + let response = try await client.request(startRequest, type: BurrowResult.self) + self.logger.log("Received start server response: \(String(describing: response))") } catch { self.logger.error("Failed to start tunnel: \(error)") throw error @@ -53,20 +41,33 @@ class PacketTunnelProvider: NEPacketTunnelProvider { override func stopTunnel(with reason: NEProviderStopReason) async { do { let client = try Client() - let command = BurrowRequest(id: 0, command: "Stop") - let data = try await client.request(command, type: Response>.self) + _ = try await client.single_request("Stop", type: BurrowResult.self) self.logger.log("Stopped client.") } catch { self.logger.error("Failed to stop tunnel: \(error)") } } - - private func generateTunSettings(from: ServerConfigData) -> NETunnelNetworkSettings? { - let cfig = from.ServerConfig + func loadTunSettings() async throws -> ServerConfig { + guard let client = self.client else { + throw BurrowError.noClient + } + let srvConfig = try await client.single_request("ServerConfig", type: BurrowResult.self) + guard let serverconfig = srvConfig.Ok else { + throw BurrowError.resultIsError + } + guard let tunNs = generateTunSettings(from: serverconfig) else { + throw BurrowError.addrDoesntExist + } + try await self.setTunnelNetworkSettings(tunNs) + self.logger.info("Set remote tunnel address to \(tunNs.tunnelRemoteAddress)") + return serverconfig + } + private func generateTunSettings(from: ServerConfig) -> NETunnelNetworkSettings? { + // Using a makeshift remote tunnel address let nst = NEPacketTunnelNetworkSettings(tunnelRemoteAddress: "1.1.1.1") var v4Addresses = [String]() var v6Addresses = [String]() - for addr in cfig.address { + for addr in from.address { if IPv4Address(addr) != nil { v6Addresses.append(addr) } @@ -81,4 +82,11 @@ class PacketTunnelProvider: NEPacketTunnelProvider { logger.log("Initialized ipv4 settings: \(nst.ipv4Settings)") return nst } + func register_events(_ client: Client) { + client.on_event(.ConfigChange) { (cfig: ServerConfig) in + self.logger.info("Config Change Notification: \(String(describing: cfig))") + self.setTunnelNetworkSettings(self.generateTunSettings(from: cfig)) + self.logger.info("Updated Tunnel Network Settings.") + } + } } diff --git a/Apple/NetworkExtension/libburrow/libburrow.h b/Apple/NetworkExtension/libburrow/libburrow.h index e500de4..2b578ab 100644 --- a/Apple/NetworkExtension/libburrow/libburrow.h +++ b/Apple/NetworkExtension/libburrow/libburrow.h @@ -1,2 +1,2 @@ -__attribute__((__swift_name__("spawnInProcess(socketPath:)"))) -extern void spawn_in_process(const char * __nullable path); +__attribute__((__swift_name__("spawnInProcess(socketPath:dbPath:)"))) +extern void spawn_in_process(const char * __nullable socket_path, const char * __nullable db_path); diff --git a/Apple/Shared/Client.swift b/Apple/Shared/Client.swift new file mode 100644 index 0000000..f643c6c --- /dev/null +++ b/Apple/Shared/Client.swift @@ -0,0 +1,106 @@ +import Foundation +import Network + +public final class Client { + let connection: NWConnection + + private let logger = Logger.logger(for: Client.self) + private var generator = SystemRandomNumberGenerator() + private var continuations: [UInt: UnsafeContinuation] = [:] + private var eventMap: [NotificationType: [(Data) throws -> Void]] = [:] + private var task: Task? + + public convenience init() throws { + self.init(url: try Constants.socketURL) + } + + public init(url: URL) { + let endpoint: NWEndpoint + if url.isFileURL { + endpoint = .unix(path: url.path(percentEncoded: false)) + } else { + endpoint = .url(url) + } + + let parameters = NWParameters.tcp + parameters.defaultProtocolStack + .applicationProtocols + .insert(NWProtocolFramer.Options(definition: NewlineProtocolFramer.definition), at: 0) + let connection = NWConnection(to: endpoint, using: parameters) + connection.start(queue: .global()) + self.connection = connection + self.task = Task { [weak self] in + while true { + let (data, _, _) = try await connection.receiveMessage() + let peek = try JSONDecoder().decode(MessagePeek.self, from: data) + switch peek.type { + case .Response: + let response = try JSONDecoder().decode(ResponsePeek.self, from: data) + self?.logger.info("Received response for \(response.id)") + guard let continuations = self?.continuations else {return} + self?.logger.debug("All keys in continuation table: \(continuations.keys)") + guard let continuation = self?.continuations[response.id] else { return } + self?.logger.debug("Got matching continuation") + continuation.resume(returning: data) + case .Notification: + let peek = try JSONDecoder().decode(NotificationPeek.self, from: data) + guard let handlers = self?.eventMap[peek.method] else { continue } + _ = try handlers.map { try $0(data) } + default: + continue + } + } + } + } + private func send(_ request: T) async throws -> U { + let data: Data = try await withUnsafeThrowingContinuation { continuation in + continuations[request.id] = continuation + do { + let data = try JSONEncoder().encode(request) + let completion: NWConnection.SendCompletion = .contentProcessed { error in + guard let error = error else { + return + } + continuation.resume(throwing: error) + } + connection.send(content: data, completion: completion) + } catch { + continuation.resume(throwing: error) + return + } + } + self.logger.debug("Got response data: \(String(describing: data.base64EncodedString()))") + let res = try JSONDecoder().decode(Response.self, from: data) + self.logger.debug("Got response data decoded: \(String(describing: res))") + return res.result + } + public func request(_ request: T, type: U.Type = U.self) async throws -> U { + let req = BurrowRequest( + id: generator.next(upperBound: UInt.max), + command: request + ) + return try await send(req) + } + public func single_request(_ request: String, type: U.Type = U.self) async throws -> U { + let req = BurrowSimpleRequest( + id: generator.next(upperBound: UInt.max), + command: request + ) + return try await send(req) + } + public func on_event(_ event: NotificationType, callable: @escaping (T) throws -> Void) { + let action = { data in + let decoded = try JSONDecoder().decode(Notification.self, from: data) + try callable(decoded.params) + } + if eventMap[event] != nil { + eventMap[event]?.append(action) + } else { + eventMap[event] = [action] + } + } + + deinit { + connection.cancel() + } +} diff --git a/Apple/Shared/Constants.swift b/Apple/Shared/Constants.swift index 634c500..a8207cd 100644 --- a/Apple/Shared/Constants.swift +++ b/Apple/Shared/Constants.swift @@ -20,4 +20,14 @@ public enum Constants { } return .success(groupContainerURL) }() + public static var socketURL: URL { + get throws { + try groupContainerURL.appending(component: "burrow.sock", directoryHint: .notDirectory) + } + } + public static var dbURL: URL { + get throws { + try groupContainerURL.appending(component: "burrow.db", directoryHint: .notDirectory) + } + } } diff --git a/Apple/Shared/DataTypes.swift b/Apple/Shared/DataTypes.swift new file mode 100644 index 0000000..ac49abc --- /dev/null +++ b/Apple/Shared/DataTypes.swift @@ -0,0 +1,139 @@ +import Foundation + +// swiftlint:disable identifier_name raw_value_for_camel_cased_codable_enum +public enum BurrowError: Error { + case addrDoesntExist + case resultIsError + case cantParseResult + case resultIsNone + case noClient +} + +public protocol Request: Codable where Params: Codable { + associatedtype Params + + var id: UInt { get set } + var method: String { get set } + var params: Params? { get set } +} + +public enum MessageType: String, Codable { + case Request + case Response + case Notification +} + +public struct MessagePeek: Codable { + public var type: MessageType + public init(type: MessageType) { + self.type = type + } +} + +public struct BurrowSimpleRequest: Request { + public var id: UInt + public var method: String + public var params: String? + public init(id: UInt, command: String, params: String? = nil) { + self.id = id + self.method = command + self.params = params + } +} + +public struct BurrowRequest: Request where T: Codable { + public var id: UInt + public var method: String + public var params: T? + public init(id: UInt, command: T) { + self.id = id + self.method = "\(T.self)" + self.params = command + } +} + +public struct Response: Decodable where T: Decodable { + public var id: UInt + public var result: T + public init(id: UInt, result: T) { + self.id = id + self.result = result + } +} + +public struct ResponsePeek: Codable { + public var id: UInt + public init(id: UInt) { + self.id = id + } +} + +public enum NotificationType: String, Codable { + case ConfigChange +} + +public struct Notification: Codable where T: Codable { + public var method: NotificationType + public var params: T + public init(method: NotificationType, params: T) { + self.method = method + self.params = params + } +} + +public struct NotificationPeek: Codable { + public var method: NotificationType + public init(method: NotificationType) { + self.method = method + } +} + +public struct AnyResponseData: Codable { + public var type: String + public init(type: String) { + self.type = type + } +} + +public struct BurrowResult: Codable where T: Codable { + public var Ok: T? + public var Err: String? + public init(Ok: T, Err: String? = nil) { + self.Ok = Ok + self.Err = Err + } +} + +public struct ServerConfig: Codable { + public let address: [String] + public let name: String? + public let mtu: Int32? + public init(address: [String], name: String?, mtu: Int32?) { + self.address = address + self.name = name + self.mtu = mtu + } +} + +public struct Start: Codable { + public struct TunOptions: Codable { + public let name: String? + public let no_pi: Bool + public let tun_excl: Bool + public let tun_retrieve: Bool + public let address: [String] + public init(name: String?, no_pi: Bool, tun_excl: Bool, tun_retrieve: Bool, address: [String]) { + self.name = name + self.no_pi = no_pi + self.tun_excl = tun_excl + self.tun_retrieve = tun_retrieve + self.address = address + } + } + public let tun: TunOptions + public init(tun: TunOptions) { + self.tun = tun + } +} + +// swiftlint:enable identifier_name raw_value_for_camel_cased_codable_enum diff --git a/Apple/NetworkExtension/NWConnection+Async.swift b/Apple/Shared/NWConnection+Async.swift similarity index 100% rename from Apple/NetworkExtension/NWConnection+Async.swift rename to Apple/Shared/NWConnection+Async.swift diff --git a/Apple/NetworkExtension/NewlineProtocolFramer.swift b/Apple/Shared/NewlineProtocolFramer.swift similarity index 100% rename from Apple/NetworkExtension/NewlineProtocolFramer.swift rename to Apple/Shared/NewlineProtocolFramer.swift diff --git a/Cargo.lock b/Cargo.lock index a75bd28..628e996 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -38,6 +38,18 @@ dependencies = [ "cpufeatures", ] +[[package]] +name = "ahash" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d713b3834d76b85304d4d525563c1276e2e30dc97cc67bfb4585a4a29fc2c89f" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + [[package]] name = "aho-corasick" version = "1.1.2" @@ -47,6 +59,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "allocator-api2" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" + [[package]] name = "anstream" version = "0.6.11" @@ -334,6 +352,7 @@ dependencies = [ "rand", "rand_core", "ring", + "rusqlite", "schemars", "serde", "serde_json", @@ -743,6 +762,18 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "fallible-iterator" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" + +[[package]] +name = "fallible-streaming-iterator" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" + [[package]] name = "fastrand" version = "2.0.1" @@ -967,6 +998,19 @@ name = "hashbrown" version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" +dependencies = [ + "ahash", + "allocator-api2", +] + +[[package]] +name = "hashlink" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "692eaaf7f7607518dd3cef090f1474b61edc5301d8012f09579920df68b725ee" +dependencies = [ + "hashbrown 0.14.3", +] [[package]] name = "hdrhistogram" @@ -1258,6 +1302,17 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "libsqlite3-sys" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c10584274047cb335c23d3e61bcef8e323adae7c5c8c760540f73610177fc3f" +dependencies = [ + "cc", + "pkg-config", + "vcpkg", +] + [[package]] name = "libsystemd" version = "0.7.0" @@ -1877,6 +1932,20 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "rusqlite" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b838eba278d213a8beaf485bd313fd580ca4505a00d5871caeb1457c55322cae" +dependencies = [ + "bitflags 2.4.2", + "fallible-iterator", + "fallible-streaming-iterator", + "hashlink", + "libsqlite3-sys", + "smallvec", +] + [[package]] name = "rustc-demangle" version = "0.1.23" @@ -2949,6 +3018,26 @@ dependencies = [ "linked-hash-map", ] +[[package]] +name = "zerocopy" +version = "0.7.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + [[package]] name = "zeroize" version = "1.7.0" diff --git a/Dockerfile b/Dockerfile index 9f54478..afd51ea 100644 --- a/Dockerfile +++ b/Dockerfile @@ -12,7 +12,7 @@ RUN set -eux && \ curl --proto '=https' --tlsv1.2 -sSf https://apt.llvm.org/llvm-snapshot.gpg.key | gpg --dearmor --output $KEYRINGS/llvm.gpg && \ echo "deb [signed-by=$KEYRINGS/llvm.gpg] http://apt.llvm.org/bookworm/ llvm-toolchain-bookworm-$LLVM_VERSION main" > /etc/apt/sources.list.d/llvm.list && \ apt-get update && \ - apt-get install --no-install-recommends -y clang-$LLVM_VERSION llvm-$LLVM_VERSION lld-$LLVM_VERSION && \ + apt-get install --no-install-recommends -y clang-$LLVM_VERSION llvm-$LLVM_VERSION lld-$LLVM_VERSION build-essential sqlite3 libsqlite3-dev musl musl-tools musl-dev && \ ln -s clang-$LLVM_VERSION /usr/bin/clang && \ ln -s clang /usr/bin/clang++ && \ ln -s lld-$LLVM_VERSION /usr/bin/ld.lld && \ @@ -24,12 +24,30 @@ RUN set -eux && \ apt-get remove -y --auto-remove && \ rm -rf /var/lib/apt/lists/* +ARG SQLITE_VERSION=3400100 + RUN case $TARGETPLATFORM in \ - "linux/arm64") LLVM_TARGET=aarch64-unknown-linux-musl ;; \ - "linux/amd64") LLVM_TARGET=x86_64-unknown-linux-musl ;; \ + "linux/arm64") LLVM_TARGET=aarch64-unknown-linux-musl MUSL_TARGET=aarch64-linux-musl ;; \ + "linux/amd64") LLVM_TARGET=x86_64-unknown-linux-musl MUSL_TARGET=x86_64-linux-musl ;; \ *) exit 1 ;; \ esac && \ - rustup target add $LLVM_TARGET + rustup target add $LLVM_TARGET && \ + curl --proto '=https' --tlsv1.2 -sSfO https://www.sqlite.org/2022/sqlite-autoconf-$SQLITE_VERSION.tar.gz && \ + tar xf sqlite-autoconf-$SQLITE_VERSION.tar.gz && \ + rm sqlite-autoconf-$SQLITE_VERSION.tar.gz && \ + cd sqlite-autoconf-$SQLITE_VERSION && \ + ./configure --disable-shared \ + CC="clang-$LLVM_VERSION -target $LLVM_TARGET" \ + CFLAGS="-I/usr/local/include -I/usr/include/$MUSL_TARGET" \ + LDFLAGS="-L/usr/local/lib -L/usr/lib/$MUSL_TARGET -L/lib/$MUSL_TARGET" && \ + make && \ + make install && \ + cd .. && \ + rm -rf sqlite-autoconf-$SQLITE_VERSION + +ENV SQLITE3_STATIC=1 \ + SQLITE3_INCLUDE_DIR=/usr/local/include \ + SQLITE3_LIB_DIR=/usr/local/lib ENV CC_x86_64_unknown_linux_musl=clang-$LLVM_VERSION \ AR_x86_64_unknown_linux_musl=llvm-ar-$LLVM_VERSION \ diff --git a/burrow-gtk/build-aux/Dockerfile b/burrow-gtk/build-aux/Dockerfile index df07c4a..4e71c05 100644 --- a/burrow-gtk/build-aux/Dockerfile +++ b/burrow-gtk/build-aux/Dockerfile @@ -4,7 +4,7 @@ ENV DEBIAN_FRONTEND=noninteractive RUN set -eux && \ dnf update -y && \ - dnf install -y clang ninja-build cmake meson gtk4-devel glib2-devel libadwaita-devel desktop-file-utils libappstream-glib util-linux wget fuse fuse-libs file + dnf install -y clang ninja-build cmake meson gtk4-devel glib2-devel libadwaita-devel desktop-file-utils libappstream-glib util-linux wget fuse fuse-libs file sqlite sqlite-devel RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable --profile minimal ENV PATH="/root/.cargo/bin:${PATH}" @@ -12,6 +12,8 @@ ENV PATH="/root/.cargo/bin:${PATH}" WORKDIR /app COPY . /app +ENV SQLITE3_STATIC=1 + RUN cd /app/burrow-gtk/ && \ ./build-aux/build_appimage.sh diff --git a/burrow-gtk/build-aux/build_appimage.sh b/burrow-gtk/build-aux/build_appimage.sh index cd58c17..f054cd9 100755 --- a/burrow-gtk/build-aux/build_appimage.sh +++ b/burrow-gtk/build-aux/build_appimage.sh @@ -22,6 +22,8 @@ elif [ "$ARCHITECTURE" == "aarch64" ]; then chmod a+x /tmp/linuxdeploy fi + +CFLAGS="-I/usr/local/include -I/usr/include/$MUSL_TARGET -fPIE" meson setup $BURROW_GTK_BUILD --bindir bin --prefix /usr --buildtype $BURROW_BUILD_TYPE meson compile -C $BURROW_GTK_BUILD DESTDIR=AppDir meson install -C $BURROW_GTK_BUILD diff --git a/burrow/Cargo.toml b/burrow/Cargo.toml index 4e7688b..0c816f8 100644 --- a/burrow/Cargo.toml +++ b/burrow/Cargo.toml @@ -10,7 +10,15 @@ crate-type = ["lib", "staticlib"] [dependencies] anyhow = "1.0" -tokio = { version = "1.21", features = ["rt", "macros", "sync", "io-util", "rt-multi-thread", "time", "tracing"] } +tokio = { version = "1.21", features = [ + "rt", + "macros", + "sync", + "io-util", + "rt-multi-thread", + "time", + "tracing", +] } tun = { version = "0.1", path = "../tun", features = ["serde", "tokio"] } clap = { version = "4.4", features = ["derive"] } tracing = "0.1" @@ -25,7 +33,10 @@ chacha20poly1305 = "0.10" rand = "0.8" rand_core = "0.6" aead = "0.5" -x25519-dalek = { version = "2.0", features = ["reusable_secrets", "static_secrets"] } +x25519-dalek = { version = "2.0", features = [ + "reusable_secrets", + "static_secrets", +] } ring = "0.17" parking_lot = "0.12" hmac = "0.12" @@ -37,9 +48,12 @@ async-channel = "2.1" schemars = "0.8" futures = "0.3.28" once_cell = "1.19" -console-subscriber = { version = "0.2.0" , optional = true } +console-subscriber = { version = "0.2.0", optional = true } console = "0.15.8" +[dependencies.rusqlite] +version = "0.31.0" + [target.'cfg(target_os = "linux")'.dependencies] caps = "0.5" libsystemd = "0.7" @@ -47,6 +61,7 @@ tracing-journald = "0.3" [target.'cfg(target_vendor = "apple")'.dependencies] nix = { version = "0.27" } +rusqlite = { version = "0.31.0", features = ["bundled"] } [dev-dependencies] insta = { version = "1.32", features = ["yaml"] } @@ -62,3 +77,4 @@ pre_uninstall_script = "../package/rpm/pre_uninstall" [features] tokio-console = ["dep:console-subscriber"] +bundled = ["rusqlite/bundled"] diff --git a/burrow/burrow.db b/burrow/burrow.db new file mode 100644 index 0000000000000000000000000000000000000000..c5b6e2c614ecb4db4c264f50691b6f2cea99772a GIT binary patch literal 20480 zcmWFz^vNtqRY=P(%1ta$FlG>7U}R))P*7lCU=U|uU|?lH0A>aT1{MUDff0#~iz&{a zSJuG`(#R{q!1sc08t)Wd5nPH##YaP6Gz3ONU^E0qLtr!nMnhmU1V%$(Gz3ONfZicc z$HFcyEzQ^%T#}fSlbV-WQl4Lw4W(F}gIpa$TopnboqSvspn?h-TnbQ-nOBlpl$MyB z8lRb>;OQ5l5ajCS8szHd>>8|4o*oaE*2qlJRPgsx2n}!n8RzU6?Cj{`3N}Wwv7Q<1 zfaXxJ1Ip9m3sO^ypcD&=1E7LbbAS%m1t7nq=A{(mXXceCgt$h8DERq@DENi?_#os9 zN|SOjljE~fD{-kv%*n|wPfdx>EGWjMq@XCZI3uwrH3e=C*nZ6bCN^HWN82kl9QqrXkB9 z2QfHiUEN)S6as=geI0`$6}(*|6&yoD{5}1ggIs-G{X!4{1#$w|{|KR+%;J*Ny!e9r zq7qOV0hxr5%q=O!6f7vpEK4j&g$EOs2uVyyDM~HI8Pq9xXi|`n2KCLkcwHFyck!3- z>+!wdTf`T`C&qh$w~N<>-uZ6SzR?gE4S~@R7!85Z5Eu=C(GVC7fzc2c4S~@R80;b7 z!o|VBz|4@U&dYEr$KN#|EG0iT)hDDP#M7y)%s3>n*efVK)xe{`($6khIH_U^2USdAr-~_TR567W$rN|LLQhA3=b(zL9R1|XAY71JH1mFA{7sYRJyiIoQ0`5rzQLB0iH+K#3bj)4Xl&R*Ju=HcZQ zhK~MaAtqSUX|Z`ug??_jc2R1Wt8borUSVowq<>&`m5YU0o{@HXWL|}#uVrO=rh!Ga zZ6hlS#2qXH?G9#$JD3OB9ZV2+Fb%LfSQyzjLFL%MIs?@IXAq!ac|B_MXb6mkz-S1J ihQMeDjE2By2#kinXb6mkz-S1JhQMeDjE2B~hX4R^(?&G_ literal 0 HcmV?d00001 diff --git a/burrow/src/daemon/apple.rs b/burrow/src/daemon/apple.rs index 9460613..c60f131 100644 --- a/burrow/src/daemon/apple.rs +++ b/burrow/src/daemon/apple.rs @@ -18,7 +18,7 @@ static BURROW_NOTIFY: OnceCell> = OnceCell::new(); static BURROW_HANDLE: OnceCell = OnceCell::new(); #[no_mangle] -pub unsafe extern "C" fn spawn_in_process(path: *const c_char) { +pub unsafe extern "C" fn spawn_in_process(path: *const c_char, db_path: *const c_char) { crate::tracing::initialize(); let notify = BURROW_NOTIFY.get_or_init(|| Arc::new(Notify::new())); @@ -28,6 +28,11 @@ pub unsafe extern "C" fn spawn_in_process(path: *const c_char) { } else { Some(PathBuf::from(CStr::from_ptr(path).to_str().unwrap())) }; + let db_path_buf = if db_path.is_null() { + None + } else { + Some(PathBuf::from(CStr::from_ptr(db_path).to_str().unwrap())) + }; let sender = notify.clone(); let (handle_tx, handle_rx) = tokio::sync::oneshot::channel(); @@ -40,7 +45,12 @@ pub unsafe extern "C" fn spawn_in_process(path: *const c_char) { .unwrap(); handle_tx.send(runtime.handle().clone()).unwrap(); runtime.block_on(async { - let result = daemon_main(path_buf.as_deref(), Some(sender.clone())).await; + let result = daemon_main( + path_buf.as_deref(), + db_path_buf.as_deref(), + Some(sender.clone()), + ) + .await; if let Err(error) = result.as_ref() { error!("Burrow thread exited: {}", error); } diff --git a/burrow/src/daemon/instance.rs b/burrow/src/daemon/instance.rs index 0d3e726..bc506bd 100644 --- a/burrow/src/daemon/instance.rs +++ b/burrow/src/daemon/instance.rs @@ -1,4 +1,7 @@ -use std::sync::Arc; +use std::{ + path::{Path, PathBuf}, + sync::Arc, +}; use anyhow::Result; use tokio::{sync::RwLock, task::JoinHandle}; @@ -6,11 +9,16 @@ use tracing::{debug, info, warn}; use tun::tokio::TunInterface; use crate::{ - daemon::{ - command::DaemonCommand, - response::{DaemonResponse, DaemonResponseData, ServerConfig, ServerInfo}, + daemon::rpc::{ + DaemonCommand, + DaemonNotification, + DaemonResponse, + DaemonResponseData, + ServerConfig, + ServerInfo, }, - wireguard::Interface, + database::{get_connection, load_interface}, + wireguard::{Config, Interface}, }; enum RunState { @@ -21,8 +29,11 @@ enum RunState { pub struct DaemonInstance { rx: async_channel::Receiver, sx: async_channel::Sender, + subx: async_channel::Sender, tun_interface: Arc>>, wg_interface: Arc>, + config: Arc>, + db_path: Option, wg_state: RunState, } @@ -30,13 +41,19 @@ impl DaemonInstance { pub fn new( rx: async_channel::Receiver, sx: async_channel::Sender, + subx: async_channel::Sender, wg_interface: Arc>, + config: Arc>, + db_path: Option<&Path>, ) -> Self { Self { rx, sx, + subx, wg_interface, tun_interface: Arc::new(RwLock::new(None)), + config, + db_path: db_path.map(|p| p.to_owned()), wg_state: RunState::Idle, } } @@ -59,24 +76,13 @@ impl DaemonInstance { self.tun_interface = self.wg_interface.read().await.get_tun(); debug!("tun_interface set: {:?}", self.tun_interface); - debug!("Cloning wg_interface"); let tmp_wg = self.wg_interface.clone(); - debug!("wg_interface cloned"); - - debug!("Spawning run task"); let run_task = tokio::spawn(async move { - debug!("Running wg_interface"); let twlock = tmp_wg.read().await; - debug!("wg_interface read lock acquired"); twlock.run().await }); - debug!("Run task spawned: {:?}", run_task); - - debug!("Setting wg_state to Running"); self.wg_state = RunState::Running(run_task); - debug!("wg_state set to Running"); - info!("Daemon started tun interface"); } } @@ -99,6 +105,17 @@ impl DaemonInstance { DaemonCommand::ServerConfig => { Ok(DaemonResponseData::ServerConfig(ServerConfig::default())) } + DaemonCommand::ReloadConfig(interface_id) => { + let conn = get_connection(self.db_path.as_deref())?; + let cfig = load_interface(&conn, &interface_id)?; + *self.config.write().await = cfig; + self.subx + .send(DaemonNotification::ConfigChange(ServerConfig::try_from( + &self.config.read().await.to_owned(), + )?)) + .await?; + Ok(DaemonResponseData::None) + } } } diff --git a/burrow/src/daemon/mod.rs b/burrow/src/daemon/mod.rs index 2a971dd..4469e90 100644 --- a/burrow/src/daemon/mod.rs +++ b/burrow/src/daemon/mod.rs @@ -1,40 +1,53 @@ use std::{path::Path, sync::Arc}; pub mod apple; -mod command; mod instance; mod net; -mod response; +pub mod rpc; use anyhow::Result; -pub use command::{DaemonCommand, DaemonStartOptions}; use instance::DaemonInstance; pub use net::{DaemonClient, Listener}; -pub use response::{DaemonResponse, DaemonResponseData, ServerInfo}; +pub use rpc::{DaemonCommand, DaemonResponseData, DaemonStartOptions}; use tokio::sync::{Notify, RwLock}; use tracing::{error, info}; -use crate::wireguard::{Config, Interface}; +use crate::{ + database::{get_connection, load_interface}, + wireguard::Interface, +}; -pub async fn daemon_main(path: Option<&Path>, notify_ready: Option>) -> Result<()> { +pub async fn daemon_main( + socket_path: Option<&Path>, + db_path: Option<&Path>, + notify_ready: Option>, +) -> Result<()> { let (commands_tx, commands_rx) = async_channel::unbounded(); let (response_tx, response_rx) = async_channel::unbounded(); + let (subscribe_tx, subscribe_rx) = async_channel::unbounded(); - let listener = if let Some(path) = path { + let listener = if let Some(path) = socket_path { info!("Creating listener... {:?}", path); - Listener::new_with_path(commands_tx, response_rx, path) + Listener::new_with_path(commands_tx, response_rx, subscribe_rx, path) } else { info!("Creating listener..."); - Listener::new(commands_tx, response_rx) + Listener::new(commands_tx, response_rx, subscribe_rx) }; if let Some(n) = notify_ready { n.notify_one() } let listener = listener?; - - let config = Config::default(); - let iface: Interface = config.try_into()?; - let mut instance = DaemonInstance::new(commands_rx, response_tx, Arc::new(RwLock::new(iface))); + let conn = get_connection(db_path)?; + let config = load_interface(&conn, "1")?; + let iface: Interface = config.clone().try_into()?; + let mut instance = DaemonInstance::new( + commands_rx, + response_tx, + subscribe_tx, + Arc::new(RwLock::new(iface)), + Arc::new(RwLock::new(config)), + db_path, + ); info!("Starting daemon..."); diff --git a/burrow/src/daemon/net/mod.rs b/burrow/src/daemon/net/mod.rs index fe35bae..242f479 100644 --- a/burrow/src/daemon/net/mod.rs +++ b/burrow/src/daemon/net/mod.rs @@ -1,6 +1,7 @@ -use serde::{Deserialize, Serialize}; -use super::DaemonCommand; + + + #[cfg(target_family = "unix")] mod unix; @@ -14,8 +15,4 @@ mod windows; #[cfg(target_os = "windows")] pub use windows::{DaemonClient, Listener}; -#[derive(Clone, Serialize, Deserialize)] -pub struct DaemonRequest { - pub id: u64, - pub command: DaemonCommand, -} + diff --git a/burrow/src/daemon/net/unix.rs b/burrow/src/daemon/net/unix.rs index 26e901d..70c4207 100644 --- a/burrow/src/daemon/net/unix.rs +++ b/burrow/src/daemon/net/unix.rs @@ -10,8 +10,14 @@ use tokio::{ }; use tracing::{debug, error, info}; -use super::*; -use crate::daemon::{DaemonCommand, DaemonResponse, DaemonResponseData}; +use crate::daemon::rpc::{ + DaemonCommand, + DaemonMessage, + DaemonNotification, + DaemonRequest, + DaemonResponse, + DaemonResponseData, +}; #[cfg(not(target_vendor = "apple"))] const UNIX_SOCKET_PATH: &str = "/run/burrow.sock"; @@ -19,10 +25,17 @@ const UNIX_SOCKET_PATH: &str = "/run/burrow.sock"; #[cfg(target_vendor = "apple")] const UNIX_SOCKET_PATH: &str = "burrow.sock"; -#[derive(Debug)] +fn get_socket_path() -> String { + if std::env::var("BURROW_SOCKET_PATH").is_ok() { + return std::env::var("BURROW_SOCKET_PATH").unwrap(); + } + UNIX_SOCKET_PATH.to_string() +} + pub struct Listener { cmd_tx: async_channel::Sender, rsp_rx: async_channel::Receiver, + sub_chan: async_channel::Receiver, inner: UnixListener, } @@ -31,9 +44,11 @@ impl Listener { pub fn new( cmd_tx: async_channel::Sender, rsp_rx: async_channel::Receiver, + sub_chan: async_channel::Receiver, ) -> Self { - let path = Path::new(OsStr::new(UNIX_SOCKET_PATH)); - Self::new_with_path(cmd_tx, rsp_rx, path)? + let socket_path = get_socket_path(); + let path = Path::new(OsStr::new(&socket_path)); + Self::new_with_path(cmd_tx, rsp_rx, sub_chan, path)? } #[throws] @@ -41,10 +56,16 @@ impl Listener { pub fn new_with_path( cmd_tx: async_channel::Sender, rsp_rx: async_channel::Receiver, + sub_chan: async_channel::Receiver, path: &Path, ) -> Self { let inner = listener_from_path_or_fd(&path, raw_fd())?; - Self { cmd_tx, rsp_rx, inner } + Self { + cmd_tx, + rsp_rx, + sub_chan, + inner, + } } #[throws] @@ -52,10 +73,16 @@ impl Listener { pub fn new_with_path( cmd_tx: async_channel::Sender, rsp_rx: async_channel::Receiver, + sub_chan: async_channel::Receiver, path: &Path, ) -> Self { let inner = listener_from_path(path)?; - Self { cmd_tx, rsp_rx, inner } + Self { + cmd_tx, + rsp_rx, + inner, + sub_chan, + } } pub async fn run(&self) -> Result<()> { @@ -64,9 +91,10 @@ impl Listener { let (stream, _) = self.inner.accept().await?; let cmd_tx = self.cmd_tx.clone(); let rsp_rxc = self.rsp_rx.clone(); + let sub_chan = self.sub_chan.clone(); tokio::task::spawn(async move { info!("Got connection: {:?}", stream); - Self::stream(stream, cmd_tx, rsp_rxc).await; + Self::stream(stream, cmd_tx, rsp_rxc, sub_chan).await; }); } } @@ -75,34 +103,46 @@ impl Listener { stream: UnixStream, cmd_tx: async_channel::Sender, rsp_rxc: async_channel::Receiver, + sub_chan: async_channel::Receiver, ) { let mut stream = stream; let (mut read_stream, mut write_stream) = stream.split(); let buf_reader = BufReader::new(&mut read_stream); let mut lines = buf_reader.lines(); - while let Ok(Some(line)) = lines.next_line().await { - info!("Line: {}", line); - let mut res: DaemonResponse = DaemonResponseData::None.into(); - let req = match serde_json::from_str::(&line) { - Ok(req) => Some(req), - Err(e) => { - res.result = Err(e.to_string()); - error!("Failed to parse request: {}", e); - None - } - }; - let mut res = serde_json::to_string(&res).unwrap(); - res.push('\n'); + loop { + tokio::select! { + Ok(Some(line)) = lines.next_line() => { + info!("Line: {}", line); + let mut res: DaemonResponse = DaemonResponseData::None.into(); + let req = match serde_json::from_str::(&line) { + Ok(req) => Some(req), + Err(e) => { + res.result = Err(e.to_string()); + error!("Failed to parse request: {}", e); + None + } + }; - if let Some(req) = req { - cmd_tx.send(req.command).await.unwrap(); - let res = rsp_rxc.recv().await.unwrap().with_id(req.id); - let mut retres = serde_json::to_string(&res).unwrap(); - retres.push('\n'); - info!("Sending response: {}", retres); - write_stream.write_all(retres.as_bytes()).await.unwrap(); - } else { - write_stream.write_all(res.as_bytes()).await.unwrap(); + let res = serde_json::to_string(&DaemonMessage::from(res)).unwrap(); + + if let Some(req) = req { + cmd_tx.send(req.command).await.unwrap(); + let res = rsp_rxc.recv().await.unwrap().with_id(req.id); + let mut payload = serde_json::to_string(&DaemonMessage::from(res)).unwrap(); + payload.push('\n'); + info!("Sending response: {}", payload); + write_stream.write_all(payload.as_bytes()).await.unwrap(); + } else { + write_stream.write_all(res.as_bytes()).await.unwrap(); + } + } + Ok(cmd) = sub_chan.recv() => { + info!("Got subscription command: {:?}", cmd); + let msg = DaemonMessage::from(cmd); + let mut payload = serde_json::to_string(&msg).unwrap(); + payload.push('\n'); + write_stream.write_all(payload.as_bytes()).await.unwrap(); + } } } } @@ -176,7 +216,8 @@ pub struct DaemonClient { impl DaemonClient { pub async fn new() -> Result { - let path = Path::new(OsStr::new(UNIX_SOCKET_PATH)); + let socket_path = get_socket_path(); + let path = Path::new(OsStr::new(&socket_path)); Self::new_with_path(path).await } diff --git a/burrow/src/daemon/rpc/mod.rs b/burrow/src/daemon/rpc/mod.rs new file mode 100644 index 0000000..4146e71 --- /dev/null +++ b/burrow/src/daemon/rpc/mod.rs @@ -0,0 +1,40 @@ +pub mod notification; +pub mod request; +pub mod response; + +pub use notification::DaemonNotification; +pub use request::{DaemonCommand, DaemonRequest, DaemonStartOptions}; +pub use response::{DaemonResponse, DaemonResponseData, ServerConfig, ServerInfo}; +use serde::{Deserialize, Serialize}; + +/// The `Message` object contains either a `DaemonRequest` or a `DaemonResponse` to be serialized / deserialized +/// for our IPC communication. Our IPC protocol is based on jsonrpc (https://www.jsonrpc.org/specification#overview), +/// but deviates from it in a few ways: +/// - We differentiate Notifications from Requests explicitly. +/// - We have a "type" field to differentiate between a request, a response, and a notification. +/// - The params field may receive any json value(such as a string), not just an object or an array. +#[derive(Serialize, Deserialize)] +#[serde(tag = "type")] +pub enum DaemonMessage { + Request(DaemonRequest), + Response(DaemonResponse), + Notification(DaemonNotification), +} + +impl From for DaemonMessage { + fn from(request: DaemonRequest) -> Self { + DaemonMessage::Request(request) + } +} + +impl From for DaemonMessage { + fn from(response: DaemonResponse) -> Self { + DaemonMessage::Response(response) + } +} + +impl From for DaemonMessage { + fn from(notification: DaemonNotification) -> Self { + DaemonMessage::Notification(notification) + } +} diff --git a/burrow/src/daemon/rpc/notification.rs b/burrow/src/daemon/rpc/notification.rs new file mode 100644 index 0000000..135b0e4 --- /dev/null +++ b/burrow/src/daemon/rpc/notification.rs @@ -0,0 +1,11 @@ +use rpc::ServerConfig; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +use crate::daemon::rpc; + +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +#[serde(tag = "method", content = "params")] +pub enum DaemonNotification { + ConfigChange(ServerConfig), +} diff --git a/burrow/src/daemon/command.rs b/burrow/src/daemon/rpc/request.rs similarity index 82% rename from burrow/src/daemon/command.rs rename to burrow/src/daemon/rpc/request.rs index 53b4108..e9480aa 100644 --- a/burrow/src/daemon/command.rs +++ b/burrow/src/daemon/rpc/request.rs @@ -3,11 +3,13 @@ use serde::{Deserialize, Serialize}; use tun::TunOptions; #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +#[serde(tag="method", content="params")] pub enum DaemonCommand { Start(DaemonStartOptions), ServerInfo, ServerConfig, Stop, + ReloadConfig(String), } #[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema)] @@ -15,6 +17,13 @@ pub struct DaemonStartOptions { pub tun: TunOptions, } +#[derive(Clone, Serialize, Deserialize)] +pub struct DaemonRequest { + pub id: u64, + #[serde(flatten)] + pub command: DaemonCommand, +} + #[test] fn test_daemoncommand_serialization() { insta::assert_snapshot!(serde_json::to_string(&DaemonCommand::Start( diff --git a/burrow/src/daemon/response.rs b/burrow/src/daemon/rpc/response.rs similarity index 89% rename from burrow/src/daemon/response.rs rename to burrow/src/daemon/rpc/response.rs index 37ee5d9..61c9c50 100644 --- a/burrow/src/daemon/response.rs +++ b/burrow/src/daemon/rpc/response.rs @@ -2,6 +2,8 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use tun::TunInterface; +use crate::wireguard::Config; + #[derive(Clone, Serialize, Deserialize, Debug, JsonSchema)] pub struct DaemonResponse { // Error types can't be serialized, so this is the second best option. @@ -62,6 +64,18 @@ pub struct ServerConfig { pub mtu: Option, } +impl TryFrom<&Config> for ServerConfig { + type Error = anyhow::Error; + + fn try_from(config: &Config) -> anyhow::Result { + Ok(ServerConfig { + address: config.interface.address.clone(), + name: None, + mtu: config.interface.mtu.map(|mtu| mtu as i32), + }) + } +} + impl Default for ServerConfig { fn default() -> Self { Self { @@ -73,6 +87,7 @@ impl Default for ServerConfig { } #[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] +#[serde(tag = "type")] pub enum DaemonResponseData { ServerInfo(ServerInfo), ServerConfig(ServerConfig), diff --git a/burrow/src/daemon/rpc/snapshots/burrow__daemon__rpc__request__daemoncommand_serialization-2.snap b/burrow/src/daemon/rpc/snapshots/burrow__daemon__rpc__request__daemoncommand_serialization-2.snap new file mode 100644 index 0000000..01ec8a7 --- /dev/null +++ b/burrow/src/daemon/rpc/snapshots/burrow__daemon__rpc__request__daemoncommand_serialization-2.snap @@ -0,0 +1,5 @@ +--- +source: burrow/src/daemon/rpc/request.rs +expression: "serde_json::to_string(&DaemonCommand::Start(DaemonStartOptions {\n tun: TunOptions { ..TunOptions::default() },\n })).unwrap()" +--- +{"method":"Start","params":{"tun":{"name":null,"no_pi":false,"tun_excl":false,"tun_retrieve":false,"address":[]}}} diff --git a/burrow/src/daemon/rpc/snapshots/burrow__daemon__rpc__request__daemoncommand_serialization-3.snap b/burrow/src/daemon/rpc/snapshots/burrow__daemon__rpc__request__daemoncommand_serialization-3.snap new file mode 100644 index 0000000..a6a0466 --- /dev/null +++ b/burrow/src/daemon/rpc/snapshots/burrow__daemon__rpc__request__daemoncommand_serialization-3.snap @@ -0,0 +1,5 @@ +--- +source: burrow/src/daemon/rpc/request.rs +expression: "serde_json::to_string(&DaemonCommand::ServerInfo).unwrap()" +--- +{"method":"ServerInfo"} diff --git a/burrow/src/daemon/rpc/snapshots/burrow__daemon__rpc__request__daemoncommand_serialization-4.snap b/burrow/src/daemon/rpc/snapshots/burrow__daemon__rpc__request__daemoncommand_serialization-4.snap new file mode 100644 index 0000000..f930051 --- /dev/null +++ b/burrow/src/daemon/rpc/snapshots/burrow__daemon__rpc__request__daemoncommand_serialization-4.snap @@ -0,0 +1,5 @@ +--- +source: burrow/src/daemon/rpc/request.rs +expression: "serde_json::to_string(&DaemonCommand::Stop).unwrap()" +--- +{"method":"Stop"} diff --git a/burrow/src/daemon/rpc/snapshots/burrow__daemon__rpc__request__daemoncommand_serialization-5.snap b/burrow/src/daemon/rpc/snapshots/burrow__daemon__rpc__request__daemoncommand_serialization-5.snap new file mode 100644 index 0000000..89dc42c --- /dev/null +++ b/burrow/src/daemon/rpc/snapshots/burrow__daemon__rpc__request__daemoncommand_serialization-5.snap @@ -0,0 +1,5 @@ +--- +source: burrow/src/daemon/rpc/request.rs +expression: "serde_json::to_string(&DaemonCommand::ServerConfig).unwrap()" +--- +{"method":"ServerConfig"} diff --git a/burrow/src/daemon/rpc/snapshots/burrow__daemon__rpc__request__daemoncommand_serialization.snap b/burrow/src/daemon/rpc/snapshots/burrow__daemon__rpc__request__daemoncommand_serialization.snap new file mode 100644 index 0000000..aeca659 --- /dev/null +++ b/burrow/src/daemon/rpc/snapshots/burrow__daemon__rpc__request__daemoncommand_serialization.snap @@ -0,0 +1,5 @@ +--- +source: burrow/src/daemon/rpc/request.rs +expression: "serde_json::to_string(&DaemonCommand::Start(DaemonStartOptions::default())).unwrap()" +--- +{"method":"Start","params":{"tun":{"name":null,"no_pi":false,"tun_excl":false,"tun_retrieve":false,"address":[]}}} diff --git a/burrow/src/daemon/rpc/snapshots/burrow__daemon__rpc__response__response_serialization-2.snap b/burrow/src/daemon/rpc/snapshots/burrow__daemon__rpc__response__response_serialization-2.snap new file mode 100644 index 0000000..d7bd712 --- /dev/null +++ b/burrow/src/daemon/rpc/snapshots/burrow__daemon__rpc__response__response_serialization-2.snap @@ -0,0 +1,5 @@ +--- +source: burrow/src/daemon/rpc/response.rs +expression: "serde_json::to_string(&DaemonResponse::new(Ok::(DaemonResponseData::ServerInfo(ServerInfo {\n name: Some(\"burrow\".to_string()),\n ip: None,\n mtu: Some(1500),\n }))))?" +--- +{"result":{"Ok":{"type":"ServerInfo","name":"burrow","ip":null,"mtu":1500}},"id":0} diff --git a/burrow/src/daemon/rpc/snapshots/burrow__daemon__rpc__response__response_serialization-3.snap b/burrow/src/daemon/rpc/snapshots/burrow__daemon__rpc__response__response_serialization-3.snap new file mode 100644 index 0000000..30068f3 --- /dev/null +++ b/burrow/src/daemon/rpc/snapshots/burrow__daemon__rpc__response__response_serialization-3.snap @@ -0,0 +1,5 @@ +--- +source: burrow/src/daemon/rpc/response.rs +expression: "serde_json::to_string(&DaemonResponse::new(Err::(\"error\".to_string())))?" +--- +{"result":{"Err":"error"},"id":0} diff --git a/burrow/src/daemon/rpc/snapshots/burrow__daemon__rpc__response__response_serialization-4.snap b/burrow/src/daemon/rpc/snapshots/burrow__daemon__rpc__response__response_serialization-4.snap new file mode 100644 index 0000000..c40db25 --- /dev/null +++ b/burrow/src/daemon/rpc/snapshots/burrow__daemon__rpc__response__response_serialization-4.snap @@ -0,0 +1,5 @@ +--- +source: burrow/src/daemon/rpc/response.rs +expression: "serde_json::to_string(&DaemonResponse::new(Ok::(DaemonResponseData::ServerConfig(ServerConfig::default()))))?" +--- +{"result":{"Ok":{"type":"ServerConfig","address":["10.13.13.2"],"name":null,"mtu":null}},"id":0} diff --git a/burrow/src/daemon/rpc/snapshots/burrow__daemon__rpc__response__response_serialization.snap b/burrow/src/daemon/rpc/snapshots/burrow__daemon__rpc__response__response_serialization.snap new file mode 100644 index 0000000..31bd84b --- /dev/null +++ b/burrow/src/daemon/rpc/snapshots/burrow__daemon__rpc__response__response_serialization.snap @@ -0,0 +1,5 @@ +--- +source: burrow/src/daemon/rpc/response.rs +expression: "serde_json::to_string(&DaemonResponse::new(Ok::(DaemonResponseData::None)))?" +--- +{"result":{"Ok":{"type":"None"}},"id":0} diff --git a/burrow/src/database.rs b/burrow/src/database.rs new file mode 100644 index 0000000..0047b01 --- /dev/null +++ b/burrow/src/database.rs @@ -0,0 +1,145 @@ +use std::path::Path; + +use anyhow::Result; +use rusqlite::{params, Connection}; + +use crate::wireguard::config::{Config, Interface, Peer}; + +#[cfg(target_vendor = "apple")] +const DB_PATH: &str = "burrow.db"; + +#[cfg(not(target_vendor = "apple"))] +const DB_PATH: &str = "/var/lib/burrow/burrow.db"; + +const CREATE_WG_INTERFACE_TABLE: &str = "CREATE TABLE IF NOT EXISTS wg_interface ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT, + listen_port INTEGER, + mtu INTEGER, + private_key TEXT NOT NULL, + address TEXT NOT NULL, + dns TEXT NOT NULL +)"; + +const CREATE_WG_PEER_TABLE: &str = "CREATE TABLE IF NOT EXISTS wg_peer ( + interface_id INT REFERENCES wg_interface(id) ON UPDATE CASCADE, + endpoint TEXT NOT NULL, + public_key TEXT NOT NULL, + allowed_ips TEXT NOT NULL, + preshared_key TEXT +)"; + +const CREATE_NETWORK_TABLE: &str = "CREATE TABLE IF NOT EXISTS network ( + interface_id INT REFERENCES wg_interface(id) ON UPDATE CASCADE +)"; + +pub fn initialize_tables(conn: &Connection) -> Result<()> { + conn.execute(CREATE_WG_INTERFACE_TABLE, [])?; + conn.execute(CREATE_WG_PEER_TABLE, [])?; + conn.execute(CREATE_NETWORK_TABLE, [])?; + Ok(()) +} + +fn parse_lst(s: &str) -> Vec { + if s.is_empty() { + return vec![]; + } + s.split(',').map(|s| s.to_string()).collect() +} + +fn to_lst(v: &Vec) -> String { + v.iter() + .map(|s| s.to_string()) + .collect::>() + .join(",") +} + +pub fn load_interface(conn: &Connection, interface_id: &str) -> Result { + let iface = conn.query_row( + "SELECT private_key, dns, address, listen_port, mtu FROM wg_interface WHERE id = ?", + [&interface_id], + |row| { + let dns_rw: String = row.get(1)?; + let dns = parse_lst(&dns_rw); + let address_rw: String = row.get(2)?; + let address = parse_lst(&address_rw); + Ok(Interface { + private_key: row.get(0)?, + dns, + address, + mtu: row.get(4)?, + listen_port: row.get(3)?, + }) + }, + )?; + let mut peers_stmt = conn.prepare("SELECT public_key, preshared_key, allowed_ips, endpoint FROM wg_peer WHERE interface_id = ?")?; + let peers = peers_stmt + .query_map([&interface_id], |row| { + let preshared_key: Option = row.get(1)?; + let allowed_ips_rw: String = row.get(2)?; + let allowed_ips: Vec = + allowed_ips_rw.split(',').map(|s| s.to_string()).collect(); + Ok(Peer { + public_key: row.get(0)?, + preshared_key, + allowed_ips, + endpoint: row.get(3)?, + persistent_keepalive: None, + name: None, + }) + })? + .collect::>>()?; + Ok(Config { interface: iface, peers }) +} + +pub fn dump_interface(conn: &Connection, config: &Config) -> Result<()> { + let mut stmt = conn.prepare("INSERT INTO wg_interface (private_key, dns, address, listen_port, mtu) VALUES (?, ?, ?, ?, ?)")?; + let cif = &config.interface; + stmt.execute(params![ + cif.private_key, + to_lst(&cif.dns), + to_lst(&cif.address), + cif.listen_port, + cif.mtu + ])?; + let interface_id = conn.last_insert_rowid(); + let mut stmt = conn.prepare("INSERT INTO wg_peer (interface_id, public_key, preshared_key, allowed_ips, endpoint) VALUES (?, ?, ?, ?, ?)")?; + for peer in &config.peers { + stmt.execute(params![ + &interface_id, + &peer.public_key, + &peer.preshared_key, + &peer.allowed_ips.join(","), + &peer.endpoint + ])?; + } + Ok(()) +} + +pub fn get_connection(path: Option<&Path>) -> Result { + let p = path.unwrap_or_else(|| std::path::Path::new(DB_PATH)); + if !p.exists() { + let conn = Connection::open(p)?; + initialize_tables(&conn)?; + dump_interface(&conn, &Config::default())?; + return Ok(conn); + } + Ok(Connection::open(p)?) +} + +#[cfg(test)] +mod tests { + use std::path::Path; + + use super::*; + + #[test] + fn test_db() { + let conn = Connection::open_in_memory().unwrap(); + initialize_tables(&conn).unwrap(); + let config = Config::default(); + dump_interface(&conn, &config).unwrap(); + let loaded = load_interface(&conn, "1").unwrap(); + assert_eq!(config, loaded); + } +} diff --git a/burrow/src/lib.rs b/burrow/src/lib.rs index c5406b2..d9ebf7e 100644 --- a/burrow/src/lib.rs +++ b/burrow/src/lib.rs @@ -3,16 +3,18 @@ pub mod wireguard; #[cfg(any(target_os = "linux", target_vendor = "apple"))] mod daemon; +#[cfg(any(target_os = "linux", target_vendor = "apple"))] +pub mod database; pub(crate) mod tracing; #[cfg(target_vendor = "apple")] pub use daemon::apple::spawn_in_process; #[cfg(any(target_os = "linux", target_vendor = "apple"))] pub use daemon::{ + rpc::DaemonResponse, + rpc::ServerInfo, DaemonClient, DaemonCommand, - DaemonResponse, DaemonResponseData, DaemonStartOptions, - ServerInfo, }; diff --git a/burrow/src/main.rs b/burrow/src/main.rs index 71d1c02..295373a 100644 --- a/burrow/src/main.rs +++ b/burrow/src/main.rs @@ -14,6 +14,9 @@ use tun::TunOptions; #[cfg(any(target_os = "linux", target_vendor = "apple"))] use crate::daemon::DaemonResponseData; +#[cfg(any(target_os = "linux", target_vendor = "apple"))] +pub mod database; + #[derive(Parser)] #[command(name = "Burrow")] #[command(author = "Hack Club ")] @@ -42,6 +45,14 @@ enum Commands { ServerInfo, /// Server config ServerConfig, + /// Reload Config + ReloadConfig(ReloadConfigArgs), +} + +#[derive(Args)] +struct ReloadConfigArgs { + #[clap(long, short)] + interface_id: String, } #[derive(Args)] @@ -69,13 +80,8 @@ async fn try_stop() -> Result<()> { } #[cfg(any(target_os = "linux", target_vendor = "apple"))] -async fn try_serverinfo() -> Result<()> { - let mut client = DaemonClient::new().await?; - let res = client.send_command(DaemonCommand::ServerInfo).await?; - match res.result { - Ok(DaemonResponseData::ServerInfo(si)) => { - println!("Got Result! {:?}", si); - } +fn handle_unexpected(res: Result) { + match res { Ok(DaemonResponseData::None) => { println!("Server not started.") } @@ -86,6 +92,17 @@ async fn try_serverinfo() -> Result<()> { println!("Error when retrieving from server: {}", e) } } +} + +#[cfg(any(target_os = "linux", target_vendor = "apple"))] +async fn try_serverinfo() -> Result<()> { + let mut client = DaemonClient::new().await?; + let res = client.send_command(DaemonCommand::ServerInfo).await?; + if let Ok(DaemonResponseData::ServerInfo(si)) = res.result { + println!("Got Result! {:?}", si); + } else { + handle_unexpected(res.result); + } Ok(()) } @@ -93,40 +110,25 @@ async fn try_serverinfo() -> Result<()> { async fn try_serverconfig() -> Result<()> { let mut client = DaemonClient::new().await?; let res = client.send_command(DaemonCommand::ServerConfig).await?; - match res.result { - Ok(DaemonResponseData::ServerConfig(cfig)) => { - println!("Got Result! {:?}", cfig); - } - Ok(DaemonResponseData::None) => { - println!("Server not started.") - } - Ok(res) => { - println!("Unexpected Response: {:?}", res) - } - Err(e) => { - println!("Error when retrieving from server: {}", e) - } + if let Ok(DaemonResponseData::ServerConfig(cfig)) = res.result { + println!("Got Result! {:?}", cfig); + } else { + handle_unexpected(res.result); } Ok(()) } -#[cfg(not(any(target_os = "linux", target_vendor = "apple")))] -async fn try_start() -> Result<()> { - Ok(()) -} - -#[cfg(not(any(target_os = "linux", target_vendor = "apple")))] -async fn try_stop() -> Result<()> { - Ok(()) -} - -#[cfg(not(any(target_os = "linux", target_vendor = "apple")))] -async fn try_serverinfo() -> Result<()> { - Ok(()) -} - -#[cfg(not(any(target_os = "linux", target_vendor = "apple")))] -async fn try_serverconfig() -> Result<()> { +#[cfg(any(target_os = "linux", target_vendor = "apple"))] +async fn try_reloadconfig(interface_id: String) -> Result<()> { + let mut client = DaemonClient::new().await?; + let res = client + .send_command(DaemonCommand::ReloadConfig(interface_id)) + .await?; + if let Ok(DaemonResponseData::ServerConfig(cfig)) = res.result { + println!("Got Result! {:?}", cfig); + } else { + handle_unexpected(res.result); + } Ok(()) } @@ -139,9 +141,10 @@ async fn main() -> Result<()> { match &cli.command { Commands::Start(..) => try_start().await?, Commands::Stop => try_stop().await?, - Commands::Daemon(_) => daemon::daemon_main(None, None).await?, + Commands::Daemon(_) => daemon::daemon_main(None, None, None).await?, Commands::ServerInfo => try_serverinfo().await?, Commands::ServerConfig => try_serverconfig().await?, + Commands::ReloadConfig(args) => try_reloadconfig(args.interface_id.clone()).await?, } Ok(()) diff --git a/burrow/src/wireguard/config.rs b/burrow/src/wireguard/config.rs index ed7b3cd..bd86a9f 100644 --- a/burrow/src/wireguard/config.rs +++ b/burrow/src/wireguard/config.rs @@ -31,6 +31,7 @@ fn parse_public_key(string: &str) -> PublicKey { /// A raw version of Peer Config that can be used later to reflect configuration files. /// This should be later converted to a `WgPeer`. /// Refers to https://github.com/pirate/wireguard-docs?tab=readme-ov-file#overview +#[derive(Debug, Clone, Eq, PartialEq)] pub struct Peer { pub public_key: String, pub preshared_key: Option, @@ -40,6 +41,7 @@ pub struct Peer { pub name: Option, } +#[derive(Debug, Clone, Eq, PartialEq)] pub struct Interface { pub private_key: String, pub address: Vec, @@ -48,6 +50,7 @@ pub struct Interface { pub mtu: Option, } +#[derive(Debug, Clone, Eq, PartialEq)] pub struct Config { pub peers: Vec, pub interface: Interface, // Support for multiple interfaces? diff --git a/burrow/src/wireguard/iface.rs b/burrow/src/wireguard/iface.rs index 6097082..84b5489 100755 --- a/burrow/src/wireguard/iface.rs +++ b/burrow/src/wireguard/iface.rs @@ -1,21 +1,26 @@ -use std::{net::IpAddr, sync::Arc}; -use std::ops::Deref; +use std::{net::IpAddr, ops::Deref, sync::Arc}; use anyhow::Error; use fehler::throws; use futures::future::join_all; use ip_network_table::IpNetworkTable; -use tokio::sync::{RwLock, Notify}; +use tokio::sync::{Notify, RwLock}; use tracing::{debug, error}; use tun::tokio::TunInterface; use super::{noise::Tunnel, Peer, PeerPcb}; -struct IndexedPcbs { +pub struct IndexedPcbs { pcbs: Vec>, allowed_ips: IpNetworkTable, } +impl Default for IndexedPcbs { + fn default() -> Self { + Self::new() + } +} + impl IndexedPcbs { pub fn new() -> Self { Self { @@ -49,12 +54,12 @@ impl FromIterator for IndexedPcbs { enum IfaceStatus { Running, - Idle + Idle, } pub struct Interface { - tun: Arc>>, - pcbs: Arc, + pub tun: Arc>>, + pub pcbs: Arc, status: Arc>, stop_notifier: Arc, } @@ -73,7 +78,12 @@ impl Interface { .collect::>()?; let pcbs = Arc::new(pcbs); - Self { pcbs, tun: Arc::new(RwLock::new(None)), status: Arc::new(RwLock::new(IfaceStatus::Idle)), stop_notifier: Arc::new(Notify::new()) } + Self { + pcbs, + tun: Arc::new(RwLock::new(None)), + status: Arc::new(RwLock::new(IfaceStatus::Idle)), + stop_notifier: Arc::new(Notify::new()), + } } pub async fn set_tun(&self, tun: TunInterface) { @@ -87,7 +97,7 @@ impl Interface { self.tun.clone() } - pub async fn remove_tun(&self){ + pub async fn remove_tun(&self) { let mut st = self.status.write().await; self.stop_notifier.notify_waiters(); *st = IfaceStatus::Idle; @@ -95,9 +105,7 @@ impl Interface { pub async fn run(&self) -> anyhow::Result<()> { let pcbs = self.pcbs.clone(); - let tun = self - .tun - .clone(); + let tun = self.tun.clone(); let status = self.status.clone(); let stop_notifier = self.stop_notifier.clone(); log::info!("Starting interface"); @@ -153,9 +161,7 @@ impl Interface { }; let mut tsks = vec![]; - let tun = self - .tun - .clone(); + let tun = self.tun.clone(); let outgoing = tokio::task::spawn(outgoing); tsks.push(outgoing); debug!("preparing to spawn read tasks"); diff --git a/burrow/src/wireguard/mod.rs b/burrow/src/wireguard/mod.rs index 15563fb..4c70a7f 100755 --- a/burrow/src/wireguard/mod.rs +++ b/burrow/src/wireguard/mod.rs @@ -1,4 +1,4 @@ -mod config; +pub mod config; mod iface; mod noise; mod pcb; diff --git a/tun/src/unix/apple/mod.rs b/tun/src/unix/apple/mod.rs index 6e859ca..74e93eb 100644 --- a/tun/src/unix/apple/mod.rs +++ b/tun/src/unix/apple/mod.rs @@ -1,11 +1,13 @@ -use std::{io::{Error, IoSlice}, mem, net::{Ipv4Addr, SocketAddrV4}, os::fd::{AsRawFd, FromRawFd, RawFd}, ptr}; -use std::net::{IpAddr, Ipv6Addr, SocketAddrV6}; -use std::ptr::addr_of; +use std::{ + io::{Error, IoSlice}, + mem, + net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddrV4}, + os::fd::{AsRawFd, FromRawFd, RawFd}, +}; use byteorder::{ByteOrder, NetworkEndian}; use fehler::throws; -use libc::{c_char, iovec, writev, AF_INET, AF_INET6, sockaddr_in6}; -use nix::sys::socket::SockaddrIn6; +use libc::{c_char, iovec, writev, AF_INET, AF_INET6}; use socket2::{Domain, SockAddr, Socket, Type}; use tracing::{self, instrument}; @@ -69,11 +71,11 @@ impl TunInterface { #[throws] fn configure(&self, options: TunOptions) { - for addr in options.address{ + for addr in options.address { if let Ok(addr) = addr.parse::() { match addr { - IpAddr::V4(addr) => {self.set_ipv4_addr(addr)?} - IpAddr::V6(addr) => {self.set_ipv6_addr(addr)?} + IpAddr::V4(addr) => self.set_ipv4_addr(addr)?, + IpAddr::V6(addr) => self.set_ipv6_addr(addr)?, } } } @@ -146,7 +148,7 @@ impl TunInterface { } #[throws] - pub fn set_ipv6_addr(&self, addr: Ipv6Addr) { + pub fn set_ipv6_addr(&self, _addr: Ipv6Addr) { // let addr = SockAddr::from(SocketAddrV6::new(addr, 0, 0, 0)); // println!("addr: {:?}", addr); // let mut iff = self.in6_ifreq()?; From bca07c33b8bf9ef44f4a57e7a1fa6fdc15ba4790 Mon Sep 17 00:00:00 2001 From: Conrad Kramer Date: Sat, 25 May 2024 09:06:53 -0700 Subject: [PATCH 05/15] Start authentication flow --- .gitignore | 1 + .../NetworkExtension/libburrow/build-rust.sh | 2 +- Cargo.lock | 1153 +++++++++++------ Cargo.toml | 5 + Dockerfile | 55 +- burrow/Cargo.toml | 15 +- burrow/src/auth/client.rs | 24 + burrow/src/auth/mod.rs | 2 + burrow/src/auth/server/db.rs | 89 ++ burrow/src/auth/server/mod.rs | 62 + burrow/src/auth/server/providers/mod.rs | 8 + burrow/src/auth/server/providers/slack.rs | 102 ++ burrow/src/lib.rs | 2 + burrow/src/main.rs | 12 +- tun/Cargo.toml | 9 +- 15 files changed, 1104 insertions(+), 437 deletions(-) create mode 100644 burrow/src/auth/client.rs create mode 100644 burrow/src/auth/mod.rs create mode 100644 burrow/src/auth/server/db.rs create mode 100644 burrow/src/auth/server/mod.rs create mode 100644 burrow/src/auth/server/providers/mod.rs create mode 100644 burrow/src/auth/server/providers/slack.rs diff --git a/.gitignore b/.gitignore index dc886ed..96b2507 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ xcuserdata # Rust target/ +.env .DS_STORE .idea/ diff --git a/Apple/NetworkExtension/libburrow/build-rust.sh b/Apple/NetworkExtension/libburrow/build-rust.sh index fffa0d0..e7204a5 100755 --- a/Apple/NetworkExtension/libburrow/build-rust.sh +++ b/Apple/NetworkExtension/libburrow/build-rust.sh @@ -70,7 +70,7 @@ fi # Run cargo without the various environment variables set by Xcode. # Those variables can confuse cargo and the build scripts it runs. -env -i PATH="$CARGO_PATH" CARGO_TARGET_DIR="${CONFIGURATION_TEMP_DIR}/target" cargo build "${CARGO_ARGS[@]}" +env -i PATH="$CARGO_PATH" CARGO_TARGET_DIR="${CONFIGURATION_TEMP_DIR}/target" IPHONEOS_DEPLOYMENT_TARGET="$IPHONEOS_DEPLOYMENT_TARGET" MACOSX_DEPLOYMENT_TARGET="$MACOSX_DEPLOYMENT_TARGET" cargo build "${CARGO_ARGS[@]}" mkdir -p "${BUILT_PRODUCTS_DIR}" diff --git a/Cargo.lock b/Cargo.lock index 628e996..ce263f9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,9 +4,9 @@ version = 3 [[package]] name = "addr2line" -version = "0.21.0" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678" dependencies = [ "gimli", ] @@ -29,9 +29,9 @@ dependencies = [ [[package]] name = "aes" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac1f845298e95f983ff1944b728ae08b8cebab80d684f0a832ed0fc74dfa27e2" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" dependencies = [ "cfg-if", "cipher", @@ -40,9 +40,9 @@ dependencies = [ [[package]] name = "ahash" -version = "0.8.9" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d713b3834d76b85304d4d525563c1276e2e30dc97cc67bfb4585a4a29fc2c89f" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" dependencies = [ "cfg-if", "once_cell", @@ -52,62 +52,57 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] -[[package]] -name = "allocator-api2" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" - [[package]] name = "anstream" -version = "0.6.11" +version = "0.6.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e2e1ebcb11de5c03c67de28a7df593d32191b44939c482e97702baaaa6ab6a5" +checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", + "is_terminal_polyfill", "utf8parse", ] [[package]] name = "anstyle" -version = "1.0.4" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" +checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" [[package]] name = "anstyle-parse" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" +checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.0.2" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" +checksum = "ad186efb764318d35165f1758e7dcef3b10628e26d41a44bc5550652e6804391" dependencies = [ "windows-sys 0.52.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.2" +version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" +checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19" dependencies = [ "anstyle", "windows-sys 0.52.0", @@ -115,18 +110,17 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.79" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca" +checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" [[package]] name = "async-channel" -version = "2.1.1" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ca33f4bc4ed1babef42cad36cc1f51fa88be00420404e5b1e80ab1b18f7678c" +checksum = "89b47800b0be77592da0afd425cc03468052844aff33b84e33cc696f64e77b6a" dependencies = [ "concurrent-queue", - "event-listener", "event-listener-strategy", "futures-core", "pin-project-lite", @@ -151,25 +145,25 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.68", ] [[package]] name = "async-trait" -version = "0.1.77" +version = "0.1.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9" +checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.68", ] [[package]] name = "autocfg" -version = "1.1.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" [[package]] name = "axum" @@ -178,13 +172,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b829e4e32b91e643de6eafe82b1d90675f5874230191a4ffbc1b336dec4d6bf" dependencies = [ "async-trait", - "axum-core", + "axum-core 0.3.4", "bitflags 1.3.2", "bytes", "futures-util", - "http", - "http-body", - "hyper", + "http 0.2.12", + "http-body 0.4.6", + "hyper 0.14.29", "itoa", "matchit", "memchr", @@ -193,12 +187,46 @@ dependencies = [ "pin-project-lite", "rustversion", "serde", - "sync_wrapper", + "sync_wrapper 0.1.2", "tower", "tower-layer", "tower-service", ] +[[package]] +name = "axum" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a6c9af12842a67734c9a2e355436e5d03b22383ed60cf13cd0c18fbfe3dcbcf" +dependencies = [ + "async-trait", + "axum-core 0.4.3", + "bytes", + "futures-util", + "http 1.1.0", + "http-body 1.0.0", + "http-body-util", + "hyper 1.4.0", + "hyper-util", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "sync_wrapper 1.0.1", + "tokio", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + [[package]] name = "axum-core" version = "0.3.4" @@ -208,8 +236,8 @@ dependencies = [ "async-trait", "bytes", "futures-util", - "http", - "http-body", + "http 0.2.12", + "http-body 0.4.6", "mime", "rustversion", "tower-layer", @@ -217,10 +245,31 @@ dependencies = [ ] [[package]] -name = "backtrace" -version = "0.3.69" +name = "axum-core" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +checksum = "a15c63fd72d41492dc4f497196f5da1fb04fb7529e631d73630d1b491e47a2e3" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http 1.1.0", + "http-body 1.0.0", + "http-body-util", + "mime", + "pin-project-lite", + "rustversion", + "sync_wrapper 0.1.2", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "backtrace" +version = "0.3.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cc23269a4f8976d0a4d2e7109211a419fe30e8d88d677cd60b6bc79c5732e0a" dependencies = [ "addr2line", "cc", @@ -237,6 +286,12 @@ version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + [[package]] name = "base64ct" version = "1.6.0" @@ -284,7 +339,7 @@ dependencies = [ "regex", "rustc-hash", "shlex", - "syn 2.0.48", + "syn 2.0.68", "which", ] @@ -296,9 +351,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.4.2" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" [[package]] name = "blake2" @@ -320,9 +375,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.14.0" +version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] name = "burrow" @@ -331,13 +386,15 @@ dependencies = [ "aead", "anyhow", "async-channel", - "base64", + "axum 0.7.5", + "base64 0.21.7", "blake2", "caps", "chacha20poly1305", "clap", "console", "console-subscriber", + "dotenv", "fehler", "futures", "hmac", @@ -351,6 +408,7 @@ dependencies = [ "parking_lot", "rand", "rand_core", + "reqwest 0.12.5", "ring", "rusqlite", "schemars", @@ -374,9 +432,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.5.0" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" +checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" [[package]] name = "bzip2" @@ -411,12 +469,13 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.83" +version = "1.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +checksum = "74b6a57f98764a267ff415d50a25e6e166f3831a5071af4995296ea97d210490" dependencies = [ "jobserver", "libc", + "once_cell", ] [[package]] @@ -471,20 +530,20 @@ dependencies = [ [[package]] name = "clang-sys" -version = "1.7.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67523a3b4be3ce1989d607a828d036249522dd9c1c8de7f4dd2dae43a37369d1" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" dependencies = [ "glob", "libc", - "libloading 0.8.1", + "libloading 0.8.4", ] [[package]] name = "clap" -version = "4.4.18" +version = "4.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e578d6ec4194633722ccf9544794b71b1385c3c027efe0c55db226fc880865c" +checksum = "84b3edb18336f4df585bc9aa31dd99c036dfa5dc5e9a2939a722a188f3a8970d" dependencies = [ "clap_builder", "clap_derive", @@ -492,9 +551,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.4.18" +version = "4.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4df4df40ec50c46000231c914968278b1eb05098cf8f1b3a518a95030e71d1c7" +checksum = "c1c09dd5ada6c6c78075d6fd0da3f90d8080651e2d6cc8eb2f1aaa4034ced708" dependencies = [ "anstream", "anstyle", @@ -504,33 +563,33 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.4.7" +version = "4.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442" +checksum = "2bac35c6dafb060fd4d275d9a4ffae97917c13a6327903a8be2153cd964f7085" dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.68", ] [[package]] name = "clap_lex" -version = "0.6.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" +checksum = "4b82cf0babdbd58558212896d1a4272303a57bdb245c2bf1147185fb45640e70" [[package]] name = "colorchoice" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" +checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" [[package]] name = "concurrent-queue" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d16048cd947b08fa32c24458a22f5dc5e835264f689f4f5653210c69fd107363" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" dependencies = [ "crossbeam-utils", ] @@ -618,27 +677,27 @@ dependencies = [ [[package]] name = "crc32fast" -version = "1.3.2" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" dependencies = [ "cfg-if", ] [[package]] name = "crossbeam-channel" -version = "0.5.11" +version = "0.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "176dc175b78f56c0f321911d9c8eb2b77a78a4860b9c19db83835fea1a46649b" +checksum = "33480d6946193aa8033910124896ca395333cae7e2d1113d1fef6c3272217df2" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-utils" -version = "0.8.19" +version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" [[package]] name = "crypto-common" @@ -653,15 +712,14 @@ dependencies = [ [[package]] name = "curve25519-dalek" -version = "4.1.1" +version = "4.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e89b8c6a2e4b1f45971ad09761aafb85514a84744b67a95e32c3cc1352d1f65c" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" dependencies = [ "cfg-if", "cpufeatures", "curve25519-dalek-derive", "fiat-crypto", - "platforms", "rustc_version", "subtle", "zeroize", @@ -675,7 +733,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.68", ] [[package]] @@ -699,16 +757,22 @@ dependencies = [ ] [[package]] -name = "dyn-clone" -version = "1.0.16" +name = "dotenv" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "545b22097d44f8a9581187cdf93de7a71e4722bf51200cfaba810865b49a495d" +checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" + +[[package]] +name = "dyn-clone" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125" [[package]] name = "either" -version = "1.9.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" [[package]] name = "encode_unicode" @@ -718,9 +782,9 @@ checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" [[package]] name = "encoding_rs" -version = "0.8.33" +version = "0.8.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1" +checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" dependencies = [ "cfg-if", ] @@ -733,9 +797,9 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" dependencies = [ "libc", "windows-sys 0.52.0", @@ -743,9 +807,9 @@ dependencies = [ [[package]] name = "event-listener" -version = "4.0.3" +version = "5.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b215c49b2b248c855fb73579eb1f4f26c38ffdc12973e20e07b91d78d5646e" +checksum = "6032be9bd27023a771701cc49f9f053c751055f71efb2e0ae5c15809093675ba" dependencies = [ "concurrent-queue", "parking", @@ -754,9 +818,9 @@ dependencies = [ [[package]] name = "event-listener-strategy" -version = "0.4.0" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "958e4d70b6d5e81971bebec42271ec641e7ff4e170a6fa605f2b8a8b65cb97d3" +checksum = "0f214dc438f977e6d4e3500aaa277f5ad94ca83fbbd9b1a15713ce2344ccc5a1" dependencies = [ "event-listener", "pin-project-lite", @@ -776,9 +840,9 @@ checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" [[package]] name = "fastrand" -version = "2.0.1" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" +checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" [[package]] name = "fehler" @@ -802,15 +866,15 @@ dependencies = [ [[package]] name = "fiat-crypto" -version = "0.2.5" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27573eac26f4dd11e2b1916c3fe1baa56407c83c71a773a8ba17ec0bca03b6b7" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" [[package]] name = "flate2" -version = "1.0.28" +version = "1.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" +checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae" dependencies = [ "crc32fast", "miniz_oxide", @@ -902,7 +966,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.68", ] [[package]] @@ -947,9 +1011,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.12" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", "libc", @@ -958,9 +1022,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.28.1" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" +checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" [[package]] name = "glob" @@ -970,17 +1034,17 @@ checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" [[package]] name = "h2" -version = "0.3.24" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb2c4422095b67ee78da96fbb51a4cc413b3b25883c7717ff7ca1ab31022c9c9" +checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" dependencies = [ "bytes", "fnv", "futures-core", "futures-sink", "futures-util", - "http", - "indexmap 2.1.0", + "http 0.2.12", + "indexmap 2.2.6", "slab", "tokio", "tokio-util", @@ -995,21 +1059,20 @@ checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] name = "hashbrown" -version = "0.14.3" +version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" dependencies = [ "ahash", - "allocator-api2", ] [[package]] name = "hashlink" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "692eaaf7f7607518dd3cef090f1474b61edc5301d8012f09579920df68b725ee" +checksum = "6ba4ff7128dee98c7dc9794b6a411377e1404dba1c97deb8d1a55297bd25d8af" dependencies = [ - "hashbrown 0.14.3", + "hashbrown 0.14.5", ] [[package]] @@ -1018,7 +1081,7 @@ version = "7.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "765c9198f173dd59ce26ff9f95ef0aafd0a0fe01fb9d72841bc5066a4c06511d" dependencies = [ - "base64", + "base64 0.21.7", "byteorder", "flate2", "nom", @@ -1027,15 +1090,15 @@ dependencies = [ [[package]] name = "heck" -version = "0.4.1" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "hermit-abi" -version = "0.3.4" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d3d0e0f38255e7fa3cf31335b3a56f05febd18025f4db5ef7a0cfb4f8da651f" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" [[package]] name = "hex" @@ -1063,9 +1126,20 @@ dependencies = [ [[package]] name = "http" -version = "0.2.11" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8947b1a6fad4393052c7ba1f4cd97bed3e953a95c79c92ad9b051a04611d9fbb" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" dependencies = [ "bytes", "fnv", @@ -1079,15 +1153,38 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" dependencies = [ "bytes", - "http", + "http 0.2.12", + "pin-project-lite", +] + +[[package]] +name = "http-body" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643" +dependencies = [ + "bytes", + "http 1.1.0", +] + +[[package]] +name = "http-body-util" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" +dependencies = [ + "bytes", + "futures-util", + "http 1.1.0", + "http-body 1.0.0", "pin-project-lite", ] [[package]] name = "httparse" -version = "1.8.0" +version = "1.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" +checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9" [[package]] name = "httpdate" @@ -1103,17 +1200,17 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "hyper" -version = "0.14.28" +version = "0.14.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80" +checksum = "f361cde2f109281a220d4307746cdfd5ee3f410da58a70377762396775634b33" dependencies = [ "bytes", "futures-channel", "futures-core", "futures-util", "h2", - "http", - "http-body", + "http 0.2.12", + "http-body 0.4.6", "httparse", "httpdate", "itoa", @@ -1125,13 +1222,51 @@ dependencies = [ "want", ] +[[package]] +name = "hyper" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4fe55fb7a772d59a5ff1dfbff4fe0258d19b89fec4b233e75d35d5d2316badc" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http 1.1.0", + "http-body 1.0.0", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ee4be2c948921a1a5320b629c4193916ed787a7f7f293fd3f7f5a6c9de74155" +dependencies = [ + "futures-util", + "http 1.1.0", + "hyper 1.4.0", + "hyper-util", + "rustls", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", + "webpki-roots", +] + [[package]] name = "hyper-timeout" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" dependencies = [ - "hyper", + "hyper 0.14.29", "pin-project-lite", "tokio", "tokio-io-timeout", @@ -1144,12 +1279,32 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" dependencies = [ "bytes", - "hyper", + "hyper 0.14.29", "native-tls", "tokio", "tokio-native-tls", ] +[[package]] +name = "hyper-util" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ab92f4f49ee4fb4f997c784b7a2e0fa70050211e0b6a287f898c3c9785ca956" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http 1.1.0", + "http-body 1.0.0", + "hyper 1.4.0", + "pin-project-lite", + "socket2", + "tokio", + "tower", + "tower-service", + "tracing", +] + [[package]] name = "idna" version = "0.5.0" @@ -1172,12 +1327,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.1.0" +version = "2.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" +checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" dependencies = [ "equivalent", - "hashbrown 0.14.3", + "hashbrown 0.14.5", ] [[package]] @@ -1191,16 +1346,15 @@ dependencies = [ [[package]] name = "insta" -version = "1.34.0" +version = "1.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d64600be34b2fcfc267740a243fa7744441bb4947a619ac4e5bb6507f35fbfc" +checksum = "810ae6042d48e2c9e9215043563a58a80b877bc863228a74cf10c49d4620a6f5" dependencies = [ "console", "lazy_static", "linked-hash-map", "serde", "similar", - "yaml-rust", ] [[package]] @@ -1232,43 +1386,49 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" [[package]] -name = "itertools" -version = "0.11.0" +name = "is_terminal_polyfill" +version = "1.70.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" + +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" dependencies = [ "either", ] [[package]] name = "itoa" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "jobserver" -version = "0.1.27" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c37f63953c4c63420ed5fd3d6d398c719489b9f872b9fa683262f8edd363c7d" +checksum = "d2b099aaa34a9751c5bf0878add70444e1ed2dd73f347be99003d4577277de6e" dependencies = [ "libc", ] [[package]] name = "js-sys" -version = "0.3.67" +version = "0.3.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a1d36f1235bc969acba30b7f5990b864423a6068a10f7c90ae8f0112e3a59d1" +checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" dependencies = [ "wasm-bindgen", ] [[package]] name = "lazy_static" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "lazycell" @@ -1278,9 +1438,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" -version = "0.2.152" +version = "0.2.155" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7" +checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" [[package]] name = "libloading" @@ -1294,12 +1454,12 @@ dependencies = [ [[package]] name = "libloading" -version = "0.8.1" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c571b676ddfc9a8c12f1f3d3085a7b163966a8fd8098a90640953ce5f6170161" +checksum = "e310b3a6b5907f99202fcdb4960ff45b93735d7c7d96b760fcff8db2dc0e103d" dependencies = [ "cfg-if", - "windows-sys 0.48.0", + "windows-targets 0.52.6", ] [[package]] @@ -1339,15 +1499,15 @@ checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" [[package]] name = "linux-raw-sys" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" [[package]] name = "lock_api" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" dependencies = [ "autocfg", "scopeguard", @@ -1355,9 +1515,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.20" +version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[package]] name = "matchers" @@ -1376,9 +1536,9 @@ checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" [[package]] name = "memchr" -version = "2.7.1" +version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "memoffset" @@ -1391,9 +1551,9 @@ dependencies = [ [[package]] name = "memoffset" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" dependencies = [ "autocfg", ] @@ -1418,7 +1578,7 @@ checksum = "49e7bc1560b95a3c4a25d03de42fe76ca718ab92d1a22a55b9b4cf67b3ae635c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.68", ] [[package]] @@ -1435,18 +1595,18 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.7.1" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" dependencies = [ "adler", ] [[package]] name = "mio" -version = "0.8.10" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09" +checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" dependencies = [ "libc", "wasi", @@ -1455,11 +1615,10 @@ dependencies = [ [[package]] name = "native-tls" -version = "0.2.11" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" +checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466" dependencies = [ - "lazy_static", "libc", "log", "openssl", @@ -1490,10 +1649,10 @@ version = "0.27.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.6.0", "cfg-if", "libc", - "memoffset 0.9.0", + "memoffset 0.9.1", ] [[package]] @@ -1517,10 +1676,16 @@ dependencies = [ ] [[package]] -name = "num-traits" -version = "0.2.17" +name = "num-conv" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", ] @@ -1537,9 +1702,9 @@ dependencies = [ [[package]] name = "object" -version = "0.32.2" +version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" +checksum = "081b846d1d56ddfc18fdf1a922e4f6e07a11768ea1b92dec44e42b72712ccfce" dependencies = [ "memchr", ] @@ -1552,17 +1717,17 @@ checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "opaque-debug" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" [[package]] name = "openssl" -version = "0.10.63" +version = "0.10.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15c9d69dd87a29568d4d017cfe8ec518706046a05184e5aea92d0af890b803c8" +checksum = "95a0481286a310808298130d22dd1fef0fa571e05a8f44ec801801e84b216b1f" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.6.0", "cfg-if", "foreign-types", "libc", @@ -1579,7 +1744,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.68", ] [[package]] @@ -1590,9 +1755,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.99" +version = "0.9.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22e1bf214306098e4832460f797824c05d25aacdf896f64a985fb0fd992454ae" +checksum = "c597637d56fbc83893a35eb0dd04b2b8e7a50c91e64e9493e398b5df4fb45fa2" dependencies = [ "cc", "libc", @@ -1614,9 +1779,9 @@ checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae" [[package]] name = "parking_lot" -version = "0.12.1" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" dependencies = [ "lock_api", "parking_lot_core", @@ -1624,15 +1789,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.9" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", - "windows-targets 0.48.5", + "windows-targets 0.52.6", ] [[package]] @@ -1672,29 +1837,29 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pin-project" -version = "1.1.4" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0302c4a0442c456bd56f841aee5c3bfd17967563f6fadc9ceb9f9c23cf3807e0" +checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.4" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "266c042b60c9c76b8d53061e52b2e0d1116abc57cefc8c5cd671619a56ac3690" +checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.68", ] [[package]] name = "pin-project-lite" -version = "0.2.13" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" [[package]] name = "pin-utils" @@ -1704,15 +1869,9 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkg-config" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2900ede94e305130c13ddd391e0ab7cbaeb783945ae07a279c268cb05109c6cb" - -[[package]] -name = "platforms" -version = "3.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "626dec3cac7cc0e1577a2ec3fc496277ec2baa084bebad95bb6fdbfae235f84c" +checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" [[package]] name = "poly1305" @@ -1739,28 +1898,28 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "prettyplease" -version = "0.2.16" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a41cf62165e97c7f814d2221421dbb9afcbcdb0a88068e5ea206e19951c2cbb5" +checksum = "5f12335488a2f3b0a83b14edad48dca9879ce89b2edd10e80237e4e852dd645e" dependencies = [ "proc-macro2", - "syn 2.0.48", + "syn 2.0.68", ] [[package]] name = "proc-macro2" -version = "1.0.78" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" dependencies = [ "unicode-ident", ] [[package]] name = "prost" -version = "0.12.3" +version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "146c289cda302b98a28d40c8b3b90498d6e526dd24ac2ecea73e4e491685b94a" +checksum = "deb1435c188b76130da55f17a466d252ff7b1418b2ad3e037d127b94e3411f29" dependencies = [ "bytes", "prost-derive", @@ -1768,31 +1927,78 @@ dependencies = [ [[package]] name = "prost-derive" -version = "0.12.3" +version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efb6c9a1dd1def8e2124d17e83a20af56f1570d6c2d2bd9e266ccb768df3840e" +checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1" dependencies = [ "anyhow", "itertools", "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.68", ] [[package]] name = "prost-types" -version = "0.12.3" +version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "193898f59edcf43c26227dcd4c8427f00d99d61e95dcde58dabd49fa291d470e" +checksum = "9091c90b0a32608e984ff2fa4091273cbdd755d54935c51d520887f4a1dbd5b0" dependencies = [ "prost", ] [[package]] -name = "quote" -version = "1.0.35" +name = "quinn" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +checksum = "e4ceeeeabace7857413798eb1ffa1e9c905a9946a57d81fb69b4b71c4d8eb3ad" +dependencies = [ + "bytes", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls", + "thiserror", + "tokio", + "tracing", +] + +[[package]] +name = "quinn-proto" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddf517c03a109db8100448a4be38d498df8a210a99fe0e1b9eaf39e78c640efe" +dependencies = [ + "bytes", + "rand", + "ring", + "rustc-hash", + "rustls", + "slab", + "thiserror", + "tinyvec", + "tracing", +] + +[[package]] +name = "quinn-udp" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9096629c45860fc7fb143e125eb826b5e721e10be3263160c7d60ca832cf8c46" +dependencies = [ + "libc", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.52.0", +] + +[[package]] +name = "quote" +version = "1.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" dependencies = [ "proc-macro2", ] @@ -1829,23 +2035,23 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.4.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +checksum = "c82cf8cff14456045f55ec4241383baeff27af886adb72ffb2162f99911de0fd" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.6.0", ] [[package]] name = "regex" -version = "1.10.3" +version = "1.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" +checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.5", - "regex-syntax 0.8.2", + "regex-automata 0.4.7", + "regex-syntax 0.8.4", ] [[package]] @@ -1859,13 +2065,13 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.5" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bb987efffd3c6d0d8f5f89510bb458559eab11e4f869acb20bf845e016259cd" +checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.8.2", + "regex-syntax 0.8.4", ] [[package]] @@ -1876,25 +2082,25 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" -version = "0.8.2" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" +checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" [[package]] name = "reqwest" -version = "0.11.23" +version = "0.11.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37b1ae8d9ac08420c66222fb9096fc5de435c3c48542bc5336c51892cffafb41" +checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" dependencies = [ - "base64", + "base64 0.21.7", "bytes", "encoding_rs", "futures-core", "futures-util", "h2", - "http", - "http-body", - "hyper", + "http 0.2.12", + "http-body 0.4.6", + "hyper 0.14.29", "hyper-tls", "ipnet", "js-sys", @@ -1904,9 +2110,11 @@ dependencies = [ "once_cell", "percent-encoding", "pin-project-lite", + "rustls-pemfile 1.0.4", "serde", "serde_json", "serde_urlencoded", + "sync_wrapper 0.1.2", "system-configuration", "tokio", "tokio-native-tls", @@ -1915,21 +2123,64 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "winreg", + "winreg 0.50.0", +] + +[[package]] +name = "reqwest" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7d6d2a27d57148378eb5e111173f4276ad26340ecc5c49a4a2152167a2d6a37" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures-core", + "futures-util", + "http 1.1.0", + "http-body 1.0.0", + "http-body-util", + "hyper 1.4.0", + "hyper-rustls", + "hyper-util", + "ipnet", + "js-sys", + "log", + "mime", + "once_cell", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls", + "rustls-pemfile 2.1.2", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper 1.0.1", + "tokio", + "tokio-rustls", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "webpki-roots", + "winreg 0.52.0", ] [[package]] name = "ring" -version = "0.17.7" +version = "0.17.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "688c63d65483050968b2a8937f7995f443e27041a0f7700aa59b0822aedebb74" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" dependencies = [ "cc", + "cfg-if", "getrandom", "libc", "spin", "untrusted", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -1938,7 +2189,7 @@ version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b838eba278d213a8beaf485bd313fd580ca4505a00d5871caeb1457c55322cae" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.6.0", "fallible-iterator", "fallible-streaming-iterator", "hashlink", @@ -1948,9 +2199,9 @@ dependencies = [ [[package]] name = "rustc-demangle" -version = "0.1.23" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] name = "rustc-hash" @@ -1969,11 +2220,11 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.30" +version = "0.38.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "322394588aaf33c24007e8bb3238ee3e4c5c09c084ab32bc73890b99ff326bca" +checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.6.0", "errno", "libc", "linux-raw-sys", @@ -1981,16 +2232,66 @@ dependencies = [ ] [[package]] -name = "rustversion" -version = "1.0.14" +name = "rustls" +version = "0.23.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" +checksum = "05cff451f60db80f490f3c182b77c35260baace73209e9cdbbe526bfe3a4d402" +dependencies = [ + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pemfile" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" +dependencies = [ + "base64 0.21.7", +] + +[[package]] +name = "rustls-pemfile" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29993a25686778eb88d4189742cd713c9bce943bc54251a33509dc63cbacf73d" +dependencies = [ + "base64 0.22.1", + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "976295e77ce332211c0d24d92c0e83e50f5c5f046d11082cea19f3df13a3562d" + +[[package]] +name = "rustls-webpki" +version = "0.102.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9a6fccd794a42c2c105b513a2f62bc3fd8f3ba57a4593677ceb0bd035164d78" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" [[package]] name = "ryu" -version = "1.0.16" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" [[package]] name = "schannel" @@ -2003,9 +2304,9 @@ dependencies = [ [[package]] name = "schemars" -version = "0.8.16" +version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45a28f4c49489add4ce10783f7911893516f15afe45d015608d41faca6bc4d29" +checksum = "09c024468a378b7e36765cd36702b7a90cc3cba11654f6685c8f233408e89e92" dependencies = [ "dyn-clone", "schemars_derive", @@ -2015,14 +2316,14 @@ dependencies = [ [[package]] name = "schemars_derive" -version = "0.8.16" +version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c767fd6fa65d9ccf9cf026122c1b555f2ef9a4f0cea69da4d7dbc3e258d30967" +checksum = "b1eee588578aff73f856ab961cd2f79e36bc45d7ded33a7562adba4667aecc0e" dependencies = [ "proc-macro2", "quote", "serde_derive_internals", - "syn 1.0.109", + "syn 2.0.68", ] [[package]] @@ -2033,11 +2334,11 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "security-framework" -version = "2.9.2" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" +checksum = "c627723fd09706bacdb5cf41499e95098555af3c3c29d014dc3c458ef6be11c0" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.6.0", "core-foundation", "core-foundation-sys", "libc", @@ -2046,9 +2347,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.9.1" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" +checksum = "317936bbbd05227752583946b9e66d7ce3b489f84e11a94a510b4437fef407d7" dependencies = [ "core-foundation-sys", "libc", @@ -2056,52 +2357,62 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.21" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97ed7a9823b74f99c7742f5336af7be5ecd3eeafcb1507d1fa93347b1d589b0" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" [[package]] name = "serde" -version = "1.0.196" +version = "1.0.203" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32" +checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.196" +version = "1.0.203" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67" +checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.68", ] [[package]] name = "serde_derive_internals" -version = "0.26.0" +version = "0.29.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85bf8229e7920a9f636479437026331ce11aa132b4dde37d121944a44d6e5f3c" +checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.68", ] [[package]] name = "serde_json" -version = "1.0.112" +version = "1.0.120" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d1bd37ce2324cf3bf85e5a25f96eb4baf0d5aa6eba43e7ae8958870c4ec48ed" +checksum = "4e0d21c9a8cae1235ad58a00c11cb40d4b1e5c784f1ef2c537876ed6ffd8b7c5" dependencies = [ "itoa", "ryu", "serde", ] +[[package]] +name = "serde_path_to_error" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af99884400da37c88f5e9146b7f1fd0fbcae8f6eec4e9da38b67d05486f814a6" +dependencies = [ + "itoa", + "serde", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -2163,10 +2474,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] -name = "similar" -version = "2.4.0" +name = "signal-hook-registry" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32fea41aca09ee824cc9724996433064c89f7777e60762749a4170a14abbfa21" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +dependencies = [ + "libc", +] + +[[package]] +name = "similar" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa42c91313f1d05da9b26f267f931cf178d4aba455b4c4622dd7355eb80c6640" [[package]] name = "slab" @@ -2179,18 +2499,18 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.13.1" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "socket2" -version = "0.5.5" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" +checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" dependencies = [ "libc", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -2205,7 +2525,7 @@ version = "9.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da7a2b3c2bc9693bcb40870c4e9b5bf0d79f9cb46273321bf855ec513e919082" dependencies = [ - "base64", + "base64 0.21.7", "digest", "hex", "miette", @@ -2217,15 +2537,15 @@ dependencies = [ [[package]] name = "strsim" -version = "0.10.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "subtle" -version = "2.5.0" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" @@ -2240,9 +2560,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.48" +version = "2.0.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" +checksum = "901fa70d88b9d6c98022e23b4136f9f3e54e4662c3bc1bd1d84a42a9a0f0c1e9" dependencies = [ "proc-macro2", "quote", @@ -2255,6 +2575,12 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" +[[package]] +name = "sync_wrapper" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" + [[package]] name = "system-configuration" version = "0.5.1" @@ -2278,42 +2604,41 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.9.0" +version = "3.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01ce4141aa927a6d1bd34a041795abd0db1cccba5d5f24b009f694bdf3a1f3fa" +checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" dependencies = [ "cfg-if", "fastrand", - "redox_syscall", "rustix", "windows-sys 0.52.0", ] [[package]] name = "thiserror" -version = "1.0.56" +version = "1.0.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad" +checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.56" +version = "1.0.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471" +checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.68", ] [[package]] name = "thread_local" -version = "1.1.7" +version = "1.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" dependencies = [ "cfg-if", "once_cell", @@ -2321,11 +2646,12 @@ dependencies = [ [[package]] name = "time" -version = "0.3.31" +version = "0.3.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f657ba42c3f86e7680e53c8cd3af8abbe56b5491790b46e22e19c0d57463583e" +checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" dependencies = [ "deranged", + "num-conv", "powerfmt", "serde", "time-core", @@ -2339,9 +2665,9 @@ checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "tinyvec" -version = "1.6.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +checksum = "ce6b6a2fb3a985e99cebfaefa9faa3024743da73304ca1c683a36429613d3d22" dependencies = [ "tinyvec_macros", ] @@ -2354,9 +2680,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.35.1" +version = "1.38.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c89b4efa943be685f629b149f53829423f8f5531ea21249408e8e2f8671ec104" +checksum = "ba4f4a02a7a80d6f274636f0aa95c7e383b912d41fe721a31f29e29698585a4a" dependencies = [ "backtrace", "bytes", @@ -2364,6 +2690,7 @@ dependencies = [ "mio", "num_cpus", "pin-project-lite", + "signal-hook-registry", "socket2", "tokio-macros", "tracing", @@ -2382,13 +2709,13 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" +checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.68", ] [[package]] @@ -2402,10 +2729,21 @@ dependencies = [ ] [[package]] -name = "tokio-stream" -version = "0.1.14" +name = "tokio-rustls" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842" +checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" +dependencies = [ + "rustls", + "rustls-pki-types", + "tokio", +] + +[[package]] +name = "tokio-stream" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "267ac89e0bec6e691e5813911606935d77c476ff49024f98abcea3e7b15e37af" dependencies = [ "futures-core", "pin-project-lite", @@ -2414,16 +2752,15 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.10" +version = "0.7.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" +checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1" dependencies = [ "bytes", "futures-core", "futures-sink", "pin-project-lite", "tokio", - "tracing", ] [[package]] @@ -2434,13 +2771,13 @@ checksum = "d560933a0de61cf715926b9cac824d4c883c2c43142f787595e48280c40a1d0e" dependencies = [ "async-stream", "async-trait", - "axum", - "base64", + "axum 0.6.20", + "base64 0.21.7", "bytes", "h2", - "http", - "http-body", - "hyper", + "http 0.2.12", + "http-body 0.4.6", + "hyper 0.14.29", "hyper-timeout", "percent-encoding", "pin-project", @@ -2491,6 +2828,7 @@ version = "0.1.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ + "log", "pin-project-lite", "tracing-attributes", "tracing-core", @@ -2504,7 +2842,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.68", ] [[package]] @@ -2603,7 +2941,7 @@ dependencies = [ "libloading 0.7.4", "log", "nix 0.26.4", - "reqwest", + "reqwest 0.11.27", "schemars", "serde", "socket2", @@ -2636,18 +2974,18 @@ checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "unicode-normalization" -version = "0.1.22" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" dependencies = [ "tinyvec", ] [[package]] name = "unicode-width" -version = "0.1.11" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" +checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" [[package]] name = "universal-hash" @@ -2667,9 +3005,9 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.5.0" +version = "2.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" +checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" dependencies = [ "form_urlencoded", "idna", @@ -2678,15 +3016,15 @@ dependencies = [ [[package]] name = "utf8parse" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.7.0" +version = "1.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f00cc9702ca12d3c81455259621e676d0f7251cec66a21e98fe2e9a37db93b2a" +checksum = "5de17fd2f7da591098415cff336e12965a28061ddace43b59cb3c430179c9439" dependencies = [ "serde", ] @@ -2726,9 +3064,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.90" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1223296a201415c7fad14792dbefaace9bd52b62d33453ade1c5b5f07555406" +checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -2736,24 +3074,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.90" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcdc935b63408d58a32f8cc9738a0bffd8f05cc7c002086c6ef20b7312ad9dcd" +checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.68", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.40" +version = "0.4.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bde2032aeb86bdfaecc8b261eef3cba735cc426c1f3a3416d1e0791be95fc461" +checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0" dependencies = [ "cfg-if", "js-sys", @@ -2763,9 +3101,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.90" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e4c238561b2d428924c49815533a8b9121c664599558a5d9ec51f8a1740a999" +checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2773,33 +3111,42 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.90" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bae1abb6806dc1ad9e560ed242107c0f6c84335f1749dd4e8ddb012ebd5e25a7" +checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.68", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.90" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d91413b1c31d7539ba5ef2451af3f0b833a005eb27a631cec32bc0635a8602b" +checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" [[package]] name = "web-sys" -version = "0.3.67" +version = "0.3.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58cd2333b6e0be7a39605f0e255892fd7418a682d8da8fe042fe25128794d2ed" +checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" dependencies = [ "js-sys", "wasm-bindgen", ] +[[package]] +name = "webpki-roots" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd7c23921eeb1713a4e851530e9b9756e4fb0e89978582942612524cf09f01cd" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "which" version = "4.4.2" @@ -2814,9 +3161,9 @@ dependencies = [ [[package]] name = "widestring" -version = "1.0.2" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "653f141f39ec16bba3c5abe400a0c60da7468261cc2cbf36805022876bc721a8" +checksum = "7219d36b6eac893fa81e84ebe06485e7dcbb616177469b142df14f1f4deb1311" [[package]] name = "winapi" @@ -2864,7 +3211,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.0", + "windows-targets 0.52.6", ] [[package]] @@ -2884,17 +3231,18 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.52.0", - "windows_aarch64_msvc 0.52.0", - "windows_i686_gnu 0.52.0", - "windows_i686_msvc 0.52.0", - "windows_x86_64_gnu 0.52.0", - "windows_x86_64_gnullvm 0.52.0", - "windows_x86_64_msvc 0.52.0", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", ] [[package]] @@ -2905,9 +3253,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" @@ -2917,9 +3265,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" @@ -2929,9 +3277,15 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" @@ -2941,9 +3295,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" @@ -2953,9 +3307,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" @@ -2965,9 +3319,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" @@ -2977,9 +3331,9 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winreg" @@ -2992,10 +3346,20 @@ dependencies = [ ] [[package]] -name = "x25519-dalek" -version = "2.0.0" +name = "winreg" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb66477291e7e8d2b0ff1bcb900bf29489a9692816d79874bea351e7a8b6de96" +checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + +[[package]] +name = "x25519-dalek" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277" dependencies = [ "curve25519-dalek", "rand_core", @@ -3005,44 +3369,35 @@ dependencies = [ [[package]] name = "xxhash-rust" -version = "0.8.8" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53be06678ed9e83edb1745eb72efc0bbcd7b5c3c35711a860906aed827a13d61" - -[[package]] -name = "yaml-rust" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" -dependencies = [ - "linked-hash-map", -] +checksum = "63658493314859b4dfdf3fb8c1defd61587839def09582db50b8a4e93afca6bb" [[package]] name = "zerocopy" -version = "0.7.32" +version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.7.32" +version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.68", ] [[package]] name = "zeroize" -version = "1.7.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" dependencies = [ "zeroize_derive", ] @@ -3055,7 +3410,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.68", ] [[package]] @@ -3099,9 +3454,9 @@ dependencies = [ [[package]] name = "zstd-sys" -version = "2.0.9+zstd.1.5.5" +version = "2.0.12+zstd.1.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e16efa8a874a0481a574084d34cc26fdb3b99627480f785888deb6386506656" +checksum = "0a4e40c320c3cb459d9a9ff6de98cff88f4751ee9275d140e2be94a2b74e4c13" dependencies = [ "cc", "pkg-config", diff --git a/Cargo.toml b/Cargo.toml index 44981a2..362ba2b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,3 +2,8 @@ members = ["burrow", "tun"] resolver = "2" exclude = ["burrow-gtk"] + +[profile.release] +lto = true +panic = "abort" +opt-level = "z" diff --git a/Dockerfile b/Dockerfile index afd51ea..e55eb58 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM docker.io/library/rust:1.76.0-slim-bookworm AS builder +FROM docker.io/library/rust:1.77-slim-bookworm AS builder ARG TARGETPLATFORM ARG LLVM_VERSION=16 @@ -8,7 +8,7 @@ ENV KEYRINGS /etc/apt/keyrings RUN set -eux && \ mkdir -p $KEYRINGS && \ apt-get update && \ - apt-get install --no-install-recommends -y gpg curl musl-dev && \ + apt-get install --no-install-recommends -y gpg curl busybox make musl-dev && \ curl --proto '=https' --tlsv1.2 -sSf https://apt.llvm.org/llvm-snapshot.gpg.key | gpg --dearmor --output $KEYRINGS/llvm.gpg && \ echo "deb [signed-by=$KEYRINGS/llvm.gpg] http://apt.llvm.org/bookworm/ llvm-toolchain-bookworm-$LLVM_VERSION main" > /etc/apt/sources.list.d/llvm.list && \ apt-get update && \ @@ -24,30 +24,31 @@ RUN set -eux && \ apt-get remove -y --auto-remove && \ rm -rf /var/lib/apt/lists/* -ARG SQLITE_VERSION=3400100 +RUN case $TARGETPLATFORM in \ + "linux/arm64") LLVM_TARGET=aarch64-unknown-linux-musl ;; \ + "linux/amd64") LLVM_TARGET=x86_64-unknown-linux-musl ;; \ + *) exit 1 ;; \ + esac && \ + rustup target add $LLVM_TARGET + +ARG SQLITE_VERSION=3460000 RUN case $TARGETPLATFORM in \ - "linux/arm64") LLVM_TARGET=aarch64-unknown-linux-musl MUSL_TARGET=aarch64-linux-musl ;; \ - "linux/amd64") LLVM_TARGET=x86_64-unknown-linux-musl MUSL_TARGET=x86_64-linux-musl ;; \ - *) exit 1 ;; \ + "linux/arm64") LLVM_TARGET=aarch64-unknown-linux-musl MUSL_TARGET=aarch64-linux-musl ;; \ + "linux/amd64") LLVM_TARGET=x86_64-unknown-linux-musl MUSL_TARGET=x86_64-linux-musl ;; \ + *) exit 1 ;; \ esac && \ - rustup target add $LLVM_TARGET && \ - curl --proto '=https' --tlsv1.2 -sSfO https://www.sqlite.org/2022/sqlite-autoconf-$SQLITE_VERSION.tar.gz && \ + curl --proto '=https' --tlsv1.2 -sSfO https://www.sqlite.org/2024/sqlite-autoconf-$SQLITE_VERSION.tar.gz && \ tar xf sqlite-autoconf-$SQLITE_VERSION.tar.gz && \ - rm sqlite-autoconf-$SQLITE_VERSION.tar.gz && \ cd sqlite-autoconf-$SQLITE_VERSION && \ - ./configure --disable-shared \ - CC="clang-$LLVM_VERSION -target $LLVM_TARGET" \ - CFLAGS="-I/usr/local/include -I/usr/include/$MUSL_TARGET" \ - LDFLAGS="-L/usr/local/lib -L/usr/lib/$MUSL_TARGET -L/lib/$MUSL_TARGET" && \ + ./configure --disable-shared --disable-dependency-tracking \ + CC="clang-$LLVM_VERSION -target $LLVM_TARGET" \ + CFLAGS="-I/usr/local/include -I/usr/include/$MUSL_TARGET" \ + LDFLAGS="-L/usr/local/lib -L/usr/lib/$MUSL_TARGET -L/lib/$MUSL_TARGET" && \ make && \ make install && \ cd .. && \ - rm -rf sqlite-autoconf-$SQLITE_VERSION - -ENV SQLITE3_STATIC=1 \ - SQLITE3_INCLUDE_DIR=/usr/local/include \ - SQLITE3_LIB_DIR=/usr/local/lib + rm -rf sqlite-autoconf-$SQLITE_VERSION sqlite-autoconf-$SQLITE_VERSION.tar.gz ENV CC_x86_64_unknown_linux_musl=clang-$LLVM_VERSION \ AR_x86_64_unknown_linux_musl=llvm-ar-$LLVM_VERSION \ @@ -55,14 +56,17 @@ ENV CC_x86_64_unknown_linux_musl=clang-$LLVM_VERSION \ AR_aarch64_unknown_linux_musl=llvm-ar-$LLVM_VERSION \ CARGO_TARGET_X86_64_UNKNOWN_LINUX_MUSL_RUSTFLAGS="-L/usr/lib/x86_64-linux-musl -L/lib/x86_64-linux-musl -C linker=rust-lld" \ CARGO_TARGET_AARCH64_UNKNOWN_LINUX_MUSL_RUSTFLAGS="-L/usr/lib/aarch64-linux-musl -L/lib/aarch64-linux-musl -C linker=rust-lld" \ - CARGO_REGISTRIES_CRATES_IO_PROTOCOL=sparse + CARGO_REGISTRIES_CRATES_IO_PROTOCOL=sparse \ + SQLITE3_STATIC=1 \ + SQLITE3_INCLUDE_DIR=/usr/local/include \ + SQLITE3_LIB_DIR=/usr/local/lib COPY . . RUN case $TARGETPLATFORM in \ - "linux/arm64") LLVM_TARGET=aarch64-unknown-linux-musl ;; \ - "linux/amd64") LLVM_TARGET=x86_64-unknown-linux-musl ;; \ - *) exit 1 ;; \ + "linux/arm64") LLVM_TARGET=aarch64-unknown-linux-musl ;; \ + "linux/amd64") LLVM_TARGET=x86_64-unknown-linux-musl ;; \ + *) exit 1 ;; \ esac && \ cargo install --path burrow --target $LLVM_TARGET @@ -71,7 +75,8 @@ WORKDIR /tmp/rootfs RUN set -eux && \ mkdir -p ./bin ./etc ./tmp ./data && \ mv /usr/local/cargo/bin/burrow ./bin/burrow && \ - echo 'burrow:x:10001:10001::/tmp:/sbin/nologin' > ./etc/passwd && \ + cp /bin/busybox ./bin/busybox && \ + echo 'burrow:x:10001:10001::/tmp:/bin/busybox' > ./etc/passwd && \ echo 'burrow:x:10001:' > ./etc/group && \ chown -R 10001:10001 ./tmp ./data && \ chmod 0777 ./tmp @@ -90,4 +95,6 @@ USER 10001:10001 COPY --from=builder /tmp/rootfs / WORKDIR /data -ENTRYPOINT ["/bin/burrow"] +EXPOSE 8080 + +CMD ["/bin/burrow", "auth-server"] diff --git a/burrow/Cargo.toml b/burrow/Cargo.toml index 0c816f8..0fb63a5 100644 --- a/burrow/Cargo.toml +++ b/burrow/Cargo.toml @@ -10,12 +10,13 @@ crate-type = ["lib", "staticlib"] [dependencies] anyhow = "1.0" -tokio = { version = "1.21", features = [ +tokio = { version = "1.37", features = [ "rt", "macros", "sync", "io-util", "rt-multi-thread", + "signal", "time", "tracing", ] } @@ -24,7 +25,7 @@ clap = { version = "4.4", features = ["derive"] } tracing = "0.1" tracing-log = "0.1" tracing-oslog = { git = "https://github.com/Stormshield-robinc/tracing-oslog" } -tracing-subscriber = { version = "0.3" , features = ["std", "env-filter"] } +tracing-subscriber = { version = "0.3", features = ["std", "env-filter"] } log = "0.4" serde = { version = "1", features = ["derive"] } serde_json = "1.0" @@ -50,9 +51,13 @@ futures = "0.3.28" once_cell = "1.19" console-subscriber = { version = "0.2.0", optional = true } console = "0.15.8" - -[dependencies.rusqlite] -version = "0.31.0" +axum = "0.7.4" +reqwest = { version = "0.12", default-features = false, features = [ + "json", + "rustls-tls", +] } +rusqlite = "0.31.0" +dotenv = "0.15.0" [target.'cfg(target_os = "linux")'.dependencies] caps = "0.5" diff --git a/burrow/src/auth/client.rs b/burrow/src/auth/client.rs new file mode 100644 index 0000000..e9721f3 --- /dev/null +++ b/burrow/src/auth/client.rs @@ -0,0 +1,24 @@ +use std::env::var; + +use anyhow::Result; +use reqwest::Url; + +pub async fn login() -> Result<()> { + let state = "vt :P"; + let nonce = "no"; + + let mut url = Url::parse("https://slack.com/openid/connect/authorize")?; + let mut q = url.query_pairs_mut(); + q.append_pair("response_type", "code"); + q.append_pair("scope", "openid profile email"); + q.append_pair("client_id", &var("CLIENT_ID")?); + q.append_pair("state", state); + q.append_pair("team", &var("SLACK_TEAM_ID")?); + q.append_pair("nonce", nonce); + q.append_pair("redirect_uri", "https://burrow.rs/callback"); + drop(q); + + println!("Continue auth in your browser:\n{}", url.as_str()); + + Ok(()) +} diff --git a/burrow/src/auth/mod.rs b/burrow/src/auth/mod.rs new file mode 100644 index 0000000..c07f47e --- /dev/null +++ b/burrow/src/auth/mod.rs @@ -0,0 +1,2 @@ +pub mod client; +pub mod server; diff --git a/burrow/src/auth/server/db.rs b/burrow/src/auth/server/db.rs new file mode 100644 index 0000000..b74f7ce --- /dev/null +++ b/burrow/src/auth/server/db.rs @@ -0,0 +1,89 @@ +use anyhow::Result; + +pub static PATH: &str = "./server.sqlite3"; + +pub fn init_db() -> Result<()> { + let conn = rusqlite::Connection::open(PATH)?; + + conn.execute( + "CREATE TABLE IF NOT EXISTS user ( + id PRIMARY KEY, + created_at TEXT NOT NULL + )", + (), + )?; + + conn.execute( + "CREATE TABLE IF NOT EXISTS user_connection ( + user_id INTEGER REFERENCES user(id) ON DELETE CASCADE, + openid_provider TEXT NOT NULL, + openid_user_id TEXT NOT NULL, + openid_user_name TEXT NOT NULL, + access_token TEXT NOT NULL, + refresh_token TEXT, + PRIMARY KEY (openid_provider, openid_user_id) + )", + (), + )?; + + conn.execute( + "CREATE TABLE IF NOT EXISTS device ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT, + public_key TEXT NOT NULL, + apns_token TEXT UNIQUE, + user_id INT REFERENCES user(id) ON DELETE CASCADE, + created_at TEXT NOT NULL DEFAULT (datetime('now')) CHECK(created_at IS datetime(created_at)), + ipv4 TEXT NOT NULL UNIQUE, + ipv6 TEXT NOT NULL UNIQUE, + access_token TEXT NOT NULL UNIQUE, + refresh_token TEXT NOT NULL UNIQUE, + expires_at TEXT NOT NULL DEFAULT (datetime('now', '+7 days')) CHECK(expires_at IS datetime(expires_at)) + )", + () + ).unwrap(); + + Ok(()) +} + +pub fn store_connection( + openid_user: super::providers::OpenIdUser, + openid_provider: &str, + access_token: &str, + refresh_token: Option<&str>, +) -> Result<()> { + log::debug!("Storing openid user {:#?}", openid_user); + let conn = rusqlite::Connection::open(PATH)?; + + conn.execute( + "INSERT OR IGNORE INTO user (id, created_at) VALUES (?, datetime('now'))", + (&openid_user.sub,), + )?; + conn.execute( + "INSERT INTO user_connection (user_id, openid_provider, openid_user_id, openid_user_name, access_token, refresh_token) VALUES ( + (SELECT id FROM user WHERE id = ?), + ?, + ?, + ?, + ?, + ? + )", + (&openid_user.sub, &openid_provider, &openid_user.sub, &openid_user.name, access_token, refresh_token), + )?; + + Ok(()) +} + +pub fn store_device( + openid_user: super::providers::OpenIdUser, + openid_provider: &str, + access_token: &str, + refresh_token: Option<&str>, +) -> Result<()> { + log::debug!("Storing openid user {:#?}", openid_user); + let conn = rusqlite::Connection::open(PATH)?; + + // TODO + + Ok(()) +} diff --git a/burrow/src/auth/server/mod.rs b/burrow/src/auth/server/mod.rs new file mode 100644 index 0000000..88b3ff3 --- /dev/null +++ b/burrow/src/auth/server/mod.rs @@ -0,0 +1,62 @@ +pub mod db; +pub mod providers; + +use anyhow::Result; +use axum::{http::StatusCode, routing::post, Router}; +use providers::slack::auth; +use tokio::signal; + +pub async fn serve() -> Result<()> { + db::init_db()?; + + let app = Router::new() + .route("/slack-auth", post(auth)) + .route("/device/new", post(device_new)); + + let listener = tokio::net::TcpListener::bind("0.0.0.0:8080").await.unwrap(); + log::info!("Starting auth server on port 8080"); + axum::serve(listener, app) + .with_graceful_shutdown(shutdown_signal()) + .await + .unwrap(); + + Ok(()) +} + +async fn device_new() -> StatusCode { + StatusCode::OK +} + +async fn shutdown_signal() { + let ctrl_c = async { + signal::ctrl_c() + .await + .expect("failed to install Ctrl+C handler"); + }; + + #[cfg(unix)] + let terminate = async { + signal::unix::signal(signal::unix::SignalKind::terminate()) + .expect("failed to install signal handler") + .recv() + .await; + }; + + #[cfg(not(unix))] + let terminate = std::future::pending::<()>(); + + tokio::select! { + _ = ctrl_c => {}, + _ = terminate => {}, + } +} + +// mod db { +// use rusqlite::{Connection, Result}; + +// #[derive(Debug)] +// struct User { +// id: i32, +// created_at: String, +// } +// } diff --git a/burrow/src/auth/server/providers/mod.rs b/burrow/src/auth/server/providers/mod.rs new file mode 100644 index 0000000..36ff0bd --- /dev/null +++ b/burrow/src/auth/server/providers/mod.rs @@ -0,0 +1,8 @@ +pub mod slack; +pub use super::db; + +#[derive(serde::Deserialize, Default, Debug)] +pub struct OpenIdUser { + pub sub: String, + pub name: String, +} diff --git a/burrow/src/auth/server/providers/slack.rs b/burrow/src/auth/server/providers/slack.rs new file mode 100644 index 0000000..581cd1e --- /dev/null +++ b/burrow/src/auth/server/providers/slack.rs @@ -0,0 +1,102 @@ +use anyhow::Result; +use axum::{ + extract::Json, + http::StatusCode, + routing::{get, post}, +}; +use reqwest::header::AUTHORIZATION; +use serde::Deserialize; + +use super::db::store_connection; + +#[derive(Deserialize)] +pub struct SlackToken { + slack_token: String, +} +pub async fn auth(Json(payload): Json) -> (StatusCode, String) { + let slack_user = match fetch_slack_user(&payload.slack_token).await { + Ok(user) => user, + Err(e) => { + log::error!("Failed to fetch Slack user: {:?}", e); + return (StatusCode::UNAUTHORIZED, String::new()); + } + }; + + log::info!( + "Slack user {} ({}) logged in.", + slack_user.name, + slack_user.sub + ); + + let conn = match store_connection(slack_user, "slack", &payload.slack_token, None) { + Ok(user) => user, + Err(e) => { + log::error!("Failed to fetch Slack user: {:?}", e); + return (StatusCode::UNAUTHORIZED, String::new()); + } + }; + + (StatusCode::OK, String::new()) +} + +async fn fetch_slack_user(access_token: &str) -> Result { + let client = reqwest::Client::new(); + let res = client + .get("https://slack.com/api/openid.connect.userInfo") + .header(AUTHORIZATION, format!("Bearer {}", access_token)) + .send() + .await? + .json::() + .await?; + + let res_ok = res + .get("ok") + .and_then(|v| v.as_bool()) + .ok_or(anyhow::anyhow!("Slack user object not ok!"))?; + + if !res_ok { + return Err(anyhow::anyhow!("Slack user object not ok!")); + } + + Ok(serde_json::from_value(res)?) +} + +// async fn fetch_save_slack_user_data(query: Query) -> anyhow::Result<()> { +// let client = reqwest::Client::new(); +// log::trace!("Code was {}", &query.code); +// let mut url = Url::parse("https://slack.com/api/openid.connect.token")?; + +// { +// let mut q = url.query_pairs_mut(); +// q.append_pair("client_id", &var("CLIENT_ID")?); +// q.append_pair("client_secret", &var("CLIENT_SECRET")?); +// q.append_pair("code", &query.code); +// q.append_pair("grant_type", "authorization_code"); +// q.append_pair("redirect_uri", "https://burrow.rs/callback"); +// } + +// let data = client +// .post(url) +// .send() +// .await? +// .json::() +// .await?; + +// if !data.ok { +// return Err(anyhow::anyhow!("Slack code exchange response not ok!")); +// } + +// if let Some(access_token) = data.access_token { +// log::trace!("Access token is {access_token}"); +// let user = slack::fetch_slack_user(&access_token) +// .await +// .map_err(|err| anyhow::anyhow!("Failed to fetch Slack user info {:#?}", err))?; + +// db::store_user(user, access_token, String::new()) +// .map_err(|_| anyhow::anyhow!("Failed to store user in db"))?; + +// Ok(()) +// } else { +// Err(anyhow::anyhow!("Access token not found in response")) +// } +// } diff --git a/burrow/src/lib.rs b/burrow/src/lib.rs index d9ebf7e..6aae1fb 100644 --- a/burrow/src/lib.rs +++ b/burrow/src/lib.rs @@ -5,6 +5,8 @@ pub mod wireguard; mod daemon; #[cfg(any(target_os = "linux", target_vendor = "apple"))] pub mod database; +#[cfg(any(target_os = "linux", target_vendor = "apple"))] +mod auth; pub(crate) mod tracing; #[cfg(target_vendor = "apple")] diff --git a/burrow/src/main.rs b/burrow/src/main.rs index 295373a..ff07d4c 100644 --- a/burrow/src/main.rs +++ b/burrow/src/main.rs @@ -7,6 +7,9 @@ pub(crate) mod tracing; #[cfg(any(target_os = "linux", target_vendor = "apple"))] mod wireguard; +#[cfg(any(target_os = "linux", target_vendor = "apple"))] +mod auth; + #[cfg(any(target_os = "linux", target_vendor = "apple"))] use daemon::{DaemonClient, DaemonCommand, DaemonStartOptions}; use tun::TunOptions; @@ -47,12 +50,15 @@ enum Commands { ServerConfig, /// Reload Config ReloadConfig(ReloadConfigArgs), + /// Authentication server + AuthServer, } #[derive(Args)] struct ReloadConfigArgs { #[clap(long, short)] interface_id: String, + } #[derive(Args)] @@ -133,9 +139,10 @@ async fn try_reloadconfig(interface_id: String) -> Result<()> { } #[cfg(any(target_os = "linux", target_vendor = "apple"))] -#[tokio::main(flavor = "current_thread")] +#[tokio::main] async fn main() -> Result<()> { tracing::initialize(); + dotenv::dotenv().ok(); let cli = Cli::parse(); match &cli.command { @@ -145,6 +152,7 @@ async fn main() -> Result<()> { Commands::ServerInfo => try_serverinfo().await?, Commands::ServerConfig => try_serverconfig().await?, Commands::ReloadConfig(args) => try_reloadconfig(args.interface_id.clone()).await?, + Commands::AuthServer => crate::auth::server::serve().await?, } Ok(()) @@ -152,5 +160,5 @@ async fn main() -> Result<()> { #[cfg(not(any(target_os = "linux", target_vendor = "apple")))] pub fn main() { - eprintln!("This platform is not supported currently.") + eprintln!("This platform is not supported") } diff --git a/tun/Cargo.toml b/tun/Cargo.toml index 7413f65..1b07833 100644 --- a/tun/Cargo.toml +++ b/tun/Cargo.toml @@ -8,7 +8,7 @@ libc = "0.2" fehler = "1.0" nix = { version = "0.26", features = ["ioctl"] } socket2 = "0.5" -tokio = { version = "1.28", features = [] } +tokio = { version = "1.37", default-features = false, optional = true } byteorder = "1.4" tracing = "0.1" log = "0.4" @@ -19,10 +19,7 @@ futures = { version = "0.3.28", optional = true } [features] serde = ["dep:serde", "dep:schemars"] -tokio = ["tokio/net", "dep:futures"] - -[target.'cfg(feature = "tokio")'.dev-dependencies] -tokio = { features = ["rt", "macros"] } +tokio = ["tokio/net", "dep:tokio", "dep:futures"] [target.'cfg(windows)'.dependencies] lazy_static = "1.4" @@ -37,7 +34,7 @@ windows = { version = "0.48", features = [ [target.'cfg(windows)'.build-dependencies] anyhow = "1.0" bindgen = "0.65" -reqwest = { version = "0.11", features = ["native-tls"] } +reqwest = { version = "0.11" } ssri = { version = "9.0", default-features = false } tokio = { version = "1.28", features = ["rt", "macros"] } zip = { version = "0.6", features = ["deflate"] } From 3c70bc2a5c4a012c0fc31f12f1f5e75fbde67586 Mon Sep 17 00:00:00 2001 From: Conrad Kramer Date: Sat, 6 Jul 2024 10:50:14 -0700 Subject: [PATCH 06/15] Remove SwiftLint from Xcode project --- Apple/Burrow.xcodeproj/project.pbxproj | 45 ---------- .../xcshareddata/swiftpm/Package.resolved | 86 ------------------- 2 files changed, 131 deletions(-) delete mode 100644 Apple/Burrow.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved diff --git a/Apple/Burrow.xcodeproj/project.pbxproj b/Apple/Burrow.xcodeproj/project.pbxproj index a3be02d..5c5e80b 100644 --- a/Apple/Burrow.xcodeproj/project.pbxproj +++ b/Apple/Burrow.xcodeproj/project.pbxproj @@ -294,7 +294,6 @@ buildRules = ( ); dependencies = ( - D082527D2B5DEB80005DA378 /* PBXTargetDependency */, ); name = Shared; productName = Shared; @@ -313,7 +312,6 @@ buildRules = ( ); dependencies = ( - D08252792B5DEB78005DA378 /* PBXTargetDependency */, D00117492B30373500D87C25 /* PBXTargetDependency */, ); name = NetworkExtension; @@ -334,7 +332,6 @@ buildRules = ( ); dependencies = ( - D082527B2B5DEB7D005DA378 /* PBXTargetDependency */, D00117472B30373100D87C25 /* PBXTargetDependency */, D020F65C29E4A697002790F6 /* PBXTargetDependency */, ); @@ -374,7 +371,6 @@ ); mainGroup = D05B9F6929E39EEC008CB1F9; packageReferences = ( - D08252772B5DEB6E005DA378 /* XCRemoteSwiftPackageReference "SwiftLint" */, ); productRefGroup = D05B9F7329E39EEC008CB1F9 /* Products */; projectDirPath = ""; @@ -513,18 +509,6 @@ target = D020F65229E4A697002790F6 /* NetworkExtension */; targetProxy = D020F65B29E4A697002790F6 /* PBXContainerItemProxy */; }; - D08252792B5DEB78005DA378 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - productRef = D08252782B5DEB78005DA378 /* SwiftLintPlugin */; - }; - D082527B2B5DEB7D005DA378 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - productRef = D082527A2B5DEB7D005DA378 /* SwiftLintPlugin */; - }; - D082527D2B5DEB80005DA378 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - productRef = D082527C2B5DEB80005DA378 /* SwiftLintPlugin */; - }; /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ @@ -624,35 +608,6 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ - -/* Begin XCRemoteSwiftPackageReference section */ - D08252772B5DEB6E005DA378 /* XCRemoteSwiftPackageReference "SwiftLint" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/realm/SwiftLint.git"; - requirement = { - branch = main; - kind = branch; - }; - }; -/* End XCRemoteSwiftPackageReference section */ - -/* Begin XCSwiftPackageProductDependency section */ - D08252782B5DEB78005DA378 /* SwiftLintPlugin */ = { - isa = XCSwiftPackageProductDependency; - package = D08252772B5DEB6E005DA378 /* XCRemoteSwiftPackageReference "SwiftLint" */; - productName = "plugin:SwiftLintPlugin"; - }; - D082527A2B5DEB7D005DA378 /* SwiftLintPlugin */ = { - isa = XCSwiftPackageProductDependency; - package = D08252772B5DEB6E005DA378 /* XCRemoteSwiftPackageReference "SwiftLint" */; - productName = "plugin:SwiftLintPlugin"; - }; - D082527C2B5DEB80005DA378 /* SwiftLintPlugin */ = { - isa = XCSwiftPackageProductDependency; - package = D08252772B5DEB6E005DA378 /* XCRemoteSwiftPackageReference "SwiftLint" */; - productName = "plugin:SwiftLintPlugin"; - }; -/* End XCSwiftPackageProductDependency section */ }; rootObject = D05B9F6A29E39EEC008CB1F9 /* Project object */; } diff --git a/Apple/Burrow.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Apple/Burrow.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved deleted file mode 100644 index 9378372..0000000 --- a/Apple/Burrow.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ /dev/null @@ -1,86 +0,0 @@ -{ - "pins" : [ - { - "identity" : "collectionconcurrencykit", - "kind" : "remoteSourceControl", - "location" : "https://github.com/JohnSundell/CollectionConcurrencyKit.git", - "state" : { - "revision" : "b4f23e24b5a1bff301efc5e70871083ca029ff95", - "version" : "0.2.0" - } - }, - { - "identity" : "cryptoswift", - "kind" : "remoteSourceControl", - "location" : "https://github.com/krzyzanowskim/CryptoSwift.git", - "state" : { - "revision" : "7892a123f7e8d0fe62f9f03728b17bbd4f94df5c", - "version" : "1.8.1" - } - }, - { - "identity" : "sourcekitten", - "kind" : "remoteSourceControl", - "location" : "https://github.com/jpsim/SourceKitten.git", - "state" : { - "revision" : "b6dc09ee51dfb0c66e042d2328c017483a1a5d56", - "version" : "0.34.1" - } - }, - { - "identity" : "swift-argument-parser", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-argument-parser.git", - "state" : { - "revision" : "8f4d2753f0e4778c76d5f05ad16c74f707390531", - "version" : "1.2.3" - } - }, - { - "identity" : "swift-syntax", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-syntax.git", - "state" : { - "revision" : "64889f0c732f210a935a0ad7cda38f77f876262d", - "version" : "509.1.1" - } - }, - { - "identity" : "swiftlint", - "kind" : "remoteSourceControl", - "location" : "https://github.com/realm/SwiftLint.git", - "state" : { - "branch" : "main", - "revision" : "7595ad3fafc1a31086dc40ba01fd898bf6b42d5f" - } - }, - { - "identity" : "swiftytexttable", - "kind" : "remoteSourceControl", - "location" : "https://github.com/scottrhoyt/SwiftyTextTable.git", - "state" : { - "revision" : "c6df6cf533d120716bff38f8ff9885e1ce2a4ac3", - "version" : "0.9.0" - } - }, - { - "identity" : "swxmlhash", - "kind" : "remoteSourceControl", - "location" : "https://github.com/drmohundro/SWXMLHash.git", - "state" : { - "revision" : "a853604c9e9a83ad9954c7e3d2a565273982471f", - "version" : "7.0.2" - } - }, - { - "identity" : "yams", - "kind" : "remoteSourceControl", - "location" : "https://github.com/jpsim/Yams.git", - "state" : { - "revision" : "0d9ee7ea8c4ebd4a489ad7a73d5c6cad55d6fed3", - "version" : "5.0.6" - } - } - ], - "version" : 2 -} From 3dedca4de308a16f1782f3fdc30c6cce5056c8d3 Mon Sep 17 00:00:00 2001 From: Conrad Kramer Date: Sat, 6 Jul 2024 17:20:46 -0700 Subject: [PATCH 07/15] Update build settings --- .github/actions/notarize/action.yml | 34 +- .github/workflows/build-apple.yml | 4 +- .github/workflows/build-rpm.yml | 2 +- .github/workflows/build-rust.yml | 10 +- .github/workflows/release-apple.yml | 41 +- Apple/App/AppDelegate.swift | 3 +- Apple/App/MainMenu.xib | 4 +- Cargo.lock | 754 ++++++++++++++-------------- Dockerfile | 3 +- 9 files changed, 407 insertions(+), 448 deletions(-) diff --git a/.github/actions/notarize/action.yml b/.github/actions/notarize/action.yml index f3f98f2..efd2159 100644 --- a/.github/actions/notarize/action.yml +++ b/.github/actions/notarize/action.yml @@ -9,12 +9,6 @@ inputs: app-store-key-issuer-id: description: App Store key issuer ID required: true - archive-path: - description: Xcode archive path - required: true - export-path: - description: The path to export the archive to - required: true runs: using: composite steps: @@ -24,28 +18,8 @@ runs: run: | echo "${{ inputs.app-store-key }}" > AuthKey_${{ inputs.app-store-key-id }}.p8 - echo '{"destination":"export","method":"developer-id"}' \ - | plutil -convert xml1 -o ExportOptions.plist - + ditto -c -k --keepParent Release/Burrow.app Upload.zip + xcrun notarytool submit --wait --issuer ${{ inputs.app-store-key-issuer-id }} --key-id ${{ inputs.app-store-key-id }} --key "${PWD}/AuthKey_${{ inputs.app-store-key-id }}.p8" Upload.zip + xcrun stapler staple Release/Burrow.app - xcodebuild -exportArchive \ - -allowProvisioningUpdates \ - -allowProvisioningDeviceRegistration \ - -skipPackagePluginValidation \ - -skipMacroValidation \ - -onlyUsePackageVersionsFromResolvedFile \ - -authenticationKeyID ${{ inputs.app-store-key-id }} \ - -authenticationKeyIssuerID ${{ inputs.app-store-key-issuer-id }} \ - -authenticationKeyPath "${PWD}/AuthKey_${{ inputs.app-store-key-id }}.p8" \ - -archivePath Wallet.xcarchive \ - -exportPath Release \ - -exportOptionsPlist ExportOptions.plist - - ditto -c -k --keepParent Release/Wallet.app Upload.zip - SUBMISSION_ID=$(xcrun notarytool submit --issuer ${{ inputs.app-store-key-issuer-id }} --key-id ${{ inputs.app-store-key-id }} --key "${PWD}/AuthKey_${{ inputs.app-store-key-id }}.p8" Upload.zip | awk '/ id:/ { print $2; exit }') - - xcrun notarytool wait $SUBMISSION_ID --issuer ${{ inputs.app-store-key-issuer-id }} --key-id ${{ inputs.app-store-key-id }} --key "${PWD}/AuthKey_${{ inputs.app-store-key-id }}.p8" - xcrun stapler staple Release/Wallet.app - - aa archive -a lzma -b 8m -d Release -subdir Wallet.app -o Wallet.app.aar - - rm -rf Upload.zip Release AuthKey_${{ inputs.app-store-key-id }}.p8 ExportOptions.plist + rm -rf AuthKey_${{ inputs.app-store-key-id }}.p8 Release diff --git a/.github/workflows/build-apple.yml b/.github/workflows/build-apple.yml index 00b6bec..84cc03a 100644 --- a/.github/workflows/build-apple.yml +++ b/.github/workflows/build-apple.yml @@ -24,7 +24,7 @@ jobs: rust-targets: - aarch64-apple-ios - scheme: App - destination: platform=iOS Simulator,OS=17.2,name=iPhone 15 Pro + destination: platform=iOS Simulator,OS=18.0,name=iPhone 15 Pro platform: iOS Simulator sdk-name: iphonesimulator rust-targets: @@ -38,7 +38,7 @@ jobs: - x86_64-apple-darwin - aarch64-apple-darwin env: - DEVELOPER_DIR: /Applications/Xcode_15.2.app/Contents/Developer + DEVELOPER_DIR: /Applications/Xcode_16.0.app/Contents/Developer steps: - name: Checkout uses: actions/checkout@v3 diff --git a/.github/workflows/build-rpm.yml b/.github/workflows/build-rpm.yml index e0ce8df..029bf16 100644 --- a/.github/workflows/build-rpm.yml +++ b/.github/workflows/build-rpm.yml @@ -5,7 +5,7 @@ jobs: name: Build RPM runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: Swatinem/rust-cache@v2 - name: Install RPM run: cargo install cargo-generate-rpm diff --git a/.github/workflows/build-rust.yml b/.github/workflows/build-rust.yml index 3255fc7..76ce9f2 100644 --- a/.github/workflows/build-rust.yml +++ b/.github/workflows/build-rust.yml @@ -22,14 +22,18 @@ jobs: targets: - aarch64-unknown-linux-gnu - os: macos-12 - platform: macOS + platform: macOS (Intel) test-targets: - x86_64-apple-darwin targets: + - x86_64-apple-ios + - os: macos-14 + platform: macOS + test-targets: - aarch64-apple-darwin + targets: - aarch64-apple-ios - aarch64-apple-ios-sim - - x86_64-apple-ios - os: windows-2022 platform: Windows test-targets: @@ -38,7 +42,7 @@ jobs: - aarch64-pc-windows-msvc runs-on: ${{ matrix.os }} env: - DEVELOPER_DIR: /Applications/Xcode_14.2.app/Contents/Developer + DEVELOPER_DIR: /Applications/Xcode_15.4.app/Contents/Developer CARGO_INCREMENTAL: 0 CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER: aarch64-linux-gnu-gcc RUST_BACKTRACE: short diff --git a/.github/workflows/release-apple.yml b/.github/workflows/release-apple.yml index 786fb54..1883008 100644 --- a/.github/workflows/release-apple.yml +++ b/.github/workflows/release-apple.yml @@ -13,7 +13,8 @@ jobs: fail-fast: false matrix: include: - - destination: generic/platform=iOS + - + destination: generic/platform=iOS platform: iOS rust-targets: - aarch64-apple-ios @@ -23,7 +24,7 @@ jobs: - x86_64-apple-darwin - aarch64-apple-darwin env: - DEVELOPER_DIR: /Applications/Xcode_15.2.app/Contents/Developer + DEVELOPER_DIR: /Applications/Xcode_15.4.app/Contents/Developer steps: - name: Checkout uses: actions/checkout@v4 @@ -50,25 +51,23 @@ jobs: app-store-key-id: ${{ secrets.APPSTORE_KEY_ID }} app-store-key-issuer-id: ${{ secrets.APPSTORE_KEY_ISSUER_ID }} archive-path: Burrow.xcarchive - - name: Notarize (macOS) - if: ${{ matrix.platform == 'macOS' }} - uses: ./.github/actions/notarize - with: - app-store-key: ${{ secrets.APPSTORE_KEY }} - app-store-key-id: ${{ secrets.APPSTORE_KEY_ID }} - app-store-key-issuer-id: ${{ secrets.APPSTORE_KEY_ISSUER_ID }} - archive-path: Burrow.xcarchive - - name: Export IPA (iOS) - if: ${{ matrix.platform == 'iOS' }} + - name: Export uses: ./.github/actions/export with: - method: ad-hoc + method: ${{ matrix.platform == 'macOS' && 'developer-id' || 'ad-hoc' }} destination: export app-store-key: ${{ secrets.APPSTORE_KEY }} app-store-key-id: ${{ secrets.APPSTORE_KEY_ID }} app-store-key-issuer-id: ${{ secrets.APPSTORE_KEY_ISSUER_ID }} archive-path: Burrow.xcarchive export-path: Release + - name: Notarize + if: ${{ matrix.platform == 'macOS' }} + uses: ./.github/actions/notarize + with: + app-store-key: ${{ secrets.APPSTORE_KEY }} + app-store-key-id: ${{ secrets.APPSTORE_KEY_ID }} + app-store-key-issuer-id: ${{ secrets.APPSTORE_KEY_ISSUER_ID }} - name: Compress (iOS) if: ${{ matrix.platform == 'iOS' }} shell: bash @@ -83,27 +82,17 @@ jobs: aa archive -a lzma -b 8m -d Apple/Release -subdir Burrow.app -o Burrow.app.aar aa archive -a lzma -b 8m -d Apple -subdir Burrow.xcarchive -o Burrow-${{ matrix.platform }}.xcarchive.aar rm -rf Apple/Release - - name: Upload to GitHub (iOS) - if: ${{ matrix.platform == 'iOS' }} + - name: Upload to GitHub uses: SierraSoftworks/gh-releases@v1.0.7 with: token: ${{ secrets.GITHUB_TOKEN }} release_tag: ${{ github.ref_name }} overwrite: 'true' files: | - Burrow.ipa - Burrow-${{ matrix.platform }}.xcarchive.aar - - name: Upload to GitHub (macOS) - if: ${{ matrix.platform == 'macOS' }} - uses: SierraSoftworks/gh-releases@v1.0.7 - with: - token: ${{ secrets.GITHUB_TOKEN }} - release_tag: ${{ github.ref_name }} - overwrite: 'true' - files: | - Burrow.aap.aar + ${{ matrix.platform == 'macOS' && 'Burrow.aap.aar' || 'Burrow.ipa' }} Burrow-${{ matrix.platform }}.xcarchive.aar - name: Upload to App Store Connect + if: ${{ matrix.platform == 'iOS' }} uses: ./.github/actions/export with: method: app-store diff --git a/Apple/App/AppDelegate.swift b/Apple/App/AppDelegate.swift index 6085d85..bd76a2f 100644 --- a/Apple/App/AppDelegate.swift +++ b/Apple/App/AppDelegate.swift @@ -2,8 +2,7 @@ import AppKit import SwiftUI -@MainActor -@NSApplicationMain +@MainActor @main class AppDelegate: NSObject, NSApplicationDelegate { private let quitItem: NSMenuItem = { let quitItem = NSMenuItem( diff --git a/Apple/App/MainMenu.xib b/Apple/App/MainMenu.xib index 8933f30..587f6c4 100644 --- a/Apple/App/MainMenu.xib +++ b/Apple/App/MainMenu.xib @@ -1,7 +1,7 @@ - + - + diff --git a/Cargo.lock b/Cargo.lock index ce263f9..5ef886c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,9 +4,9 @@ version = 3 [[package]] name = "addr2line" -version = "0.22.0" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" dependencies = [ "gimli", ] @@ -29,9 +29,9 @@ dependencies = [ [[package]] name = "aes" -version = "0.8.4" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +checksum = "ac1f845298e95f983ff1944b728ae08b8cebab80d684f0a832ed0fc74dfa27e2" dependencies = [ "cfg-if", "cipher", @@ -40,9 +40,9 @@ dependencies = [ [[package]] name = "ahash" -version = "0.8.11" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +checksum = "d713b3834d76b85304d4d525563c1276e2e30dc97cc67bfb4585a4a29fc2c89f" dependencies = [ "cfg-if", "once_cell", @@ -52,57 +52,62 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.1.3" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" dependencies = [ "memchr", ] [[package]] -name = "anstream" -version = "0.6.14" +name = "allocator-api2" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b" +checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" + +[[package]] +name = "anstream" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e2e1ebcb11de5c03c67de28a7df593d32191b44939c482e97702baaaa6ab6a5" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", - "is_terminal_polyfill", "utf8parse", ] [[package]] name = "anstyle" -version = "1.0.7" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" +checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" [[package]] name = "anstyle-parse" -version = "0.2.4" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4" +checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.1.0" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad186efb764318d35165f1758e7dcef3b10628e26d41a44bc5550652e6804391" +checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" dependencies = [ "windows-sys 0.52.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.3" +version = "3.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19" +checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" dependencies = [ "anstyle", "windows-sys 0.52.0", @@ -110,17 +115,18 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.86" +version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" +checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca" [[package]] name = "async-channel" -version = "2.3.1" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89b47800b0be77592da0afd425cc03468052844aff33b84e33cc696f64e77b6a" +checksum = "1ca33f4bc4ed1babef42cad36cc1f51fa88be00420404e5b1e80ab1b18f7678c" dependencies = [ "concurrent-queue", + "event-listener", "event-listener-strategy", "futures-core", "pin-project-lite", @@ -145,25 +151,25 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.48", ] [[package]] name = "async-trait" -version = "0.1.80" +version = "0.1.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca" +checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.48", ] [[package]] name = "autocfg" -version = "1.3.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "axum" @@ -176,9 +182,9 @@ dependencies = [ "bitflags 1.3.2", "bytes", "futures-util", - "http 0.2.12", + "http 0.2.11", "http-body 0.4.6", - "hyper 0.14.29", + "hyper 0.14.28", "itoa", "matchit", "memchr", @@ -236,7 +242,7 @@ dependencies = [ "async-trait", "bytes", "futures-util", - "http 0.2.12", + "http 0.2.11", "http-body 0.4.6", "mime", "rustversion", @@ -267,9 +273,9 @@ dependencies = [ [[package]] name = "backtrace" -version = "0.3.73" +version = "0.3.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cc23269a4f8976d0a4d2e7109211a419fe30e8d88d677cd60b6bc79c5732e0a" +checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" dependencies = [ "addr2line", "cc", @@ -339,7 +345,7 @@ dependencies = [ "regex", "rustc-hash", "shlex", - "syn 2.0.68", + "syn 2.0.48", "which", ] @@ -351,9 +357,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.6.0" +version = "2.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" [[package]] name = "blake2" @@ -375,9 +381,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.16.0" +version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" +checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" [[package]] name = "burrow" @@ -432,9 +438,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.6.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" +checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" [[package]] name = "bzip2" @@ -469,13 +475,12 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.104" +version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74b6a57f98764a267ff415d50a25e6e166f3831a5071af4995296ea97d210490" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" dependencies = [ "jobserver", "libc", - "once_cell", ] [[package]] @@ -530,20 +535,20 @@ dependencies = [ [[package]] name = "clang-sys" -version = "1.8.1" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +checksum = "67523a3b4be3ce1989d607a828d036249522dd9c1c8de7f4dd2dae43a37369d1" dependencies = [ "glob", "libc", - "libloading 0.8.4", + "libloading 0.8.1", ] [[package]] name = "clap" -version = "4.5.8" +version = "4.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84b3edb18336f4df585bc9aa31dd99c036dfa5dc5e9a2939a722a188f3a8970d" +checksum = "1e578d6ec4194633722ccf9544794b71b1385c3c027efe0c55db226fc880865c" dependencies = [ "clap_builder", "clap_derive", @@ -551,9 +556,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.8" +version = "4.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1c09dd5ada6c6c78075d6fd0da3f90d8080651e2d6cc8eb2f1aaa4034ced708" +checksum = "4df4df40ec50c46000231c914968278b1eb05098cf8f1b3a518a95030e71d1c7" dependencies = [ "anstream", "anstyle", @@ -563,33 +568,33 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.8" +version = "4.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bac35c6dafb060fd4d275d9a4ffae97917c13a6327903a8be2153cd964f7085" +checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442" dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.48", ] [[package]] name = "clap_lex" -version = "0.7.1" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b82cf0babdbd58558212896d1a4272303a57bdb245c2bf1147185fb45640e70" +checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" [[package]] name = "colorchoice" -version = "1.0.1" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" [[package]] name = "concurrent-queue" -version = "2.5.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +checksum = "d16048cd947b08fa32c24458a22f5dc5e835264f689f4f5653210c69fd107363" dependencies = [ "crossbeam-utils", ] @@ -677,27 +682,27 @@ dependencies = [ [[package]] name = "crc32fast" -version = "1.4.2" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" dependencies = [ "cfg-if", ] [[package]] name = "crossbeam-channel" -version = "0.5.13" +version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33480d6946193aa8033910124896ca395333cae7e2d1113d1fef6c3272217df2" +checksum = "176dc175b78f56c0f321911d9c8eb2b77a78a4860b9c19db83835fea1a46649b" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-utils" -version = "0.8.20" +version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" +checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" [[package]] name = "crypto-common" @@ -712,14 +717,15 @@ dependencies = [ [[package]] name = "curve25519-dalek" -version = "4.1.3" +version = "4.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" +checksum = "e89b8c6a2e4b1f45971ad09761aafb85514a84744b67a95e32c3cc1352d1f65c" dependencies = [ "cfg-if", "cpufeatures", "curve25519-dalek-derive", "fiat-crypto", + "platforms", "rustc_version", "subtle", "zeroize", @@ -733,7 +739,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.48", ] [[package]] @@ -764,15 +770,15 @@ checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" [[package]] name = "dyn-clone" -version = "1.0.17" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125" +checksum = "545b22097d44f8a9581187cdf93de7a71e4722bf51200cfaba810865b49a495d" [[package]] name = "either" -version = "1.13.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" +checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" [[package]] name = "encode_unicode" @@ -782,9 +788,9 @@ checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" [[package]] name = "encoding_rs" -version = "0.8.34" +version = "0.8.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" +checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1" dependencies = [ "cfg-if", ] @@ -797,9 +803,9 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.9" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" dependencies = [ "libc", "windows-sys 0.52.0", @@ -807,9 +813,9 @@ dependencies = [ [[package]] name = "event-listener" -version = "5.3.1" +version = "4.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6032be9bd27023a771701cc49f9f053c751055f71efb2e0ae5c15809093675ba" +checksum = "67b215c49b2b248c855fb73579eb1f4f26c38ffdc12973e20e07b91d78d5646e" dependencies = [ "concurrent-queue", "parking", @@ -818,9 +824,9 @@ dependencies = [ [[package]] name = "event-listener-strategy" -version = "0.5.2" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f214dc438f977e6d4e3500aaa277f5ad94ca83fbbd9b1a15713ce2344ccc5a1" +checksum = "958e4d70b6d5e81971bebec42271ec641e7ff4e170a6fa605f2b8a8b65cb97d3" dependencies = [ "event-listener", "pin-project-lite", @@ -840,9 +846,9 @@ checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" [[package]] name = "fastrand" -version = "2.1.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" +checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" [[package]] name = "fehler" @@ -866,15 +872,15 @@ dependencies = [ [[package]] name = "fiat-crypto" -version = "0.2.9" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" +checksum = "27573eac26f4dd11e2b1916c3fe1baa56407c83c71a773a8ba17ec0bca03b6b7" [[package]] name = "flate2" -version = "1.0.30" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae" +checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" dependencies = [ "crc32fast", "miniz_oxide", @@ -966,7 +972,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.48", ] [[package]] @@ -1011,9 +1017,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.15" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" dependencies = [ "cfg-if", "libc", @@ -1022,9 +1028,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.29.0" +version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" [[package]] name = "glob" @@ -1034,17 +1040,17 @@ checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" [[package]] name = "h2" -version = "0.3.26" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" +checksum = "bb2c4422095b67ee78da96fbb51a4cc413b3b25883c7717ff7ca1ab31022c9c9" dependencies = [ "bytes", "fnv", "futures-core", "futures-sink", "futures-util", - "http 0.2.12", - "indexmap 2.2.6", + "http 0.2.11", + "indexmap 2.1.0", "slab", "tokio", "tokio-util", @@ -1059,20 +1065,21 @@ checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] name = "hashbrown" -version = "0.14.5" +version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" dependencies = [ "ahash", + "allocator-api2", ] [[package]] name = "hashlink" -version = "0.9.1" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ba4ff7128dee98c7dc9794b6a411377e1404dba1c97deb8d1a55297bd25d8af" +checksum = "692eaaf7f7607518dd3cef090f1474b61edc5301d8012f09579920df68b725ee" dependencies = [ - "hashbrown 0.14.5", + "hashbrown 0.14.3", ] [[package]] @@ -1090,15 +1097,15 @@ dependencies = [ [[package]] name = "heck" -version = "0.5.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "hermit-abi" -version = "0.3.9" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" +checksum = "5d3d0e0f38255e7fa3cf31335b3a56f05febd18025f4db5ef7a0cfb4f8da651f" [[package]] name = "hex" @@ -1126,9 +1133,9 @@ dependencies = [ [[package]] name = "http" -version = "0.2.12" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +checksum = "8947b1a6fad4393052c7ba1f4cd97bed3e953a95c79c92ad9b051a04611d9fbb" dependencies = [ "bytes", "fnv", @@ -1153,7 +1160,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" dependencies = [ "bytes", - "http 0.2.12", + "http 0.2.11", "pin-project-lite", ] @@ -1182,9 +1189,9 @@ dependencies = [ [[package]] name = "httparse" -version = "1.9.4" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" [[package]] name = "httpdate" @@ -1200,16 +1207,16 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "hyper" -version = "0.14.29" +version = "0.14.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f361cde2f109281a220d4307746cdfd5ee3f410da58a70377762396775634b33" +checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80" dependencies = [ "bytes", "futures-channel", "futures-core", "futures-util", "h2", - "http 0.2.12", + "http 0.2.11", "http-body 0.4.6", "httparse", "httpdate", @@ -1266,7 +1273,7 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" dependencies = [ - "hyper 0.14.29", + "hyper 0.14.28", "pin-project-lite", "tokio", "tokio-io-timeout", @@ -1279,7 +1286,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" dependencies = [ "bytes", - "hyper 0.14.29", + "hyper 0.14.28", "native-tls", "tokio", "tokio-native-tls", @@ -1327,12 +1334,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.2.6" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" +checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" dependencies = [ "equivalent", - "hashbrown 0.14.5", + "hashbrown 0.14.3", ] [[package]] @@ -1346,15 +1353,16 @@ dependencies = [ [[package]] name = "insta" -version = "1.39.0" +version = "1.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "810ae6042d48e2c9e9215043563a58a80b877bc863228a74cf10c49d4620a6f5" +checksum = "5d64600be34b2fcfc267740a243fa7744441bb4947a619ac4e5bb6507f35fbfc" dependencies = [ "console", "lazy_static", "linked-hash-map", "serde", "similar", + "yaml-rust", ] [[package]] @@ -1385,50 +1393,44 @@ version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" -[[package]] -name = "is_terminal_polyfill" -version = "1.70.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" - [[package]] name = "itertools" -version = "0.12.1" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" dependencies = [ "either", ] [[package]] name = "itoa" -version = "1.0.11" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" [[package]] name = "jobserver" -version = "0.1.31" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2b099aaa34a9751c5bf0878add70444e1ed2dd73f347be99003d4577277de6e" +checksum = "8c37f63953c4c63420ed5fd3d6d398c719489b9f872b9fa683262f8edd363c7d" dependencies = [ "libc", ] [[package]] name = "js-sys" -version = "0.3.69" +version = "0.3.67" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" +checksum = "9a1d36f1235bc969acba30b7f5990b864423a6068a10f7c90ae8f0112e3a59d1" dependencies = [ "wasm-bindgen", ] [[package]] name = "lazy_static" -version = "1.5.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "lazycell" @@ -1438,9 +1440,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" -version = "0.2.155" +version = "0.2.152" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" +checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7" [[package]] name = "libloading" @@ -1454,12 +1456,12 @@ dependencies = [ [[package]] name = "libloading" -version = "0.8.4" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e310b3a6b5907f99202fcdb4960ff45b93735d7c7d96b760fcff8db2dc0e103d" +checksum = "c571b676ddfc9a8c12f1f3d3085a7b163966a8fd8098a90640953ce5f6170161" dependencies = [ "cfg-if", - "windows-targets 0.52.6", + "windows-sys 0.48.0", ] [[package]] @@ -1499,15 +1501,15 @@ checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" [[package]] name = "linux-raw-sys" -version = "0.4.14" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" +checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" [[package]] name = "lock_api" -version = "0.4.12" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" dependencies = [ "autocfg", "scopeguard", @@ -1515,9 +1517,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.22" +version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" [[package]] name = "matchers" @@ -1536,9 +1538,9 @@ checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" [[package]] name = "memchr" -version = "2.7.4" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" [[package]] name = "memoffset" @@ -1551,9 +1553,9 @@ dependencies = [ [[package]] name = "memoffset" -version = "0.9.1" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" dependencies = [ "autocfg", ] @@ -1578,7 +1580,7 @@ checksum = "49e7bc1560b95a3c4a25d03de42fe76ca718ab92d1a22a55b9b4cf67b3ae635c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.48", ] [[package]] @@ -1595,18 +1597,18 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.7.4" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" +checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" dependencies = [ "adler", ] [[package]] name = "mio" -version = "0.8.11" +version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" +checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09" dependencies = [ "libc", "wasi", @@ -1615,10 +1617,11 @@ dependencies = [ [[package]] name = "native-tls" -version = "0.2.12" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466" +checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" dependencies = [ + "lazy_static", "libc", "log", "openssl", @@ -1649,10 +1652,10 @@ version = "0.27.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.4.2", "cfg-if", "libc", - "memoffset 0.9.1", + "memoffset 0.9.0", ] [[package]] @@ -1675,17 +1678,11 @@ dependencies = [ "winapi", ] -[[package]] -name = "num-conv" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" - [[package]] name = "num-traits" -version = "0.2.19" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" dependencies = [ "autocfg", ] @@ -1702,9 +1699,9 @@ dependencies = [ [[package]] name = "object" -version = "0.36.1" +version = "0.32.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "081b846d1d56ddfc18fdf1a922e4f6e07a11768ea1b92dec44e42b72712ccfce" +checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" dependencies = [ "memchr", ] @@ -1717,17 +1714,17 @@ checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "opaque-debug" -version = "0.3.1" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" [[package]] name = "openssl" -version = "0.10.64" +version = "0.10.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95a0481286a310808298130d22dd1fef0fa571e05a8f44ec801801e84b216b1f" +checksum = "15c9d69dd87a29568d4d017cfe8ec518706046a05184e5aea92d0af890b803c8" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.4.2", "cfg-if", "foreign-types", "libc", @@ -1744,7 +1741,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.48", ] [[package]] @@ -1755,9 +1752,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.102" +version = "0.9.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c597637d56fbc83893a35eb0dd04b2b8e7a50c91e64e9493e398b5df4fb45fa2" +checksum = "22e1bf214306098e4832460f797824c05d25aacdf896f64a985fb0fd992454ae" dependencies = [ "cc", "libc", @@ -1779,9 +1776,9 @@ checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae" [[package]] name = "parking_lot" -version = "0.12.3" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" dependencies = [ "lock_api", "parking_lot_core", @@ -1789,15 +1786,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.10" +version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", - "windows-targets 0.52.6", + "windows-targets 0.48.5", ] [[package]] @@ -1837,29 +1834,29 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pin-project" -version = "1.1.5" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" +checksum = "0302c4a0442c456bd56f841aee5c3bfd17967563f6fadc9ceb9f9c23cf3807e0" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.5" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" +checksum = "266c042b60c9c76b8d53061e52b2e0d1116abc57cefc8c5cd671619a56ac3690" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.48", ] [[package]] name = "pin-project-lite" -version = "0.2.14" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" [[package]] name = "pin-utils" @@ -1869,9 +1866,15 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkg-config" -version = "0.3.30" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" +checksum = "2900ede94e305130c13ddd391e0ab7cbaeb783945ae07a279c268cb05109c6cb" + +[[package]] +name = "platforms" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "626dec3cac7cc0e1577a2ec3fc496277ec2baa084bebad95bb6fdbfae235f84c" [[package]] name = "poly1305" @@ -1898,28 +1901,28 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "prettyplease" -version = "0.2.20" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f12335488a2f3b0a83b14edad48dca9879ce89b2edd10e80237e4e852dd645e" +checksum = "a41cf62165e97c7f814d2221421dbb9afcbcdb0a88068e5ea206e19951c2cbb5" dependencies = [ "proc-macro2", - "syn 2.0.68", + "syn 2.0.48", ] [[package]] name = "proc-macro2" -version = "1.0.86" +version = "1.0.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" dependencies = [ "unicode-ident", ] [[package]] name = "prost" -version = "0.12.6" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "deb1435c188b76130da55f17a466d252ff7b1418b2ad3e037d127b94e3411f29" +checksum = "146c289cda302b98a28d40c8b3b90498d6e526dd24ac2ecea73e4e491685b94a" dependencies = [ "bytes", "prost-derive", @@ -1927,22 +1930,22 @@ dependencies = [ [[package]] name = "prost-derive" -version = "0.12.6" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1" +checksum = "efb6c9a1dd1def8e2124d17e83a20af56f1570d6c2d2bd9e266ccb768df3840e" dependencies = [ "anyhow", "itertools", "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.48", ] [[package]] name = "prost-types" -version = "0.12.6" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9091c90b0a32608e984ff2fa4091273cbdd755d54935c51d520887f4a1dbd5b0" +checksum = "193898f59edcf43c26227dcd4c8427f00d99d61e95dcde58dabd49fa291d470e" dependencies = [ "prost", ] @@ -1996,9 +1999,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.36" +version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" dependencies = [ "proc-macro2", ] @@ -2035,23 +2038,23 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.2" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c82cf8cff14456045f55ec4241383baeff27af886adb72ffb2162f99911de0fd" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" dependencies = [ - "bitflags 2.6.0", + "bitflags 1.3.2", ] [[package]] name = "regex" -version = "1.10.5" +version = "1.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f" +checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.7", - "regex-syntax 0.8.4", + "regex-automata 0.4.5", + "regex-syntax 0.8.2", ] [[package]] @@ -2065,13 +2068,13 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.7" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +checksum = "5bb987efffd3c6d0d8f5f89510bb458559eab11e4f869acb20bf845e016259cd" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.8.4", + "regex-syntax 0.8.2", ] [[package]] @@ -2082,15 +2085,15 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" -version = "0.8.4" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" [[package]] name = "reqwest" -version = "0.11.27" +version = "0.11.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" +checksum = "37b1ae8d9ac08420c66222fb9096fc5de435c3c48542bc5336c51892cffafb41" dependencies = [ "base64 0.21.7", "bytes", @@ -2098,9 +2101,9 @@ dependencies = [ "futures-core", "futures-util", "h2", - "http 0.2.12", + "http 0.2.11", "http-body 0.4.6", - "hyper 0.14.29", + "hyper 0.14.28", "hyper-tls", "ipnet", "js-sys", @@ -2110,11 +2113,9 @@ dependencies = [ "once_cell", "percent-encoding", "pin-project-lite", - "rustls-pemfile 1.0.4", "serde", "serde_json", "serde_urlencoded", - "sync_wrapper 0.1.2", "system-configuration", "tokio", "tokio-native-tls", @@ -2151,7 +2152,7 @@ dependencies = [ "pin-project-lite", "quinn", "rustls", - "rustls-pemfile 2.1.2", + "rustls-pemfile", "rustls-pki-types", "serde", "serde_json", @@ -2170,17 +2171,16 @@ dependencies = [ [[package]] name = "ring" -version = "0.17.8" +version = "0.17.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" +checksum = "688c63d65483050968b2a8937f7995f443e27041a0f7700aa59b0822aedebb74" dependencies = [ "cc", - "cfg-if", "getrandom", "libc", "spin", "untrusted", - "windows-sys 0.52.0", + "windows-sys 0.48.0", ] [[package]] @@ -2189,7 +2189,7 @@ version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b838eba278d213a8beaf485bd313fd580ca4505a00d5871caeb1457c55322cae" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.4.2", "fallible-iterator", "fallible-streaming-iterator", "hashlink", @@ -2199,9 +2199,9 @@ dependencies = [ [[package]] name = "rustc-demangle" -version = "0.1.24" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" [[package]] name = "rustc-hash" @@ -2220,11 +2220,11 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.34" +version = "0.38.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" +checksum = "322394588aaf33c24007e8bb3238ee3e4c5c09c084ab32bc73890b99ff326bca" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.4.2", "errno", "libc", "linux-raw-sys", @@ -2245,15 +2245,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "rustls-pemfile" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" -dependencies = [ - "base64 0.21.7", -] - [[package]] name = "rustls-pemfile" version = "2.1.2" @@ -2283,15 +2274,15 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.17" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" +checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" [[package]] name = "ryu" -version = "1.0.18" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" [[package]] name = "schannel" @@ -2304,9 +2295,9 @@ dependencies = [ [[package]] name = "schemars" -version = "0.8.21" +version = "0.8.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09c024468a378b7e36765cd36702b7a90cc3cba11654f6685c8f233408e89e92" +checksum = "45a28f4c49489add4ce10783f7911893516f15afe45d015608d41faca6bc4d29" dependencies = [ "dyn-clone", "schemars_derive", @@ -2316,14 +2307,14 @@ dependencies = [ [[package]] name = "schemars_derive" -version = "0.8.21" +version = "0.8.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1eee588578aff73f856ab961cd2f79e36bc45d7ded33a7562adba4667aecc0e" +checksum = "c767fd6fa65d9ccf9cf026122c1b555f2ef9a4f0cea69da4d7dbc3e258d30967" dependencies = [ "proc-macro2", "quote", "serde_derive_internals", - "syn 2.0.68", + "syn 1.0.109", ] [[package]] @@ -2334,11 +2325,11 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "security-framework" -version = "2.11.0" +version = "2.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c627723fd09706bacdb5cf41499e95098555af3c3c29d014dc3c458ef6be11c0" +checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" dependencies = [ - "bitflags 2.6.0", + "bitflags 1.3.2", "core-foundation", "core-foundation-sys", "libc", @@ -2347,9 +2338,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.11.0" +version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "317936bbbd05227752583946b9e66d7ce3b489f84e11a94a510b4437fef407d7" +checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" dependencies = [ "core-foundation-sys", "libc", @@ -2357,46 +2348,46 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.23" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" +checksum = "b97ed7a9823b74f99c7742f5336af7be5ecd3eeafcb1507d1fa93347b1d589b0" [[package]] name = "serde" -version = "1.0.203" +version = "1.0.196" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" +checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.203" +version = "1.0.196" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" +checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.48", ] [[package]] name = "serde_derive_internals" -version = "0.29.1" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" +checksum = "85bf8229e7920a9f636479437026331ce11aa132b4dde37d121944a44d6e5f3c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 1.0.109", ] [[package]] name = "serde_json" -version = "1.0.120" +version = "1.0.112" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e0d21c9a8cae1235ad58a00c11cb40d4b1e5c784f1ef2c537876ed6ffd8b7c5" +checksum = "4d1bd37ce2324cf3bf85e5a25f96eb4baf0d5aa6eba43e7ae8958870c4ec48ed" dependencies = [ "itoa", "ryu", @@ -2484,9 +2475,9 @@ dependencies = [ [[package]] name = "similar" -version = "2.5.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa42c91313f1d05da9b26f267f931cf178d4aba455b4c4622dd7355eb80c6640" +checksum = "32fea41aca09ee824cc9724996433064c89f7777e60762749a4170a14abbfa21" [[package]] name = "slab" @@ -2499,18 +2490,18 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.13.2" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" [[package]] name = "socket2" -version = "0.5.7" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" +checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.48.0", ] [[package]] @@ -2537,15 +2528,15 @@ dependencies = [ [[package]] name = "strsim" -version = "0.11.1" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "subtle" -version = "2.6.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" [[package]] name = "syn" @@ -2560,9 +2551,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.68" +version = "2.0.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "901fa70d88b9d6c98022e23b4136f9f3e54e4662c3bc1bd1d84a42a9a0f0c1e9" +checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" dependencies = [ "proc-macro2", "quote", @@ -2604,41 +2595,42 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.10.1" +version = "3.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" +checksum = "01ce4141aa927a6d1bd34a041795abd0db1cccba5d5f24b009f694bdf3a1f3fa" dependencies = [ "cfg-if", "fastrand", + "redox_syscall", "rustix", "windows-sys 0.52.0", ] [[package]] name = "thiserror" -version = "1.0.61" +version = "1.0.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" +checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.61" +version = "1.0.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" +checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.48", ] [[package]] name = "thread_local" -version = "1.1.8" +version = "1.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" dependencies = [ "cfg-if", "once_cell", @@ -2646,12 +2638,11 @@ dependencies = [ [[package]] name = "time" -version = "0.3.36" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" +checksum = "f657ba42c3f86e7680e53c8cd3af8abbe56b5491790b46e22e19c0d57463583e" dependencies = [ "deranged", - "num-conv", "powerfmt", "serde", "time-core", @@ -2665,9 +2656,9 @@ checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "tinyvec" -version = "1.7.0" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce6b6a2fb3a985e99cebfaefa9faa3024743da73304ca1c683a36429613d3d22" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" dependencies = [ "tinyvec_macros", ] @@ -2715,7 +2706,7 @@ checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.48", ] [[package]] @@ -2741,9 +2732,9 @@ dependencies = [ [[package]] name = "tokio-stream" -version = "0.1.15" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "267ac89e0bec6e691e5813911606935d77c476ff49024f98abcea3e7b15e37af" +checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842" dependencies = [ "futures-core", "pin-project-lite", @@ -2752,15 +2743,16 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.11" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1" +checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" dependencies = [ "bytes", "futures-core", "futures-sink", "pin-project-lite", "tokio", + "tracing", ] [[package]] @@ -2775,9 +2767,9 @@ dependencies = [ "base64 0.21.7", "bytes", "h2", - "http 0.2.12", + "http 0.2.11", "http-body 0.4.6", - "hyper 0.14.29", + "hyper 0.14.28", "hyper-timeout", "percent-encoding", "pin-project", @@ -2842,7 +2834,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.48", ] [[package]] @@ -2941,7 +2933,7 @@ dependencies = [ "libloading 0.7.4", "log", "nix 0.26.4", - "reqwest 0.11.27", + "reqwest 0.11.23", "schemars", "serde", "socket2", @@ -2974,18 +2966,18 @@ checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "unicode-normalization" -version = "0.1.23" +version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" dependencies = [ "tinyvec", ] [[package]] name = "unicode-width" -version = "0.1.13" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" +checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" [[package]] name = "universal-hash" @@ -3005,9 +2997,9 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.5.2" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" +checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" dependencies = [ "form_urlencoded", "idna", @@ -3016,15 +3008,15 @@ dependencies = [ [[package]] name = "utf8parse" -version = "0.2.2" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" [[package]] name = "uuid" -version = "1.9.1" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5de17fd2f7da591098415cff336e12965a28061ddace43b59cb3c430179c9439" +checksum = "f00cc9702ca12d3c81455259621e676d0f7251cec66a21e98fe2e9a37db93b2a" dependencies = [ "serde", ] @@ -3064,9 +3056,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.92" +version = "0.2.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" +checksum = "b1223296a201415c7fad14792dbefaace9bd52b62d33453ade1c5b5f07555406" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -3074,24 +3066,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.92" +version = "0.2.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" +checksum = "fcdc935b63408d58a32f8cc9738a0bffd8f05cc7c002086c6ef20b7312ad9dcd" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.48", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.42" +version = "0.4.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0" +checksum = "bde2032aeb86bdfaecc8b261eef3cba735cc426c1f3a3416d1e0791be95fc461" dependencies = [ "cfg-if", "js-sys", @@ -3101,9 +3093,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.92" +version = "0.2.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" +checksum = "3e4c238561b2d428924c49815533a8b9121c664599558a5d9ec51f8a1740a999" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3111,28 +3103,28 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.92" +version = "0.2.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" +checksum = "bae1abb6806dc1ad9e560ed242107c0f6c84335f1749dd4e8ddb012ebd5e25a7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.48", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.92" +version = "0.2.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" +checksum = "4d91413b1c31d7539ba5ef2451af3f0b833a005eb27a631cec32bc0635a8602b" [[package]] name = "web-sys" -version = "0.3.69" +version = "0.3.67" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" +checksum = "58cd2333b6e0be7a39605f0e255892fd7418a682d8da8fe042fe25128794d2ed" dependencies = [ "js-sys", "wasm-bindgen", @@ -3161,9 +3153,9 @@ dependencies = [ [[package]] name = "widestring" -version = "1.1.0" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7219d36b6eac893fa81e84ebe06485e7dcbb616177469b142df14f1f4deb1311" +checksum = "653f141f39ec16bba3c5abe400a0c60da7468261cc2cbf36805022876bc721a8" [[package]] name = "winapi" @@ -3211,7 +3203,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.6", + "windows-targets 0.52.0", ] [[package]] @@ -3231,18 +3223,17 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.6" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" dependencies = [ - "windows_aarch64_gnullvm 0.52.6", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm", - "windows_i686_msvc 0.52.6", - "windows_x86_64_gnu 0.52.6", - "windows_x86_64_gnullvm 0.52.6", - "windows_x86_64_msvc 0.52.6", + "windows_aarch64_gnullvm 0.52.0", + "windows_aarch64_msvc 0.52.0", + "windows_i686_gnu 0.52.0", + "windows_i686_msvc 0.52.0", + "windows_x86_64_gnu 0.52.0", + "windows_x86_64_gnullvm 0.52.0", + "windows_x86_64_msvc 0.52.0", ] [[package]] @@ -3253,9 +3244,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.6" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" [[package]] name = "windows_aarch64_msvc" @@ -3265,9 +3256,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.52.6" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" [[package]] name = "windows_i686_gnu" @@ -3277,15 +3268,9 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.52.6" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" [[package]] name = "windows_i686_msvc" @@ -3295,9 +3280,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.52.6" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" [[package]] name = "windows_x86_64_gnu" @@ -3307,9 +3292,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.52.6" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" [[package]] name = "windows_x86_64_gnullvm" @@ -3319,9 +3304,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.6" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" [[package]] name = "windows_x86_64_msvc" @@ -3331,9 +3316,9 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.52.6" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" [[package]] name = "winreg" @@ -3357,9 +3342,9 @@ dependencies = [ [[package]] name = "x25519-dalek" -version = "2.0.1" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277" +checksum = "fb66477291e7e8d2b0ff1bcb900bf29489a9692816d79874bea351e7a8b6de96" dependencies = [ "curve25519-dalek", "rand_core", @@ -3369,35 +3354,44 @@ dependencies = [ [[package]] name = "xxhash-rust" -version = "0.8.11" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63658493314859b4dfdf3fb8c1defd61587839def09582db50b8a4e93afca6bb" +checksum = "53be06678ed9e83edb1745eb72efc0bbcd7b5c3c35711a860906aed827a13d61" + +[[package]] +name = "yaml-rust" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" +dependencies = [ + "linked-hash-map", +] [[package]] name = "zerocopy" -version = "0.7.35" +version = "0.7.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.7.35" +version = "0.7.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.48", ] [[package]] name = "zeroize" -version = "1.8.1" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" dependencies = [ "zeroize_derive", ] @@ -3410,7 +3404,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.48", ] [[package]] @@ -3454,9 +3448,9 @@ dependencies = [ [[package]] name = "zstd-sys" -version = "2.0.12+zstd.1.5.6" +version = "2.0.9+zstd.1.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a4e40c320c3cb459d9a9ff6de98cff88f4751ee9275d140e2be94a2b74e4c13" +checksum = "9e16efa8a874a0481a574084d34cc26fdb3b99627480f785888deb6386506656" dependencies = [ "cc", "pkg-config", diff --git a/Dockerfile b/Dockerfile index e55eb58..8e17812 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM docker.io/library/rust:1.77-slim-bookworm AS builder +FROM docker.io/library/rust:1.79-slim-bookworm AS builder ARG TARGETPLATFORM ARG LLVM_VERSION=16 @@ -56,7 +56,6 @@ ENV CC_x86_64_unknown_linux_musl=clang-$LLVM_VERSION \ AR_aarch64_unknown_linux_musl=llvm-ar-$LLVM_VERSION \ CARGO_TARGET_X86_64_UNKNOWN_LINUX_MUSL_RUSTFLAGS="-L/usr/lib/x86_64-linux-musl -L/lib/x86_64-linux-musl -C linker=rust-lld" \ CARGO_TARGET_AARCH64_UNKNOWN_LINUX_MUSL_RUSTFLAGS="-L/usr/lib/aarch64-linux-musl -L/lib/aarch64-linux-musl -C linker=rust-lld" \ - CARGO_REGISTRIES_CRATES_IO_PROTOCOL=sparse \ SQLITE3_STATIC=1 \ SQLITE3_INCLUDE_DIR=/usr/local/include \ SQLITE3_LIB_DIR=/usr/local/lib From 951b4ddae2f33e15c7d1976337de64001797d0e9 Mon Sep 17 00:00:00 2001 From: Jett Chen Date: Sat, 13 Jul 2024 18:09:09 -0700 Subject: [PATCH 08/15] add protobuf definition file --- proto/burrow.proto | 72 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 proto/burrow.proto diff --git a/proto/burrow.proto b/proto/burrow.proto new file mode 100644 index 0000000..3e15219 --- /dev/null +++ b/proto/burrow.proto @@ -0,0 +1,72 @@ +syntax = "proto3"; +package burrow; + +import "google/protobuf/timestamp.proto"; + +service Tunnel { + rpc TunnelConfiguration (Empty) returns (TunnelConfigurationResponse); + rpc TunnelStart (Empty) returns (Empty); + rpc TunnelStop (Empty) returns (Empty); + rpc TunnelStatus (Empty) returns (stream TunnelStatusResponse); +} + +service Networks { + rpc NetworkAdd (Empty) returns (Empty); + rpc NetworkList (Empty) returns (stream NetworkListResponse); + rpc NetworkReorder (NetworkReorderRequest) returns (Empty); + rpc NetworkDelete (NetworkDeleteRequest) returns (Empty); +} + +message NetworkReorderRequest { + int32 id = 1; + int32 index = 2; +} + +message WireGuardPeer { + string endpoint = 1; + repeated string subnet = 2; +} + +message WireGuardNetwork { + string address = 1; + string dns = 2; + repeated WireGuardPeer peer = 3; +} + +message NetworkDeleteRequest { + int32 id = 1; +} + +message Network { + int32 id = 1; + NetworkType type = 2; + bytes payload = 3; +} + +enum NetworkType { + WireGuard = 0; + HackClub = 1; +} + +message NetworkListResponse { + repeated Network network = 1; +} + +message Empty { + +} + +enum State { + Stopped = 0; + Running = 1; +} + +message TunnelStatusResponse { + State state = 1; + optional google.protobuf.Timestamp start = 2; +} + +message TunnelConfigurationResponse { + repeated string addresses = 1; + int32 mtu = 2; +} From aa634d03e2560dbaf500b19f584d19696abb7161 Mon Sep 17 00:00:00 2001 From: Jett Chen Date: Sat, 13 Jul 2024 18:14:00 -0700 Subject: [PATCH 09/15] update protobuf definition file --- proto/burrow.proto | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proto/burrow.proto b/proto/burrow.proto index 3e15219..2d29c78 100644 --- a/proto/burrow.proto +++ b/proto/burrow.proto @@ -4,7 +4,7 @@ package burrow; import "google/protobuf/timestamp.proto"; service Tunnel { - rpc TunnelConfiguration (Empty) returns (TunnelConfigurationResponse); + rpc TunnelConfiguration (Empty) returns (stream TunnelConfigurationResponse); rpc TunnelStart (Empty) returns (Empty); rpc TunnelStop (Empty) returns (Empty); rpc TunnelStatus (Empty) returns (stream TunnelStatusResponse); From 62a5739d86feb8c2d23ec3d6187d2f1c6dffc9d3 Mon Sep 17 00:00:00 2001 From: Conrad Kramer Date: Sat, 7 Sep 2024 17:01:17 -0700 Subject: [PATCH 10/15] Update pipelines with various fixes --- .github/actions/download-profiles/action.yml | 27 ++++++++++++ .github/workflows/build-appimage.yml | 3 ++ .github/workflows/build-docker.yml | 3 ++ .github/workflows/build-rust.yml | 2 +- .github/workflows/lint-swift.yml | 2 +- .github/workflows/release-appimage.yml | 29 ------------- .github/workflows/release-apple.yml | 5 ++- .github/workflows/release-if-needed.yaml | 2 + .github/workflows/release-linux.yml | 43 +++++++++----------- 9 files changed, 59 insertions(+), 57 deletions(-) create mode 100644 .github/actions/download-profiles/action.yml delete mode 100644 .github/workflows/release-appimage.yml diff --git a/.github/actions/download-profiles/action.yml b/.github/actions/download-profiles/action.yml new file mode 100644 index 0000000..98961aa --- /dev/null +++ b/.github/actions/download-profiles/action.yml @@ -0,0 +1,27 @@ +name: Download Provisioning Profiles +inputs: + app-store-key: + description: App Store key in PEM PKCS#8 format + required: true + app-store-key-id: + description: App Store key ID + required: true + app-store-key-issuer-id: + description: App Store key issuer ID + required: true +runs: + using: composite + steps: + - shell: bash + run: | + cat << EOF > api-key.json + { + "key_id": "${{ inputs.app-store-key-id }}", + "issuer_id": "${{ inputs.app-store-key-issuer-id }}", + "key": "${{ inputs.app-store-key }}" + } + EOF + + fastlane sigh download_all --api_key_path api-key.json --download_xcode_profiles + + rm -rf api-key.json diff --git a/.github/workflows/build-appimage.yml b/.github/workflows/build-appimage.yml index bb510fb..bd29b07 100644 --- a/.github/workflows/build-appimage.yml +++ b/.github/workflows/build-appimage.yml @@ -6,6 +6,9 @@ on: pull_request: branches: - "*" +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true jobs: appimage: name: Build AppImage diff --git a/.github/workflows/build-docker.yml b/.github/workflows/build-docker.yml index 307a93c..6a3dae1 100644 --- a/.github/workflows/build-docker.yml +++ b/.github/workflows/build-docker.yml @@ -6,6 +6,9 @@ on: pull_request: branches: - "*" +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true jobs: build: name: Build Docker Image diff --git a/.github/workflows/build-rust.yml b/.github/workflows/build-rust.yml index 76ce9f2..11ff60d 100644 --- a/.github/workflows/build-rust.yml +++ b/.github/workflows/build-rust.yml @@ -58,7 +58,7 @@ jobs: run: | sudo apt-get update sudo apt-get install -y ${{ join(matrix.packages, ' ') }} - - name: Install Windows Deps + - name: Configure LLVM if: matrix.os == 'windows-2022' shell: bash run: echo "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Tools\Llvm\x64\bin" >> $GITHUB_PATH diff --git a/.github/workflows/lint-swift.yml b/.github/workflows/lint-swift.yml index a2cc96a..857f575 100644 --- a/.github/workflows/lint-swift.yml +++ b/.github/workflows/lint-swift.yml @@ -13,4 +13,4 @@ jobs: - name: Checkout uses: actions/checkout@v4 - name: Lint - run: swiftlint lint --reporter github-actions-logging + run: swiftlint lint --strict --reporter github-actions-logging diff --git a/.github/workflows/release-appimage.yml b/.github/workflows/release-appimage.yml deleted file mode 100644 index e566186..0000000 --- a/.github/workflows/release-appimage.yml +++ /dev/null @@ -1,29 +0,0 @@ -name: Release (AppImage) -on: - release: - types: - - created -jobs: - appimage: - name: Build AppImage - runs-on: ubuntu-latest - container: docker - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - name: Build - run: | - docker build -t appimage-builder . -f burrow-gtk/build-aux/Dockerfile - docker create --name temp appimage-builder - docker cp temp:/app/burrow-gtk/build-appimage/Burrow-x86_64.AppImage . - docker rm temp - - name: Upload to GitHub - uses: SierraSoftworks/gh-releases@v1.0.7 - with: - token: ${{ secrets.GITHUB_TOKEN }} - release_tag: ${{ github.ref_name }} - overwrite: 'true' - files: | - Burrow-x86_64.AppImage diff --git a/.github/workflows/release-apple.yml b/.github/workflows/release-apple.yml index 1883008..f1ee5dd 100644 --- a/.github/workflows/release-apple.yml +++ b/.github/workflows/release-apple.yml @@ -24,7 +24,7 @@ jobs: - x86_64-apple-darwin - aarch64-apple-darwin env: - DEVELOPER_DIR: /Applications/Xcode_15.4.app/Contents/Developer + DEVELOPER_DIR: /Applications/Xcode_16.0.app/Contents/Developer steps: - name: Checkout uses: actions/checkout@v4 @@ -40,8 +40,9 @@ jobs: with: targets: ${{ join(matrix.rust-targets, ', ') }} - name: Configure Version + id: version shell: bash - run: Tools/version.sh + run: echo "BUILD_NUMBER=$(Tools/version.sh)" >> $GITHUB_OUTPUT - name: Archive uses: ./.github/actions/archive with: diff --git a/.github/workflows/release-if-needed.yaml b/.github/workflows/release-if-needed.yaml index 0d2eb97..79f0d63 100644 --- a/.github/workflows/release-if-needed.yaml +++ b/.github/workflows/release-if-needed.yaml @@ -9,6 +9,8 @@ jobs: create: name: Create Release If Needed runs-on: ubuntu-latest + env: + GH_TOKEN: ${{ github.token }} steps: - name: Checkout uses: actions/checkout@v4 diff --git a/.github/workflows/release-linux.yml b/.github/workflows/release-linux.yml index 6709edb..7db9bcf 100644 --- a/.github/workflows/release-linux.yml +++ b/.github/workflows/release-linux.yml @@ -2,33 +2,28 @@ name: Release (Linux) on: release: types: - - created + - created jobs: appimage: name: Build AppImage runs-on: ubuntu-latest container: docker steps: - - name: Checkout - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - name: Build AppImage - run: | - docker build -t appimage-builder . -f burrow-gtk/build-aux/Dockerfile - docker create --name temp appimage-builder - docker cp temp:/app/burrow-gtk/build-appimage/Burrow-x86_64.AppImage . - docker rm temp - - name: Get Build Number - id: version - shell: bash - run: | - echo "BUILD_NUMBER=$(Tools/version.sh)" >> $GITHUB_OUTPUT - - name: Attach Artifacts - uses: SierraSoftworks/gh-releases@v1.0.7 - with: - token: ${{ secrets.GITHUB_TOKEN }} - release_tag: builds/${{ steps.version.outputs.BUILD_NUMBER }} - overwrite: "true" - files: | - Burrow-x86_64.AppImage + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Build AppImage + run: | + docker build -t appimage-builder . -f burrow-gtk/build-aux/Dockerfile + docker create --name temp appimage-builder + docker cp temp:/app/burrow-gtk/build-appimage/Burrow-x86_64.AppImage . + docker rm temp + - name: Attach Artifacts + uses: SierraSoftworks/gh-releases@v1.0.7 + with: + token: ${{ secrets.GITHUB_TOKEN }} + release_tag: ${{ github.ref_name }} + overwrite: "true" + files: | + Burrow-x86_64.AppImage From fa1ef6fcda7acf4f9a0cf66d811baf1e626ac2a4 Mon Sep 17 00:00:00 2001 From: Conrad Kramer Date: Sat, 7 Sep 2024 17:08:02 -0700 Subject: [PATCH 11/15] Download provisioning profiles in release pipeline --- .github/actions/download-profiles/action.yml | 7 +++++-- .github/actions/export/action.yml | 10 +++------- .github/workflows/build-rust.yml | 2 +- .github/workflows/release-apple.yml | 21 ++++++++++++-------- 4 files changed, 22 insertions(+), 18 deletions(-) diff --git a/.github/actions/download-profiles/action.yml b/.github/actions/download-profiles/action.yml index 98961aa..32b615c 100644 --- a/.github/actions/download-profiles/action.yml +++ b/.github/actions/download-profiles/action.yml @@ -13,15 +13,18 @@ runs: using: composite steps: - shell: bash + env: + FASTLANE_OPT_OUT_USAGE: 'YES' run: | + APP_STORE_KEY=$(echo "${{ inputs.app-store-key }}" | jq -sR .) cat << EOF > api-key.json { "key_id": "${{ inputs.app-store-key-id }}", "issuer_id": "${{ inputs.app-store-key-issuer-id }}", - "key": "${{ inputs.app-store-key }}" + "key": $APP_STORE_KEY } EOF - fastlane sigh download_all --api_key_path api-key.json --download_xcode_profiles + fastlane sigh download_all --api_key_path api-key.json rm -rf api-key.json diff --git a/.github/actions/export/action.yml b/.github/actions/export/action.yml index 8f891be..75b748f 100644 --- a/.github/actions/export/action.yml +++ b/.github/actions/export/action.yml @@ -12,11 +12,8 @@ inputs: archive-path: description: Xcode archive path required: true - destination: - description: The Xcode export destination. This can either be "export" or "upload" - required: true - method: - description: The Xcode export method. This can be one of app-store, validation, ad-hoc, package, enterprise, development, developer-id, or mac-application. + export-options: + description: The export options in JSON format required: true export-path: description: The path to export the archive to @@ -29,8 +26,7 @@ runs: run: | echo "${{ inputs.app-store-key }}" > AuthKey_${{ inputs.app-store-key-id }}.p8 - echo '{"destination":"${{ inputs.destination }}","method":"${{ inputs.method }}"}' \ - | plutil -convert xml1 -o ExportOptions.plist - + echo '${{ inputs.export-options }}' | plutil -convert xml1 -o ExportOptions.plist - xcodebuild \ -exportArchive \ diff --git a/.github/workflows/build-rust.yml b/.github/workflows/build-rust.yml index 11ff60d..22bf83a 100644 --- a/.github/workflows/build-rust.yml +++ b/.github/workflows/build-rust.yml @@ -42,7 +42,7 @@ jobs: - aarch64-pc-windows-msvc runs-on: ${{ matrix.os }} env: - DEVELOPER_DIR: /Applications/Xcode_15.4.app/Contents/Developer + DEVELOPER_DIR: /Applications/Xcode_16.0.app/Contents/Developer CARGO_INCREMENTAL: 0 CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER: aarch64-linux-gnu-gcc RUST_BACKTRACE: short diff --git a/.github/workflows/release-apple.yml b/.github/workflows/release-apple.yml index f1ee5dd..bb9c15a 100644 --- a/.github/workflows/release-apple.yml +++ b/.github/workflows/release-apple.yml @@ -13,13 +13,10 @@ jobs: fail-fast: false matrix: include: - - - destination: generic/platform=iOS - platform: iOS + - platform: iOS rust-targets: - aarch64-apple-ios - - destination: generic/platform=macOS - platform: macOS + - platform: macOS rust-targets: - x86_64-apple-darwin - aarch64-apple-darwin @@ -35,6 +32,12 @@ jobs: with: certificate: ${{ secrets.DEVELOPER_CERT }} password: ${{ secrets.DEVELOPER_CERT_PASSWORD }} + - name: Download Provisioning Profiles + uses: ./.github/actions/download-profiles + with: + app-store-key: ${{ secrets.APPSTORE_KEY }} + app-store-key-id: ${{ secrets.APPSTORE_KEY_ID }} + app-store-key-issuer-id: ${{ secrets.APPSTORE_KEY_ISSUER_ID }} - name: Install Rust uses: dtolnay/rust-toolchain@stable with: @@ -47,7 +50,7 @@ jobs: uses: ./.github/actions/archive with: scheme: App - destination: ${{ matrix.destination }} + destination: generic/platform=${{ matrix.platform }} app-store-key: ${{ secrets.APPSTORE_KEY }} app-store-key-id: ${{ secrets.APPSTORE_KEY_ID }} app-store-key-issuer-id: ${{ secrets.APPSTORE_KEY_ISSUER_ID }} @@ -61,6 +64,8 @@ jobs: app-store-key-id: ${{ secrets.APPSTORE_KEY_ID }} app-store-key-issuer-id: ${{ secrets.APPSTORE_KEY_ISSUER_ID }} archive-path: Burrow.xcarchive + export-options: | + {"teamID":"P6PV2R9443","destination":"export","method":"developer-id","provisioningProfiles":{"com.hackclub.burrow":"Burrow Developer ID","com.hackclub.burrow.network":"Burrow Network Developer ID"},"signingCertificate":"Developer ID Application","signingStyle":"manual"} export-path: Release - name: Notarize if: ${{ matrix.platform == 'macOS' }} @@ -96,10 +101,10 @@ jobs: if: ${{ matrix.platform == 'iOS' }} uses: ./.github/actions/export with: - method: app-store - destination: upload app-store-key: ${{ secrets.APPSTORE_KEY }} app-store-key-id: ${{ secrets.APPSTORE_KEY_ID }} app-store-key-issuer-id: ${{ secrets.APPSTORE_KEY_ISSUER_ID }} archive-path: Burrow.xcarchive + export-options: | + {"method": "app-store", "destination": "upload"} export-path: Release From 3fbb520a106101761ca3cff49ce62029a88408fa Mon Sep 17 00:00:00 2001 From: Conrad Kramer Date: Sat, 7 Sep 2024 17:36:48 -0700 Subject: [PATCH 12/15] Fix SwiftLint errors --- .github/actions/build-for-testing/action.yml | 2 + .github/workflows/build-rust.yml | 6 ++- Apple/App/AppDelegate.swift | 3 +- Apple/App/BurrowView.swift | 1 - Apple/App/OAuth2.swift | 46 +++++++++++--------- 5 files changed, 33 insertions(+), 25 deletions(-) diff --git a/.github/actions/build-for-testing/action.yml b/.github/actions/build-for-testing/action.yml index 084ba81..185c4ab 100644 --- a/.github/actions/build-for-testing/action.yml +++ b/.github/actions/build-for-testing/action.yml @@ -27,7 +27,9 @@ runs: Apple/DerivedData key: ${{ runner.os }}-${{ inputs.scheme }}-${{ hashFiles('**/Package.resolved') }} restore-keys: | + ${{ runner.os }}-${{ inputs.scheme }}-${{ hashFiles('**/Package.resolved') }} ${{ runner.os }}-${{ inputs.scheme }}- + ${{ runner.os }}- - name: Build shell: bash working-directory: Apple diff --git a/.github/workflows/build-rust.yml b/.github/workflows/build-rust.yml index 22bf83a..84ac9d8 100644 --- a/.github/workflows/build-rust.yml +++ b/.github/workflows/build-rust.yml @@ -21,14 +21,16 @@ jobs: - x86_64-unknown-linux-gnu targets: - aarch64-unknown-linux-gnu - - os: macos-12 + - os: macos-13 platform: macOS (Intel) + xcode: /Applications/Xcode_15.2.app test-targets: - x86_64-apple-darwin targets: - x86_64-apple-ios - os: macos-14 platform: macOS + xcode: /Applications/Xcode_16.0.app test-targets: - aarch64-apple-darwin targets: @@ -42,7 +44,7 @@ jobs: - aarch64-pc-windows-msvc runs-on: ${{ matrix.os }} env: - DEVELOPER_DIR: /Applications/Xcode_16.0.app/Contents/Developer + DEVELOPER_DIR: ${{ matrix.xcode }}/Contents/Developer CARGO_INCREMENTAL: 0 CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER: aarch64-linux-gnu-gcc RUST_BACKTRACE: short diff --git a/Apple/App/AppDelegate.swift b/Apple/App/AppDelegate.swift index bd76a2f..b0c5546 100644 --- a/Apple/App/AppDelegate.swift +++ b/Apple/App/AppDelegate.swift @@ -2,7 +2,8 @@ import AppKit import SwiftUI -@MainActor @main +@main +@MainActor class AppDelegate: NSObject, NSApplicationDelegate { private let quitItem: NSMenuItem = { let quitItem = NSMenuItem( diff --git a/Apple/App/BurrowView.swift b/Apple/App/BurrowView.swift index 8447592..3a53762 100644 --- a/Apple/App/BurrowView.swift +++ b/Apple/App/BurrowView.swift @@ -42,7 +42,6 @@ struct BurrowView: View { } private func addWireGuardNetwork() { - } private func authenticateWithSlack() async throws { diff --git a/Apple/App/OAuth2.swift b/Apple/App/OAuth2.swift index dc8c62b..9a930c9 100644 --- a/Apple/App/OAuth2.swift +++ b/Apple/App/OAuth2.swift @@ -1,6 +1,6 @@ import AuthenticationServices -import SwiftUI import Foundation +import SwiftUI enum OAuth2 { enum Error: Swift.Error { @@ -35,7 +35,7 @@ enum OAuth2 { } } - public init( + init( authorizationEndpoint: URL, tokenEndpoint: URL, redirectURI: URL, @@ -125,7 +125,11 @@ enum OAuth2 { var refreshToken: String? var credential: Credential { - .init(accessToken: accessToken, refreshToken: refreshToken, expirationDate: expiresIn.map { Date.init(timeIntervalSinceNow: $0) }) + .init( + accessToken: accessToken, + refreshToken: refreshToken, + expirationDate: expiresIn.map { Date(timeIntervalSinceNow: $0) } + ) } } @@ -203,7 +207,24 @@ enum OAuth2 { } extension WebAuthenticationSession { - func start(url: URL, redirectURI: URL) async throws -> URL { +#if canImport(BrowserEngineKit) + @available(iOS 17.4, macOS 14.4, tvOS 17.4, watchOS 10.4, *) + fileprivate static func callback(for redirectURI: URL) throws -> ASWebAuthenticationSession.Callback { + switch redirectURI.scheme { + case "https": + guard let host = redirectURI.host else { throw OAuth2.Error.invalidRedirectURI } + return .https(host: host, path: redirectURI.path) + case "http": + throw OAuth2.Error.invalidRedirectURI + case .some(let scheme): + return .customScheme(scheme) + case .none: + throw OAuth2.Error.invalidRedirectURI + } + } +#endif + + fileprivate func start(url: URL, redirectURI: URL) async throws -> URL { #if canImport(BrowserEngineKit) if #available(iOS 17.4, macOS 14.4, tvOS 17.4, watchOS 10.4, *) { return try await authenticate( @@ -231,23 +252,6 @@ extension WebAuthenticationSession { return url } } - - #if canImport(BrowserEngineKit) - @available(iOS 17.4, macOS 14.4, tvOS 17.4, watchOS 10.4, *) - fileprivate static func callback(for redirectURI: URL) throws -> ASWebAuthenticationSession.Callback { - switch redirectURI.scheme { - case "https": - guard let host = redirectURI.host else { throw OAuth2.Error.invalidRedirectURI } - return .https(host: host, path: redirectURI.path) - case "http": - throw OAuth2.Error.invalidRedirectURI - case .some(let scheme): - return .customScheme(scheme) - case .none: - throw OAuth2.Error.invalidRedirectURI - } - } - #endif } extension View { From e4b0f1660bff2112d0e20316c228926bed47f270 Mon Sep 17 00:00:00 2001 From: Jett Chen Date: Sat, 13 Jul 2024 17:32:49 -0700 Subject: [PATCH 13/15] GRPC Server Support - Deprecates old json-rpc system - Add GRPC daemon over uds --- .github/workflows/build-apple.yml | 9 +- .github/workflows/build-rust.yml | 7 +- .gitignore | 5 + .vscode/settings.json | 9 +- .../NetworkExtension/libburrow/build-rust.sh | 2 + Cargo.lock | 341 +++++++++++++++++- Dockerfile | 2 +- Makefile | 6 + burrow/Cargo.toml | 18 +- burrow/build.rs | 4 + burrow/burrow.db | Bin 20480 -> 0 bytes burrow/src/auth/server/db.rs | 2 + burrow/src/daemon/instance.rs | 300 ++++++++++----- burrow/src/daemon/mod.rs | 72 ++-- burrow/src/daemon/net/mod.rs | 9 +- burrow/src/daemon/net/unix.rs | 4 +- burrow/src/daemon/rpc/client.rs | 31 ++ burrow/src/daemon/rpc/grpc_defs.rs | 5 + burrow/src/daemon/rpc/mod.rs | 3 + burrow/src/database.rs | 109 +++++- burrow/src/main.rs | 156 +++++++- burrow/src/wireguard/config.rs | 94 ++++- burrow/src/wireguard/iface.rs | 14 +- burrow/src/wireguard/inifield.rs | 81 +++++ burrow/src/wireguard/mod.rs | 1 + ...guard__config__tests__tst_config_toml.snap | 16 + burrow/tmp/conrd.conf | 8 + proto/burrow.proto | 2 +- 28 files changed, 1110 insertions(+), 200 deletions(-) create mode 100644 burrow/build.rs delete mode 100644 burrow/burrow.db create mode 100644 burrow/src/daemon/rpc/client.rs create mode 100644 burrow/src/daemon/rpc/grpc_defs.rs create mode 100644 burrow/src/wireguard/inifield.rs create mode 100644 burrow/src/wireguard/snapshots/burrow__wireguard__config__tests__tst_config_toml.snap create mode 100644 burrow/tmp/conrd.conf diff --git a/.github/workflows/build-apple.yml b/.github/workflows/build-apple.yml index 84cc03a..b628001 100644 --- a/.github/workflows/build-apple.yml +++ b/.github/workflows/build-apple.yml @@ -1,7 +1,7 @@ name: Build Apple Apps on: push: - branches: + branches: - main pull_request: branches: @@ -39,6 +39,7 @@ jobs: - aarch64-apple-darwin env: DEVELOPER_DIR: /Applications/Xcode_16.0.app/Contents/Developer + PROTOC_VERSION: 3.25.1 steps: - name: Checkout uses: actions/checkout@v3 @@ -54,6 +55,10 @@ jobs: uses: dtolnay/rust-toolchain@stable with: targets: ${{ join(matrix.rust-targets, ', ') }} + - name: Install protoc + uses: taiki-e/install-action@v2 + with: + tool: protoc@${{ env.PROTOC_VERSION }} - name: Build id: build uses: ./.github/actions/build-for-testing @@ -82,4 +87,4 @@ jobs: destination: ${{ matrix.destination }} test-plan: ${{ matrix.xcode-ui-test }} artifact-prefix: ui-tests-${{ matrix.sdk-name }} - check-name: Xcode UI Tests (${{ matrix.platform }}) + check-name: Xcode UI Tests (${{ matrix.platform }}) \ No newline at end of file diff --git a/.github/workflows/build-rust.yml b/.github/workflows/build-rust.yml index 84ac9d8..95fc628 100644 --- a/.github/workflows/build-rust.yml +++ b/.github/workflows/build-rust.yml @@ -48,6 +48,7 @@ jobs: CARGO_INCREMENTAL: 0 CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER: aarch64-linux-gnu-gcc RUST_BACKTRACE: short + PROTOC_VERSION: 3.25.1 steps: - name: Checkout uses: actions/checkout@v3 @@ -64,6 +65,10 @@ jobs: if: matrix.os == 'windows-2022' shell: bash run: echo "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Tools\Llvm\x64\bin" >> $GITHUB_PATH + - name: Install protoc + uses: taiki-e/install-action@v2 + with: + tool: protoc@${{ env.PROTOC_VERSION }} - name: Install Rust uses: dtolnay/rust-toolchain@stable with: @@ -77,4 +82,4 @@ jobs: run: cargo build --verbose --workspace --all-features --target ${{ join(matrix.targets, ' --target ') }} --target ${{ join(matrix.test-targets, ' --target ') }} - name: Test shell: bash - run: cargo test --verbose --workspace --all-features --target ${{ join(matrix.test-targets, ' --target ') }} + run: cargo test --verbose --workspace --all-features --target ${{ join(matrix.test-targets, ' --target ') }} \ No newline at end of file diff --git a/.gitignore b/.gitignore index 96b2507..997d4d5 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,8 @@ target/ .DS_STORE .idea/ + +tmp/ + +*.db +*.sock \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index a760137..eb85504 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -15,5 +15,12 @@ "rust-analyzer.inlayHints.typeHints.enable": false, "rust-analyzer.linkedProjects": [ "./burrow/Cargo.toml" - ] + ], + "[yaml]": { + "editor.insertSpaces": true, + "editor.tabSize": 2, + "editor.autoIndent": "advanced", + "diffEditor.ignoreTrimWhitespace": false, + "editor.formatOnSave": false + } } diff --git a/Apple/NetworkExtension/libburrow/build-rust.sh b/Apple/NetworkExtension/libburrow/build-rust.sh index e7204a5..00c3652 100755 --- a/Apple/NetworkExtension/libburrow/build-rust.sh +++ b/Apple/NetworkExtension/libburrow/build-rust.sh @@ -68,6 +68,8 @@ else CARGO_PATH="$(dirname $(readlink -f $(which cargo))):/usr/bin" fi +CARGO_PATH="$(dirname $(readlink -f $(which protoc))):$CARGO_PATH" + # Run cargo without the various environment variables set by Xcode. # Those variables can confuse cargo and the build scripts it runs. env -i PATH="$CARGO_PATH" CARGO_TARGET_DIR="${CONFIGURATION_TEMP_DIR}/target" IPHONEOS_DEPLOYMENT_TARGET="$IPHONEOS_DEPLOYMENT_TARGET" MACOSX_DEPLOYMENT_TARGET="$MACOSX_DEPLOYMENT_TARGET" cargo build "${CARGO_ARGS[@]}" diff --git a/Cargo.lock b/Cargo.lock index 5ef886c..309fc08 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -132,17 +132,38 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "async-stream" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22068c0c19514942eefcfd4daf8976ef1aad84e61539f95cd200c35202f80af5" +dependencies = [ + "async-stream-impl 0.2.1", + "futures-core", +] + [[package]] name = "async-stream" version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd56dd203fef61ac097dd65721a419ddccb106b2d2b70ba60a6b529f03961a51" dependencies = [ - "async-stream-impl", + "async-stream-impl 0.3.5", "futures-core", "pin-project-lite", ] +[[package]] +name = "async-stream-impl" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25f9db3b38af870bf7e5cc649167533b493928e50744e2c30ae350230b414670" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "async-stream-impl" version = "0.3.5" @@ -165,6 +186,12 @@ dependencies = [ "syn 2.0.48", ] +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + [[package]] name = "autocfg" version = "1.1.0" @@ -392,6 +419,8 @@ dependencies = [ "aead", "anyhow", "async-channel", + "async-stream 0.2.1", + "async-stream 0.2.1", "axum 0.7.5", "base64 0.21.7", "blake2", @@ -404,6 +433,7 @@ dependencies = [ "fehler", "futures", "hmac", + "hyper-util", "insta", "ip_network", "ip_network_table", @@ -412,15 +442,24 @@ dependencies = [ "nix 0.27.1", "once_cell", "parking_lot", + "prost 0.13.1", + "prost-types 0.13.1", + "prost 0.13.2", + "prost-types 0.13.2", "rand", "rand_core", "reqwest 0.12.5", "ring", "rusqlite", + "rust-ini", "schemars", "serde", "serde_json", "tokio", + "tokio-stream", + "tonic 0.12.2", + "tonic-build", + "tower", "tracing", "tracing-journald", "tracing-log 0.1.4", @@ -619,9 +658,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd326812b3fd01da5bb1af7d340d0d555fd3d4b641e7f1dfcf5962a902952787" dependencies = [ "futures-core", - "prost", - "prost-types", - "tonic", + "prost 0.12.3", + "prost-types 0.12.3", + "tonic 0.10.2", "tracing-core", ] @@ -637,18 +676,38 @@ dependencies = [ "futures-task", "hdrhistogram", "humantime", - "prost-types", + "prost-types 0.12.3", "serde", "serde_json", "thread_local", "tokio", "tokio-stream", - "tonic", + "tonic 0.10.2", "tracing", "tracing-core", "tracing-subscriber", ] +[[package]] +name = "const-random" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359" +dependencies = [ + "const-random-macro", +] + +[[package]] +name = "const-random-macro" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" +dependencies = [ + "getrandom", + "once_cell", + "tiny-keccak", +] + [[package]] name = "constant_time_eq" version = "0.1.5" @@ -704,6 +763,12 @@ version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + [[package]] name = "crypto-common" version = "0.1.6" @@ -762,6 +827,15 @@ dependencies = [ "subtle", ] +[[package]] +name = "dlv-list" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "442039f5147480ba31067cb00ada1adae6892028e40e45fc5de7b7df6dcc1b5f" +dependencies = [ + "const-random", +] + [[package]] name = "dotenv" version = "0.15.0" @@ -876,6 +950,12 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "27573eac26f4dd11e2b1916c3fe1baa56407c83c71a773a8ba17ec0bca03b6b7" +[[package]] +name = "fixedbitset" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" + [[package]] name = "flate2" version = "1.0.28" @@ -1057,6 +1137,25 @@ dependencies = [ "tracing", ] +[[package]] +name = "h2" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524e8ac6999421f49a846c2d4411f337e53497d8ec55d67753beffa43c5d9205" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http 1.1.0", + "indexmap 2.1.0", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "hashbrown" version = "0.12.3" @@ -1215,7 +1314,7 @@ dependencies = [ "futures-channel", "futures-core", "futures-util", - "h2", + "h2 0.3.24", "http 0.2.11", "http-body 0.4.6", "httparse", @@ -1238,6 +1337,7 @@ dependencies = [ "bytes", "futures-channel", "futures-util", + "h2 0.4.6", "http 1.1.0", "http-body 1.0.0", "httparse", @@ -1279,6 +1379,19 @@ dependencies = [ "tokio-io-timeout", ] +[[package]] +name = "hyper-timeout" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3203a961e5c83b6f5498933e78b6b263e208c197b63e9c6c53cc82ffd3f63793" +dependencies = [ + "hyper 1.4.0", + "hyper-util", + "pin-project-lite", + "tokio", + "tower-service", +] + [[package]] name = "hyper-tls" version = "0.5.0" @@ -1615,6 +1728,12 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "multimap" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "defc4c55412d89136f966bbb339008b474350e5e6e78d2714439c386b3137a03" + [[package]] name = "native-tls" version = "0.2.11" @@ -1762,6 +1881,16 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "ordered-multimap" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49203cdcae0030493bad186b28da2fa25645fa276a51b6fec8010d281e02ef79" +dependencies = [ + "dlv-list", + "hashbrown 0.14.3", +] + [[package]] name = "overload" version = "0.1.1" @@ -1832,6 +1961,16 @@ version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +[[package]] +name = "petgraph" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" +dependencies = [ + "fixedbitset", + "indexmap 2.1.0", +] + [[package]] name = "pin-project" version = "1.1.4" @@ -1925,7 +2064,38 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "146c289cda302b98a28d40c8b3b90498d6e526dd24ac2ecea73e4e491685b94a" dependencies = [ "bytes", - "prost-derive", + "prost-derive 0.12.3", +] + +[[package]] +name = "prost" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b2ecbe40f08db5c006b5764a2645f7f3f141ce756412ac9e1dd6087e6d32995" +dependencies = [ + "bytes", + "prost-derive 0.13.2", +] + +[[package]] +name = "prost-build" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8650aabb6c35b860610e9cff5dc1af886c9e25073b7b1712a68972af4281302" +dependencies = [ + "bytes", + "heck", + "itertools", + "log", + "multimap", + "once_cell", + "petgraph", + "prettyplease", + "prost 0.13.2", + "prost-types 0.13.2", + "regex", + "syn 2.0.48", + "tempfile", ] [[package]] @@ -1941,13 +2111,35 @@ dependencies = [ "syn 2.0.48", ] +[[package]] +name = "prost-derive" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acf0c195eebb4af52c752bec4f52f645da98b6e92077a04110c7f349477ae5ac" +dependencies = [ + "anyhow", + "itertools", + "proc-macro2", + "quote", + "syn 2.0.48", +] + [[package]] name = "prost-types" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "193898f59edcf43c26227dcd4c8427f00d99d61e95dcde58dabd49fa291d470e" dependencies = [ - "prost", + "prost 0.12.3", +] + +[[package]] +name = "prost-types" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60caa6738c7369b940c3d49246a8d1749323674c65cb13010134f5c9bad5b519" +dependencies = [ + "prost 0.13.2", ] [[package]] @@ -2100,7 +2292,7 @@ dependencies = [ "encoding_rs", "futures-core", "futures-util", - "h2", + "h2 0.3.24", "http 0.2.11", "http-body 0.4.6", "hyper 0.14.28", @@ -2197,6 +2389,17 @@ dependencies = [ "smallvec", ] +[[package]] +name = "rust-ini" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d625ed57d8f49af6cfa514c42e1a71fadcff60eb0b1c517ff82fe41aa025b41" +dependencies = [ + "cfg-if", + "ordered-multimap", + "trim-in-place", +] + [[package]] name = "rustc-demangle" version = "0.1.23" @@ -2404,6 +2607,15 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_spanned" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79e674e01f999af37c49f70a6ede167a8a60b2503e56c5599532a65baa5969a0" +dependencies = [ + "serde", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -2654,6 +2866,15 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + [[package]] name = "tinyvec" version = "1.6.0" @@ -2755,25 +2976,59 @@ dependencies = [ "tracing", ] +[[package]] +name = "toml" +version = "0.8.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac2caab0bf757388c6c0ae23b3293fdb463fee59434529014f85e3263b995c28" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4badfd56924ae69bcc9039335b2e017639ce3f9b001c393c1b2d1ef846ce2cbf" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.22.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "278f3d518e152219c994ce877758516bca5e118eaed6996192a774fb9fbf0788" +dependencies = [ + "indexmap 2.1.0", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + [[package]] name = "tonic" version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d560933a0de61cf715926b9cac824d4c883c2c43142f787595e48280c40a1d0e" dependencies = [ - "async-stream", + "async-stream 0.3.5", "async-trait", "axum 0.6.20", "base64 0.21.7", "bytes", - "h2", + "h2 0.3.24", "http 0.2.11", "http-body 0.4.6", "hyper 0.14.28", - "hyper-timeout", + "hyper-timeout 0.4.1", "percent-encoding", "pin-project", - "prost", + "prost 0.12.3", "tokio", "tokio-stream", "tower", @@ -2782,6 +3037,49 @@ dependencies = [ "tracing", ] +[[package]] +name = "tonic" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6f6ba989e4b2c58ae83d862d3a3e27690b6e3ae630d0deb59f3697f32aa88ad" +dependencies = [ + "async-stream 0.3.5", + "async-trait", + "axum 0.7.5", + "base64 0.22.1", + "bytes", + "h2 0.4.6", + "http 1.1.0", + "http-body 1.0.0", + "http-body-util", + "hyper 1.4.0", + "hyper-timeout 0.5.1", + "hyper-util", + "percent-encoding", + "pin-project", + "prost 0.13.2", + "socket2", + "tokio", + "tokio-stream", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tonic-build" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe4ee8877250136bd7e3d2331632810a4df4ea5e004656990d8d66d2f5ee8a67" +dependencies = [ + "prettyplease", + "proc-macro2", + "prost-build", + "quote", + "syn 2.0.48", +] + [[package]] name = "tower" version = "0.4.13" @@ -2913,6 +3211,12 @@ dependencies = [ "tracing-log 0.2.0", ] +[[package]] +name = "trim-in-place" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "343e926fc669bc8cde4fa3129ab681c63671bae288b1f1081ceee6d9d37904fc" + [[package]] name = "try-lock" version = "0.2.5" @@ -3320,6 +3624,15 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" +[[package]] +name = "winnow" +version = "0.6.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "557404e450152cd6795bb558bca69e43c585055f4606e3bcae5894fc6dac9ba0" +dependencies = [ + "memchr", +] + [[package]] name = "winreg" version = "0.50.0" diff --git a/Dockerfile b/Dockerfile index 8e17812..404179b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -12,7 +12,7 @@ RUN set -eux && \ curl --proto '=https' --tlsv1.2 -sSf https://apt.llvm.org/llvm-snapshot.gpg.key | gpg --dearmor --output $KEYRINGS/llvm.gpg && \ echo "deb [signed-by=$KEYRINGS/llvm.gpg] http://apt.llvm.org/bookworm/ llvm-toolchain-bookworm-$LLVM_VERSION main" > /etc/apt/sources.list.d/llvm.list && \ apt-get update && \ - apt-get install --no-install-recommends -y clang-$LLVM_VERSION llvm-$LLVM_VERSION lld-$LLVM_VERSION build-essential sqlite3 libsqlite3-dev musl musl-tools musl-dev && \ + apt-get install --no-install-recommends -y clang-$LLVM_VERSION llvm-$LLVM_VERSION lld-$LLVM_VERSION build-essential sqlite3 libsqlite3-dev musl musl-tools musl-dev protobuf-compiler libprotobuf-dev && \ ln -s clang-$LLVM_VERSION /usr/bin/clang && \ ln -s clang /usr/bin/clang++ && \ ln -s lld-$LLVM_VERSION /usr/bin/ld.lld && \ diff --git a/Makefile b/Makefile index d0c9bd9..6563ab1 100644 --- a/Makefile +++ b/Makefile @@ -20,6 +20,12 @@ start: stop: @$(cargo_norm) stop +status: + @$(cargo_norm) server-status + +tunnel-config: + @$(cargo_norm) tunnel-config + test-dns: @sudo route delete 8.8.8.8 @sudo route add 8.8.8.8 -interface $(tun) diff --git a/burrow/Cargo.toml b/burrow/Cargo.toml index 0fb63a5..d5e56c1 100644 --- a/burrow/Cargo.toml +++ b/burrow/Cargo.toml @@ -19,6 +19,7 @@ tokio = { version = "1.37", features = [ "signal", "time", "tracing", + "fs", ] } tun = { version = "0.1", path = "../tun", features = ["serde", "tokio"] } clap = { version = "4.4", features = ["derive"] } @@ -56,8 +57,17 @@ reqwest = { version = "0.12", default-features = false, features = [ "json", "rustls-tls", ] } -rusqlite = "0.31.0" +rusqlite = { version = "0.31.0", features = ["blob"] } dotenv = "0.15.0" +tonic = "0.12.0" +prost = "0.13.1" +prost-types = "0.13.1" +tokio-stream = "0.1" +async-stream = "0.2" +tower = "0.4.13" +hyper-util = "0.1.6" +toml = "0.8.15" +rust-ini = "0.21.0" [target.'cfg(target_os = "linux")'.dependencies] caps = "0.5" @@ -66,7 +76,7 @@ tracing-journald = "0.3" [target.'cfg(target_vendor = "apple")'.dependencies] nix = { version = "0.27" } -rusqlite = { version = "0.31.0", features = ["bundled"] } +rusqlite = { version = "0.31.0", features = ["bundled", "blob"] } [dev-dependencies] insta = { version = "1.32", features = ["yaml"] } @@ -83,3 +93,7 @@ pre_uninstall_script = "../package/rpm/pre_uninstall" [features] tokio-console = ["dep:console-subscriber"] bundled = ["rusqlite/bundled"] + + +[build-dependencies] +tonic-build = "0.12.0" diff --git a/burrow/build.rs b/burrow/build.rs new file mode 100644 index 0000000..8eea5dc --- /dev/null +++ b/burrow/build.rs @@ -0,0 +1,4 @@ +fn main() -> Result<(), Box> { + tonic_build::compile_protos("../proto/burrow.proto")?; + Ok(()) +} diff --git a/burrow/burrow.db b/burrow/burrow.db deleted file mode 100644 index c5b6e2c614ecb4db4c264f50691b6f2cea99772a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 20480 zcmWFz^vNtqRY=P(%1ta$FlG>7U}R))P*7lCU=U|uU|?lH0A>aT1{MUDff0#~iz&{a zSJuG`(#R{q!1sc08t)Wd5nPH##YaP6Gz3ONU^E0qLtr!nMnhmU1V%$(Gz3ONfZicc z$HFcyEzQ^%T#}fSlbV-WQl4Lw4W(F}gIpa$TopnboqSvspn?h-TnbQ-nOBlpl$MyB z8lRb>;OQ5l5ajCS8szHd>>8|4o*oaE*2qlJRPgsx2n}!n8RzU6?Cj{`3N}Wwv7Q<1 zfaXxJ1Ip9m3sO^ypcD&=1E7LbbAS%m1t7nq=A{(mXXceCgt$h8DERq@DENi?_#os9 zN|SOjljE~fD{-kv%*n|wPfdx>EGWjMq@XCZI3uwrH3e=C*nZ6bCN^HWN82kl9QqrXkB9 z2QfHiUEN)S6as=geI0`$6}(*|6&yoD{5}1ggIs-G{X!4{1#$w|{|KR+%;J*Ny!e9r zq7qOV0hxr5%q=O!6f7vpEK4j&g$EOs2uVyyDM~HI8Pq9xXi|`n2KCLkcwHFyck!3- z>+!wdTf`T`C&qh$w~N<>-uZ6SzR?gE4S~@R7!85Z5Eu=C(GVC7fzc2c4S~@R80;b7 z!o|VBz|4@U&dYEr$KN#|EG0iT)hDDP#M7y)%s3>n*efVK)xe{`($6khIH_U^2USdAr-~_TR567W$rN|LLQhA3=b(zL9R1|XAY71JH1mFA{7sYRJyiIoQ0`5rzQLB0iH+K#3bj)4Xl&R*Ju=HcZQ zhK~MaAtqSUX|Z`ug??_jc2R1Wt8borUSVowq<>&`m5YU0o{@HXWL|}#uVrO=rh!Ga zZ6hlS#2qXH?G9#$JD3OB9ZV2+Fb%LfSQyzjLFL%MIs?@IXAq!ac|B_MXb6mkz-S1J ihQMeDjE2By2#kinXb6mkz-S1JhQMeDjE2B~hX4R^(?&G_ diff --git a/burrow/src/auth/server/db.rs b/burrow/src/auth/server/db.rs index b74f7ce..995e64b 100644 --- a/burrow/src/auth/server/db.rs +++ b/burrow/src/auth/server/db.rs @@ -1,5 +1,7 @@ use anyhow::Result; +use crate::daemon::rpc::grpc_defs::{Network, NetworkType}; + pub static PATH: &str = "./server.sqlite3"; pub fn init_db() -> Result<()> { diff --git a/burrow/src/daemon/instance.rs b/burrow/src/daemon/instance.rs index bc506bd..ce96fa5 100644 --- a/burrow/src/daemon/instance.rs +++ b/burrow/src/daemon/instance.rs @@ -1,13 +1,30 @@ use std::{ + ops::Deref, path::{Path, PathBuf}, sync::Arc, + time::Duration, }; use anyhow::Result; -use tokio::{sync::RwLock, task::JoinHandle}; +use rusqlite::Connection; +use tokio::sync::{mpsc, watch, Notify, RwLock}; +use tokio_stream::wrappers::ReceiverStream; +use tonic::{Request, Response, Status as RspStatus}; use tracing::{debug, info, warn}; -use tun::tokio::TunInterface; +use tun::{tokio::TunInterface, TunOptions}; +use super::rpc::grpc_defs::{ + networks_server::Networks, + tunnel_server::Tunnel, + Empty, + Network, + NetworkDeleteRequest, + NetworkListResponse, + NetworkReorderRequest, + State as RPCTunnelState, + TunnelConfigurationResponse, + TunnelStatusResponse, +}; use crate::{ daemon::rpc::{ DaemonCommand, @@ -17,114 +34,223 @@ use crate::{ ServerConfig, ServerInfo, }, - database::{get_connection, load_interface}, + database::{ + add_network, + delete_network, + get_connection, + list_networks, + load_interface, + reorder_network, + }, wireguard::{Config, Interface}, }; +#[derive(Debug, Clone)] enum RunState { - Running(JoinHandle>), + Running, Idle, } -pub struct DaemonInstance { - rx: async_channel::Receiver, - sx: async_channel::Sender, - subx: async_channel::Sender, +impl RunState { + pub fn to_rpc(&self) -> RPCTunnelState { + match self { + RunState::Running => RPCTunnelState::Running, + RunState::Idle => RPCTunnelState::Stopped, + } + } +} + +#[derive(Clone)] +pub struct DaemonRPCServer { tun_interface: Arc>>, wg_interface: Arc>, config: Arc>, db_path: Option, - wg_state: RunState, + wg_state_chan: (watch::Sender, watch::Receiver), + network_update_chan: (watch::Sender<()>, watch::Receiver<()>), } -impl DaemonInstance { +impl DaemonRPCServer { pub fn new( - rx: async_channel::Receiver, - sx: async_channel::Sender, - subx: async_channel::Sender, wg_interface: Arc>, config: Arc>, db_path: Option<&Path>, - ) -> Self { - Self { - rx, - sx, - subx, - wg_interface, + ) -> Result { + Ok(Self { tun_interface: Arc::new(RwLock::new(None)), + wg_interface, config, db_path: db_path.map(|p| p.to_owned()), - wg_state: RunState::Idle, - } + wg_state_chan: watch::channel(RunState::Idle), + network_update_chan: watch::channel(()), + }) } - async fn proc_command(&mut self, command: DaemonCommand) -> Result { - info!("Daemon got command: {:?}", command); - match command { - DaemonCommand::Start(st) => { - match self.wg_state { - RunState::Running(_) => { - warn!("Got start, but tun interface already up."); - } - RunState::Idle => { - let tun_if = st.tun.open()?; - debug!("Setting tun on wg_interface"); - self.wg_interface.read().await.set_tun(tun_if).await; - debug!("tun set on wg_interface"); - - debug!("Setting tun_interface"); - self.tun_interface = self.wg_interface.read().await.get_tun(); - debug!("tun_interface set: {:?}", self.tun_interface); - - debug!("Cloning wg_interface"); - let tmp_wg = self.wg_interface.clone(); - let run_task = tokio::spawn(async move { - let twlock = tmp_wg.read().await; - twlock.run().await - }); - self.wg_state = RunState::Running(run_task); - info!("Daemon started tun interface"); - } - } - Ok(DaemonResponseData::None) - } - DaemonCommand::ServerInfo => match &self.tun_interface.read().await.as_ref() { - None => Ok(DaemonResponseData::None), - Some(ti) => { - info!("{:?}", ti); - Ok(DaemonResponseData::ServerInfo(ServerInfo::try_from( - ti.inner.get_ref(), - )?)) - } - }, - DaemonCommand::Stop => { - self.wg_interface.read().await.remove_tun().await; - self.wg_state = RunState::Idle; - Ok(DaemonResponseData::None) - } - DaemonCommand::ServerConfig => { - Ok(DaemonResponseData::ServerConfig(ServerConfig::default())) - } - DaemonCommand::ReloadConfig(interface_id) => { - let conn = get_connection(self.db_path.as_deref())?; - let cfig = load_interface(&conn, &interface_id)?; - *self.config.write().await = cfig; - self.subx - .send(DaemonNotification::ConfigChange(ServerConfig::try_from( - &self.config.read().await.to_owned(), - )?)) - .await?; - Ok(DaemonResponseData::None) - } - } + pub fn get_connection(&self) -> Result { + get_connection(self.db_path.as_deref()).map_err(proc_err) } - pub async fn run(&mut self) -> Result<()> { - while let Ok(command) = self.rx.recv().await { - let response = self.proc_command(command).await; - info!("Daemon response: {:?}", response); - self.sx.send(DaemonResponse::new(response)).await?; - } - Ok(()) + async fn set_wg_state(&self, state: RunState) -> Result<(), RspStatus> { + self.wg_state_chan.0.send(state).map_err(proc_err) + } + + async fn get_wg_state(&self) -> RunState { + self.wg_state_chan.1.borrow().to_owned() + } + + async fn notify_network_update(&self) -> Result<(), RspStatus> { + self.network_update_chan.0.send(()).map_err(proc_err) + } +} + +#[tonic::async_trait] +impl Tunnel for DaemonRPCServer { + type TunnelConfigurationStream = ReceiverStream>; + type TunnelStatusStream = ReceiverStream>; + + async fn tunnel_configuration( + &self, + _request: Request, + ) -> Result, RspStatus> { + let (tx, rx) = mpsc::channel(10); + tokio::spawn(async move { + let serv_config = ServerConfig::default(); + tx.send(Ok(TunnelConfigurationResponse { + mtu: serv_config.mtu.unwrap_or(1000), + addresses: serv_config.address, + })) + .await + }); + Ok(Response::new(ReceiverStream::new(rx))) + } + + async fn tunnel_start(&self, _request: Request) -> Result, RspStatus> { + let wg_state = self.get_wg_state().await; + match wg_state { + RunState::Idle => { + let tun_if = TunOptions::new().open()?; + debug!("Setting tun on wg_interface"); + self.tun_interface.write().await.replace(tun_if); + self.wg_interface + .write() + .await + .set_tun_ref(self.tun_interface.clone()) + .await; + debug!("tun set on wg_interface"); + + debug!("Setting tun_interface"); + debug!("tun_interface set: {:?}", self.tun_interface); + + debug!("Cloning wg_interface"); + let tmp_wg = self.wg_interface.clone(); + let run_task = tokio::spawn(async move { + let twlock = tmp_wg.read().await; + twlock.run().await + }); + self.set_wg_state(RunState::Running).await?; + } + + RunState::Running => { + warn!("Got start, but tun interface already up."); + } + } + + return Ok(Response::new(Empty {})); + } + + async fn tunnel_stop(&self, _request: Request) -> Result, RspStatus> { + self.wg_interface.write().await.remove_tun().await; + self.set_wg_state(RunState::Idle).await?; + return Ok(Response::new(Empty {})); + } + + async fn tunnel_status( + &self, + _request: Request, + ) -> Result, RspStatus> { + let (tx, rx) = mpsc::channel(10); + let mut state_rx = self.wg_state_chan.1.clone(); + tokio::spawn(async move { + let cur = state_rx.borrow_and_update().to_owned(); + tx.send(Ok(status_rsp(cur))).await; + loop { + state_rx.changed().await.unwrap(); + let cur = state_rx.borrow().to_owned(); + let res = tx.send(Ok(status_rsp(cur))).await; + if res.is_err() { + eprintln!("Tunnel status channel closed"); + break; + } + } + }); + Ok(Response::new(ReceiverStream::new(rx))) + } +} + +#[tonic::async_trait] +impl Networks for DaemonRPCServer { + type NetworkListStream = ReceiverStream>; + + async fn network_add(&self, request: Request) -> Result, RspStatus> { + let conn = self.get_connection()?; + let network = request.into_inner(); + add_network(&conn, &network).map_err(proc_err)?; + self.notify_network_update().await?; + Ok(Response::new(Empty {})) + } + + async fn network_list( + &self, + _request: Request, + ) -> Result, RspStatus> { + debug!("Mock network_list called"); + let (tx, rx) = mpsc::channel(10); + let conn = self.get_connection()?; + let mut sub = self.network_update_chan.1.clone(); + tokio::spawn(async move { + loop { + let networks = list_networks(&conn) + .map(|res| NetworkListResponse { network: res }) + .map_err(proc_err); + let res = tx.send(networks).await; + if res.is_err() { + eprintln!("Network list channel closed"); + break; + } + sub.changed().await.unwrap(); + } + }); + Ok(Response::new(ReceiverStream::new(rx))) + } + + async fn network_reorder( + &self, + request: Request, + ) -> Result, RspStatus> { + let conn = self.get_connection()?; + reorder_network(&conn, request.into_inner()).map_err(proc_err)?; + self.notify_network_update().await?; + Ok(Response::new(Empty {})) + } + + async fn network_delete( + &self, + request: Request, + ) -> Result, RspStatus> { + let conn = self.get_connection()?; + delete_network(&conn, request.into_inner()).map_err(proc_err)?; + self.notify_network_update().await?; + Ok(Response::new(Empty {})) + } +} + +fn proc_err(err: impl ToString) -> RspStatus { + RspStatus::internal(err.to_string()) +} + +fn status_rsp(state: RunState) -> TunnelStatusResponse { + TunnelStatusResponse { + state: state.to_rpc().into(), + start: None, // TODO: Add timestamp } } diff --git a/burrow/src/daemon/mod.rs b/burrow/src/daemon/mod.rs index 4469e90..f6b973f 100644 --- a/burrow/src/daemon/mod.rs +++ b/burrow/src/daemon/mod.rs @@ -5,14 +5,20 @@ mod instance; mod net; pub mod rpc; -use anyhow::Result; -use instance::DaemonInstance; -pub use net::{DaemonClient, Listener}; +use anyhow::{Error as AhError, Result}; +use instance::DaemonRPCServer; +pub use net::{get_socket_path, DaemonClient}; pub use rpc::{DaemonCommand, DaemonResponseData, DaemonStartOptions}; -use tokio::sync::{Notify, RwLock}; +use tokio::{ + net::UnixListener, + sync::{Notify, RwLock}, +}; +use tokio_stream::wrappers::UnixListenerStream; +use tonic::transport::Server; use tracing::{error, info}; use crate::{ + daemon::rpc::grpc_defs::{networks_server::NetworksServer, tunnel_server::TunnelServer}, database::{get_connection, load_interface}, wireguard::Interface, }; @@ -22,52 +28,36 @@ pub async fn daemon_main( db_path: Option<&Path>, notify_ready: Option>, ) -> Result<()> { - let (commands_tx, commands_rx) = async_channel::unbounded(); - let (response_tx, response_rx) = async_channel::unbounded(); - let (subscribe_tx, subscribe_rx) = async_channel::unbounded(); - - let listener = if let Some(path) = socket_path { - info!("Creating listener... {:?}", path); - Listener::new_with_path(commands_tx, response_rx, subscribe_rx, path) - } else { - info!("Creating listener..."); - Listener::new(commands_tx, response_rx, subscribe_rx) - }; if let Some(n) = notify_ready { n.notify_one() } - let listener = listener?; let conn = get_connection(db_path)?; let config = load_interface(&conn, "1")?; - let iface: Interface = config.clone().try_into()?; - let mut instance = DaemonInstance::new( - commands_rx, - response_tx, - subscribe_tx, - Arc::new(RwLock::new(iface)), + let burrow_server = DaemonRPCServer::new( + Arc::new(RwLock::new(config.clone().try_into()?)), Arc::new(RwLock::new(config)), - db_path, - ); + db_path.clone(), + )?; + let spp = socket_path.clone(); + let tmp = get_socket_path(); + let sock_path = spp.unwrap_or(Path::new(tmp.as_str())); + if sock_path.exists() { + std::fs::remove_file(sock_path)?; + } + let uds = UnixListener::bind(sock_path)?; + let serve_job = tokio::spawn(async move { + let uds_stream = UnixListenerStream::new(uds); + let _srv = Server::builder() + .add_service(TunnelServer::new(burrow_server.clone())) + .add_service(NetworksServer::new(burrow_server)) + .serve_with_incoming(uds_stream) + .await?; + Ok::<(), AhError>(()) + }); info!("Starting daemon..."); - let main_job = tokio::spawn(async move { - let result = instance.run().await; - if let Err(e) = result.as_ref() { - error!("Instance exited: {}", e); - } - result - }); - - let listener_job = tokio::spawn(async move { - let result = listener.run().await; - if let Err(e) = result.as_ref() { - error!("Listener exited: {}", e); - } - result - }); - - tokio::try_join!(main_job, listener_job) + tokio::try_join!(serve_job) .map(|_| ()) .map_err(|e| e.into()) } diff --git a/burrow/src/daemon/net/mod.rs b/burrow/src/daemon/net/mod.rs index 242f479..eb45335 100644 --- a/burrow/src/daemon/net/mod.rs +++ b/burrow/src/daemon/net/mod.rs @@ -1,18 +1,11 @@ - - - - - #[cfg(target_family = "unix")] mod unix; #[cfg(target_family = "unix")] -pub use unix::{DaemonClient, Listener}; +pub use unix::{get_socket_path, DaemonClient, Listener}; #[cfg(target_os = "windows")] mod windows; #[cfg(target_os = "windows")] pub use windows::{DaemonClient, Listener}; - - diff --git a/burrow/src/daemon/net/unix.rs b/burrow/src/daemon/net/unix.rs index 70c4207..975c470 100644 --- a/burrow/src/daemon/net/unix.rs +++ b/burrow/src/daemon/net/unix.rs @@ -25,7 +25,7 @@ const UNIX_SOCKET_PATH: &str = "/run/burrow.sock"; #[cfg(target_vendor = "apple")] const UNIX_SOCKET_PATH: &str = "burrow.sock"; -fn get_socket_path() -> String { +pub fn get_socket_path() -> String { if std::env::var("BURROW_SOCKET_PATH").is_ok() { return std::env::var("BURROW_SOCKET_PATH").unwrap(); } @@ -36,7 +36,7 @@ pub struct Listener { cmd_tx: async_channel::Sender, rsp_rx: async_channel::Receiver, sub_chan: async_channel::Receiver, - inner: UnixListener, + pub inner: UnixListener, } impl Listener { diff --git a/burrow/src/daemon/rpc/client.rs b/burrow/src/daemon/rpc/client.rs new file mode 100644 index 0000000..862e34c --- /dev/null +++ b/burrow/src/daemon/rpc/client.rs @@ -0,0 +1,31 @@ +use anyhow::Result; +use hyper_util::rt::TokioIo; +use tokio::net::UnixStream; +use tonic::transport::{Endpoint, Uri}; +use tower::service_fn; + +use super::grpc_defs::{networks_client::NetworksClient, tunnel_client::TunnelClient}; +use crate::daemon::get_socket_path; + +pub struct BurrowClient { + pub networks_client: NetworksClient, + pub tunnel_client: TunnelClient, +} + +impl BurrowClient { + #[cfg(any(target_os = "linux", target_vendor = "apple"))] + pub async fn from_uds() -> Result { + let channel = Endpoint::try_from("http://[::]:50051")? // NOTE: this is a hack(?) + .connect_with_connector(service_fn(|_: Uri| async { + let sock_path = get_socket_path(); + Ok::<_, std::io::Error>(TokioIo::new(UnixStream::connect(sock_path).await?)) + })) + .await?; + let nw_client = NetworksClient::new(channel.clone()); + let tun_client = TunnelClient::new(channel.clone()); + Ok(BurrowClient { + networks_client: nw_client, + tunnel_client: tun_client, + }) + } +} diff --git a/burrow/src/daemon/rpc/grpc_defs.rs b/burrow/src/daemon/rpc/grpc_defs.rs new file mode 100644 index 0000000..f3085ee --- /dev/null +++ b/burrow/src/daemon/rpc/grpc_defs.rs @@ -0,0 +1,5 @@ +pub use burrowgrpc::*; + +mod burrowgrpc { + tonic::include_proto!("burrow"); +} diff --git a/burrow/src/daemon/rpc/mod.rs b/burrow/src/daemon/rpc/mod.rs index 4146e71..512662c 100644 --- a/burrow/src/daemon/rpc/mod.rs +++ b/burrow/src/daemon/rpc/mod.rs @@ -1,7 +1,10 @@ +pub mod client; +pub mod grpc_defs; pub mod notification; pub mod request; pub mod response; +pub use client::BurrowClient; pub use notification::DaemonNotification; pub use request::{DaemonCommand, DaemonRequest, DaemonStartOptions}; pub use response::{DaemonResponse, DaemonResponseData, ServerConfig, ServerInfo}; diff --git a/burrow/src/database.rs b/burrow/src/database.rs index 0047b01..9a9aac3 100644 --- a/burrow/src/database.rs +++ b/burrow/src/database.rs @@ -3,7 +3,15 @@ use std::path::Path; use anyhow::Result; use rusqlite::{params, Connection}; -use crate::wireguard::config::{Config, Interface, Peer}; +use crate::{ + daemon::rpc::grpc_defs::{ + Network as RPCNetwork, + NetworkDeleteRequest, + NetworkReorderRequest, + NetworkType, + }, + wireguard::config::{Config, Interface, Peer}, +}; #[cfg(target_vendor = "apple")] const DB_PATH: &str = "burrow.db"; @@ -30,8 +38,20 @@ const CREATE_WG_PEER_TABLE: &str = "CREATE TABLE IF NOT EXISTS wg_peer ( )"; const CREATE_NETWORK_TABLE: &str = "CREATE TABLE IF NOT EXISTS network ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + type TEXT NOT NULL, + payload BLOB, + idx INTEGER, interface_id INT REFERENCES wg_interface(id) ON UPDATE CASCADE -)"; +); +CREATE TRIGGER IF NOT EXISTS increment_network_idx +AFTER INSERT ON network +BEGIN + UPDATE network + SET idx = (SELECT COALESCE(MAX(idx), 0) + 1 FROM network) + WHERE id = NEW.id; +END; +"; pub fn initialize_tables(conn: &Connection) -> Result<()> { conn.execute(CREATE_WG_INTERFACE_TABLE, [])?; @@ -40,20 +60,6 @@ pub fn initialize_tables(conn: &Connection) -> Result<()> { Ok(()) } -fn parse_lst(s: &str) -> Vec { - if s.is_empty() { - return vec![]; - } - s.split(',').map(|s| s.to_string()).collect() -} - -fn to_lst(v: &Vec) -> String { - v.iter() - .map(|s| s.to_string()) - .collect::>() - .join(",") -} - pub fn load_interface(conn: &Connection, interface_id: &str) -> Result { let iface = conn.query_row( "SELECT private_key, dns, address, listen_port, mtu FROM wg_interface WHERE id = ?", @@ -99,7 +105,7 @@ pub fn dump_interface(conn: &Connection, config: &Config) -> Result<()> { cif.private_key, to_lst(&cif.dns), to_lst(&cif.address), - cif.listen_port, + cif.listen_port.unwrap_or(51820), cif.mtu ])?; let interface_id = conn.last_insert_rowid(); @@ -127,10 +133,75 @@ pub fn get_connection(path: Option<&Path>) -> Result { Ok(Connection::open(p)?) } +pub fn add_network(conn: &Connection, network: &RPCNetwork) -> Result<()> { + let mut stmt = conn.prepare("INSERT INTO network (id, type, payload) VALUES (?, ?, ?)")?; + stmt.execute(params![ + network.id, + network.r#type().as_str_name(), + &network.payload + ])?; + if network.r#type() == NetworkType::WireGuard { + let payload_str = String::from_utf8(network.payload.clone())?; + let wg_config = Config::from_content_fmt(&payload_str, "ini")?; + dump_interface(conn, &wg_config)?; + } + Ok(()) +} + +pub fn list_networks(conn: &Connection) -> Result> { + let mut stmt = conn.prepare("SELECT id, type, payload FROM network ORDER BY idx")?; + let networks: Vec = stmt + .query_map([], |row| { + println!("row: {:?}", row); + let network_id: i32 = row.get(0)?; + let network_type: String = row.get(1)?; + let network_type = NetworkType::from_str_name(network_type.as_str()) + .ok_or(rusqlite::Error::InvalidQuery)?; + let payload: Vec = row.get(2)?; + Ok(RPCNetwork { + id: network_id, + r#type: network_type.into(), + payload: payload.into(), + }) + })? + .collect::, rusqlite::Error>>()?; + Ok(networks) +} + +pub fn reorder_network(conn: &Connection, req: NetworkReorderRequest) -> Result<()> { + let mut stmt = conn.prepare("UPDATE network SET idx = ? WHERE id = ?")?; + let res = stmt.execute(params![req.index, req.id])?; + if res == 0 { + return Err(anyhow::anyhow!("No such network exists")); + } + Ok(()) +} + +pub fn delete_network(conn: &Connection, req: NetworkDeleteRequest) -> Result<()> { + let mut stmt = conn.prepare("DELETE FROM network WHERE id = ?")?; + let res = stmt.execute(params![req.id])?; + if res == 0 { + return Err(anyhow::anyhow!("No such network exists")); + } + Ok(()) +} + +fn parse_lst(s: &str) -> Vec { + if s.is_empty() { + return vec![]; + } + s.split(',').map(|s| s.to_string()).collect() +} + +fn to_lst(v: &Vec) -> String { + v.iter() + .map(|s| s.to_string()) + .collect::>() + .join(",") +} + #[cfg(test)] mod tests { - use std::path::Path; - use super::*; #[test] diff --git a/burrow/src/main.rs b/burrow/src/main.rs index ff07d4c..e87b4c9 100644 --- a/burrow/src/main.rs +++ b/burrow/src/main.rs @@ -11,8 +11,7 @@ mod wireguard; mod auth; #[cfg(any(target_os = "linux", target_vendor = "apple"))] -use daemon::{DaemonClient, DaemonCommand, DaemonStartOptions}; -use tun::TunOptions; +use daemon::{DaemonClient, DaemonCommand}; #[cfg(any(target_os = "linux", target_vendor = "apple"))] use crate::daemon::DaemonResponseData; @@ -20,6 +19,9 @@ use crate::daemon::DaemonResponseData; #[cfg(any(target_os = "linux", target_vendor = "apple"))] pub mod database; +#[cfg(any(target_os = "linux", target_vendor = "apple"))] +use crate::daemon::rpc::{grpc_defs::Empty, BurrowClient}; + #[derive(Parser)] #[command(name = "Burrow")] #[command(author = "Hack Club ")] @@ -52,13 +54,24 @@ enum Commands { ReloadConfig(ReloadConfigArgs), /// Authentication server AuthServer, + /// Server Status + ServerStatus, + /// Tunnel Config + TunnelConfig, + /// Add Network + NetworkAdd(NetworkAddArgs), + /// List Networks + NetworkList, + /// Reorder Network + NetworkReorder(NetworkReorderArgs), + /// Delete Network + NetworkDelete(NetworkDeleteArgs), } #[derive(Args)] struct ReloadConfigArgs { #[clap(long, short)] interface_id: String, - } #[derive(Args)] @@ -67,21 +80,132 @@ struct StartArgs {} #[derive(Args)] struct DaemonArgs {} +#[derive(Args)] +struct NetworkAddArgs { + id: i32, + network_type: i32, + payload_path: String, +} + +#[derive(Args)] +struct NetworkReorderArgs { + id: i32, + index: i32, +} + +#[derive(Args)] +struct NetworkDeleteArgs { + id: i32, +} + #[cfg(any(target_os = "linux", target_vendor = "apple"))] async fn try_start() -> Result<()> { - let mut client = DaemonClient::new().await?; - client - .send_command(DaemonCommand::Start(DaemonStartOptions { - tun: TunOptions::new().address(vec!["10.13.13.2", "::2"]), - })) - .await - .map(|_| ()) + let mut client = BurrowClient::from_uds().await?; + let res = client.tunnel_client.tunnel_start(Empty {}).await?; + println!("Got results! {:?}", res); + Ok(()) } #[cfg(any(target_os = "linux", target_vendor = "apple"))] async fn try_stop() -> Result<()> { - let mut client = DaemonClient::new().await?; - client.send_command(DaemonCommand::Stop).await?; + let mut client = BurrowClient::from_uds().await?; + let res = client.tunnel_client.tunnel_stop(Empty {}).await?; + println!("Got results! {:?}", res); + Ok(()) +} + +#[cfg(any(target_os = "linux", target_vendor = "apple"))] +async fn try_serverstatus() -> Result<()> { + let mut client = BurrowClient::from_uds().await?; + let mut res = client + .tunnel_client + .tunnel_status(Empty {}) + .await? + .into_inner(); + if let Some(st) = res.message().await? { + println!("Server Status: {:?}", st); + } else { + println!("Server Status is None"); + } + Ok(()) +} + +#[cfg(any(target_os = "linux", target_vendor = "apple"))] +async fn try_tun_config() -> Result<()> { + let mut client = BurrowClient::from_uds().await?; + let mut res = client + .tunnel_client + .tunnel_configuration(Empty {}) + .await? + .into_inner(); + if let Some(config) = res.message().await? { + println!("Tunnel Config: {:?}", config); + } else { + println!("Tunnel Config is None"); + } + Ok(()) +} + +#[cfg(any(target_os = "linux", target_vendor = "apple"))] +async fn try_network_add(id: i32, network_type: i32, payload_path: &str) -> Result<()> { + use tokio::{fs::File, io::AsyncReadExt}; + + use crate::daemon::rpc::grpc_defs::Network; + + let mut file = File::open(payload_path).await?; + let mut payload = Vec::new(); + file.read_to_end(&mut payload).await?; + + let mut client = BurrowClient::from_uds().await?; + let network = Network { + id, + r#type: network_type, + payload, + }; + let res = client.networks_client.network_add(network).await?; + println!("Network Add Response: {:?}", res); + Ok(()) +} + +#[cfg(any(target_os = "linux", target_vendor = "apple"))] +async fn try_network_list() -> Result<()> { + let mut client = BurrowClient::from_uds().await?; + let mut res = client + .networks_client + .network_list(Empty {}) + .await? + .into_inner(); + while let Some(network_list) = res.message().await? { + println!("Network List: {:?}", network_list); + } + Ok(()) +} + +#[cfg(any(target_os = "linux", target_vendor = "apple"))] +async fn try_network_reorder(id: i32, index: i32) -> Result<()> { + use crate::daemon::rpc::grpc_defs::NetworkReorderRequest; + + let mut client = BurrowClient::from_uds().await?; + let reorder_request = NetworkReorderRequest { id, index }; + let res = client + .networks_client + .network_reorder(reorder_request) + .await?; + println!("Network Reorder Response: {:?}", res); + Ok(()) +} + +#[cfg(any(target_os = "linux", target_vendor = "apple"))] +async fn try_network_delete(id: i32) -> Result<()> { + use crate::daemon::rpc::grpc_defs::NetworkDeleteRequest; + + let mut client = BurrowClient::from_uds().await?; + let delete_request = NetworkDeleteRequest { id }; + let res = client + .networks_client + .network_delete(delete_request) + .await?; + println!("Network Delete Response: {:?}", res); Ok(()) } @@ -153,6 +277,14 @@ async fn main() -> Result<()> { Commands::ServerConfig => try_serverconfig().await?, Commands::ReloadConfig(args) => try_reloadconfig(args.interface_id.clone()).await?, Commands::AuthServer => crate::auth::server::serve().await?, + Commands::ServerStatus => try_serverstatus().await?, + Commands::TunnelConfig => try_tun_config().await?, + Commands::NetworkAdd(args) => { + try_network_add(args.id, args.network_type, &args.payload_path).await? + } + Commands::NetworkList => try_network_list().await?, + Commands::NetworkReorder(args) => try_network_reorder(args.id, args.index).await?, + Commands::NetworkDelete(args) => try_network_delete(args.id).await?, } Ok(()) diff --git a/burrow/src/wireguard/config.rs b/burrow/src/wireguard/config.rs index bd86a9f..5766675 100644 --- a/burrow/src/wireguard/config.rs +++ b/burrow/src/wireguard/config.rs @@ -3,9 +3,12 @@ use std::{net::ToSocketAddrs, str::FromStr}; use anyhow::{anyhow, Error, Result}; use base64::{engine::general_purpose, Engine}; use fehler::throws; +use ini::{Ini, Properties}; use ip_network::IpNetwork; +use serde::{Deserialize, Serialize}; use x25519_dalek::{PublicKey, StaticSecret}; +use super::inifield::IniField; use crate::wireguard::{Interface as WgInterface, Peer as WgPeer}; #[throws] @@ -31,7 +34,7 @@ fn parse_public_key(string: &str) -> PublicKey { /// A raw version of Peer Config that can be used later to reflect configuration files. /// This should be later converted to a `WgPeer`. /// Refers to https://github.com/pirate/wireguard-docs?tab=readme-ov-file#overview -#[derive(Debug, Clone, Eq, PartialEq)] +#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] pub struct Peer { pub public_key: String, pub preshared_key: Option, @@ -41,17 +44,18 @@ pub struct Peer { pub name: Option, } -#[derive(Debug, Clone, Eq, PartialEq)] +#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] pub struct Interface { pub private_key: String, pub address: Vec, - pub listen_port: u32, + pub listen_port: Option, pub dns: Vec, pub mtu: Option, } -#[derive(Debug, Clone, Eq, PartialEq)] +#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] pub struct Config { + #[serde(rename = "Peer")] pub peers: Vec, pub interface: Interface, // Support for multiple interfaces? } @@ -98,7 +102,7 @@ impl Default for Config { interface: Interface { private_key: "OEPVdomeLTxTIBvv3TYsJRge0Hp9NMiY0sIrhT8OWG8=".into(), address: vec!["10.13.13.2/24".into()], - listen_port: 51820, + listen_port: Some(51820), dns: Default::default(), mtu: Default::default(), }, @@ -113,3 +117,83 @@ impl Default for Config { } } } + +fn props_get(props: &Properties, key: &str) -> Result +where + T: TryFrom, +{ + IniField::try_from(props.get(key))?.try_into() +} + +impl TryFrom<&Properties> for Interface { + type Error = anyhow::Error; + + fn try_from(props: &Properties) -> Result { + Ok(Self { + private_key: props_get(props, "PrivateKey")?, + address: props_get(props, "Address")?, + listen_port: props_get(props, "ListenPort")?, + dns: props_get(props, "DNS")?, + mtu: props_get(props, "MTU")?, + }) + } +} + +impl TryFrom<&Properties> for Peer { + type Error = anyhow::Error; + + fn try_from(props: &Properties) -> Result { + Ok(Self { + public_key: props_get(props, "PublicKey")?, + preshared_key: props_get(props, "PresharedKey")?, + allowed_ips: props_get(props, "AllowedIPs")?, + endpoint: props_get(props, "Endpoint")?, + persistent_keepalive: props_get(props, "PersistentKeepalive")?, + name: props_get(props, "Name")?, + }) + } +} + +impl Config { + pub fn from_toml(toml: &str) -> Result { + toml::from_str(toml).map_err(Into::into) + } + + pub fn from_ini(ini: &str) -> Result { + let ini = Ini::load_from_str(ini)?; + let interface = ini + .section(Some("Interface")) + .ok_or(anyhow!("Interface section not found"))?; + let peers = ini.section_all(Some("Peer")); + Ok(Self { + interface: Interface::try_from(interface)?, + peers: peers + .into_iter() + .map(|v| Peer::try_from(v)) + .collect::>>()?, + }) + } + + pub fn from_content_fmt(content: &str, fmt: &str) -> Result { + match fmt { + "toml" => Self::from_toml(content), + "ini" | "conf" => Self::from_ini(content), + _ => Err(anyhow::anyhow!("Unsupported format: {}", fmt)), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn tst_config_toml() { + let cfig = Config::default(); + let toml = toml::to_string(&cfig).unwrap(); + println!("{}", &toml); + insta::assert_snapshot!(toml); + let cfig2: Config = toml::from_str(&toml).unwrap(); + assert_eq!(cfig, cfig2); + } +} diff --git a/burrow/src/wireguard/iface.rs b/burrow/src/wireguard/iface.rs index 84b5489..321801b 100755 --- a/burrow/src/wireguard/iface.rs +++ b/burrow/src/wireguard/iface.rs @@ -93,6 +93,12 @@ impl Interface { *st = IfaceStatus::Running; } + pub async fn set_tun_ref(&mut self, tun: Arc>>) { + self.tun = tun; + let mut st = self.status.write().await; + *st = IfaceStatus::Running; + } + pub fn get_tun(&self) -> Arc>> { self.tun.clone() } @@ -135,7 +141,7 @@ impl Interface { Some(addr) => addr, None => { debug!("No destination found"); - continue + continue; } }; @@ -154,7 +160,7 @@ impl Interface { } Err(e) => { log::error!("Failed to send packet {}", e); - continue + continue; } }; } @@ -175,7 +181,7 @@ impl Interface { let main_tsk = async move { if let Err(e) = pcb.open_if_closed().await { log::error!("failed to open pcb: {}", e); - return + return; } let r2 = pcb.run(tun).await; if let Err(e) = r2 { @@ -195,7 +201,7 @@ impl Interface { Ok(..) => (), Err(e) => { error!("Failed to update timers: {}", e); - return + return; } } } diff --git a/burrow/src/wireguard/inifield.rs b/burrow/src/wireguard/inifield.rs new file mode 100644 index 0000000..946868d --- /dev/null +++ b/burrow/src/wireguard/inifield.rs @@ -0,0 +1,81 @@ +use std::str::FromStr; + +use anyhow::{Error, Result}; + +pub struct IniField(String); + +impl FromStr for IniField { + type Err = Error; + + fn from_str(s: &str) -> Result { + Ok(Self(s.to_string())) + } +} + +impl TryFrom for Vec { + type Error = Error; + + fn try_from(field: IniField) -> Result { + Ok(field.0.split(',').map(|s| s.trim().to_string()).collect()) + } +} + +impl TryFrom for u32 { + type Error = Error; + + fn try_from(value: IniField) -> Result { + value.0.parse().map_err(Error::from) + } +} + +impl TryFrom for Option { + type Error = Error; + + fn try_from(value: IniField) -> Result { + if value.0.is_empty() { + Ok(None) + } else { + value.0.parse().map(Some).map_err(Error::from) + } + } +} + +impl TryFrom for String { + type Error = Error; + + fn try_from(value: IniField) -> Result { + Ok(value.0) + } +} + +impl TryFrom for Option { + type Error = Error; + + fn try_from(value: IniField) -> Result { + if value.0.is_empty() { + Ok(None) + } else { + Ok(Some(value.0)) + } + } +} + +impl TryFrom> for IniField +where + T: ToString, +{ + type Error = Error; + + fn try_from(value: Option) -> Result { + Ok(match value { + Some(v) => Self(v.to_string()), + None => Self(String::new()), + }) + } +} + +impl IniField { + fn new(value: &str) -> Self { + Self(value.to_string()) + } +} diff --git a/burrow/src/wireguard/mod.rs b/burrow/src/wireguard/mod.rs index 4c70a7f..cfb4585 100755 --- a/burrow/src/wireguard/mod.rs +++ b/burrow/src/wireguard/mod.rs @@ -1,5 +1,6 @@ pub mod config; mod iface; +mod inifield; mod noise; mod pcb; mod peer; diff --git a/burrow/src/wireguard/snapshots/burrow__wireguard__config__tests__tst_config_toml.snap b/burrow/src/wireguard/snapshots/burrow__wireguard__config__tests__tst_config_toml.snap new file mode 100644 index 0000000..3800647 --- /dev/null +++ b/burrow/src/wireguard/snapshots/burrow__wireguard__config__tests__tst_config_toml.snap @@ -0,0 +1,16 @@ +--- +source: burrow/src/wireguard/config.rs +expression: toml +--- +[[Peer]] +public_key = "8GaFjVO6c4luCHG4ONO+1bFG8tO+Zz5/Gy+Geht1USM=" +preshared_key = "ha7j4BjD49sIzyF9SNlbueK0AMHghlj6+u0G3bzC698=" +allowed_ips = ["8.8.8.8/32", "0.0.0.0/0"] +endpoint = "wg.burrow.rs:51820" + +[interface] +private_key = "OEPVdomeLTxTIBvv3TYsJRge0Hp9NMiY0sIrhT8OWG8=" +address = ["10.13.13.2/24"] +listen_port = 51820 +dns = [] + diff --git a/burrow/tmp/conrd.conf b/burrow/tmp/conrd.conf new file mode 100644 index 0000000..52572d1 --- /dev/null +++ b/burrow/tmp/conrd.conf @@ -0,0 +1,8 @@ +[Interface] +PrivateKey = gAaK0KFGOpxY7geGo59XXDufcxeoSNXXNC12mCQmlVs= +Address = 10.1.11.2/32 +DNS = 10.1.11.1 +[Peer] +PublicKey = Ab6V2mgPHiCXaAZfQrNts8ha8RkEzC49VnmMQfe5Yg4= +AllowedIPs = 10.1.11.1/32,10.1.11.2/32,0.0.0.0/0 +Endpoint = 172.251.163.175:51820 \ No newline at end of file diff --git a/proto/burrow.proto b/proto/burrow.proto index 2d29c78..2355b8d 100644 --- a/proto/burrow.proto +++ b/proto/burrow.proto @@ -11,7 +11,7 @@ service Tunnel { } service Networks { - rpc NetworkAdd (Empty) returns (Empty); + rpc NetworkAdd (Network) returns (Empty); rpc NetworkList (Empty) returns (stream NetworkListResponse); rpc NetworkReorder (NetworkReorderRequest) returns (Empty); rpc NetworkDelete (NetworkDeleteRequest) returns (Empty); From 25a0f7c42158831ceb5f6bbe7defe3c067eb586c Mon Sep 17 00:00:00 2001 From: Conrad Kramer Date: Sat, 7 Sep 2024 20:35:28 -0700 Subject: [PATCH 14/15] Add Developer ID Profiles to build --- .github/workflows/release-apple.yml | 5 +++++ .../Burrow_Developer_ID.provisionprofile | Bin 0 -> 13091 bytes ...Burrow_Network_Developer_ID.provisionprofile | Bin 0 -> 13027 bytes 3 files changed, 5 insertions(+) create mode 100644 Apple/Profiles/Burrow_Developer_ID.provisionprofile create mode 100644 Apple/Profiles/Burrow_Network_Developer_ID.provisionprofile diff --git a/.github/workflows/release-apple.yml b/.github/workflows/release-apple.yml index bb9c15a..c0a34a9 100644 --- a/.github/workflows/release-apple.yml +++ b/.github/workflows/release-apple.yml @@ -38,6 +38,11 @@ jobs: app-store-key: ${{ secrets.APPSTORE_KEY }} app-store-key-id: ${{ secrets.APPSTORE_KEY_ID }} app-store-key-issuer-id: ${{ secrets.APPSTORE_KEY_ISSUER_ID }} + - name: Install Provisioning Profiles + shell: bash + run: | + mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles/ + cp -f Apple/Profiles/* ~/Library/MobileDevice/Provisioning\ Profiles/ - name: Install Rust uses: dtolnay/rust-toolchain@stable with: diff --git a/Apple/Profiles/Burrow_Developer_ID.provisionprofile b/Apple/Profiles/Burrow_Developer_ID.provisionprofile new file mode 100644 index 0000000000000000000000000000000000000000..3ecd831fe2614bb8b5aea636ca5c080a48d99a98 GIT binary patch literal 13091 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 Uf<>W=OI4qBvCm4;3e_kF0D*cnOaK4? literal 0 HcmV?d00001 From 85640ffce18eac6ac1b6fa85ff278a457c955198 Mon Sep 17 00:00:00 2001 From: Conrad Kramer Date: Sat, 13 Jul 2024 18:08:43 -0700 Subject: [PATCH 15/15] Switch to gRPC client in Swift app --- .github/workflows/build-apple.yml | 9 +- .github/workflows/release-apple.yml | 4 + .gitignore | 3 + .swiftlint.yml | 1 - Apple/App/AppDelegate.swift | 1 + Apple/App/BurrowApp.swift | 3 +- Apple/App/MainMenu.xib | 4 +- Apple/App/Networks/Network.swift | 10 - Apple/App/Tunnel.swift | 50 - Apple/Burrow.xcodeproj/project.pbxproj | 814 +++++++++---- .../xcshareddata/swiftpm/Package.resolved | 123 ++ .../xcshareddata/xcschemes/App.xcscheme | 5 +- .../xcschemes/NetworkExtension.xcscheme | 6 +- Apple/Configuration/App.xcconfig | 6 +- Apple/Configuration/Compiler.xcconfig | 41 +- .../Configuration.xcconfig} | 5 +- .../Constants/Constants.h | 0 .../Constants}/Constants.swift | 31 +- .../Constants/module.modulemap | 2 +- Apple/Configuration/Debug.xcconfig | 26 + Apple/Configuration/Extension.xcconfig | 6 +- Apple/Configuration/Framework.xcconfig | 14 + Apple/Core/Client.swift | 32 + Apple/Core/Client/burrow.proto | 1 + Apple/Core/Client/grpc-swift-config.json | 11 + Apple/Core/Client/swift-protobuf-config.json | 10 + Apple/{Shared => Core}/Logging.swift | 2 +- .../PacketTunnelProvider.swift | 108 +- .../NetworkExtension/libburrow/build-rust.sh | 5 +- Apple/NetworkExtension/libburrow/libburrow.h | 2 +- Apple/Shared/Client.swift | 106 -- Apple/Shared/DataTypes.swift | 139 --- Apple/Shared/NWConnection+Async.swift | 32 - Apple/Shared/NewlineProtocolFramer.swift | 54 - .../AccentColor.colorset/Contents.json | 0 .../AppIcon.appiconset/100.png | Bin .../AppIcon.appiconset/1024.png | Bin .../AppIcon.appiconset/114.png | Bin .../AppIcon.appiconset/120.png | Bin .../AppIcon.appiconset/128.png | Bin .../AppIcon.appiconset/144.png | Bin .../AppIcon.appiconset/152.png | Bin .../Assets.xcassets/AppIcon.appiconset/16.png | Bin .../AppIcon.appiconset/167.png | Bin .../AppIcon.appiconset/172.png | Bin .../AppIcon.appiconset/180.png | Bin .../AppIcon.appiconset/196.png | Bin .../Assets.xcassets/AppIcon.appiconset/20.png | Bin .../AppIcon.appiconset/216.png | Bin .../AppIcon.appiconset/256.png | Bin .../Assets.xcassets/AppIcon.appiconset/29.png | Bin .../Assets.xcassets/AppIcon.appiconset/32.png | Bin .../Assets.xcassets/AppIcon.appiconset/40.png | Bin .../Assets.xcassets/AppIcon.appiconset/48.png | Bin .../Assets.xcassets/AppIcon.appiconset/50.png | Bin .../AppIcon.appiconset/512.png | Bin .../Assets.xcassets/AppIcon.appiconset/55.png | Bin .../Assets.xcassets/AppIcon.appiconset/57.png | Bin .../Assets.xcassets/AppIcon.appiconset/58.png | Bin .../Assets.xcassets/AppIcon.appiconset/60.png | Bin .../Assets.xcassets/AppIcon.appiconset/64.png | Bin .../Assets.xcassets/AppIcon.appiconset/72.png | Bin .../Assets.xcassets/AppIcon.appiconset/76.png | Bin .../Assets.xcassets/AppIcon.appiconset/80.png | Bin .../Assets.xcassets/AppIcon.appiconset/87.png | Bin .../Assets.xcassets/AppIcon.appiconset/88.png | Bin .../AppIcon.appiconset/Contents.json | 0 .../{App => UI}/Assets.xcassets/Contents.json | 0 .../HackClub.colorset/Contents.json | 0 .../HackClub.imageset/Contents.json | 0 .../flag-standalone-wtransparent.pdf | Bin .../WireGuard.colorset/Contents.json | 0 .../WireGuard.imageset/Contents.json | 0 .../WireGuard.imageset/WireGuard.svg | 0 .../WireGuardTitle.imageset/Contents.json | 0 .../WireGuardTitle.svg | 0 Apple/{App => UI}/BurrowView.swift | 7 +- Apple/{App => UI}/FloatingButtonStyle.swift | 0 Apple/{App => UI}/MenuItemToggleView.swift | 11 +- Apple/{App => UI}/NetworkCarouselView.swift | 8 +- .../{App => UI}/NetworkExtension+Async.swift | 6 +- .../{App => UI}/NetworkExtensionTunnel.swift | 72 +- Apple/{App => UI}/NetworkView.swift | 0 Apple/{App => UI}/Networks/HackClub.swift | 8 +- Apple/UI/Networks/Network.swift | 36 + Apple/{App => UI}/Networks/WireGuard.swift | 8 +- Apple/{App => UI}/OAuth2.swift | 21 +- Apple/UI/Tunnel.swift | 61 + Apple/{App => UI}/TunnelButton.swift | 2 +- Apple/{App => UI}/TunnelStatusView.swift | 2 +- Apple/UI/UI.xcconfig | 3 + Cargo.lock | 1080 +++++++++-------- burrow-gtk/build-aux/Dockerfile | 2 +- 93 files changed, 1666 insertions(+), 1327 deletions(-) delete mode 100644 Apple/App/Networks/Network.swift delete mode 100644 Apple/App/Tunnel.swift create mode 100644 Apple/Burrow.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved rename Apple/{Shared/Shared.xcconfig => Configuration/Configuration.xcconfig} (65%) rename Apple/{Shared => Configuration}/Constants/Constants.h (100%) rename Apple/{Shared => Configuration/Constants}/Constants.swift (61%) rename Apple/{Shared => Configuration}/Constants/module.modulemap (66%) create mode 100644 Apple/Configuration/Debug.xcconfig create mode 100644 Apple/Configuration/Framework.xcconfig create mode 100644 Apple/Core/Client.swift create mode 120000 Apple/Core/Client/burrow.proto create mode 100644 Apple/Core/Client/grpc-swift-config.json create mode 100644 Apple/Core/Client/swift-protobuf-config.json rename Apple/{Shared => Core}/Logging.swift (88%) delete mode 100644 Apple/Shared/Client.swift delete mode 100644 Apple/Shared/DataTypes.swift delete mode 100644 Apple/Shared/NWConnection+Async.swift delete mode 100644 Apple/Shared/NewlineProtocolFramer.swift rename Apple/{App => UI}/Assets.xcassets/AccentColor.colorset/Contents.json (100%) rename Apple/{App => UI}/Assets.xcassets/AppIcon.appiconset/100.png (100%) rename Apple/{App => UI}/Assets.xcassets/AppIcon.appiconset/1024.png (100%) rename Apple/{App => UI}/Assets.xcassets/AppIcon.appiconset/114.png (100%) rename Apple/{App => UI}/Assets.xcassets/AppIcon.appiconset/120.png (100%) rename Apple/{App => UI}/Assets.xcassets/AppIcon.appiconset/128.png (100%) rename Apple/{App => UI}/Assets.xcassets/AppIcon.appiconset/144.png (100%) rename Apple/{App => UI}/Assets.xcassets/AppIcon.appiconset/152.png (100%) rename Apple/{App => UI}/Assets.xcassets/AppIcon.appiconset/16.png (100%) rename Apple/{App => UI}/Assets.xcassets/AppIcon.appiconset/167.png (100%) rename Apple/{App => UI}/Assets.xcassets/AppIcon.appiconset/172.png (100%) rename Apple/{App => UI}/Assets.xcassets/AppIcon.appiconset/180.png (100%) rename Apple/{App => UI}/Assets.xcassets/AppIcon.appiconset/196.png (100%) rename Apple/{App => UI}/Assets.xcassets/AppIcon.appiconset/20.png (100%) rename Apple/{App => UI}/Assets.xcassets/AppIcon.appiconset/216.png (100%) rename Apple/{App => UI}/Assets.xcassets/AppIcon.appiconset/256.png (100%) rename Apple/{App => UI}/Assets.xcassets/AppIcon.appiconset/29.png (100%) rename Apple/{App => UI}/Assets.xcassets/AppIcon.appiconset/32.png (100%) rename Apple/{App => UI}/Assets.xcassets/AppIcon.appiconset/40.png (100%) rename Apple/{App => UI}/Assets.xcassets/AppIcon.appiconset/48.png (100%) rename Apple/{App => UI}/Assets.xcassets/AppIcon.appiconset/50.png (100%) rename Apple/{App => UI}/Assets.xcassets/AppIcon.appiconset/512.png (100%) rename Apple/{App => UI}/Assets.xcassets/AppIcon.appiconset/55.png (100%) rename Apple/{App => UI}/Assets.xcassets/AppIcon.appiconset/57.png (100%) rename Apple/{App => UI}/Assets.xcassets/AppIcon.appiconset/58.png (100%) rename Apple/{App => UI}/Assets.xcassets/AppIcon.appiconset/60.png (100%) rename Apple/{App => UI}/Assets.xcassets/AppIcon.appiconset/64.png (100%) rename Apple/{App => UI}/Assets.xcassets/AppIcon.appiconset/72.png (100%) rename Apple/{App => UI}/Assets.xcassets/AppIcon.appiconset/76.png (100%) rename Apple/{App => UI}/Assets.xcassets/AppIcon.appiconset/80.png (100%) rename Apple/{App => UI}/Assets.xcassets/AppIcon.appiconset/87.png (100%) rename Apple/{App => UI}/Assets.xcassets/AppIcon.appiconset/88.png (100%) rename Apple/{App => UI}/Assets.xcassets/AppIcon.appiconset/Contents.json (100%) rename Apple/{App => UI}/Assets.xcassets/Contents.json (100%) rename Apple/{App => UI}/Assets.xcassets/HackClub.colorset/Contents.json (100%) rename Apple/{App => UI}/Assets.xcassets/HackClub.imageset/Contents.json (100%) rename Apple/{App => UI}/Assets.xcassets/HackClub.imageset/flag-standalone-wtransparent.pdf (100%) rename Apple/{App => UI}/Assets.xcassets/WireGuard.colorset/Contents.json (100%) rename Apple/{App => UI}/Assets.xcassets/WireGuard.imageset/Contents.json (100%) rename Apple/{App => UI}/Assets.xcassets/WireGuard.imageset/WireGuard.svg (100%) rename Apple/{App => UI}/Assets.xcassets/WireGuardTitle.imageset/Contents.json (100%) rename Apple/{App => UI}/Assets.xcassets/WireGuardTitle.imageset/WireGuardTitle.svg (100%) rename Apple/{App => UI}/BurrowView.swift (95%) rename Apple/{App => UI}/FloatingButtonStyle.swift (100%) rename Apple/{App => UI}/MenuItemToggleView.swift (87%) rename Apple/{App => UI}/NetworkCarouselView.swift (90%) rename Apple/{App => UI}/NetworkExtension+Async.swift (82%) rename Apple/{App => UI}/NetworkExtensionTunnel.swift (67%) rename Apple/{App => UI}/NetworkView.swift (100%) rename Apple/{App => UI}/Networks/HackClub.swift (76%) create mode 100644 Apple/UI/Networks/Network.swift rename Apple/{App => UI}/Networks/WireGuard.swift (82%) rename Apple/{App => UI}/OAuth2.swift (94%) create mode 100644 Apple/UI/Tunnel.swift rename Apple/{App => UI}/TunnelButton.swift (95%) rename Apple/{App => UI}/TunnelStatusView.swift (95%) create mode 100644 Apple/UI/UI.xcconfig diff --git a/.github/workflows/build-apple.yml b/.github/workflows/build-apple.yml index b628001..7ae8c4c 100644 --- a/.github/workflows/build-apple.yml +++ b/.github/workflows/build-apple.yml @@ -39,7 +39,7 @@ jobs: - aarch64-apple-darwin env: DEVELOPER_DIR: /Applications/Xcode_16.0.app/Contents/Developer - PROTOC_VERSION: 3.25.1 + PROTOC_PATH: /opt/homebrew/bin/protoc steps: - name: Checkout uses: actions/checkout@v3 @@ -55,10 +55,9 @@ jobs: uses: dtolnay/rust-toolchain@stable with: targets: ${{ join(matrix.rust-targets, ', ') }} - - name: Install protoc - uses: taiki-e/install-action@v2 - with: - tool: protoc@${{ env.PROTOC_VERSION }} + - name: Install Protobuf + shell: bash + run: brew install protobuf - name: Build id: build uses: ./.github/actions/build-for-testing diff --git a/.github/workflows/release-apple.yml b/.github/workflows/release-apple.yml index c0a34a9..c869d6a 100644 --- a/.github/workflows/release-apple.yml +++ b/.github/workflows/release-apple.yml @@ -22,6 +22,7 @@ jobs: - aarch64-apple-darwin env: DEVELOPER_DIR: /Applications/Xcode_16.0.app/Contents/Developer + PROTOC_PATH: /opt/homebrew/bin/protoc steps: - name: Checkout uses: actions/checkout@v4 @@ -47,6 +48,9 @@ jobs: uses: dtolnay/rust-toolchain@stable with: targets: ${{ join(matrix.rust-targets, ', ') }} + - name: Install Protobuf + shell: bash + run: brew install protobuf - name: Configure Version id: version shell: bash diff --git a/.gitignore b/.gitignore index 997d4d5..1b300b4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,9 @@ # Xcode xcuserdata +# Swift +Apple/Package/.swiftpm/ + # Rust target/ .env diff --git a/.swiftlint.yml b/.swiftlint.yml index 22ef035..8efc85e 100644 --- a/.swiftlint.yml +++ b/.swiftlint.yml @@ -30,7 +30,6 @@ opt_in_rules: - function_default_parameter_at_end - ibinspectable_in_extension - identical_operands -- implicitly_unwrapped_optional - indentation_width - joined_default_parameter - last_where diff --git a/Apple/App/AppDelegate.swift b/Apple/App/AppDelegate.swift index b0c5546..0ea93f4 100644 --- a/Apple/App/AppDelegate.swift +++ b/Apple/App/AppDelegate.swift @@ -1,5 +1,6 @@ #if os(macOS) import AppKit +import BurrowUI import SwiftUI @main diff --git a/Apple/App/BurrowApp.swift b/Apple/App/BurrowApp.swift index 21ebf84..838ef54 100644 --- a/Apple/App/BurrowApp.swift +++ b/Apple/App/BurrowApp.swift @@ -1,6 +1,7 @@ +#if !os(macOS) +import BurrowUI import SwiftUI -#if !os(macOS) @MainActor @main struct BurrowApp: App { diff --git a/Apple/App/MainMenu.xib b/Apple/App/MainMenu.xib index 587f6c4..50ba431 100644 --- a/Apple/App/MainMenu.xib +++ b/Apple/App/MainMenu.xib @@ -1,7 +1,7 @@ - + - + diff --git a/Apple/App/Networks/Network.swift b/Apple/App/Networks/Network.swift deleted file mode 100644 index d441d24..0000000 --- a/Apple/App/Networks/Network.swift +++ /dev/null @@ -1,10 +0,0 @@ -import SwiftUI - -protocol Network { - associatedtype Label: View - - var id: String { get } - var backgroundColor: Color { get } - - var label: Label { get } -} diff --git a/Apple/App/Tunnel.swift b/Apple/App/Tunnel.swift deleted file mode 100644 index 8db366f..0000000 --- a/Apple/App/Tunnel.swift +++ /dev/null @@ -1,50 +0,0 @@ -import SwiftUI - -protocol Tunnel { - var status: TunnelStatus { get } - - func start() - func stop() - func enable() -} - -enum TunnelStatus: Equatable, Hashable { - case unknown - case permissionRequired - case disabled - case connecting - case connected(Date) - case disconnecting - case disconnected - case reasserting - case invalid - case configurationReadWriteFailed -} - -struct TunnelKey: EnvironmentKey { - static let defaultValue: any Tunnel = NetworkExtensionTunnel() -} - -extension EnvironmentValues { - var tunnel: any Tunnel { - get { self[TunnelKey.self] } - set { self[TunnelKey.self] = newValue } - } -} - -#if DEBUG -@Observable -class PreviewTunnel: Tunnel { - var status: TunnelStatus = .permissionRequired - - func start() { - status = .connected(.now) - } - func stop() { - status = .disconnected - } - func enable() { - status = .disconnected - } -} -#endif diff --git a/Apple/Burrow.xcodeproj/project.pbxproj b/Apple/Burrow.xcodeproj/project.pbxproj index 5c5e80b..617b88f 100644 --- a/Apple/Burrow.xcodeproj/project.pbxproj +++ b/Apple/Burrow.xcodeproj/project.pbxproj @@ -7,52 +7,50 @@ objects = { /* Begin PBXBuildFile section */ - 0BA6D73B2BA638D900BD4B55 /* Client.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0B46E8DF2AC918CA00BA2A3C /* Client.swift */; }; - 0BA6D73C2BA6393200BD4B55 /* NWConnection+Async.swift in Sources */ = {isa = PBXBuildFile; fileRef = D00117302B2FFFC900D87C25 /* NWConnection+Async.swift */; }; - 0BA6D73D2BA6393B00BD4B55 /* NewlineProtocolFramer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D00117322B3001A400D87C25 /* NewlineProtocolFramer.swift */; }; - 0BA6D73E2BA6394B00BD4B55 /* DataTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0B28F1552ABF463A000D44B0 /* DataTypes.swift */; }; - 43AA26D82A10004900F14CE6 /* MenuItemToggleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43AA26D72A10004900F14CE6 /* MenuItemToggleView.swift */; }; - D000363D2BB8928E00E582EC /* NetworkCarouselView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D000363C2BB8928E00E582EC /* NetworkCarouselView.swift */; }; - D000363F2BB895FB00E582EC /* OAuth2.swift in Sources */ = {isa = PBXBuildFile; fileRef = D000363E2BB895FB00E582EC /* OAuth2.swift */; }; - D001173B2B30341C00D87C25 /* Logging.swift in Sources */ = {isa = PBXBuildFile; fileRef = D001173A2B30341C00D87C25 /* Logging.swift */; }; - D00117442B30372900D87C25 /* libBurrowShared.a in Frameworks */ = {isa = PBXBuildFile; fileRef = D00117382B30341C00D87C25 /* libBurrowShared.a */; }; - D00117452B30372C00D87C25 /* libBurrowShared.a in Frameworks */ = {isa = PBXBuildFile; fileRef = D00117382B30341C00D87C25 /* libBurrowShared.a */; }; D00AA8972A4669BC005C8102 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D00AA8962A4669BC005C8102 /* AppDelegate.swift */; }; - D01A79312B81630D0024EC91 /* NetworkView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01A79302B81630D0024EC91 /* NetworkView.swift */; }; D020F65829E4A697002790F6 /* PacketTunnelProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = D020F65729E4A697002790F6 /* PacketTunnelProvider.swift */; }; D020F65D29E4A697002790F6 /* BurrowNetworkExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = D020F65329E4A697002790F6 /* BurrowNetworkExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; - D032E6522B8A79C20006B8AD /* HackClub.swift in Sources */ = {isa = PBXBuildFile; fileRef = D032E6512B8A79C20006B8AD /* HackClub.swift */; }; - D032E6542B8A79DA0006B8AD /* WireGuard.swift in Sources */ = {isa = PBXBuildFile; fileRef = D032E6532B8A79DA0006B8AD /* WireGuard.swift */; }; + D03383AD2C8E67E300F7C44E /* SwiftProtobuf in Frameworks */ = {isa = PBXBuildFile; productRef = D078F7E22C8DA375008A8CEC /* SwiftProtobuf */; }; + D03383AE2C8E67E300F7C44E /* NIO in Frameworks */ = {isa = PBXBuildFile; productRef = D044EE902C8DAB2000778185 /* NIO */; }; + D03383AF2C8E67E300F7C44E /* NIOConcurrencyHelpers in Frameworks */ = {isa = PBXBuildFile; productRef = D044EE922C8DAB2000778185 /* NIOConcurrencyHelpers */; }; + D03383B02C8E67E300F7C44E /* NIOTransportServices in Frameworks */ = {isa = PBXBuildFile; productRef = D044EE952C8DAB2800778185 /* NIOTransportServices */; }; D05B9F7629E39EEC008CB1F9 /* BurrowApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05B9F7529E39EEC008CB1F9 /* BurrowApp.swift */; }; - D05B9F7829E39EEC008CB1F9 /* BurrowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05B9F7729E39EEC008CB1F9 /* BurrowView.swift */; }; - D05B9F7A29E39EED008CB1F9 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D05B9F7929E39EED008CB1F9 /* Assets.xcassets */; }; - D05EF8C82B81818D0017AB4F /* FloatingButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05EF8C72B81818D0017AB4F /* FloatingButtonStyle.swift */; }; - D08252762B5C9FC4005DA378 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08252752B5C9FC4005DA378 /* Constants.swift */; }; - D09150422B9D2AF700BE3CB0 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = D09150412B9D2AF700BE3CB0 /* MainMenu.xib */; }; - D0BCC5FD2A086D4700AD070D /* NetworkExtension+Async.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BCC5FC2A086D4700AD070D /* NetworkExtension+Async.swift */; }; - D0BCC6082A0981FE00AD070D /* Tunnel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B98FC629FDC5B5004E7149 /* Tunnel.swift */; }; + D09150422B9D2AF700BE3CB0 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = D09150412B9D2AF700BE3CB0 /* MainMenu.xib */; platformFilters = (macos, ); }; + D0B1D1102C436152004B7823 /* AsyncAlgorithms in Frameworks */ = {isa = PBXBuildFile; productRef = D0B1D10F2C436152004B7823 /* AsyncAlgorithms */; }; D0BCC6092A09A03E00AD070D /* libburrow.a in Frameworks */ = {isa = PBXBuildFile; fileRef = D0BCC6032A09535900AD070D /* libburrow.a */; }; - D0FAB5922B818A5900F6A84B /* NetworkExtensionTunnel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0FAB5912B818A5900F6A84B /* NetworkExtensionTunnel.swift */; }; - D0FAB5962B818B2900F6A84B /* TunnelButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0FAB5952B818B2900F6A84B /* TunnelButton.swift */; }; - D0FAB5982B818B8200F6A84B /* TunnelStatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0FAB5972B818B8200F6A84B /* TunnelStatusView.swift */; }; - D0FAB59A2B818B9600F6A84B /* Network.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0FAB5992B818B9600F6A84B /* Network.swift */; }; + D0BF09522C8E66F6000D8DEC /* BurrowConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D0D4E5622C8D9BF4007F820A /* BurrowConfiguration.framework */; }; + D0BF09552C8E66FD000D8DEC /* BurrowConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D0D4E5622C8D9BF4007F820A /* BurrowConfiguration.framework */; }; + D0D4E53A2C8D996F007F820A /* BurrowCore.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D0D4E5312C8D996F007F820A /* BurrowCore.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + D0D4E56B2C8D9C2F007F820A /* Logging.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0D4E49A2C8D921A007F820A /* Logging.swift */; }; + D0D4E5702C8D9C62007F820A /* BurrowCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D0D4E5312C8D996F007F820A /* BurrowCore.framework */; }; + D0D4E5712C8D9C6F007F820A /* HackClub.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0D4E49D2C8D921A007F820A /* HackClub.swift */; }; + D0D4E5722C8D9C6F007F820A /* Network.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0D4E49E2C8D921A007F820A /* Network.swift */; }; + D0D4E5732C8D9C6F007F820A /* WireGuard.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0D4E49F2C8D921A007F820A /* WireGuard.swift */; }; + D0D4E5742C8D9C6F007F820A /* BurrowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0D4E4A22C8D921A007F820A /* BurrowView.swift */; }; + D0D4E5752C8D9C6F007F820A /* FloatingButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0D4E4A32C8D921A007F820A /* FloatingButtonStyle.swift */; }; + D0D4E5762C8D9C6F007F820A /* MenuItemToggleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0D4E4A42C8D921A007F820A /* MenuItemToggleView.swift */; }; + D0D4E5772C8D9C6F007F820A /* NetworkCarouselView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0D4E4A52C8D921A007F820A /* NetworkCarouselView.swift */; }; + D0D4E5782C8D9C6F007F820A /* NetworkExtension+Async.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0D4E4A62C8D921A007F820A /* NetworkExtension+Async.swift */; }; + D0D4E5792C8D9C6F007F820A /* NetworkExtensionTunnel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0D4E4A72C8D921A007F820A /* NetworkExtensionTunnel.swift */; }; + D0D4E57A2C8D9C6F007F820A /* NetworkView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0D4E4A82C8D921A007F820A /* NetworkView.swift */; }; + D0D4E57B2C8D9C6F007F820A /* OAuth2.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0D4E4A92C8D921A007F820A /* OAuth2.swift */; }; + D0D4E57C2C8D9C6F007F820A /* Tunnel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0D4E4AA2C8D921A007F820A /* Tunnel.swift */; }; + D0D4E57D2C8D9C6F007F820A /* TunnelButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0D4E4AB2C8D921A007F820A /* TunnelButton.swift */; }; + D0D4E57E2C8D9C6F007F820A /* TunnelStatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0D4E4AC2C8D921A007F820A /* TunnelStatusView.swift */; }; + D0D4E5892C8D9C94007F820A /* BurrowUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D0D4E5582C8D9BF2007F820A /* BurrowUI.framework */; }; + D0D4E58A2C8D9C9E007F820A /* BurrowUI.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D0D4E5582C8D9BF2007F820A /* BurrowUI.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + D0D4E58B2C8D9CA4007F820A /* BurrowConfiguration.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D0D4E5622C8D9BF4007F820A /* BurrowConfiguration.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + D0D4E5922C8D9D15007F820A /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0D4E58F2C8D9D0A007F820A /* Constants.swift */; }; + D0D4E5A62C8D9E65007F820A /* BurrowCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D0D4E5312C8D996F007F820A /* BurrowCore.framework */; }; + D0F4FAD32C8DC79C0068730A /* BurrowCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D0D4E5312C8D996F007F820A /* BurrowCore.framework */; }; + D0F7594E2C8DAB6B00126CF3 /* GRPC in Frameworks */ = {isa = PBXBuildFile; productRef = D078F7E02C8DA375008A8CEC /* GRPC */; }; + D0F759612C8DB24B00126CF3 /* grpc-swift-config.json in Sources */ = {isa = PBXBuildFile; fileRef = D0D4E4962C8D921A007F820A /* grpc-swift-config.json */; }; + D0F759622C8DB24B00126CF3 /* swift-protobuf-config.json in Sources */ = {isa = PBXBuildFile; fileRef = D0D4E4972C8D921A007F820A /* swift-protobuf-config.json */; }; + D0F7597E2C8DB30500126CF3 /* CGRPCZlib in Frameworks */ = {isa = PBXBuildFile; productRef = D0F7597D2C8DB30500126CF3 /* CGRPCZlib */; }; + D0F7598D2C8DB3DA00126CF3 /* Client.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0D4E4992C8D921A007F820A /* Client.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ - D00117462B30373100D87C25 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = D05B9F6A29E39EEC008CB1F9 /* Project object */; - proxyType = 1; - remoteGlobalIDString = D00117372B30341C00D87C25; - remoteInfo = Shared; - }; - D00117482B30373500D87C25 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = D05B9F6A29E39EEC008CB1F9 /* Project object */; - proxyType = 1; - remoteGlobalIDString = D00117372B30341C00D87C25; - remoteInfo = Shared; - }; D020F65B29E4A697002790F6 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = D05B9F6A29E39EEC008CB1F9 /* Project object */; @@ -60,6 +58,48 @@ remoteGlobalIDString = D020F65229E4A697002790F6; remoteInfo = BurrowNetworkExtension; }; + D0BF09502C8E66F1000D8DEC /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = D05B9F6A29E39EEC008CB1F9 /* Project object */; + proxyType = 1; + remoteGlobalIDString = D0D4E55A2C8D9BF4007F820A; + remoteInfo = Configuration; + }; + D0BF09532C8E66FA000D8DEC /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = D05B9F6A29E39EEC008CB1F9 /* Project object */; + proxyType = 1; + remoteGlobalIDString = D0D4E55A2C8D9BF4007F820A; + remoteInfo = Configuration; + }; + D0D4E56E2C8D9C5D007F820A /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = D05B9F6A29E39EEC008CB1F9 /* Project object */; + proxyType = 1; + remoteGlobalIDString = D0D4E5302C8D996F007F820A; + remoteInfo = Core; + }; + D0D4E57F2C8D9C78007F820A /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = D05B9F6A29E39EEC008CB1F9 /* Project object */; + proxyType = 1; + remoteGlobalIDString = D0D4E5302C8D996F007F820A; + remoteInfo = Core; + }; + D0D4E5872C8D9C88007F820A /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = D05B9F6A29E39EEC008CB1F9 /* Project object */; + proxyType = 1; + remoteGlobalIDString = D0D4E5502C8D9BF2007F820A; + remoteInfo = UI; + }; + D0F4FAD12C8DC7960068730A /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = D05B9F6A29E39EEC008CB1F9 /* Project object */; + proxyType = 1; + remoteGlobalIDString = D0D4E5302C8D996F007F820A; + remoteInfo = Core; + }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -74,22 +114,24 @@ name = "Embed Foundation Extensions"; runOnlyForDeploymentPostprocessing = 0; }; + D0D4E53F2C8D996F007F820A /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + D0D4E58B2C8D9CA4007F820A /* BurrowConfiguration.framework in Embed Frameworks */, + D0D4E58A2C8D9C9E007F820A /* BurrowUI.framework in Embed Frameworks */, + D0D4E53A2C8D996F007F820A /* BurrowCore.framework in Embed Frameworks */, + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ - 0B28F1552ABF463A000D44B0 /* DataTypes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataTypes.swift; sourceTree = ""; }; - 0B46E8DF2AC918CA00BA2A3C /* Client.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Client.swift; sourceTree = ""; }; - 43AA26D72A10004900F14CE6 /* MenuItemToggleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuItemToggleView.swift; sourceTree = ""; }; - D000363C2BB8928E00E582EC /* NetworkCarouselView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkCarouselView.swift; sourceTree = ""; }; - D000363E2BB895FB00E582EC /* OAuth2.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OAuth2.swift; sourceTree = ""; }; - D00117302B2FFFC900D87C25 /* NWConnection+Async.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NWConnection+Async.swift"; sourceTree = ""; }; - D00117322B3001A400D87C25 /* NewlineProtocolFramer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewlineProtocolFramer.swift; sourceTree = ""; }; - D00117382B30341C00D87C25 /* libBurrowShared.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libBurrowShared.a; sourceTree = BUILT_PRODUCTS_DIR; }; - D001173A2B30341C00D87C25 /* Logging.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logging.swift; sourceTree = ""; }; - D00117412B30347800D87C25 /* module.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = module.modulemap; sourceTree = ""; }; - D00117422B30348D00D87C25 /* Shared.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Shared.xcconfig; sourceTree = ""; }; + D00117422B30348D00D87C25 /* Configuration.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Configuration.xcconfig; sourceTree = ""; }; D00AA8962A4669BC005C8102 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; - D01A79302B81630D0024EC91 /* NetworkView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkView.swift; sourceTree = ""; }; D020F63D29E4A1FF002790F6 /* Identity.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Identity.xcconfig; sourceTree = ""; }; D020F64029E4A1FF002790F6 /* Compiler.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Compiler.xcconfig; sourceTree = ""; }; D020F64229E4A1FF002790F6 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -104,43 +146,54 @@ D020F66729E4A95D002790F6 /* NetworkExtension-iOS.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "NetworkExtension-iOS.entitlements"; sourceTree = ""; }; D020F66829E4AA74002790F6 /* App-iOS.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "App-iOS.entitlements"; sourceTree = ""; }; D020F66929E4AA74002790F6 /* App-macOS.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "App-macOS.entitlements"; sourceTree = ""; }; - D032E6512B8A79C20006B8AD /* HackClub.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HackClub.swift; sourceTree = ""; }; - D032E6532B8A79DA0006B8AD /* WireGuard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WireGuard.swift; sourceTree = ""; }; D04A3E1D2BAF465F0043EC85 /* Version.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Version.xcconfig; sourceTree = ""; }; D05B9F7229E39EEC008CB1F9 /* Burrow.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Burrow.app; sourceTree = BUILT_PRODUCTS_DIR; }; D05B9F7529E39EEC008CB1F9 /* BurrowApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BurrowApp.swift; sourceTree = ""; }; - D05B9F7729E39EEC008CB1F9 /* BurrowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BurrowView.swift; sourceTree = ""; }; - D05B9F7929E39EED008CB1F9 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; - D05EF8C72B81818D0017AB4F /* FloatingButtonStyle.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FloatingButtonStyle.swift; sourceTree = ""; }; - D08252742B5C9DEB005DA378 /* Constants.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Constants.h; sourceTree = ""; }; - D08252752B5C9FC4005DA378 /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; }; D09150412B9D2AF700BE3CB0 /* MainMenu.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = MainMenu.xib; sourceTree = ""; }; D0B98FBF29FD8072004E7149 /* build-rust.sh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = "build-rust.sh"; sourceTree = ""; }; - D0B98FC629FDC5B5004E7149 /* Tunnel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tunnel.swift; sourceTree = ""; }; D0B98FD829FDDB6F004E7149 /* libburrow.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = libburrow.h; sourceTree = ""; }; D0B98FDC29FDDDCF004E7149 /* module.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = module.modulemap; sourceTree = ""; }; - D0BCC5FC2A086D4700AD070D /* NetworkExtension+Async.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NetworkExtension+Async.swift"; sourceTree = ""; }; D0BCC6032A09535900AD070D /* libburrow.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libburrow.a; sourceTree = BUILT_PRODUCTS_DIR; }; - D0FAB5912B818A5900F6A84B /* NetworkExtensionTunnel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkExtensionTunnel.swift; sourceTree = ""; }; - D0FAB5952B818B2900F6A84B /* TunnelButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelButton.swift; sourceTree = ""; }; - D0FAB5972B818B8200F6A84B /* TunnelStatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelStatusView.swift; sourceTree = ""; }; - D0FAB5992B818B9600F6A84B /* Network.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Network.swift; sourceTree = ""; }; + D0BF09582C8E6789000D8DEC /* UI.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = UI.xcconfig; sourceTree = ""; }; + D0D4E4952C8D921A007F820A /* burrow.proto */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.protobuf; path = burrow.proto; sourceTree = ""; }; + D0D4E4962C8D921A007F820A /* grpc-swift-config.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "grpc-swift-config.json"; sourceTree = ""; }; + D0D4E4972C8D921A007F820A /* swift-protobuf-config.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "swift-protobuf-config.json"; sourceTree = ""; }; + D0D4E4992C8D921A007F820A /* Client.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Client.swift; sourceTree = ""; }; + D0D4E49A2C8D921A007F820A /* Logging.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logging.swift; sourceTree = ""; }; + D0D4E49D2C8D921A007F820A /* HackClub.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HackClub.swift; sourceTree = ""; }; + D0D4E49E2C8D921A007F820A /* Network.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Network.swift; sourceTree = ""; }; + D0D4E49F2C8D921A007F820A /* WireGuard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WireGuard.swift; sourceTree = ""; }; + D0D4E4A12C8D921A007F820A /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + D0D4E4A22C8D921A007F820A /* BurrowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BurrowView.swift; sourceTree = ""; }; + D0D4E4A32C8D921A007F820A /* FloatingButtonStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FloatingButtonStyle.swift; sourceTree = ""; }; + D0D4E4A42C8D921A007F820A /* MenuItemToggleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuItemToggleView.swift; sourceTree = ""; }; + D0D4E4A52C8D921A007F820A /* NetworkCarouselView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkCarouselView.swift; sourceTree = ""; }; + D0D4E4A62C8D921A007F820A /* NetworkExtension+Async.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NetworkExtension+Async.swift"; sourceTree = ""; }; + D0D4E4A72C8D921A007F820A /* NetworkExtensionTunnel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkExtensionTunnel.swift; sourceTree = ""; }; + D0D4E4A82C8D921A007F820A /* NetworkView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkView.swift; sourceTree = ""; }; + D0D4E4A92C8D921A007F820A /* OAuth2.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OAuth2.swift; sourceTree = ""; }; + D0D4E4AA2C8D921A007F820A /* Tunnel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tunnel.swift; sourceTree = ""; }; + D0D4E4AB2C8D921A007F820A /* TunnelButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelButton.swift; sourceTree = ""; }; + D0D4E4AC2C8D921A007F820A /* TunnelStatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelStatusView.swift; sourceTree = ""; }; + D0D4E4F62C8D932D007F820A /* Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; + D0D4E4F72C8D941D007F820A /* Framework.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Framework.xcconfig; sourceTree = ""; }; + D0D4E5312C8D996F007F820A /* BurrowCore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = BurrowCore.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + D0D4E5582C8D9BF2007F820A /* BurrowUI.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = BurrowUI.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + D0D4E5622C8D9BF4007F820A /* BurrowConfiguration.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = BurrowConfiguration.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + D0D4E58E2C8D9D0A007F820A /* Constants.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Constants.h; sourceTree = ""; }; + D0D4E58F2C8D9D0A007F820A /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; }; + D0D4E5902C8D9D0A007F820A /* module.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = module.modulemap; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ - D00117352B30341C00D87C25 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; D020F65029E4A697002790F6 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - D00117442B30372900D87C25 /* libBurrowShared.a in Frameworks */, + D0BF09522C8E66F6000D8DEC /* BurrowConfiguration.framework in Frameworks */, + D0D4E5A62C8D9E65007F820A /* BurrowCore.framework in Frameworks */, D0BCC6092A09A03E00AD070D /* libburrow.a in Frameworks */, + D0B1D1102C436152004B7823 /* AsyncAlgorithms in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -148,37 +201,36 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - D00117452B30372C00D87C25 /* libBurrowShared.a in Frameworks */, + D0BF09552C8E66FD000D8DEC /* BurrowConfiguration.framework in Frameworks */, + D0F4FAD32C8DC79C0068730A /* BurrowCore.framework in Frameworks */, + D0D4E5892C8D9C94007F820A /* BurrowUI.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + D078F7CF2C8DA213008A8CEC /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + D03383B02C8E67E300F7C44E /* NIOTransportServices in Frameworks */, + D03383AF2C8E67E300F7C44E /* NIOConcurrencyHelpers in Frameworks */, + D03383AE2C8E67E300F7C44E /* NIO in Frameworks */, + D03383AD2C8E67E300F7C44E /* SwiftProtobuf in Frameworks */, + D0F7594E2C8DAB6B00126CF3 /* GRPC in Frameworks */, + D0F7597E2C8DB30500126CF3 /* CGRPCZlib in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + D0D4E5532C8D9BF2007F820A /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + D0D4E5702C8D9C62007F820A /* BurrowCore.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - D00117392B30341C00D87C25 /* Shared */ = { - isa = PBXGroup; - children = ( - 0B28F1552ABF463A000D44B0 /* DataTypes.swift */, - D00117322B3001A400D87C25 /* NewlineProtocolFramer.swift */, - D00117302B2FFFC900D87C25 /* NWConnection+Async.swift */, - 0B46E8DF2AC918CA00BA2A3C /* Client.swift */, - D001173A2B30341C00D87C25 /* Logging.swift */, - D08252752B5C9FC4005DA378 /* Constants.swift */, - D00117422B30348D00D87C25 /* Shared.xcconfig */, - D001173F2B30347800D87C25 /* Constants */, - ); - path = Shared; - sourceTree = ""; - }; - D001173F2B30347800D87C25 /* Constants */ = { - isa = PBXGroup; - children = ( - D08252742B5C9DEB005DA378 /* Constants.h */, - D00117412B30347800D87C25 /* module.modulemap */, - ); - path = Constants; - sourceTree = ""; - }; D00117432B30372900D87C25 /* Frameworks */ = { isa = PBXGroup; children = ( @@ -192,9 +244,13 @@ D020F63D29E4A1FF002790F6 /* Identity.xcconfig */, D020F64A29E4A452002790F6 /* App.xcconfig */, D020F66329E4A703002790F6 /* Extension.xcconfig */, + D0D4E4F72C8D941D007F820A /* Framework.xcconfig */, D020F64029E4A1FF002790F6 /* Compiler.xcconfig */, + D0D4E4F62C8D932D007F820A /* Debug.xcconfig */, D04A3E1D2BAF465F0043EC85 /* Version.xcconfig */, D020F64229E4A1FF002790F6 /* Info.plist */, + D0D4E5912C8D9D0A007F820A /* Constants */, + D00117422B30348D00D87C25 /* Configuration.xcconfig */, ); path = Configuration; sourceTree = ""; @@ -212,22 +268,13 @@ path = NetworkExtension; sourceTree = ""; }; - D032E64D2B8A69C90006B8AD /* Networks */ = { - isa = PBXGroup; - children = ( - D0FAB5992B818B9600F6A84B /* Network.swift */, - D032E6512B8A79C20006B8AD /* HackClub.swift */, - D032E6532B8A79DA0006B8AD /* WireGuard.swift */, - ); - path = Networks; - sourceTree = ""; - }; D05B9F6929E39EEC008CB1F9 = { isa = PBXGroup; children = ( D05B9F7429E39EEC008CB1F9 /* App */, D020F65629E4A697002790F6 /* NetworkExtension */, - D00117392B30341C00D87C25 /* Shared */, + D0D4E49C2C8D921A007F820A /* Core */, + D0D4E4AD2C8D921A007F820A /* UI */, D020F63C29E4A1FF002790F6 /* Configuration */, D05B9F7329E39EEC008CB1F9 /* Products */, D00117432B30372900D87C25 /* Frameworks */, @@ -239,7 +286,10 @@ children = ( D05B9F7229E39EEC008CB1F9 /* Burrow.app */, D020F65329E4A697002790F6 /* BurrowNetworkExtension.appex */, - D00117382B30341C00D87C25 /* libBurrowShared.a */, + D0BCC6032A09535900AD070D /* libburrow.a */, + D0D4E5312C8D996F007F820A /* BurrowCore.framework */, + D0D4E5582C8D9BF2007F820A /* BurrowUI.framework */, + D0D4E5622C8D9BF4007F820A /* BurrowConfiguration.framework */, ); name = Products; sourceTree = ""; @@ -249,19 +299,6 @@ children = ( D05B9F7529E39EEC008CB1F9 /* BurrowApp.swift */, D00AA8962A4669BC005C8102 /* AppDelegate.swift */, - 43AA26D72A10004900F14CE6 /* MenuItemToggleView.swift */, - D05B9F7729E39EEC008CB1F9 /* BurrowView.swift */, - D000363C2BB8928E00E582EC /* NetworkCarouselView.swift */, - D01A79302B81630D0024EC91 /* NetworkView.swift */, - D000363E2BB895FB00E582EC /* OAuth2.swift */, - D032E64D2B8A69C90006B8AD /* Networks */, - D0FAB5972B818B8200F6A84B /* TunnelStatusView.swift */, - D0FAB5952B818B2900F6A84B /* TunnelButton.swift */, - D0B98FC629FDC5B5004E7149 /* Tunnel.swift */, - D0FAB5912B818A5900F6A84B /* NetworkExtensionTunnel.swift */, - D0BCC5FC2A086D4700AD070D /* NetworkExtension+Async.swift */, - D05EF8C72B81818D0017AB4F /* FloatingButtonStyle.swift */, - D05B9F7929E39EED008CB1F9 /* Assets.xcassets */, D09150412B9D2AF700BE3CB0 /* MainMenu.xib */, D020F66829E4AA74002790F6 /* App-iOS.entitlements */, D020F66929E4AA74002790F6 /* App-macOS.entitlements */, @@ -276,30 +313,74 @@ D0B98FBF29FD8072004E7149 /* build-rust.sh */, D0B98FDC29FDDDCF004E7149 /* module.modulemap */, D0B98FD829FDDB6F004E7149 /* libburrow.h */, - D0BCC6032A09535900AD070D /* libburrow.a */, ); path = libburrow; sourceTree = ""; }; + D0D4E4982C8D921A007F820A /* Client */ = { + isa = PBXGroup; + children = ( + D0D4E4952C8D921A007F820A /* burrow.proto */, + D0D4E4962C8D921A007F820A /* grpc-swift-config.json */, + D0D4E4972C8D921A007F820A /* swift-protobuf-config.json */, + ); + path = Client; + sourceTree = ""; + }; + D0D4E49C2C8D921A007F820A /* Core */ = { + isa = PBXGroup; + children = ( + D0D4E49A2C8D921A007F820A /* Logging.swift */, + D0D4E4992C8D921A007F820A /* Client.swift */, + D0D4E4982C8D921A007F820A /* Client */, + ); + path = Core; + sourceTree = ""; + }; + D0D4E4A02C8D921A007F820A /* Networks */ = { + isa = PBXGroup; + children = ( + D0D4E49D2C8D921A007F820A /* HackClub.swift */, + D0D4E49E2C8D921A007F820A /* Network.swift */, + D0D4E49F2C8D921A007F820A /* WireGuard.swift */, + ); + path = Networks; + sourceTree = ""; + }; + D0D4E4AD2C8D921A007F820A /* UI */ = { + isa = PBXGroup; + children = ( + D0D4E4A22C8D921A007F820A /* BurrowView.swift */, + D0D4E4A02C8D921A007F820A /* Networks */, + D0D4E4A32C8D921A007F820A /* FloatingButtonStyle.swift */, + D0D4E4A42C8D921A007F820A /* MenuItemToggleView.swift */, + D0D4E4A52C8D921A007F820A /* NetworkCarouselView.swift */, + D0D4E4A62C8D921A007F820A /* NetworkExtension+Async.swift */, + D0D4E4A72C8D921A007F820A /* NetworkExtensionTunnel.swift */, + D0D4E4A82C8D921A007F820A /* NetworkView.swift */, + D0D4E4A92C8D921A007F820A /* OAuth2.swift */, + D0D4E4AA2C8D921A007F820A /* Tunnel.swift */, + D0D4E4AB2C8D921A007F820A /* TunnelButton.swift */, + D0D4E4AC2C8D921A007F820A /* TunnelStatusView.swift */, + D0D4E4A12C8D921A007F820A /* Assets.xcassets */, + D0BF09582C8E6789000D8DEC /* UI.xcconfig */, + ); + path = UI; + sourceTree = ""; + }; + D0D4E5912C8D9D0A007F820A /* Constants */ = { + isa = PBXGroup; + children = ( + D0D4E58E2C8D9D0A007F820A /* Constants.h */, + D0D4E58F2C8D9D0A007F820A /* Constants.swift */, + D0D4E5902C8D9D0A007F820A /* module.modulemap */, + ); + path = Constants; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ - D00117372B30341C00D87C25 /* Shared */ = { - isa = PBXNativeTarget; - buildConfigurationList = D001173C2B30341C00D87C25 /* Build configuration list for PBXNativeTarget "Shared" */; - buildPhases = ( - D00117342B30341C00D87C25 /* Sources */, - D00117352B30341C00D87C25 /* Frameworks */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = Shared; - productName = Shared; - productReference = D00117382B30341C00D87C25 /* libBurrowShared.a */; - productType = "com.apple.product-type.library.static"; - }; D020F65229E4A697002790F6 /* NetworkExtension */ = { isa = PBXNativeTarget; buildConfigurationList = D020F65E29E4A697002790F6 /* Build configuration list for PBXNativeTarget "NetworkExtension" */; @@ -307,12 +388,12 @@ D0BCC60B2A09A0C100AD070D /* Compile Rust */, D020F64F29E4A697002790F6 /* Sources */, D020F65029E4A697002790F6 /* Frameworks */, - D020F65129E4A697002790F6 /* Resources */, ); buildRules = ( ); dependencies = ( - D00117492B30373500D87C25 /* PBXTargetDependency */, + D0BF09512C8E66F1000D8DEC /* PBXTargetDependency */, + D0D4E5802C8D9C78007F820A /* PBXTargetDependency */, ); name = NetworkExtension; productName = BurrowNetworkExtension; @@ -323,16 +404,18 @@ isa = PBXNativeTarget; buildConfigurationList = D05B9F8129E39EED008CB1F9 /* Build configuration list for PBXNativeTarget "App" */; buildPhases = ( - D04A3E232BAF4AE50043EC85 /* Update Build Number */, D05B9F6E29E39EEC008CB1F9 /* Sources */, D05B9F6F29E39EEC008CB1F9 /* Frameworks */, D05B9F7029E39EEC008CB1F9 /* Resources */, + D0D4E53F2C8D996F007F820A /* Embed Frameworks */, D020F66129E4A697002790F6 /* Embed Foundation Extensions */, ); buildRules = ( ); dependencies = ( - D00117472B30373100D87C25 /* PBXTargetDependency */, + D0BF09542C8E66FA000D8DEC /* PBXTargetDependency */, + D0F4FAD22C8DC7960068730A /* PBXTargetDependency */, + D0D4E5882C8D9C88007F820A /* PBXTargetDependency */, D020F65C29E4A697002790F6 /* PBXTargetDependency */, ); name = App; @@ -340,6 +423,71 @@ productReference = D05B9F7229E39EEC008CB1F9 /* Burrow.app */; productType = "com.apple.product-type.application"; }; + D0D4E5302C8D996F007F820A /* Core */ = { + isa = PBXNativeTarget; + buildConfigurationList = D0D4E53C2C8D996F007F820A /* Build configuration list for PBXNativeTarget "Core" */; + buildPhases = ( + D0D4E52D2C8D996F007F820A /* Sources */, + D078F7CF2C8DA213008A8CEC /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + D0F7598A2C8DB34200126CF3 /* PBXTargetDependency */, + D0F7595E2C8DB24400126CF3 /* PBXTargetDependency */, + D0F759602C8DB24400126CF3 /* PBXTargetDependency */, + ); + name = Core; + packageProductDependencies = ( + D078F7E02C8DA375008A8CEC /* GRPC */, + D078F7E22C8DA375008A8CEC /* SwiftProtobuf */, + D044EE902C8DAB2000778185 /* NIO */, + D044EE922C8DAB2000778185 /* NIOConcurrencyHelpers */, + D044EE952C8DAB2800778185 /* NIOTransportServices */, + D0F7597D2C8DB30500126CF3 /* CGRPCZlib */, + ); + productName = Core; + productReference = D0D4E5312C8D996F007F820A /* BurrowCore.framework */; + productType = "com.apple.product-type.framework"; + }; + D0D4E5502C8D9BF2007F820A /* UI */ = { + isa = PBXNativeTarget; + buildConfigurationList = D0D4E5552C8D9BF2007F820A /* Build configuration list for PBXNativeTarget "UI" */; + buildPhases = ( + D0D4E5522C8D9BF2007F820A /* Sources */, + D0D4E5532C8D9BF2007F820A /* Frameworks */, + D0D4E5542C8D9BF2007F820A /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + D0D4E56F2C8D9C5D007F820A /* PBXTargetDependency */, + ); + name = UI; + packageProductDependencies = ( + ); + productName = Core; + productReference = D0D4E5582C8D9BF2007F820A /* BurrowUI.framework */; + productType = "com.apple.product-type.framework"; + }; + D0D4E55A2C8D9BF4007F820A /* Configuration */ = { + isa = PBXNativeTarget; + buildConfigurationList = D0D4E55F2C8D9BF4007F820A /* Build configuration list for PBXNativeTarget "Configuration" */; + buildPhases = ( + D0F759912C8DB49E00126CF3 /* Configure Version */, + D0D4E55C2C8D9BF4007F820A /* Sources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Configuration; + packageProductDependencies = ( + ); + productName = Core; + productReference = D0D4E5622C8D9BF4007F820A /* BurrowConfiguration.framework */; + productType = "com.apple.product-type.framework"; + }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ @@ -347,18 +495,18 @@ isa = PBXProject; attributes = { BuildIndependentTargetsInParallel = 1; - LastSwiftUpdateCheck = 1510; + LastSwiftUpdateCheck = 1600; LastUpgradeCheck = 1520; TargetAttributes = { - D00117372B30341C00D87C25 = { - CreatedOnToolsVersion = 15.1; - }; D020F65229E4A697002790F6 = { CreatedOnToolsVersion = 14.3; }; D05B9F7129E39EEC008CB1F9 = { CreatedOnToolsVersion = 14.3; }; + D0D4E5302C8D996F007F820A = { + CreatedOnToolsVersion = 16.0; + }; }; }; buildConfigurationList = D05B9F6D29E39EEC008CB1F9 /* Build configuration list for PBXProject "Burrow" */; @@ -371,6 +519,11 @@ ); mainGroup = D05B9F6929E39EEC008CB1F9; packageReferences = ( + D0B1D10E2C436152004B7823 /* XCRemoteSwiftPackageReference "swift-async-algorithms" */, + D0D4E4822C8D8EF6007F820A /* XCRemoteSwiftPackageReference "grpc-swift" */, + D0D4E4852C8D8F29007F820A /* XCRemoteSwiftPackageReference "swift-protobuf" */, + D044EE8F2C8DAB2000778185 /* XCRemoteSwiftPackageReference "swift-nio" */, + D044EE942C8DAB2800778185 /* XCRemoteSwiftPackageReference "swift-nio-transport-services" */, ); productRefGroup = D05B9F7329E39EEC008CB1F9 /* Products */; projectDirPath = ""; @@ -378,52 +531,32 @@ targets = ( D05B9F7129E39EEC008CB1F9 /* App */, D020F65229E4A697002790F6 /* NetworkExtension */, - D00117372B30341C00D87C25 /* Shared */, + D0D4E5502C8D9BF2007F820A /* UI */, + D0D4E5302C8D996F007F820A /* Core */, + D0D4E55A2C8D9BF4007F820A /* Configuration */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ - D020F65129E4A697002790F6 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; D05B9F7029E39EEC008CB1F9 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - D05B9F7A29E39EED008CB1F9 /* Assets.xcassets in Resources */, D09150422B9D2AF700BE3CB0 /* MainMenu.xib in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; + D0D4E5542C8D9BF2007F820A /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - D04A3E232BAF4AE50043EC85 /* Update Build Number */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - "$(PROJECT_DIR)/../Tools/version.sh", - "$(PROJECT_DIR)/../.git", - ); - name = "Update Build Number"; - outputFileListPaths = ( - ); - outputPaths = ( - "$(PROJECT_DIR)/Configuration/Version.xcconfig", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"$PROJECT_DIR/../Tools/version.sh\"\n"; - }; D0BCC60B2A09A0C100AD070D /* Compile Rust */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; @@ -444,22 +577,31 @@ shellScript = "\"${PROJECT_DIR}/NetworkExtension/libburrow/build-rust.sh\"\n"; showEnvVarsInLog = 0; }; + D0F759912C8DB49E00126CF3 /* Configure Version */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "$(PROJECT_DIR)/../Tools/version.sh", + "$(PROJECT_DIR)/../.git", + ); + name = "Configure Version"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(PROJECT_DIR)/Configuration/Version.xcconfig", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"$PROJECT_DIR/../Tools/version.sh\"\n"; + showEnvVarsInLog = 0; + }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ - D00117342B30341C00D87C25 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - D001173B2B30341C00D87C25 /* Logging.swift in Sources */, - 0BA6D73C2BA6393200BD4B55 /* NWConnection+Async.swift in Sources */, - D08252762B5C9FC4005DA378 /* Constants.swift in Sources */, - 0BA6D73E2BA6394B00BD4B55 /* DataTypes.swift in Sources */, - 0BA6D73B2BA638D900BD4B55 /* Client.swift in Sources */, - 0BA6D73D2BA6393B00BD4B55 /* NewlineProtocolFramer.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; D020F64F29E4A697002790F6 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -472,60 +614,104 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - D0FAB59A2B818B9600F6A84B /* Network.swift in Sources */, - D0BCC6082A0981FE00AD070D /* Tunnel.swift in Sources */, - D0FAB5982B818B8200F6A84B /* TunnelStatusView.swift in Sources */, - 43AA26D82A10004900F14CE6 /* MenuItemToggleView.swift in Sources */, - D05B9F7829E39EEC008CB1F9 /* BurrowView.swift in Sources */, - D0FAB5922B818A5900F6A84B /* NetworkExtensionTunnel.swift in Sources */, - D000363F2BB895FB00E582EC /* OAuth2.swift in Sources */, - D0FAB5962B818B2900F6A84B /* TunnelButton.swift in Sources */, D00AA8972A4669BC005C8102 /* AppDelegate.swift in Sources */, - D05EF8C82B81818D0017AB4F /* FloatingButtonStyle.swift in Sources */, - D032E6522B8A79C20006B8AD /* HackClub.swift in Sources */, D05B9F7629E39EEC008CB1F9 /* BurrowApp.swift in Sources */, - D01A79312B81630D0024EC91 /* NetworkView.swift in Sources */, - D032E6542B8A79DA0006B8AD /* WireGuard.swift in Sources */, - D0BCC5FD2A086D4700AD070D /* NetworkExtension+Async.swift in Sources */, - D000363D2BB8928E00E582EC /* NetworkCarouselView.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + D0D4E52D2C8D996F007F820A /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + D0F759612C8DB24B00126CF3 /* grpc-swift-config.json in Sources */, + D0F759622C8DB24B00126CF3 /* swift-protobuf-config.json in Sources */, + D0F7598D2C8DB3DA00126CF3 /* Client.swift in Sources */, + D0D4E56B2C8D9C2F007F820A /* Logging.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + D0D4E5522C8D9BF2007F820A /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + D0D4E5712C8D9C6F007F820A /* HackClub.swift in Sources */, + D0D4E5722C8D9C6F007F820A /* Network.swift in Sources */, + D0D4E5732C8D9C6F007F820A /* WireGuard.swift in Sources */, + D0D4E5742C8D9C6F007F820A /* BurrowView.swift in Sources */, + D0D4E5752C8D9C6F007F820A /* FloatingButtonStyle.swift in Sources */, + D0D4E5762C8D9C6F007F820A /* MenuItemToggleView.swift in Sources */, + D0D4E5772C8D9C6F007F820A /* NetworkCarouselView.swift in Sources */, + D0D4E5782C8D9C6F007F820A /* NetworkExtension+Async.swift in Sources */, + D0D4E5792C8D9C6F007F820A /* NetworkExtensionTunnel.swift in Sources */, + D0D4E57A2C8D9C6F007F820A /* NetworkView.swift in Sources */, + D0D4E57B2C8D9C6F007F820A /* OAuth2.swift in Sources */, + D0D4E57C2C8D9C6F007F820A /* Tunnel.swift in Sources */, + D0D4E57D2C8D9C6F007F820A /* TunnelButton.swift in Sources */, + D0D4E57E2C8D9C6F007F820A /* TunnelStatusView.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + D0D4E55C2C8D9BF4007F820A /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + D0D4E5922C8D9D15007F820A /* Constants.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ - D00117472B30373100D87C25 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = D00117372B30341C00D87C25 /* Shared */; - targetProxy = D00117462B30373100D87C25 /* PBXContainerItemProxy */; - }; - D00117492B30373500D87C25 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = D00117372B30341C00D87C25 /* Shared */; - targetProxy = D00117482B30373500D87C25 /* PBXContainerItemProxy */; - }; D020F65C29E4A697002790F6 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = D020F65229E4A697002790F6 /* NetworkExtension */; targetProxy = D020F65B29E4A697002790F6 /* PBXContainerItemProxy */; }; + D0BF09512C8E66F1000D8DEC /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = D0D4E55A2C8D9BF4007F820A /* Configuration */; + targetProxy = D0BF09502C8E66F1000D8DEC /* PBXContainerItemProxy */; + }; + D0BF09542C8E66FA000D8DEC /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = D0D4E55A2C8D9BF4007F820A /* Configuration */; + targetProxy = D0BF09532C8E66FA000D8DEC /* PBXContainerItemProxy */; + }; + D0D4E56F2C8D9C5D007F820A /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = D0D4E5302C8D996F007F820A /* Core */; + targetProxy = D0D4E56E2C8D9C5D007F820A /* PBXContainerItemProxy */; + }; + D0D4E5802C8D9C78007F820A /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = D0D4E5302C8D996F007F820A /* Core */; + targetProxy = D0D4E57F2C8D9C78007F820A /* PBXContainerItemProxy */; + }; + D0D4E5882C8D9C88007F820A /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = D0D4E5502C8D9BF2007F820A /* UI */; + targetProxy = D0D4E5872C8D9C88007F820A /* PBXContainerItemProxy */; + }; + D0F4FAD22C8DC7960068730A /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = D0D4E5302C8D996F007F820A /* Core */; + targetProxy = D0F4FAD12C8DC7960068730A /* PBXContainerItemProxy */; + }; + D0F7595E2C8DB24400126CF3 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + productRef = D0F7595D2C8DB24400126CF3 /* GRPCSwiftPlugin */; + }; + D0F759602C8DB24400126CF3 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + productRef = D0F7595F2C8DB24400126CF3 /* SwiftProtobufPlugin */; + }; + D0F7598A2C8DB34200126CF3 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + productRef = D0F759892C8DB34200126CF3 /* GRPC */; + }; /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ - D001173D2B30341C00D87C25 /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = D00117422B30348D00D87C25 /* Shared.xcconfig */; - buildSettings = { - }; - name = Debug; - }; - D001173E2B30341C00D87C25 /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = D00117422B30348D00D87C25 /* Shared.xcconfig */; - buildSettings = { - }; - name = Release; - }; D020F65F29E4A697002790F6 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = D020F66229E4A6E5002790F6 /* NetworkExtension.xcconfig */; @@ -568,18 +754,51 @@ }; name = Release; }; + D0D4E53D2C8D996F007F820A /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D0D4E4F72C8D941D007F820A /* Framework.xcconfig */; + buildSettings = { + }; + name = Debug; + }; + D0D4E53E2C8D996F007F820A /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D0D4E4F72C8D941D007F820A /* Framework.xcconfig */; + buildSettings = { + }; + name = Release; + }; + D0D4E5562C8D9BF2007F820A /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D0BF09582C8E6789000D8DEC /* UI.xcconfig */; + buildSettings = { + }; + name = Debug; + }; + D0D4E5572C8D9BF2007F820A /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D0BF09582C8E6789000D8DEC /* UI.xcconfig */; + buildSettings = { + }; + name = Release; + }; + D0D4E5602C8D9BF4007F820A /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D00117422B30348D00D87C25 /* Configuration.xcconfig */; + buildSettings = { + }; + name = Debug; + }; + D0D4E5612C8D9BF4007F820A /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D00117422B30348D00D87C25 /* Configuration.xcconfig */; + buildSettings = { + }; + name = Release; + }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ - D001173C2B30341C00D87C25 /* Build configuration list for PBXNativeTarget "Shared" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - D001173D2B30341C00D87C25 /* Debug */, - D001173E2B30341C00D87C25 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; D020F65E29E4A697002790F6 /* Build configuration list for PBXNativeTarget "NetworkExtension" */ = { isa = XCConfigurationList; buildConfigurations = ( @@ -607,7 +826,130 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + D0D4E53C2C8D996F007F820A /* Build configuration list for PBXNativeTarget "Core" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + D0D4E53D2C8D996F007F820A /* Debug */, + D0D4E53E2C8D996F007F820A /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + D0D4E5552C8D9BF2007F820A /* Build configuration list for PBXNativeTarget "UI" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + D0D4E5562C8D9BF2007F820A /* Debug */, + D0D4E5572C8D9BF2007F820A /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + D0D4E55F2C8D9BF4007F820A /* Build configuration list for PBXNativeTarget "Configuration" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + D0D4E5602C8D9BF4007F820A /* Debug */, + D0D4E5612C8D9BF4007F820A /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; /* End XCConfigurationList section */ + +/* Begin XCRemoteSwiftPackageReference section */ + D044EE8F2C8DAB2000778185 /* XCRemoteSwiftPackageReference "swift-nio" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/apple/swift-nio.git"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 2.72.0; + }; + }; + D044EE942C8DAB2800778185 /* XCRemoteSwiftPackageReference "swift-nio-transport-services" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/apple/swift-nio-transport-services.git"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 1.21.0; + }; + }; + D0B1D10E2C436152004B7823 /* XCRemoteSwiftPackageReference "swift-async-algorithms" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/apple/swift-async-algorithms.git"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 1.0.1; + }; + }; + D0D4E4822C8D8EF6007F820A /* XCRemoteSwiftPackageReference "grpc-swift" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/grpc/grpc-swift.git"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 1.23.0; + }; + }; + D0D4E4852C8D8F29007F820A /* XCRemoteSwiftPackageReference "swift-protobuf" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/apple/swift-protobuf.git"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 1.28.1; + }; + }; +/* End XCRemoteSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + D044EE902C8DAB2000778185 /* NIO */ = { + isa = XCSwiftPackageProductDependency; + package = D044EE8F2C8DAB2000778185 /* XCRemoteSwiftPackageReference "swift-nio" */; + productName = NIO; + }; + D044EE922C8DAB2000778185 /* NIOConcurrencyHelpers */ = { + isa = XCSwiftPackageProductDependency; + package = D044EE8F2C8DAB2000778185 /* XCRemoteSwiftPackageReference "swift-nio" */; + productName = NIOConcurrencyHelpers; + }; + D044EE952C8DAB2800778185 /* NIOTransportServices */ = { + isa = XCSwiftPackageProductDependency; + package = D044EE942C8DAB2800778185 /* XCRemoteSwiftPackageReference "swift-nio-transport-services" */; + productName = NIOTransportServices; + }; + D078F7E02C8DA375008A8CEC /* GRPC */ = { + isa = XCSwiftPackageProductDependency; + package = D0D4E4822C8D8EF6007F820A /* XCRemoteSwiftPackageReference "grpc-swift" */; + productName = GRPC; + }; + D078F7E22C8DA375008A8CEC /* SwiftProtobuf */ = { + isa = XCSwiftPackageProductDependency; + package = D0D4E4852C8D8F29007F820A /* XCRemoteSwiftPackageReference "swift-protobuf" */; + productName = SwiftProtobuf; + }; + D0B1D10F2C436152004B7823 /* AsyncAlgorithms */ = { + isa = XCSwiftPackageProductDependency; + package = D0B1D10E2C436152004B7823 /* XCRemoteSwiftPackageReference "swift-async-algorithms" */; + productName = AsyncAlgorithms; + }; + D0F7595D2C8DB24400126CF3 /* GRPCSwiftPlugin */ = { + isa = XCSwiftPackageProductDependency; + package = D0D4E4822C8D8EF6007F820A /* XCRemoteSwiftPackageReference "grpc-swift" */; + productName = "plugin:GRPCSwiftPlugin"; + }; + D0F7595F2C8DB24400126CF3 /* SwiftProtobufPlugin */ = { + isa = XCSwiftPackageProductDependency; + package = D0D4E4852C8D8F29007F820A /* XCRemoteSwiftPackageReference "swift-protobuf" */; + productName = "plugin:SwiftProtobufPlugin"; + }; + D0F7597D2C8DB30500126CF3 /* CGRPCZlib */ = { + isa = XCSwiftPackageProductDependency; + package = D0D4E4822C8D8EF6007F820A /* XCRemoteSwiftPackageReference "grpc-swift" */; + productName = CGRPCZlib; + }; + D0F759892C8DB34200126CF3 /* GRPC */ = { + isa = XCSwiftPackageProductDependency; + package = D0D4E4822C8D8EF6007F820A /* XCRemoteSwiftPackageReference "grpc-swift" */; + productName = GRPC; + }; +/* End XCSwiftPackageProductDependency section */ }; rootObject = D05B9F6A29E39EEC008CB1F9 /* Project object */; } diff --git a/Apple/Burrow.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Apple/Burrow.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved new file mode 100644 index 0000000..739b77c --- /dev/null +++ b/Apple/Burrow.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -0,0 +1,123 @@ +{ + "originHash" : "fa512b990383b7e309c5854a5279817052294a8191a6d3c55c49cfb38e88c0c3", + "pins" : [ + { + "identity" : "grpc-swift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/grpc/grpc-swift.git", + "state" : { + "revision" : "6a90b7e77e29f9bda6c2b3a4165a40d6c02cfda1", + "version" : "1.23.0" + } + }, + { + "identity" : "swift-async-algorithms", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-async-algorithms.git", + "state" : { + "revision" : "6ae9a051f76b81cc668305ceed5b0e0a7fd93d20", + "version" : "1.0.1" + } + }, + { + "identity" : "swift-atomics", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-atomics.git", + "state" : { + "revision" : "cd142fd2f64be2100422d658e7411e39489da985", + "version" : "1.2.0" + } + }, + { + "identity" : "swift-collections", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-collections.git", + "state" : { + "revision" : "9bf03ff58ce34478e66aaee630e491823326fd06", + "version" : "1.1.3" + } + }, + { + "identity" : "swift-http-types", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-http-types", + "state" : { + "revision" : "ae67c8178eb46944fd85e4dc6dd970e1f3ed6ccd", + "version" : "1.3.0" + } + }, + { + "identity" : "swift-log", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-log.git", + "state" : { + "revision" : "9cb486020ebf03bfa5b5df985387a14a98744537", + "version" : "1.6.1" + } + }, + { + "identity" : "swift-nio", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-nio.git", + "state" : { + "revision" : "9746cf80e29edfef2a39924a66731249223f42a3", + "version" : "2.72.0" + } + }, + { + "identity" : "swift-nio-extras", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-nio-extras.git", + "state" : { + "revision" : "d1ead62745cc3269e482f1c51f27608057174379", + "version" : "1.24.0" + } + }, + { + "identity" : "swift-nio-http2", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-nio-http2.git", + "state" : { + "revision" : "b5f7062b60e4add1e8c343ba4eb8da2e324b3a94", + "version" : "1.34.0" + } + }, + { + "identity" : "swift-nio-ssl", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-nio-ssl.git", + "state" : { + "revision" : "7b84abbdcef69cc3be6573ac12440220789dcd69", + "version" : "2.27.2" + } + }, + { + "identity" : "swift-nio-transport-services", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-nio-transport-services.git", + "state" : { + "revision" : "38ac8221dd20674682148d6451367f89c2652980", + "version" : "1.21.0" + } + }, + { + "identity" : "swift-protobuf", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-protobuf.git", + "state" : { + "revision" : "edb6ed4919f7756157fe02f2552b7e3850a538e5", + "version" : "1.28.1" + } + }, + { + "identity" : "swift-system", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-system.git", + "state" : { + "revision" : "d2ba781702a1d8285419c15ee62fd734a9437ff5", + "version" : "1.3.2" + } + } + ], + "version" : 3 +} diff --git a/Apple/Burrow.xcodeproj/xcshareddata/xcschemes/App.xcscheme b/Apple/Burrow.xcodeproj/xcshareddata/xcschemes/App.xcscheme index 670823d..a524e87 100644 --- a/Apple/Burrow.xcodeproj/xcshareddata/xcschemes/App.xcscheme +++ b/Apple/Burrow.xcodeproj/xcshareddata/xcschemes/App.xcscheme @@ -1,10 +1,11 @@ + buildImplicitDependencies = "YES" + buildArchitectures = "Automatic"> + buildImplicitDependencies = "YES" + buildArchitectures = "Automatic"> = { - guard let groupContainerURL = FileManager.default - .containerURL(forSecurityApplicationGroupIdentifier: appGroupIdentifier) else { - return .failure(.invalidAppGroupIdentifier) - } - return .success(groupContainerURL) - }() public static var socketURL: URL { get throws { try groupContainerURL.appending(component: "burrow.sock", directoryHint: .notDirectory) } } - public static var dbURL: URL { + public static var databaseURL: URL { get throws { try groupContainerURL.appending(component: "burrow.db", directoryHint: .notDirectory) } } + + private static var groupContainerURL: URL { + get throws { try _groupContainerURL.get() } + } + private static let _groupContainerURL: Result = { + switch FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroupIdentifier) { + case .some(let url): .success(url) + case .none: .failure(.invalidAppGroupIdentifier) + } + }() +} + +extension Logger { + @_dynamicReplacement(for: subsystem) + public static var subsystem: String { Constants.bundleIdentifier } } diff --git a/Apple/Shared/Constants/module.modulemap b/Apple/Configuration/Constants/module.modulemap similarity index 66% rename from Apple/Shared/Constants/module.modulemap rename to Apple/Configuration/Constants/module.modulemap index 7ee21fc..0e60f32 100644 --- a/Apple/Shared/Constants/module.modulemap +++ b/Apple/Configuration/Constants/module.modulemap @@ -1,4 +1,4 @@ -module Constants { +module CConstants { header "Constants.h" export * } diff --git a/Apple/Configuration/Debug.xcconfig b/Apple/Configuration/Debug.xcconfig new file mode 100644 index 0000000..9529dbd --- /dev/null +++ b/Apple/Configuration/Debug.xcconfig @@ -0,0 +1,26 @@ +// Release +DEBUG_INFORMATION_FORMAT = dwarf-with-dsym +SWIFT_COMPILATION_MODE = wholemodule +SWIFT_OPTIMIZATION_LEVEL = -Osize +LLVM_LTO = YES +DEAD_CODE_STRIPPING = YES +STRIP_INSTALLED_PRODUCT = YES +STRIP_SWIFT_SYMBOLS = YES +COPY_PHASE_STRIP = NO +VALIDATE_PRODUCT = YES +ENABLE_MODULE_VERIFIER = YES + +// Debug +ONLY_ACTIVE_ARCH[config=Debug] = YES +DEBUG_INFORMATION_FORMAT[config=Debug] = dwarf +ENABLE_TESTABILITY[config=Debug] = YES +GCC_PREPROCESSOR_DEFINITIONS[config=Debug] = DEBUG=1 $(inherited) +SWIFT_OPTIMIZATION_LEVEL[config=Debug] = -Onone +SWIFT_ACTIVE_COMPILATION_CONDITIONS[config=Debug] = DEBUG +SWIFT_COMPILATION_MODE[config=Debug] = singlefile +LLVM_LTO[config=Debug] = NO +DEAD_CODE_STRIPPING[config=Debug] = NO +VALIDATE_PRODUCT[config=Debug] = NO +STRIP_INSTALLED_PRODUCT[config=Debug] = NO +STRIP_SWIFT_SYMBOLS[config=Debug] = NO +ENABLE_MODULE_VERIFIER[config=Debug] = NO diff --git a/Apple/Configuration/Extension.xcconfig b/Apple/Configuration/Extension.xcconfig index f8d90a3..5885c31 100644 --- a/Apple/Configuration/Extension.xcconfig +++ b/Apple/Configuration/Extension.xcconfig @@ -1,4 +1,6 @@ -MERGED_BINARY_TYPE = manual +LD_EXPORT_SYMBOLS = NO + +OTHER_SWIFT_FLAGS = $(inherited) -Xfrontend -disable-autolink-framework -Xfrontend UIKit -Xfrontend -disable-autolink-framework -Xfrontend AppKit -Xfrontend -disable-autolink-framework -Xfrontend SwiftUI LD_RUNPATH_SEARCH_PATHS = $(inherited) @executable_path/Frameworks @executable_path/../../Frameworks -LD_RUNPATH_SEARCH_PATHS[sdk=macos*] = $(inherited) @executable_path/../Frameworks @executable_path/../../../../Frameworks +LD_RUNPATH_SEARCH_PATHS[sdk=macosx*] = $(inherited) @executable_path/../Frameworks @executable_path/../../../../Frameworks diff --git a/Apple/Configuration/Framework.xcconfig b/Apple/Configuration/Framework.xcconfig new file mode 100644 index 0000000..6fa4f19 --- /dev/null +++ b/Apple/Configuration/Framework.xcconfig @@ -0,0 +1,14 @@ +PRODUCT_NAME = Burrow$(TARGET_NAME:c99extidentifier) +PRODUCT_BUNDLE_IDENTIFIER = $(APP_BUNDLE_IDENTIFIER).$(TARGET_NAME:c99extidentifier) +APPLICATION_EXTENSION_API_ONLY = YES +SWIFT_INSTALL_OBJC_HEADER = NO +SWIFT_SKIP_AUTOLINKING_FRAMEWORKS = YES +SWIFT_SKIP_AUTOLINKING_LIBRARIES = YES + +LD_RUNPATH_SEARCH_PATHS = $(inherited) @executable_path/Frameworks @loader_path/Frameworks +LD_RUNPATH_SEARCH_PATHS[sdk=macosx*] = $(inherited) @executable_path/../Frameworks @loader_path/Frameworks + +DYLIB_INSTALL_NAME_BASE = @rpath +DYLIB_COMPATIBILITY_VERSION = 1 +DYLIB_CURRENT_VERSION = 1 +VERSIONING_SYSTEM = diff --git a/Apple/Core/Client.swift b/Apple/Core/Client.swift new file mode 100644 index 0000000..8874e3b --- /dev/null +++ b/Apple/Core/Client.swift @@ -0,0 +1,32 @@ +import GRPC +import NIOTransportServices + +public typealias TunnelClient = Burrow_TunnelAsyncClient +public typealias NetworksClient = Burrow_NetworksAsyncClient + +public protocol Client { + init(channel: GRPCChannel) +} + +extension Client { + public static func unix(socketURL: URL) -> Self { + let group = NIOTSEventLoopGroup() + let configuration = ClientConnection.Configuration.default( + target: .unixDomainSocket(socketURL.path), + eventLoopGroup: group + ) + return Self(channel: ClientConnection(configuration: configuration)) + } +} + +extension TunnelClient: Client { + public init(channel: any GRPCChannel) { + self.init(channel: channel, defaultCallOptions: .init(), interceptors: .none) + } +} + +extension NetworksClient: Client { + public init(channel: any GRPCChannel) { + self.init(channel: channel, defaultCallOptions: .init(), interceptors: .none) + } +} diff --git a/Apple/Core/Client/burrow.proto b/Apple/Core/Client/burrow.proto new file mode 120000 index 0000000..03e86a5 --- /dev/null +++ b/Apple/Core/Client/burrow.proto @@ -0,0 +1 @@ +../../../proto/burrow.proto \ No newline at end of file diff --git a/Apple/Core/Client/grpc-swift-config.json b/Apple/Core/Client/grpc-swift-config.json new file mode 100644 index 0000000..2d89698 --- /dev/null +++ b/Apple/Core/Client/grpc-swift-config.json @@ -0,0 +1,11 @@ +{ + "invocations": [ + { + "protoFiles": [ + "burrow.proto", + ], + "server": false, + "visibility": "public" + } + ] +} diff --git a/Apple/Core/Client/swift-protobuf-config.json b/Apple/Core/Client/swift-protobuf-config.json new file mode 100644 index 0000000..87aaec3 --- /dev/null +++ b/Apple/Core/Client/swift-protobuf-config.json @@ -0,0 +1,10 @@ +{ + "invocations": [ + { + "protoFiles": [ + "burrow.proto", + ], + "visibility": "public" + } + ] +} diff --git a/Apple/Shared/Logging.swift b/Apple/Core/Logging.swift similarity index 88% rename from Apple/Shared/Logging.swift rename to Apple/Core/Logging.swift index 36f024c..ba40888 100644 --- a/Apple/Shared/Logging.swift +++ b/Apple/Core/Logging.swift @@ -4,7 +4,7 @@ import os extension Logger { private static let loggers: OSAllocatedUnfairLock<[String: Logger]> = OSAllocatedUnfairLock(initialState: [:]) - public static let subsystem = Constants.bundleIdentifier + public dynamic static var subsystem: String { "com.hackclub.burrow" } public static func logger(for type: Any.Type) -> Logger { let category = String(describing: type) diff --git a/Apple/NetworkExtension/PacketTunnelProvider.swift b/Apple/NetworkExtension/PacketTunnelProvider.swift index 89e0de6..a8e42e0 100644 --- a/Apple/NetworkExtension/PacketTunnelProvider.swift +++ b/Apple/NetworkExtension/PacketTunnelProvider.swift @@ -1,92 +1,74 @@ -import BurrowShared +import AsyncAlgorithms +import BurrowConfiguration +import BurrowCore import libburrow import NetworkExtension import os class PacketTunnelProvider: NEPacketTunnelProvider { + enum Error: Swift.Error { + case missingTunnelConfiguration + } + private let logger = Logger.logger(for: PacketTunnelProvider.self) - private var client: Client? + + private var client: TunnelClient { + get throws { try _client.get() } + } + private let _client: Result = Result { + try TunnelClient.unix(socketURL: Constants.socketURL) + } override init() { do { libburrow.spawnInProcess( socketPath: try Constants.socketURL.path(percentEncoded: false), - dbPath: try Constants.dbURL.path(percentEncoded: false) + databasePath: try Constants.databaseURL.path(percentEncoded: false) ) } catch { - logger.error("Failed to spawn: \(error)") + logger.error("Failed to spawn networking thread: \(error)") } } override func startTunnel(options: [String: NSObject]? = nil) async throws { do { - let client = try Client() - self.client = client - register_events(client) - - _ = try await self.loadTunSettings() - let startRequest = Start( - tun: Start.TunOptions( - name: nil, no_pi: false, tun_excl: false, tun_retrieve: true, address: [] - ) - ) - let response = try await client.request(startRequest, type: BurrowResult.self) - self.logger.log("Received start server response: \(String(describing: response))") + let configuration = try await Array(client.tunnelConfiguration(.init()).prefix(1)).first + guard let settings = configuration?.settings else { + throw Error.missingTunnelConfiguration + } + try await setTunnelNetworkSettings(settings) + _ = try await client.tunnelStart(.init()) + logger.log("Started tunnel with network settings: \(settings)") } catch { - self.logger.error("Failed to start tunnel: \(error)") + logger.error("Failed to start tunnel: \(error)") throw error } } override func stopTunnel(with reason: NEProviderStopReason) async { do { - let client = try Client() - _ = try await client.single_request("Stop", type: BurrowResult.self) - self.logger.log("Stopped client.") + _ = try await client.tunnelStop(.init()) + logger.log("Stopped client") } catch { - self.logger.error("Failed to stop tunnel: \(error)") - } - } - func loadTunSettings() async throws -> ServerConfig { - guard let client = self.client else { - throw BurrowError.noClient - } - let srvConfig = try await client.single_request("ServerConfig", type: BurrowResult.self) - guard let serverconfig = srvConfig.Ok else { - throw BurrowError.resultIsError - } - guard let tunNs = generateTunSettings(from: serverconfig) else { - throw BurrowError.addrDoesntExist - } - try await self.setTunnelNetworkSettings(tunNs) - self.logger.info("Set remote tunnel address to \(tunNs.tunnelRemoteAddress)") - return serverconfig - } - private func generateTunSettings(from: ServerConfig) -> NETunnelNetworkSettings? { - // Using a makeshift remote tunnel address - let nst = NEPacketTunnelNetworkSettings(tunnelRemoteAddress: "1.1.1.1") - var v4Addresses = [String]() - var v6Addresses = [String]() - for addr in from.address { - if IPv4Address(addr) != nil { - v6Addresses.append(addr) - } - if IPv6Address(addr) != nil { - v4Addresses.append(addr) - } - } - nst.ipv4Settings = NEIPv4Settings(addresses: v4Addresses, subnetMasks: v4Addresses.map { _ in - "255.255.255.0" - }) - nst.ipv6Settings = NEIPv6Settings(addresses: v6Addresses, networkPrefixLengths: v6Addresses.map { _ in 64 }) - logger.log("Initialized ipv4 settings: \(nst.ipv4Settings)") - return nst - } - func register_events(_ client: Client) { - client.on_event(.ConfigChange) { (cfig: ServerConfig) in - self.logger.info("Config Change Notification: \(String(describing: cfig))") - self.setTunnelNetworkSettings(self.generateTunSettings(from: cfig)) - self.logger.info("Updated Tunnel Network Settings.") + logger.error("Failed to stop tunnel: \(error)") } } } + +extension Burrow_TunnelConfigurationResponse { + fileprivate var settings: NEPacketTunnelNetworkSettings { + let ipv6Addresses = addresses.filter { IPv6Address($0) != nil } + + let settings = NEPacketTunnelNetworkSettings(tunnelRemoteAddress: "1.1.1.1") + settings.mtu = NSNumber(value: mtu) + settings.ipv4Settings = NEIPv4Settings( + addresses: addresses.filter { IPv4Address($0) != nil }, + subnetMasks: ["255.255.255.0"] + ) + settings.ipv6Settings = NEIPv6Settings( + addresses: ipv6Addresses, + networkPrefixLengths: ipv6Addresses.map { _ in 64 } + ) + return settings + } +} diff --git a/Apple/NetworkExtension/libburrow/build-rust.sh b/Apple/NetworkExtension/libburrow/build-rust.sh index 00c3652..6f455a9 100755 --- a/Apple/NetworkExtension/libburrow/build-rust.sh +++ b/Apple/NetworkExtension/libburrow/build-rust.sh @@ -68,11 +68,12 @@ else CARGO_PATH="$(dirname $(readlink -f $(which cargo))):/usr/bin" fi -CARGO_PATH="$(dirname $(readlink -f $(which protoc))):$CARGO_PATH" +PROTOC=$(readlink -f $(which protoc)) +CARGO_PATH="$(dirname $PROTOC):$CARGO_PATH" # Run cargo without the various environment variables set by Xcode. # Those variables can confuse cargo and the build scripts it runs. -env -i PATH="$CARGO_PATH" CARGO_TARGET_DIR="${CONFIGURATION_TEMP_DIR}/target" IPHONEOS_DEPLOYMENT_TARGET="$IPHONEOS_DEPLOYMENT_TARGET" MACOSX_DEPLOYMENT_TARGET="$MACOSX_DEPLOYMENT_TARGET" cargo build "${CARGO_ARGS[@]}" +env -i PATH="$CARGO_PATH" PROTOC="$PROTOC" CARGO_TARGET_DIR="${CONFIGURATION_TEMP_DIR}/target" IPHONEOS_DEPLOYMENT_TARGET="$IPHONEOS_DEPLOYMENT_TARGET" MACOSX_DEPLOYMENT_TARGET="$MACOSX_DEPLOYMENT_TARGET" cargo build "${CARGO_ARGS[@]}" mkdir -p "${BUILT_PRODUCTS_DIR}" diff --git a/Apple/NetworkExtension/libburrow/libburrow.h b/Apple/NetworkExtension/libburrow/libburrow.h index 2b578ab..59b4734 100644 --- a/Apple/NetworkExtension/libburrow/libburrow.h +++ b/Apple/NetworkExtension/libburrow/libburrow.h @@ -1,2 +1,2 @@ -__attribute__((__swift_name__("spawnInProcess(socketPath:dbPath:)"))) +__attribute__((__swift_name__("spawnInProcess(socketPath:databasePath:)"))) extern void spawn_in_process(const char * __nullable socket_path, const char * __nullable db_path); diff --git a/Apple/Shared/Client.swift b/Apple/Shared/Client.swift deleted file mode 100644 index f643c6c..0000000 --- a/Apple/Shared/Client.swift +++ /dev/null @@ -1,106 +0,0 @@ -import Foundation -import Network - -public final class Client { - let connection: NWConnection - - private let logger = Logger.logger(for: Client.self) - private var generator = SystemRandomNumberGenerator() - private var continuations: [UInt: UnsafeContinuation] = [:] - private var eventMap: [NotificationType: [(Data) throws -> Void]] = [:] - private var task: Task? - - public convenience init() throws { - self.init(url: try Constants.socketURL) - } - - public init(url: URL) { - let endpoint: NWEndpoint - if url.isFileURL { - endpoint = .unix(path: url.path(percentEncoded: false)) - } else { - endpoint = .url(url) - } - - let parameters = NWParameters.tcp - parameters.defaultProtocolStack - .applicationProtocols - .insert(NWProtocolFramer.Options(definition: NewlineProtocolFramer.definition), at: 0) - let connection = NWConnection(to: endpoint, using: parameters) - connection.start(queue: .global()) - self.connection = connection - self.task = Task { [weak self] in - while true { - let (data, _, _) = try await connection.receiveMessage() - let peek = try JSONDecoder().decode(MessagePeek.self, from: data) - switch peek.type { - case .Response: - let response = try JSONDecoder().decode(ResponsePeek.self, from: data) - self?.logger.info("Received response for \(response.id)") - guard let continuations = self?.continuations else {return} - self?.logger.debug("All keys in continuation table: \(continuations.keys)") - guard let continuation = self?.continuations[response.id] else { return } - self?.logger.debug("Got matching continuation") - continuation.resume(returning: data) - case .Notification: - let peek = try JSONDecoder().decode(NotificationPeek.self, from: data) - guard let handlers = self?.eventMap[peek.method] else { continue } - _ = try handlers.map { try $0(data) } - default: - continue - } - } - } - } - private func send(_ request: T) async throws -> U { - let data: Data = try await withUnsafeThrowingContinuation { continuation in - continuations[request.id] = continuation - do { - let data = try JSONEncoder().encode(request) - let completion: NWConnection.SendCompletion = .contentProcessed { error in - guard let error = error else { - return - } - continuation.resume(throwing: error) - } - connection.send(content: data, completion: completion) - } catch { - continuation.resume(throwing: error) - return - } - } - self.logger.debug("Got response data: \(String(describing: data.base64EncodedString()))") - let res = try JSONDecoder().decode(Response.self, from: data) - self.logger.debug("Got response data decoded: \(String(describing: res))") - return res.result - } - public func request(_ request: T, type: U.Type = U.self) async throws -> U { - let req = BurrowRequest( - id: generator.next(upperBound: UInt.max), - command: request - ) - return try await send(req) - } - public func single_request(_ request: String, type: U.Type = U.self) async throws -> U { - let req = BurrowSimpleRequest( - id: generator.next(upperBound: UInt.max), - command: request - ) - return try await send(req) - } - public func on_event(_ event: NotificationType, callable: @escaping (T) throws -> Void) { - let action = { data in - let decoded = try JSONDecoder().decode(Notification.self, from: data) - try callable(decoded.params) - } - if eventMap[event] != nil { - eventMap[event]?.append(action) - } else { - eventMap[event] = [action] - } - } - - deinit { - connection.cancel() - } -} diff --git a/Apple/Shared/DataTypes.swift b/Apple/Shared/DataTypes.swift deleted file mode 100644 index ac49abc..0000000 --- a/Apple/Shared/DataTypes.swift +++ /dev/null @@ -1,139 +0,0 @@ -import Foundation - -// swiftlint:disable identifier_name raw_value_for_camel_cased_codable_enum -public enum BurrowError: Error { - case addrDoesntExist - case resultIsError - case cantParseResult - case resultIsNone - case noClient -} - -public protocol Request: Codable where Params: Codable { - associatedtype Params - - var id: UInt { get set } - var method: String { get set } - var params: Params? { get set } -} - -public enum MessageType: String, Codable { - case Request - case Response - case Notification -} - -public struct MessagePeek: Codable { - public var type: MessageType - public init(type: MessageType) { - self.type = type - } -} - -public struct BurrowSimpleRequest: Request { - public var id: UInt - public var method: String - public var params: String? - public init(id: UInt, command: String, params: String? = nil) { - self.id = id - self.method = command - self.params = params - } -} - -public struct BurrowRequest: Request where T: Codable { - public var id: UInt - public var method: String - public var params: T? - public init(id: UInt, command: T) { - self.id = id - self.method = "\(T.self)" - self.params = command - } -} - -public struct Response: Decodable where T: Decodable { - public var id: UInt - public var result: T - public init(id: UInt, result: T) { - self.id = id - self.result = result - } -} - -public struct ResponsePeek: Codable { - public var id: UInt - public init(id: UInt) { - self.id = id - } -} - -public enum NotificationType: String, Codable { - case ConfigChange -} - -public struct Notification: Codable where T: Codable { - public var method: NotificationType - public var params: T - public init(method: NotificationType, params: T) { - self.method = method - self.params = params - } -} - -public struct NotificationPeek: Codable { - public var method: NotificationType - public init(method: NotificationType) { - self.method = method - } -} - -public struct AnyResponseData: Codable { - public var type: String - public init(type: String) { - self.type = type - } -} - -public struct BurrowResult: Codable where T: Codable { - public var Ok: T? - public var Err: String? - public init(Ok: T, Err: String? = nil) { - self.Ok = Ok - self.Err = Err - } -} - -public struct ServerConfig: Codable { - public let address: [String] - public let name: String? - public let mtu: Int32? - public init(address: [String], name: String?, mtu: Int32?) { - self.address = address - self.name = name - self.mtu = mtu - } -} - -public struct Start: Codable { - public struct TunOptions: Codable { - public let name: String? - public let no_pi: Bool - public let tun_excl: Bool - public let tun_retrieve: Bool - public let address: [String] - public init(name: String?, no_pi: Bool, tun_excl: Bool, tun_retrieve: Bool, address: [String]) { - self.name = name - self.no_pi = no_pi - self.tun_excl = tun_excl - self.tun_retrieve = tun_retrieve - self.address = address - } - } - public let tun: TunOptions - public init(tun: TunOptions) { - self.tun = tun - } -} - -// swiftlint:enable identifier_name raw_value_for_camel_cased_codable_enum diff --git a/Apple/Shared/NWConnection+Async.swift b/Apple/Shared/NWConnection+Async.swift deleted file mode 100644 index c21fdc0..0000000 --- a/Apple/Shared/NWConnection+Async.swift +++ /dev/null @@ -1,32 +0,0 @@ -import Foundation -import Network - -extension NWConnection { - // swiftlint:disable:next large_tuple - func receiveMessage() async throws -> (Data, NWConnection.ContentContext?, Bool) { - try await withUnsafeThrowingContinuation { continuation in - receiveMessage { completeContent, contentContext, isComplete, error in - if let error { - continuation.resume(throwing: error) - } else { - guard let completeContent = completeContent else { - fatalError("Both error and completeContent were nil") - } - continuation.resume(returning: (completeContent, contentContext, isComplete)) - } - } - } - } - - func send(content: Data) async throws { - try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in - send(content: content, completion: .contentProcessed { error in - if let error { - continuation.resume(throwing: error) - } else { - continuation.resume(returning: ()) - } - }) - } - } -} diff --git a/Apple/Shared/NewlineProtocolFramer.swift b/Apple/Shared/NewlineProtocolFramer.swift deleted file mode 100644 index d2f71e5..0000000 --- a/Apple/Shared/NewlineProtocolFramer.swift +++ /dev/null @@ -1,54 +0,0 @@ -import Foundation -import Network - -final class NewlineProtocolFramer: NWProtocolFramerImplementation { - private static let delimeter: UInt8 = 10 // `\n` - - static let definition = NWProtocolFramer.Definition(implementation: NewlineProtocolFramer.self) - static let label = "Lines" - - init(framer: NWProtocolFramer.Instance) { } - - func start(framer: NWProtocolFramer.Instance) -> NWProtocolFramer.StartResult { .ready } - func stop(framer: NWProtocolFramer.Instance) -> Bool { true } - - func wakeup(framer: NWProtocolFramer.Instance) { } - func cleanup(framer: NWProtocolFramer.Instance) { } - - func handleInput(framer: NWProtocolFramer.Instance) -> Int { - while true { - var result: [Data] = [] - let parsed = framer.parseInput(minimumIncompleteLength: 1, maximumLength: 16_000) { buffer, _ in - guard let buffer else { return 0 } - var lines = buffer - .split(separator: Self.delimeter, omittingEmptySubsequences: false) - .map { Data($0) } - guard lines.count > 1 else { return 0 } - _ = lines.popLast() - - result = lines - return lines.reduce(lines.count) { $0 + $1.count } - } - - guard parsed && !result.isEmpty else { break } - - for line in result { - framer.deliverInput(data: line, message: .init(instance: framer), isComplete: true) - } - } - return 0 - } - - func handleOutput( - framer: NWProtocolFramer.Instance, - message: NWProtocolFramer.Message, - messageLength: Int, - isComplete: Bool - ) { - do { - try framer.writeOutputNoCopy(length: messageLength) - framer.writeOutput(data: [Self.delimeter]) - } catch { - } - } -} diff --git a/Apple/App/Assets.xcassets/AccentColor.colorset/Contents.json b/Apple/UI/Assets.xcassets/AccentColor.colorset/Contents.json similarity index 100% rename from Apple/App/Assets.xcassets/AccentColor.colorset/Contents.json rename to Apple/UI/Assets.xcassets/AccentColor.colorset/Contents.json diff --git a/Apple/App/Assets.xcassets/AppIcon.appiconset/100.png b/Apple/UI/Assets.xcassets/AppIcon.appiconset/100.png similarity index 100% rename from Apple/App/Assets.xcassets/AppIcon.appiconset/100.png rename to Apple/UI/Assets.xcassets/AppIcon.appiconset/100.png diff --git a/Apple/App/Assets.xcassets/AppIcon.appiconset/1024.png b/Apple/UI/Assets.xcassets/AppIcon.appiconset/1024.png similarity index 100% rename from Apple/App/Assets.xcassets/AppIcon.appiconset/1024.png rename to Apple/UI/Assets.xcassets/AppIcon.appiconset/1024.png diff --git a/Apple/App/Assets.xcassets/AppIcon.appiconset/114.png b/Apple/UI/Assets.xcassets/AppIcon.appiconset/114.png similarity index 100% rename from Apple/App/Assets.xcassets/AppIcon.appiconset/114.png rename to Apple/UI/Assets.xcassets/AppIcon.appiconset/114.png diff --git a/Apple/App/Assets.xcassets/AppIcon.appiconset/120.png b/Apple/UI/Assets.xcassets/AppIcon.appiconset/120.png similarity index 100% rename from Apple/App/Assets.xcassets/AppIcon.appiconset/120.png rename to Apple/UI/Assets.xcassets/AppIcon.appiconset/120.png diff --git a/Apple/App/Assets.xcassets/AppIcon.appiconset/128.png b/Apple/UI/Assets.xcassets/AppIcon.appiconset/128.png similarity index 100% rename from Apple/App/Assets.xcassets/AppIcon.appiconset/128.png rename to Apple/UI/Assets.xcassets/AppIcon.appiconset/128.png diff --git a/Apple/App/Assets.xcassets/AppIcon.appiconset/144.png b/Apple/UI/Assets.xcassets/AppIcon.appiconset/144.png similarity index 100% rename from Apple/App/Assets.xcassets/AppIcon.appiconset/144.png rename to Apple/UI/Assets.xcassets/AppIcon.appiconset/144.png diff --git a/Apple/App/Assets.xcassets/AppIcon.appiconset/152.png b/Apple/UI/Assets.xcassets/AppIcon.appiconset/152.png similarity index 100% rename from Apple/App/Assets.xcassets/AppIcon.appiconset/152.png rename to Apple/UI/Assets.xcassets/AppIcon.appiconset/152.png diff --git a/Apple/App/Assets.xcassets/AppIcon.appiconset/16.png b/Apple/UI/Assets.xcassets/AppIcon.appiconset/16.png similarity index 100% rename from Apple/App/Assets.xcassets/AppIcon.appiconset/16.png rename to Apple/UI/Assets.xcassets/AppIcon.appiconset/16.png diff --git a/Apple/App/Assets.xcassets/AppIcon.appiconset/167.png b/Apple/UI/Assets.xcassets/AppIcon.appiconset/167.png similarity index 100% rename from Apple/App/Assets.xcassets/AppIcon.appiconset/167.png rename to Apple/UI/Assets.xcassets/AppIcon.appiconset/167.png diff --git a/Apple/App/Assets.xcassets/AppIcon.appiconset/172.png b/Apple/UI/Assets.xcassets/AppIcon.appiconset/172.png similarity index 100% rename from Apple/App/Assets.xcassets/AppIcon.appiconset/172.png rename to Apple/UI/Assets.xcassets/AppIcon.appiconset/172.png diff --git a/Apple/App/Assets.xcassets/AppIcon.appiconset/180.png b/Apple/UI/Assets.xcassets/AppIcon.appiconset/180.png similarity index 100% rename from Apple/App/Assets.xcassets/AppIcon.appiconset/180.png rename to Apple/UI/Assets.xcassets/AppIcon.appiconset/180.png diff --git a/Apple/App/Assets.xcassets/AppIcon.appiconset/196.png b/Apple/UI/Assets.xcassets/AppIcon.appiconset/196.png similarity index 100% rename from Apple/App/Assets.xcassets/AppIcon.appiconset/196.png rename to Apple/UI/Assets.xcassets/AppIcon.appiconset/196.png diff --git a/Apple/App/Assets.xcassets/AppIcon.appiconset/20.png b/Apple/UI/Assets.xcassets/AppIcon.appiconset/20.png similarity index 100% rename from Apple/App/Assets.xcassets/AppIcon.appiconset/20.png rename to Apple/UI/Assets.xcassets/AppIcon.appiconset/20.png diff --git a/Apple/App/Assets.xcassets/AppIcon.appiconset/216.png b/Apple/UI/Assets.xcassets/AppIcon.appiconset/216.png similarity index 100% rename from Apple/App/Assets.xcassets/AppIcon.appiconset/216.png rename to Apple/UI/Assets.xcassets/AppIcon.appiconset/216.png diff --git a/Apple/App/Assets.xcassets/AppIcon.appiconset/256.png b/Apple/UI/Assets.xcassets/AppIcon.appiconset/256.png similarity index 100% rename from Apple/App/Assets.xcassets/AppIcon.appiconset/256.png rename to Apple/UI/Assets.xcassets/AppIcon.appiconset/256.png diff --git a/Apple/App/Assets.xcassets/AppIcon.appiconset/29.png b/Apple/UI/Assets.xcassets/AppIcon.appiconset/29.png similarity index 100% rename from Apple/App/Assets.xcassets/AppIcon.appiconset/29.png rename to Apple/UI/Assets.xcassets/AppIcon.appiconset/29.png diff --git a/Apple/App/Assets.xcassets/AppIcon.appiconset/32.png b/Apple/UI/Assets.xcassets/AppIcon.appiconset/32.png similarity index 100% rename from Apple/App/Assets.xcassets/AppIcon.appiconset/32.png rename to Apple/UI/Assets.xcassets/AppIcon.appiconset/32.png diff --git a/Apple/App/Assets.xcassets/AppIcon.appiconset/40.png b/Apple/UI/Assets.xcassets/AppIcon.appiconset/40.png similarity index 100% rename from Apple/App/Assets.xcassets/AppIcon.appiconset/40.png rename to Apple/UI/Assets.xcassets/AppIcon.appiconset/40.png diff --git a/Apple/App/Assets.xcassets/AppIcon.appiconset/48.png b/Apple/UI/Assets.xcassets/AppIcon.appiconset/48.png similarity index 100% rename from Apple/App/Assets.xcassets/AppIcon.appiconset/48.png rename to Apple/UI/Assets.xcassets/AppIcon.appiconset/48.png diff --git a/Apple/App/Assets.xcassets/AppIcon.appiconset/50.png b/Apple/UI/Assets.xcassets/AppIcon.appiconset/50.png similarity index 100% rename from Apple/App/Assets.xcassets/AppIcon.appiconset/50.png rename to Apple/UI/Assets.xcassets/AppIcon.appiconset/50.png diff --git a/Apple/App/Assets.xcassets/AppIcon.appiconset/512.png b/Apple/UI/Assets.xcassets/AppIcon.appiconset/512.png similarity index 100% rename from Apple/App/Assets.xcassets/AppIcon.appiconset/512.png rename to Apple/UI/Assets.xcassets/AppIcon.appiconset/512.png diff --git a/Apple/App/Assets.xcassets/AppIcon.appiconset/55.png b/Apple/UI/Assets.xcassets/AppIcon.appiconset/55.png similarity index 100% rename from Apple/App/Assets.xcassets/AppIcon.appiconset/55.png rename to Apple/UI/Assets.xcassets/AppIcon.appiconset/55.png diff --git a/Apple/App/Assets.xcassets/AppIcon.appiconset/57.png b/Apple/UI/Assets.xcassets/AppIcon.appiconset/57.png similarity index 100% rename from Apple/App/Assets.xcassets/AppIcon.appiconset/57.png rename to Apple/UI/Assets.xcassets/AppIcon.appiconset/57.png diff --git a/Apple/App/Assets.xcassets/AppIcon.appiconset/58.png b/Apple/UI/Assets.xcassets/AppIcon.appiconset/58.png similarity index 100% rename from Apple/App/Assets.xcassets/AppIcon.appiconset/58.png rename to Apple/UI/Assets.xcassets/AppIcon.appiconset/58.png diff --git a/Apple/App/Assets.xcassets/AppIcon.appiconset/60.png b/Apple/UI/Assets.xcassets/AppIcon.appiconset/60.png similarity index 100% rename from Apple/App/Assets.xcassets/AppIcon.appiconset/60.png rename to Apple/UI/Assets.xcassets/AppIcon.appiconset/60.png diff --git a/Apple/App/Assets.xcassets/AppIcon.appiconset/64.png b/Apple/UI/Assets.xcassets/AppIcon.appiconset/64.png similarity index 100% rename from Apple/App/Assets.xcassets/AppIcon.appiconset/64.png rename to Apple/UI/Assets.xcassets/AppIcon.appiconset/64.png diff --git a/Apple/App/Assets.xcassets/AppIcon.appiconset/72.png b/Apple/UI/Assets.xcassets/AppIcon.appiconset/72.png similarity index 100% rename from Apple/App/Assets.xcassets/AppIcon.appiconset/72.png rename to Apple/UI/Assets.xcassets/AppIcon.appiconset/72.png diff --git a/Apple/App/Assets.xcassets/AppIcon.appiconset/76.png b/Apple/UI/Assets.xcassets/AppIcon.appiconset/76.png similarity index 100% rename from Apple/App/Assets.xcassets/AppIcon.appiconset/76.png rename to Apple/UI/Assets.xcassets/AppIcon.appiconset/76.png diff --git a/Apple/App/Assets.xcassets/AppIcon.appiconset/80.png b/Apple/UI/Assets.xcassets/AppIcon.appiconset/80.png similarity index 100% rename from Apple/App/Assets.xcassets/AppIcon.appiconset/80.png rename to Apple/UI/Assets.xcassets/AppIcon.appiconset/80.png diff --git a/Apple/App/Assets.xcassets/AppIcon.appiconset/87.png b/Apple/UI/Assets.xcassets/AppIcon.appiconset/87.png similarity index 100% rename from Apple/App/Assets.xcassets/AppIcon.appiconset/87.png rename to Apple/UI/Assets.xcassets/AppIcon.appiconset/87.png diff --git a/Apple/App/Assets.xcassets/AppIcon.appiconset/88.png b/Apple/UI/Assets.xcassets/AppIcon.appiconset/88.png similarity index 100% rename from Apple/App/Assets.xcassets/AppIcon.appiconset/88.png rename to Apple/UI/Assets.xcassets/AppIcon.appiconset/88.png diff --git a/Apple/App/Assets.xcassets/AppIcon.appiconset/Contents.json b/Apple/UI/Assets.xcassets/AppIcon.appiconset/Contents.json similarity index 100% rename from Apple/App/Assets.xcassets/AppIcon.appiconset/Contents.json rename to Apple/UI/Assets.xcassets/AppIcon.appiconset/Contents.json diff --git a/Apple/App/Assets.xcassets/Contents.json b/Apple/UI/Assets.xcassets/Contents.json similarity index 100% rename from Apple/App/Assets.xcassets/Contents.json rename to Apple/UI/Assets.xcassets/Contents.json diff --git a/Apple/App/Assets.xcassets/HackClub.colorset/Contents.json b/Apple/UI/Assets.xcassets/HackClub.colorset/Contents.json similarity index 100% rename from Apple/App/Assets.xcassets/HackClub.colorset/Contents.json rename to Apple/UI/Assets.xcassets/HackClub.colorset/Contents.json diff --git a/Apple/App/Assets.xcassets/HackClub.imageset/Contents.json b/Apple/UI/Assets.xcassets/HackClub.imageset/Contents.json similarity index 100% rename from Apple/App/Assets.xcassets/HackClub.imageset/Contents.json rename to Apple/UI/Assets.xcassets/HackClub.imageset/Contents.json diff --git a/Apple/App/Assets.xcassets/HackClub.imageset/flag-standalone-wtransparent.pdf b/Apple/UI/Assets.xcassets/HackClub.imageset/flag-standalone-wtransparent.pdf similarity index 100% rename from Apple/App/Assets.xcassets/HackClub.imageset/flag-standalone-wtransparent.pdf rename to Apple/UI/Assets.xcassets/HackClub.imageset/flag-standalone-wtransparent.pdf diff --git a/Apple/App/Assets.xcassets/WireGuard.colorset/Contents.json b/Apple/UI/Assets.xcassets/WireGuard.colorset/Contents.json similarity index 100% rename from Apple/App/Assets.xcassets/WireGuard.colorset/Contents.json rename to Apple/UI/Assets.xcassets/WireGuard.colorset/Contents.json diff --git a/Apple/App/Assets.xcassets/WireGuard.imageset/Contents.json b/Apple/UI/Assets.xcassets/WireGuard.imageset/Contents.json similarity index 100% rename from Apple/App/Assets.xcassets/WireGuard.imageset/Contents.json rename to Apple/UI/Assets.xcassets/WireGuard.imageset/Contents.json diff --git a/Apple/App/Assets.xcassets/WireGuard.imageset/WireGuard.svg b/Apple/UI/Assets.xcassets/WireGuard.imageset/WireGuard.svg similarity index 100% rename from Apple/App/Assets.xcassets/WireGuard.imageset/WireGuard.svg rename to Apple/UI/Assets.xcassets/WireGuard.imageset/WireGuard.svg diff --git a/Apple/App/Assets.xcassets/WireGuardTitle.imageset/Contents.json b/Apple/UI/Assets.xcassets/WireGuardTitle.imageset/Contents.json similarity index 100% rename from Apple/App/Assets.xcassets/WireGuardTitle.imageset/Contents.json rename to Apple/UI/Assets.xcassets/WireGuardTitle.imageset/Contents.json diff --git a/Apple/App/Assets.xcassets/WireGuardTitle.imageset/WireGuardTitle.svg b/Apple/UI/Assets.xcassets/WireGuardTitle.imageset/WireGuardTitle.svg similarity index 100% rename from Apple/App/Assets.xcassets/WireGuardTitle.imageset/WireGuardTitle.svg rename to Apple/UI/Assets.xcassets/WireGuardTitle.imageset/WireGuardTitle.svg diff --git a/Apple/App/BurrowView.swift b/Apple/UI/BurrowView.swift similarity index 95% rename from Apple/App/BurrowView.swift rename to Apple/UI/BurrowView.swift index 3a53762..96467c7 100644 --- a/Apple/App/BurrowView.swift +++ b/Apple/UI/BurrowView.swift @@ -2,11 +2,11 @@ import AuthenticationServices import SwiftUI #if !os(macOS) -struct BurrowView: View { +public struct BurrowView: View { @Environment(\.webAuthenticationSession) private var webAuthenticationSession - var body: some View { + public var body: some View { NavigationStack { VStack { HStack { @@ -35,6 +35,9 @@ struct BurrowView: View { } } + public init() { + } + private func addHackClubNetwork() { Task { try await authenticateWithSlack() diff --git a/Apple/App/FloatingButtonStyle.swift b/Apple/UI/FloatingButtonStyle.swift similarity index 100% rename from Apple/App/FloatingButtonStyle.swift rename to Apple/UI/FloatingButtonStyle.swift diff --git a/Apple/App/MenuItemToggleView.swift b/Apple/UI/MenuItemToggleView.swift similarity index 87% rename from Apple/App/MenuItemToggleView.swift rename to Apple/UI/MenuItemToggleView.swift index 07db51d..ef5e8ee 100644 --- a/Apple/App/MenuItemToggleView.swift +++ b/Apple/UI/MenuItemToggleView.swift @@ -7,11 +7,11 @@ import SwiftUI -struct MenuItemToggleView: View { +public struct MenuItemToggleView: View { @Environment(\.tunnel) var tunnel: Tunnel - var body: some View { + public var body: some View { HStack { VStack(alignment: .leading) { Text("Burrow") @@ -30,10 +30,13 @@ struct MenuItemToggleView: View { .padding(10) .frame(minWidth: 300, minHeight: 32, maxHeight: 32) } + + public init() { + } } extension Tunnel { - fileprivate var toggleDisabled: Bool { + @MainActor fileprivate var toggleDisabled: Bool { switch status { case .disconnected, .permissionRequired, .connected, .disconnecting: false @@ -42,7 +45,7 @@ extension Tunnel { } } - var toggleIsOn: Binding { + @MainActor var toggleIsOn: Binding { Binding { switch status { case .connecting, .reasserting, .connected: diff --git a/Apple/App/NetworkCarouselView.swift b/Apple/UI/NetworkCarouselView.swift similarity index 90% rename from Apple/App/NetworkCarouselView.swift rename to Apple/UI/NetworkCarouselView.swift index b120c60..f969356 100644 --- a/Apple/App/NetworkCarouselView.swift +++ b/Apple/UI/NetworkCarouselView.swift @@ -2,10 +2,10 @@ import SwiftUI struct NetworkCarouselView: View { var networks: [any Network] = [ - HackClub(id: "1"), - HackClub(id: "2"), - WireGuard(id: "4"), - HackClub(id: "5"), + HackClub(id: 1), + HackClub(id: 2), + WireGuard(id: 4), + HackClub(id: 5) ] var body: some View { diff --git a/Apple/App/NetworkExtension+Async.swift b/Apple/UI/NetworkExtension+Async.swift similarity index 82% rename from Apple/App/NetworkExtension+Async.swift rename to Apple/UI/NetworkExtension+Async.swift index 4833efb..5820e7f 100644 --- a/Apple/App/NetworkExtension+Async.swift +++ b/Apple/UI/NetworkExtension+Async.swift @@ -1,6 +1,6 @@ import NetworkExtension -extension NEVPNManager { +extension NEVPNManager: @unchecked @retroactive Sendable { func remove() async throws { _ = try await withUnsafeThrowingContinuation { continuation in removeFromPreferences(completionHandler: completion(continuation)) @@ -14,7 +14,7 @@ extension NEVPNManager { } } -extension NETunnelProviderManager { +extension NETunnelProviderManager: @unchecked @retroactive Sendable { class var managers: [NETunnelProviderManager] { get async throws { try await withUnsafeThrowingContinuation { continuation in @@ -34,7 +34,7 @@ private func completion(_ continuation: UnsafeContinuation) -> (Err } } -private func completion(_ continuation: UnsafeContinuation) -> (T?, Error?) -> Void { +private func completion(_ continuation: UnsafeContinuation) -> (T?, Error?) -> Void { return { value, error in if let error { continuation.resume(throwing: error) diff --git a/Apple/App/NetworkExtensionTunnel.swift b/Apple/UI/NetworkExtensionTunnel.swift similarity index 67% rename from Apple/App/NetworkExtensionTunnel.swift rename to Apple/UI/NetworkExtensionTunnel.swift index 08002de..7aaa3b1 100644 --- a/Apple/App/NetworkExtensionTunnel.swift +++ b/Apple/UI/NetworkExtensionTunnel.swift @@ -1,22 +1,23 @@ -import BurrowShared +import BurrowCore import NetworkExtension @Observable -class NetworkExtensionTunnel: Tunnel { - @MainActor private(set) var status: TunnelStatus = .unknown - private var error: NEVPNError? +public final class NetworkExtensionTunnel: Tunnel { + @MainActor public private(set) var status: TunnelStatus = .unknown + @MainActor private var error: NEVPNError? private let logger = Logger.logger(for: Tunnel.self) private let bundleIdentifier: String - private var tasks: [Task] = [] + private let configurationChanged: Task + private let statusChanged: Task // Each manager corresponds to one entry in the Settings app. // Our goal is to maintain a single manager, so we create one if none exist and delete any extra. - private var managers: [NEVPNManager]? { + @MainActor private var managers: [NEVPNManager]? { didSet { Task { await updateStatus() } } } - private var currentStatus: TunnelStatus { + @MainActor private var currentStatus: TunnelStatus { guard let managers = managers else { guard let error = error else { return .unknown @@ -41,35 +42,40 @@ class NetworkExtensionTunnel: Tunnel { return manager.connection.tunnelStatus } - convenience init() { - self.init(Constants.networkExtensionBundleIdentifier) - } - - init(_ bundleIdentifier: String) { + public init(bundleIdentifier: String) { self.bundleIdentifier = bundleIdentifier let center = NotificationCenter.default - let configurationChanged = Task { [weak self] in - for try await _ in center.notifications(named: .NEVPNConfigurationChange).map({ _ in () }) { - await self?.update() + let tunnel: OSAllocatedUnfairLock = .init(initialState: .none) + configurationChanged = Task { + for try await _ in center.notifications(named: .NEVPNConfigurationChange) { + try Task.checkCancellation() + await tunnel.withLock { $0 }?.update() } } - let statusChanged = Task { [weak self] in - for try await _ in center.notifications(named: .NEVPNStatusDidChange).map({ _ in () }) { - await self?.updateStatus() + statusChanged = Task { + for try await _ in center.notifications(named: .NEVPNStatusDidChange) { + try Task.checkCancellation() + await tunnel.withLock { $0 }?.updateStatus() } } - tasks = [configurationChanged, statusChanged] + tunnel.withLock { $0 = self } Task { await update() } } private func update() async { do { - managers = try await NETunnelProviderManager.managers + let result = try await NETunnelProviderManager.managers + await MainActor.run { + managers = result + status = currentStatus + } await self.updateStatus() } catch let vpnError as NEVPNError { - error = vpnError + await MainActor.run { + error = vpnError + } } catch { logger.error("Failed to update VPN configurations: \(error)") } @@ -82,12 +88,7 @@ class NetworkExtensionTunnel: Tunnel { } func configure() async throws { - if managers == nil { - await update() - } - - guard let managers = managers else { return } - + let managers = try await NETunnelProviderManager.managers if managers.count > 1 { try await withThrowingTaskGroup(of: Void.self, returning: Void.self) { group in for manager in managers.suffix(from: 1) { @@ -110,9 +111,9 @@ class NetworkExtensionTunnel: Tunnel { try await manager.save() } - func start() { - guard let manager = managers?.first else { return } + public func start() { Task { + guard let manager = try await NETunnelProviderManager.managers.first else { return } do { if !manager.isEnabled { manager.isEnabled = true @@ -125,12 +126,14 @@ class NetworkExtensionTunnel: Tunnel { } } - func stop() { - guard let manager = managers?.first else { return } - manager.connection.stopVPNTunnel() + public func stop() { + Task { + guard let manager = try await NETunnelProviderManager.managers.first else { return } + manager.connection.stopVPNTunnel() + } } - func enable() { + public func enable() { Task { do { try await configure() @@ -141,7 +144,8 @@ class NetworkExtensionTunnel: Tunnel { } deinit { - tasks.forEach { $0.cancel() } + configurationChanged.cancel() + statusChanged.cancel() } } diff --git a/Apple/App/NetworkView.swift b/Apple/UI/NetworkView.swift similarity index 100% rename from Apple/App/NetworkView.swift rename to Apple/UI/NetworkView.swift diff --git a/Apple/App/Networks/HackClub.swift b/Apple/UI/Networks/HackClub.swift similarity index 76% rename from Apple/App/Networks/HackClub.swift rename to Apple/UI/Networks/HackClub.swift index f7df674..b1c2023 100644 --- a/Apple/App/Networks/HackClub.swift +++ b/Apple/UI/Networks/HackClub.swift @@ -1,10 +1,14 @@ +import BurrowCore import SwiftUI struct HackClub: Network { - var id: String + typealias NetworkType = Burrow_WireGuardNetwork + static let type: Burrow_NetworkType = .hackClub + + var id: Int32 var backgroundColor: Color { .init("HackClub") } - var label: some View { + @MainActor var label: some View { GeometryReader { reader in VStack(alignment: .leading) { Image("HackClub") diff --git a/Apple/UI/Networks/Network.swift b/Apple/UI/Networks/Network.swift new file mode 100644 index 0000000..c6d5fba --- /dev/null +++ b/Apple/UI/Networks/Network.swift @@ -0,0 +1,36 @@ +import Atomics +import BurrowCore +import SwiftProtobuf +import SwiftUI + +protocol Network { + associatedtype NetworkType: Message + associatedtype Label: View + + static var type: Burrow_NetworkType { get } + + var id: Int32 { get } + var backgroundColor: Color { get } + + @MainActor var label: Label { get } +} + +@Observable +@MainActor +final class NetworkViewModel: Sendable { + private(set) var networks: [Burrow_Network] = [] + + private var task: Task! + + init(socketURL: URL) { + task = Task { [weak self] in + let client = NetworksClient.unix(socketURL: socketURL) + for try await networks in client.networkList(.init()) { + guard let viewModel = self else { continue } + Task { @MainActor in + viewModel.networks = networks.network + } + } + } + } +} diff --git a/Apple/App/Networks/WireGuard.swift b/Apple/UI/Networks/WireGuard.swift similarity index 82% rename from Apple/App/Networks/WireGuard.swift rename to Apple/UI/Networks/WireGuard.swift index 499288a..cba67ef 100644 --- a/Apple/App/Networks/WireGuard.swift +++ b/Apple/UI/Networks/WireGuard.swift @@ -1,10 +1,14 @@ +import BurrowCore import SwiftUI struct WireGuard: Network { - var id: String + typealias NetworkType = Burrow_WireGuardNetwork + static let type: BurrowCore.Burrow_NetworkType = .wireGuard + + var id: Int32 var backgroundColor: Color { .init("WireGuard") } - var label: some View { + @MainActor var label: some View { GeometryReader { reader in VStack(alignment: .leading) { HStack { diff --git a/Apple/App/OAuth2.swift b/Apple/UI/OAuth2.swift similarity index 94% rename from Apple/App/OAuth2.swift rename to Apple/UI/OAuth2.swift index 9a930c9..0fafc8d 100644 --- a/Apple/App/OAuth2.swift +++ b/Apple/UI/OAuth2.swift @@ -1,5 +1,6 @@ import AuthenticationServices import Foundation +import os import SwiftUI enum OAuth2 { @@ -25,11 +26,16 @@ enum OAuth2 { var clientID: String var clientSecret: String - fileprivate static var queue: [Int: CheckedContinuation] = [:] + fileprivate static let queue: OSAllocatedUnfairLock<[Int: CheckedContinuation]> = { + .init(initialState: [:]) + }() fileprivate static func handle(url: URL) { - let continuations = queue - queue.removeAll() + let continuations = queue.withLock { continuations in + let copy = continuations + continuations.removeAll() + return copy + } for (_, continuation) in continuations { continuation.resume(returning: url) } @@ -56,7 +62,7 @@ enum OAuth2 { var queryItems: [URLQueryItem] = [ .init(name: "client_id", value: clientID), .init(name: "response_type", value: responseType.rawValue), - .init(name: "redirect_uri", value: redirectURI.absoluteString), + .init(name: "redirect_uri", value: redirectURI.absoluteString) ] if !scopes.isEmpty { queryItems.append(.init(name: "scope", value: scopes.joined(separator: ","))) @@ -206,6 +212,9 @@ enum OAuth2 { } } +extension WebAuthenticationSession: @unchecked @retroactive Sendable { +} + extension WebAuthenticationSession { #if canImport(BrowserEngineKit) @available(iOS 17.4, macOS 14.4, tvOS 17.4, watchOS 10.4, *) @@ -243,12 +252,12 @@ extension WebAuthenticationSession { let id = Int.random(in: 0..