pax_global_header00006660000000000000000000000064125770745050014526gustar00rootroot0000000000000052 comment=4c633c324bc9490004434b28adc46bcb7b778b38 ippusbxd-1.23/000077500000000000000000000000001257707450500133115ustar00rootroot00000000000000ippusbxd-1.23/.gitignore000066400000000000000000000000061257707450500152750ustar00rootroot00000000000000exe/* ippusbxd-1.23/.travis.yml000066400000000000000000000014241257707450500154230ustar00rootroot00000000000000language: c script: make compiler: - clang - gcc before_install: - sudo apt-get update -qq - sudo apt-get install -y libusb-1.0-0-dev env: global: - secure: CtAND8k/8Lfs6o4dmrD9Jp4yLjLQRrv5TZA1MkhSO/LRfViKlbzy7ENv1/ku0xgtnsegFRDFY75/dLHxZUiax5TGNVrHqpkkGSE9h2a5YYHxbOtINb8Hdzk+S2/EN9YPmrOEX1ZjMg/wH08c3MntdW6gDUuddQhjCuaNnH+8fUc= - secure: foN6qnWGy6p47PEutfZtUYUcg3DVHtuY3hfODDLDjc3TF7w7W9p/Mf/w4u++rkLuoKu/hESoiyj7/bSePx98EJgXsDprS/JkT8KKCaSsyViaIizrnF4YTQac/FC+A+7BUdg6xil0FI1xMipHQ/Z+nSwMT2ueeRThApNgufz66qw= addons: coverity_scan: project: name: daniel-dressler/ippusbxd description: IPP over USB daemon driver for linux notification_email: danieru.dressler@gmail.com build_command_prepend: build_command: make branch_pattern: coverity_check ippusbxd-1.23/doc/000077500000000000000000000000001257707450500140565ustar00rootroot00000000000000ippusbxd-1.23/doc/behaviour000066400000000000000000000001401257707450500157600ustar00rootroot00000000000000- Must use CRLF for transfer: chunked messages - Rotates USB interfaces between TCP connections ippusbxd-1.23/doc/ippusbxd.1000066400000000000000000000032261257707450500160010ustar00rootroot00000000000000.TH IPPUSBXD 1 .SH NAME ippusbxd \- userland driver for IPP-over-USB class printers .SH SYNOPSIS .B ippusbxd [\fB\-v \fR \fIVENDOR_ID\fR] [\fB\-m \fR \fIPRODUCT_ID\fR] [\fB\-s \fR \fISERIAL_NUMBER\fR] [\fB\-p \fR \fIPORT_NUMBER\fR] [\fB\-P \fR \fIPORT_NUMBER\fR] [\fB\-l\fR] [\fB\-d\fR] [\fB\-q\fR] [\fB\-n\fR] [\fB\-d\fR] [\fB\-N\fR] .SH DESCRIPTION .B ippusbxd connects to a IPP-over-USB printer and exposes it to localhost. Upon successful startup the TCP port it is listening on is printed to stdout. It will shut itself down when the connected printer disconnects. By default \fBippusbxd\fR scans and connects to the first available IPP-over-USB printer. .SH OPTIONS .TP .BR \-h Show help message. .TP .BR \-v = \fIVENDOR_ID\fR USB vendor id of desired printer. .TP .BR \-m = \fIPRODUCT_ID\fR USB product id of desired printer. .TP .BR \-s = \fISERIAL_NUMBER\fR Serial number of desired printer. .TP .BR \-p = \fIPORT_NUMBER\fR Port number ippusbxd will expose the printer over. If this port is already taken, ippusbxd will error out. .TP .BR \-P = \fIPORT_NUMBER\fR Port number ippusbxd will expose the printer over. If this port is already taken, ippusbxd will increase the port number by 1 and try again until it finds a free port. .TP .BR \-l Send all logging to syslog. .TP .BR \-q Enable verbose logging. .TP .BR \-n Enables no fork mode. Disables deamonization. .TP .BR \-d Enables debug mode. Implies \-q and \-n. Verbose logging will be sent to stdout .TP .BR \-N No-printer mode, debug/developer mode which makes ippusbxd run without IPP-over-USB printer .SH EXAMPLES To bind to the first available IPP-over-USB printer and see trace statements: .PP .nf .RS ippusbxd \-d ippusbxd-1.23/doc/ippusbxd_logo.svg000066400000000000000000000066451257707450500174700ustar00rootroot00000000000000 image/svg+xml Extreme! IPP over USB ippusbxd ippusbxd-1.23/doc/ippusbxd_presentation.odp000066400000000000000000001406121257707450500212170ustar00rootroot00000000000000PKLE3&//mimetypeapplication/vnd.oasis.opendocument.presentationPKLE,,Thumbnails/thumbnail.pngPNG  IHDRd*1+IDATxwֆ{v6.JP$䬄 J" (AD (H$ \ HR$U^ ,6|a+fvW@Wfz_OOuթPU]v-"V ``p+a``k!a``k!a``k!a``k!a``k!a``kFv;9PiԊj(Z1VPJ˕INӨ튠$Α3-8 ukbpk`Sݻ7]tiiBBBl^}Ոm#D";v=: 3(PYfjm#'Nwy'ڙ!ce͚c AF/Yd֭wuW\\\i$ 9rdڴizҢ-[`"hhLL ?9Jv- 2f̸`F.\Xn,Fb?Ceʔi· .n7N ѕ 䧟~3gNdddZ>PN`͚5ׯQ` XI_&O|93q{M&J*'p 87n\jUJ#?#ݜy0ֵ ;cϞ=x֭[!A;(}ꌏOrک}-D_p`֬Yϖ-d_A@ϟ?׮]˗'UWˣ7 : y}yM(.eȐ{̘1IExmˊ=***}CbºvJn lѶĨwԉ(+&&F܈h9#@%*WlyRNp3gE}=MkdĺA'|KP-ܷoI(n۷oWzF{n''%4mڴCsFaonLd|4tL8ŋ>=< +W. ::ʕ+h**K@Ot=*&<<c߶m[xUBnݺXC<Ak9sFDxrZ"N.x =]v ~zn:(^?oy̿\8hРM6֭[WTZXj֭[*T/bX^8Vz?lpsqc2Hdɒ#G=Zj1" @ /|2_m$s͐! NWA l9I:!Á7xCF?|G.]%2l?po 7%-4y5Vg`ޡU*.cML!$wVMI DǧO.T.],o8)p-+8X "J_!ObQߨ:J=>ԝQe;:sҿ_\k] NUH{",m3**ԩSɚlժUɒ%Wvz}ݖ'7ڛ+0{D IV3tIYZ*`Y/EREnmO54G|ͧ~?):Aț3gζmZd˖ͧf]Ĝ[Gay虴f+ P ( [G ]}s|&HnQ1*_ u|l>%gz) >BҴgW\!zjժխ[^N/XPgb u=xgO@L/Μ93g*TP yhsz6?6m={6^E$ ۵kt뮻W^A B%hݻ9Y|,XPkQA{ԩt9|O<-+V|GQF.Rѣio׮s!@ܹRldV# J, fOҲkZzu_Y Pcj*jG-~;a2QB8Ei♼ybQJ ( zjU8HPLy*f!R~$!"$Sa6QTƟ@4"Mϟ$&DΝ;@fFm_/U¼hLD|Mp'LVLB%(1^q#L 0A!cI>6r8/n6(OHi蓟EsjK~X TbBSlj =ReZ)c}*@AݦMl3E  q$h~99I,뭈97Tyb#FOcmFN Iw=툯CecgЕ~ 9oB6i+gΜb* [?Q2x@yȢ@;E.Yѣ2_CqpUHCdo@[\NJ@wQA=h>'9&G͚5 "Do-7 gO?v˗fbK{E8R TbHFsȁwBWp/+x衇,1۱/]tʕ˕+'J6"a.& ? 1bŊu֍H>?H@ iJJ>`[C5[l]Fta_~IRW8 vQo]ՃΩ%]v݄.Wd^8(Ish3є G*2Z-$t`F|%6u`1?v{Kʯ,W'PR]H@!̼KoGJLL'b?7ږۻϘE<E*i/x Ibˣ5k:uJȄP:}jӭ R֭Wm3&tR8 g?9z >73X(px٤IlNg*E!,X@sy,>y$[~$(S,"R\(n˻ ]BԈ(M\YUJmT2HO+۷߿c ̞=Yf+>HvءduGwy aN4N:y [V^]a,o5~5`(i:) HONz;6ʔ)S G(MBL50b͛'$+Z^KFA<- %9C1g=^Wd1L\mRJDm۶%>p m0s$dGB2u=+WٳP^nT_$\B``$V4 àA`' Ep-ΝS|FnDY}PNSBŋ `<ȑ#~a޽]7c۶m#OF 3!'a[1FA"WQrȑ}PM%$Z2!ѣG6mU08CE! Kx.\K,ю껨#mai */FP޼yK0"9>}kB4>gH}ѣGSaݺueW}C4&Bd@Pʒf_)ō]ХZjT/:sϑM0OGX U8˗-[O?4"u ۇ䦝8qg$#(,{g t>,('ϓ'O˖-.P;6c/%g Ly}Qb8@ ,STRIXfM:,"@HL8VZ9AVp3( /l}N%eHȆʅ\< (HbӰaí["!aa, /ʹxܹCo߾ٳg93:5~x8PX1:E*U?c ']n uD4ԒEۻG;|,Ɇef%w_\U Oucd8}B0Y;v,JK%?D5X8 u_sM ЪU+D FsXh(ڣ zĂrz ԇ4hj%>~ۻu)Zt]6wq#xŋ8O ˻x饗p'۽{7fUFT(˫ %bI3&OM'|\>fBOѢ#he?Zr= Nl*U"(Q#V@{rPYOJۼys1Y΀Ip G$@(Fԃ6Өhre/_> T%Kl֬I6Ya*Xbt_q=C`?("(F"Dj.UNf֬Y$9bh_Dy4~T(,o*/^ B@H= 5ɛϟOI'Aˑ/MX^=z m$L H k'~駲gH_QS]lӦUo0)H+b [,YD Du_ETڵktt44 oy "" HdR2Dk ?@%12f,~,**J7 & FIs1ehi^tK$)8߮];<'@Z8g%fٖBuH&0x+KI5N  Pp۶mN:ezpCqIv…z+[lbyZm9ͫRJ\hûw)gEw7Y3B 6لhܸqlyӧ˔)ӬY;v̜9SVqUzVV+{liJ/sp>eqa5\paΝU]vaA~Gng֭f:p@bhzIB (x^~~)fy'Y< =޽{˯'ݿ%dih.KnZ.ˌ Ecbb^˳JOWϟ?=k.ث*9~@٭=~g'|{}B/+VȔ)ܹs=zؽ{7(dr瓵kڵz!UIJ};`y0 \9 (M ן~)B/{\ҦM:N)St>` 4(]4koNw ҴȊ#CRHH&`HV!h pxߙ$nr͈=:{lyW.δi5jD\\9,_Μ98@x-ZU?Yl/^ܩS~{ɒ%`РA\8jԨUVPaݳg% WSEs͛#虼}4rA0YN1wN֦)@E=s \B>(e˖'NU û0] 9ېi0(e886n8~ e Ч]3-LU`PeD/%iРU; vp r %cyTeʔ!t%K-ZT6Qm׮]9vz_f9xYFf xPtL;dS,N4I"XgX6ƒ]-m@&2&'еkF`(!Kd2쁧<tpX͜9sryMA-ԧHI4yҺv UvxHOt_>B(4ad'YEH%/L빁.8RXVeKV}_w)\Kk_ @P@GJkQdvTb=TTt}JEN~J))omBP#>NH {Jջn^m0+x Om߫Rfէ)]챺:I̟ Ssp酓* ^-005  l C[005  l C[005  l C[005  l C[005  l C[005  l C[005  l C[005  l C[005  l C[005  l C[005  l C np1t{;v@w{J8_CBBNI~O _BC_(} JBp*{yJ&&&'|}+W!nRm]GU}6'a Q/\xyNSnڙ#ӹLW#ĉNHHq:hnB|\hXݟ1jPWB h|\\XxE&ДD>= <<4dTW9nw“8C Ǟne3%PgR1ˁJYzտ` xH>v#Og K.~Lʐ!Z@)/_q-ِ0T+᜿{W뷬Ot$v]BE'q|KgHfu.Ɔ;ĸCG^O!+66bCK ng$;lu}W̭s>/a̙0["Ӈޛ1o;/oվ{ݾ%&1ŋ.wS'l^1 2.6}҈GhHH*mqߗp s?p%>JJp:[v?2fTܼOXЦ/֗o>[ŒOV?S:/w5t삵KzZ.kS> T/:_~{<̺v3qGƟP/?.kړ~KɮÉ[-eջ20׫iأˍg߹{u &lXU7K*2HIB ];ʔ(0sdv̥?.\_j.^,o̅h)2}ؙQXwCUg($1+CziǞ1grb]}zIU ]V~5kw= 8{c69Cc}oFN$%!&)F֭YPh;^nK3߹b'Gyg.s\y 哰󵮰{re #MT,rR}?}귮ݺgΒ7W,6.>;,8rprvp,_s|uo뺯bNϕ*+qi3V~7f;.fg񉉱b#]둢/S \_z\,ZF`Dd~ ϐi -}_VO4]x9k~ӼSY"lvGOQάwۃwLeG2xa#+5pÇwyTJp7yQHσ6C_xFحtYrumbHH3m6oӪȅ_,~{Nn}*,V1~gh;+Uj݇ՓS{>ZYx{۞! URA Ę?gG!Ipg =)py3bǺd&$$&]2I4D3\lD"1$iMIeVOdϵNg(%'HU!޹99o?EyX(ˋ0EKu6JC*$1tK깜%dIo4Z!U᫵=x<4V(ߝQF/\?S$N̎ o 5H67KͲRzM gtqd#yUO)[ˌX{R\7?ޅ1iic<QR>[ y`E0 &I06qᚘ9%(%kf0Aq9M @k߶K~?#B<.+]?_A}OBݏp,]ON-A,Bg%'s5\+h~,fg 1 A]U_PK$ _.PKLE settings.xmlZ[o~ -Рu%Y+mHKrPHM-m7 -ڗ[?:$+SZE`Is83T@NX V{Plc;F=~7`;!߆I5jXJ(hwQIno4YLbJRTj;%(N%oN:d+7C]U+ ;X"8w'M%* sC(ʨ9ǣѵ"hQ.mByaQQo{nH<5IlO#̫PcmfR&OxDOȭ}X^'!c)4$zVx5v?rꆾ9؈|@u5~um}=(~XR9x i.CM-b1ܔ*@maq&r2|L0FC?+cqzWj"fʚO2";Gb/" bujV&)c/CPaz˶ :5 A@ezδVz4u4wpУYo(pc-E{nzLH-pro6%ffTp` 1wMU A9tc/ՏhN],gS.i WX(b.yktӈoᩚ9CxMX>M TPbU=+j*)@g t w22p,5W_; 65 >vY[yޡm_~Xc^,>vo7q3]&#rmn·6M]E,bUYDž T(.hIi$k(P*X~O*hݎF0hԀweWJU ԓX˞vѮN)qydmo@zJm:kxsOOz3Uc H@\T|dr[. `6˭g8*5+Ԭt[.JD<'XH[̐:^J Ů٭V*r-%yg^\JnRL<Ѐ(E]'P TauB]2Nj"ʧbr{vRs,a!⍲\;`4{ ژtY\R`VsƤxB;-WΔbjS&J Jis:q\ }+@v2cuLٲ]F8CIqfǖ+in7R VGY9WE*tSTӇWe+ u8 *'}o}jL[< SRLg¤^ B$u9>.|B]‚IrW*w$TCN.z\C挗TlglRnu8yҡbbU֠~.ސ.7T?P4cSՋQc3g'E(*/]EOb ߖk$EFKL]9S$Uf?'=^npZJnqt {a ._cE;i exUW͐QkdB7,)v'6~~_~ ~]^M Nm: BS#?p@hMeJ[zEGZF.0>.T>NȅF3U4\xJ{sȴ fgd~M{;vfϘdv Et)ZB Tq*U⹊ͮB.f wag|CEIn$תK&y~e2ɺ:-Omݾ=[9y#;[˜ ˤ_9 7;cɑ3 x;g\Vj{+)tu~)f6f ]wƭ</ mP"(zҶ2b95A~9gf#)fV,}w]yM`smG΢6IX%(aU_SDmpkaԌo+h 7 >.X @%,9*jh?LeA7>ָ,8T7AxJc}C5Ru/o^"BU}5Pm/im=!TS;"Bi j_:U'֮U=c.{QoNոk^]n͏ѡ{Cۋ3㾃(Q֞Pۋ;{pr_ 2ճqxyaiTѓ8yHgٜ<2D9lDrੴU]P3/J~LeL>BbextftkQt<(̶OSLB:d˰07jdtQlz.7y?>26U긐eka>Q&ꩌdѓW*TpfЗM;C 0UyO$RZ_^=txQcēN$nc}~ ucԞ=3ڝǏ%W QSb(ǷvItFzce.k-:>sbfb8l=+ d 1'y8e%Ij*r% FYDgSzҒǙ1yӐj2)*,JůI ptP\ʁE8'!ٌ*N?%_@<ۇr^JXgy# j7bcddnRhC4P=DFC478=?=LJv#3ن$BG`[2 X9H.l;"ps.B[W_gWS% T04[zmepeҤP쬛n~ κ8> 9r<}Ղ~X.]k,ѺYՌ+,{zyqS<&OOMiV/֮|d_X^{pE{Ne*"d]{"*Hfem%z,wxߔū7 'CYk̓kHT^u<8uW1vaE+n+\ j/d<>GsBIT|d pHYs&s&slu tEXtSoftwarewww.inkscape.org< IDATxw]Uo&AH* {лtBAIA齉KzGiR ɼ=d2dfns}gIϚ-g֠.DDDDD OHR" """"ҁt %"""""HHR" """"ҁt %"""""HHR" """"ҁt %"""""HHR" """"ҁt %"""""HHR" """"ҁt %"""""HHR" """"ҁt %"""""HH;雙 ̓;i[<T BDDD֠ %RlAHJ*̦!)<Tӯs!a53 w"""RA]]]claQt~C";)VH ;)V*tO܁H9"Pf6pb8cM;)7w67r!"""֠ 0)s"`ywHӊ@5e-r!"""ӊ@ff4 0(w,"DDDDNEIT,AbiE #3pI8D& Br""""Њ@&f60"w"}8!w"""R%̞;~ofBDDDAٜ3c>&w """V8% ;IO+%3s!Rw܁H:Z( & i%D\KB¹țH'BDDDQ"P_3B kٺ4tXf6?0,w,"M74G+8 %BDDD I#B97s""""ӊ@L,M ;ib,;lcfKBDDDD f6qdj'""Ҕ`AhE3Y DDD1:,\3[ x; ,DDDDb BDDD')gJ@DDDvZH&NH&G@Z{sB$lAH5(3xB%N0;VdfV)h)hM;;̒; w"""7n<L;zXGDDDDN+;%"Y-w"""2aZhܖ;0PN܁՘q$aC 8Y;Vdfg#AH`fg;#Rh@DDDdvu,M ;\;@;uLh'"""D>".`kYB~vK8W1 s!"""(- ;.ts!t:l ;&Jjw2ucAȄ{#i%r!"""ٺZH(w#w7w>w ~FDDDv.M;^ pH|;DDDDƧ Db`w_]IH5@?l&9`ܱ$t;MMxΘ;| dSDDڴ"пi$` G >@%$@DDtFf4u8;4p^J^6rUvM6VGbvws!s{?厃Ѓ``!w"w0"""m &F#3r!IL]AA!"""us"=AH$um!lkQuus!٤p-)V7JF{Brπ} 'JDDDړV"3[PBafw+' 8_K'$c0 `A9JD&K4ܿDcTm|/ kJo :J>[.P"$wmf{m{f")m%%#rPDcurǑr |~=p o:JDDD[GPq$p\ $=5u9CN] X(wBqgӁJDDD/"`f3#B*``F@:. t۝&w uʅjADDDZCGm 2ŀrǑ؅` DDDDtT" DBBDDDDZO$fZ8;Hlگq h"""""uD f ݿOlVq$v%w""""Һ>&DBc=r!""""3[0yQUkw r YS8'x} Ǚ ,&H0WҞOHmg)[L;prbY5[žBeo䎥FSf wgfS 6%ro:o'ﭺ%/ #3.x}9[vgj8v''@C؆JD"@XʫH!4 X>w, 1pYU+Y)rR!l܁H:qjv\Z+Ա%d?9-DL4~pku\r뮮CڙPjQ;|"`f+8A[U&gouVzD(a@$SesQ O÷Mo3V?)L Ұf6pr!$4WS&pmLd^n z78#_Wcf;#+ As\bf4'&tf:% A7k) 7~+i|JNTuӁ5^lFH*-;swrbuvִWMk4 "άI 3z6H`\[[R BsTI\#3Z6=ͬ64@,vT8{LD+^gN k}aI5 BX"nCRL.AҸfV!X \I(]=z ΥZ (BI]wgAc+O^G^!vql6L%F$4{h'{pR̍AnٷL1|/u8.={% 7{l2Mь}DBow!2 t]Mݽ'n>],wE䊀GXj'׹67UeSؚt{Gfw=}wp#}"U/) 6ph-g&!fvH{(wg δf+UN]53SSz$TRx0 2=V%Ƕ]Q}-mtoo&,&f#4&[Xrk+ -x]iΉ XhCCfԾ: Fh:aԄҞ \-Kx eO3ݟ{#a-BKl4UAKf6 i$ B' ]&lXjҶ\"`fGbsS&0B2 PsB I TjkP<(mM{$ qs~V!@(:"<@0wfU+ u4|R5QzЙ4oA8r#`2cJ 8ݷs ?w?Jp)g~df5uv2S+P^z>l¤LѺGs5&_Vg6X"JPo'$5w}o?#,mvga`w_iv0w7KX5(hEs՟KMݟp+3~}SnJ:V&wVL]j-' b \`fVc2$.:s(V#z|55oѲ-ے*􄥥v%&IO˺}2qfk;`#¹E5W!n,5V*/ՄiK(Ka물&`gBS;G?'lko?W?df"aLloWΔÄ3gǦT&Db{Xݟ/"~a(8v?R}P~M2nǛض |k:Pjy}okE koG]!S5qǨoa ;#H\l@1ILȍFsm *UWD̾MΈ*'eU3+Sƒf\Acc,֮EjXbliHWunDt37Hh۞bͬE7'fIX8{Q~_=0a{^v`/$T븭U ;b3ʲ}N'6)᛻?lHQMl ?7[T%$S\AX+eG2pCAP´x$x(f65ae$ʨzW)`qrn}:le {"`f=.Yج٫Y 'fL~^]E x&8l!؆Ћ?=4K9SǟO!]M Lm([?~aPlkZD&:F .1˙[aFcKdcN,x́TjY5!n0@[Ҽԕ^3Y{H=nI?D*L"l}1IKOR^? epZ7XҰ͎qQlr"U껥ZJ^- $w6RnKJ_ݶw X=Wp8i3[+w0-bd(|̈?^*@:a::x5s I$"fZѴcw"} \Kg̀R}x<; Q˙YQ St*@\?g ;wO<^b(m9~ J{p͑; K}ISfX㦞)xR+%}H@35w"P"Jwۢ?4ߖW{L',u"qlNB9"M\jfPc'pZof&'i ! kjŹG[w_"od Sl&(u"Sq|ph '')Ȩ@_3(f> ;p%tf6QbE`c6"df$Ͻ״㶤f`.`LAedڨcLմh&$\!/c[`5oy>x͘ ,ifMw5wn4aIj0fQ&-X5ef?*`f~_&">cf䌡JCw7!l]eW2 F[oRYX 񘏻{Cc-Ө! OG҅]J' \"AȜ&:o2=J0 D`pxI-}woaҔear 04+?v?*]j(rAc}k{#ܗ \3"Tj)ez4̆Rlӷ*rǑfZ kZI9 gd#3a~MlS4QI_fn5o D1gL)`̾Y?g(}W;X(Q Ff]/bekfwӽ!qj[L_"Sؗ6sʥnwON5*`=qkHL~ł}VV%yן ֗ pC2Iwf pbNkb Mlo+qX8VDGb;B&ܜ9yٛf ֮ }3v.Ox8x 7uw*e\6bVnw27=]"`hɀKͬmKq[8zC6,BߐVD tz;9j:Ukp9m,K58S]Tv1zZ6aٖ(~o3}K,1(nu2p.; {u8zZ̊&[33ۅPU:p1g:耥2۲/[U-wBfVDŞ>`dBQ :,πSeef3)Ѓ Ql3˲%$Ek5*~5i_wϛrv,=[r\}$ŝ hfk4ʼn?*D{S̡\(K5m\Ⱦz-~L([W~6<{/6/2׸{g6'l9+ƺXw3sI3['U`,cA±18M go+]u3pN19ٌt?.l4<6pm]1?S| JjD nUB>pWlMdf&̤9pajA}mfQ/"T(ؽ|sP^ozl3;pSVm6Uyf\I}驄ckߊ:gfCl8p >or ]SجwZS X2 ]䜙MefRgrHk@tF ;"d–1Sh #XEF Wpa3[6`f68.{X#ŘߙpGP ̪)3#Zv峅 [} ,*p]ifeܟ 3]K8V~1'&~ppmM$r^CC`pSmv/h(>X3{xИL,4-e̖q [{fSO[L3fax $̨OA Zt%כLP-.[~q3ngfϻ(AiD7)"Hٶ3ߺùLNieD&v,_7e%į"%TDZ|%\g lAn!l^1#pD9I#(ϟwЬgWIx?EM<~'BTg?X:^-1)'RK\*`T໹0 .Y h9kf䴋 7ԥ%'sj,fN^LL%/]\w{z@Ld M-{l6T ^"t i&.5kY6%VOu^j#mZjW5q$RRvSRbmŽC` Tu-[#ʺ`l繁f$\H8 }iff1.a;y좒O oj=} %W*u[]MMXS[_Z/[P\o. h+-D'RLGΜFQ[]%yX<;$dnTMbMc{P|,~=*/jpŠugNtK{49{]AqXHn =!qIH[Bv1&a _%n.hD .ELK?m,_eq[I>N*h^EnSjW^3٫aj<`nfkvao;t/R \g ? h+-H3=w0A4S׋-~G(؊\>r1?t{YUI*GГ}Zz|ezt@!g%lo]pKC.{TphW(yf- /jӚB衐QJ~XOC\|@}杒8a;Z'ڽjV+^/a` wy~_̜ 3IռW7nLCNvg.ZuO-c!;MlAtx.v}+ݯ@b9a~IXn') H%זYKhVe+˙؁Pn+eag-Zm]/܁ e[KN5f7H{V-`ۄ) OٰtbBIњ 3BV&t:axw merǑjbKM`U{`%wo7"wPGq.tJ^%ޠ|ԫP9eU:c/uOg>%T82p{XodfSf'Jv~u„d8&l%>1v9u.H37I3kצE9r Kw厥+Ҍ wXꕫ{X :2}`n`Zw{OpJnUU&٨VkEܽg%w~mj5[_w4 }P&l g }mg8&% V!oNWFQܹ] ,fc!e {s \=dwX pƦ ʊ&ed/*il3`˄<">؜U19w߄pv /=)mI뽏LP$~#aafDr{ŀ(л/E㶚c~7eu`w_[+,CB˄Fʩ%8tMo?%tW}ookR(fLe{@#+(s MXyLiQb_sL \lfCj Uq)w߿w؄|խh/ %w!l1(#!y%L]\5ffV5vm`ׁT1գ]fKBOpjUIz2%G_ Vr2Wu;ϜUIz2 7 ǯ%9M27y.+ůz|=+*Eu]X90 }BW !̺.yljBBar_P[E.gyPA/TeExkSꉯϓ׷=n lc;<4+-ȸ,aϦc|yɍ+{kD̶^iUD/f6 mj(–U1[c n'%(V|hf6{0z<''5) v;oK%&ݯǯeIŕݯoݟX}i &[*w, 56Ai7VIDDD$v<, $d:׹׶|UM+-;immD4[DDDDObH{f^ DDDDu}"GΆE:1[G$z~Hl^0#w 7wbf3BDDDDZO$pBWv1pt DDDDtT"Gb[ҹQ@t0u ;i-HlE3$w""""::.NDb#lAHkDG{#9s!""""#w)wgfsBDDDDchOA$4)0"w""""R}Hl3[)w""""RmD"SL""""ҧYt/sǑR6D :x,wefSBDDDDIc=rǑL!jR"Hl͛;%^%O)XJpkIY Ǯ5|/6Ho}}c3kV{qRtwϘ[#dwĘYfcsp5-?ky~~ ,Ϸ _ [cJwK62f_hܖSV?i 2 逓73\83;E afӛ5_? ``#!3fI`}\aff!+$ٱIT~x''"_9mFMR_֨sۆ0 XX0 a1H7[m?/^V lyZm=`3ݯ#/ *0Eaf/)H`;`܁dPAHF7=Hث =j3[?q ^83.alfjfK ) b$H{ofCu#Eof׻o=bfnueUu%N]^sID*o#/wN-"ٙWV' wW^;f# h㯧1uݼ%w#Hr;Y)+.SB%n[|}oRy(wqEz B瀛}T#?GL(7w6mt>̮"'if =.+%u3g#<ׁ׀jEcMLou5eMk4:پA9hɢ #TV_lrBeI۸9L,Q^fզ6ÁsRMK{'ǚЗxxM`IB LS"}r*~{ Ys}sX CSҒN=k> @|V]é D<=~Nue_½{*/O%!c#ud6}<| 79jᯈ#ؤ'(S,Z==s{GO^sshӕmރ_w;\^-r:a_r']j]$m7z_awaOxrkH&RԵ-$w| *-./BcغQ`fJ/,7Nɾހ{g*ǘ/pCHvWrv uY%"=\/Žt?0ےvN5ߑ]Ư%9X4ݳ./&f+%%efH^~9*nL#i=h,jW\qZ Si:^f;T=~OLK: 82|+~U_o4?gK8\ ~_朗\qh@2tf?g#5x)G^+C֬&\]3}̠̞QJN:&E( ىs<2^> `'cF\Jyś@OKJ|A }N1=EE(_6Ւm$uh1̌ٙE# s]kŤW[%"|q+`#3d5I٭.7_7w?3ś(1 L0(9_uM}ÀwյW3{fR酏ΓϵefYDEzqNfl`f_c,~K-yMPO7W;E_ ̶Jk졉4j%;ҽ+N'pH>`f=ʸp-="_S3~ۀlv36ISn4s!]$%n " M=+5!/#r+WqO-bbvUBNcf?|[H|}/d66b ׾O+3/)?.½.Oz7&5Ȯ2fÍ$-of|`'98&-cfWEǥOK:o2r7m#Nڭ㟀8ż}K)63^KŸ/QrBS$lXOYYN_ | Qz?D6>1Sj*yx])kM{r~HϱY=!_{Vt/St-RcxIh#kdhpcVe#q$TtIIԥ,;Z8>gFI.7!_f%mRY(z(!)i/#OV}s-P[E^iU*6V:WҬUg țKnf/=G=W@x2gZ/#GX *[Im\0oW5XOEу:eӘsr:aʑӛ=N=ᒱw_Kx%{A;PW}(Nëa}+a-ͧ]4 :UxwX='A}ļpDcX/:NiYkfgHڄ׹~Fg/ L3jY~rVtS C-jafKmw7SAhKT~ZWHF1e8V-,?D)҄'铅'WK^r*zypo%f֭hD S2ɍl\`f%G̿W [9^bmv ɝ KZ )ͬ_2nVuHF7oTŖuXa3QexEİz3\zxueNIDx {(WHj6nu_,U^{ ~<6 $=KP֫,_BDaۤjSň52lPr!M.+xI̠pCwny'BKhWV޷}/Jԍ8I=P6O?k={Ex-Q=^2w;_I>qǟ7H~~#a OW z"]dX bfJ?^r^%$!v)osvbk$MJ򅺴 <ֱ#4P5i'^?ſx#ՅJ<  Ӧ?K"lYj~S!0 ?j/KP% zm:~]NH^w2 ~R4>r ҍv]ZR=G >8M7Iڢ1Α<^IooTun"`fcpIbTHIݔ=dx-n\X>ؐto(e9E/fouMkZC'x8$I׌MURs ]%f6_@ְ!&w/GG<OUޯZsJEZ%F&;xt!k3S Q=bjWOf*IFax3+ \.Y]K Ii|1KkMBjVI;`o%mH>Te_52NfOR_]7-u#yvVȮ 5U{;X^>$BغWV]R%6y\$yu1A 1KF6. ʸ/U/7z:Y?ޮv0~Y!tבyb,.Ln .j)aШ[Sx^j m-7R>רa(FcC*y%|*j&/)x݅8PR=̭j"b)Ixm hTnX)hn4CJ M C4ĕ"J~X6cfmrJdjNjOusAU4_zDt|fD$ntl@s/d5b~rƗ%CJQ߾P\C^|P޾3w)n;<6|^4ma>qiHŲ%M8aeml{ibR.dm'a fp|JHڟrTa%L9?JZs"M CHR'yLG pN r[J~O ~E I"< \´,85Rv)3Z逜") HZT=-zI7 1+('n W'Lk_$m?[wL]nTQHz%pU5anz2S /&8ɛWahq^$5K$hnѫlBgS CS(O%{ǥjO>O{zZAJTwv_ʺfy#pJ|Rf \UxoڭE\,^6O?XLI+B=$'*6Y"dJc.DXxRI9oER_ fAϣF|Jr7𼀗^Vf*z5%TpjվNxU%}[k??'2_;V^ Jfg,`a k̥gLH{P^tNZ6%K(7:ّ̺'B0pݚ"JZ_QJ79 W;w/i ZsJXe [=:Az-sl}O3>7( ZMX7jMfvo< x1-IS0`zig D3~ʋ87e5:I3=t%>|w& fPҮxw큻$}V>I9;O餃~.CW?^y._EB,3}ʲFbf~6LOZߓ"~M;mf;~cf?ng5ooI5Qc?vnvmfH|78aI~7|,gyi>&bEG'Lѡocafvc&>8tsiaw &sHz酡̬iKVo3%m5~xY,Ug'zh Ia࣒n=JuqIzrP3CY%Oҵ+:͛ f]IU~2΅$O]?oj=:#$zdmwiwH7w7IzOp~_$<_C̬@1K%݁/^h`v|~z+i%]'2O{=K<`fvODkU"p* -po@jv0FzN1zNMaf Oոnޏ }.r0P&4v},Oˌg G0f%pwpx1O] #`tI&rs&3oF 1Nf_NHQM?>Z7xbmf,e0G# č#ih}3;U#2_ه(94>]{md~(̞t<a1,~WKZxvHCS%Sg$ O<^w=q_1=&?OK:IDATKxg NN~@lIRlF3{ʮ/A.>HKnq~j>EJc4\ƨ]uuuWuۻA ZUABͯJH 49ZB/a7+Vy- t-Adw"_PP0kiim°TՇ.$}M1F.Vd[-0 @nTKރ6)֮y UŰ뫊am Z+n^m*XЦǯ-&#VI6)@$_Nܪ$k%ehNc O^:nm1mϩ =ͩj@/p(dV^d,[n2HmL:lcWs#)T+QjZ5Dl}BL_"'K <0pHpir C#*}R'% ia֥2=Cx 6 f^MËtE &鰚?\K D@ &jC[WwKxayn@P3bGRl` %ڿk:2^XP٦FlN9 ;@K}F-P9!<s-@@т[Lu9#C6Ne3 b'x 1y5㒬-DגS{3TVS7.se.tnJ*t߲5$֐QBλd?2!HəIX7gZGg{V)vV.UBOC!W!p > L;Vא9%3@qs<qSH7 BxH"?$[.KBNɉ = e9/ V0TTy2x" /Q@,seO^D 1%*1rׯ󝒭 uv.lI՗6#ЈFU[j>KT`?-3goQuB1 ynSPI|xLISX+ڢd:'OpZC&/PCdG'%|NbFjlB!h0#TLQ΅ Ba E9MP(ccHO|(e?4r:aڦftH$s2byr]X%)UdU2q}MpCm'`kae(6n#a9ET8-`1eU6AXSxpy!1=8-"4H!+DhBE3n ӓ\6[D2ij{l2ǫ2Μ̼B؃H2/%]8(TcZJshnŔF%p7OJ ԈhNG 6]䉢?CSȬ.Q-m£ ,,k $^a XG$~ܚu<>`goY58@1yY2ߠUat*ڴ5<\ڄIRrrJH6L-s&g*4O B][B\hń #]qIo SaIWdߣnNRc0Yiԙ䬗[h%}RQ0lЍFOoY@epZ}MӁo)rȬ-tȐÓ Ct A 1_nt+rQ^Halalc s"i# S`$8;l0:K:i9K9~~vC͚GwNps] v8'`t5{.%_97@-VV@ectR*6uOp8[o̊ql2beyf{kO kI.C隻$JCk>,k.4&۵i^$>kjԴeOج¤.hrĠwA& g4)MrlIP>fт48ͥĊaMl1h/ץxV_oA+x7+J 3ð;5lA;x5I8^V>ƭ5z6xMZkl2[krxia~nVIiim,mTmwaHwaHwaș@uttSwaȓ9?KK8ͅ!T6^Nu'UIUwRu'&b$]t13Et1.FHy#HZ>c -Ƕ b%_HCZ>J qH>$*uTf}\<-z vǵxw+I|uqj#MUQ\,nx NDSq &@`&ک؍~|j8ߴ[Co<߄N7s3cЇL ֖T2Tǻ -UĘ*S~C'|גxYU*!}1]\笏)CS,1NJ&  ͷ9EX vL *Ew -j7AYX;\7Sv1_W|񕚡 ;6הЮ/sϨvzhnn`,v QoۉS s2bۥ>ۤ.=gcyh\d00B EsžcCqi:?8-&^xZ)|7zcNzv\#]^uwEG6kYUͫ]]Gѥ B]GѥtEK?Tonmv>?v6fl7Bf[ȵ.yKFl1'n~'Mgzi5hRR  Q~V8O]̄4"9™fIP| %\ 4m-V˩ 1Tt3ɯF =Kd*0xq;Te01NDz>9<ޏ {,~\;Qwȝ>7@0G#;5-!4) )J{|I0kJ3Y&Tmzo0Ou<5]:&]M`ÚtsdSi>R,ZwXue}ëk5BbZ˚o:x}>G ;@/#]qQъ75"i# 1I?O "v*c\6uΌ|\IBc=fd/_I^nيU7{0F -H gk Ҏ M@(pȦ2j|q2N8pE몢6@ɯR)׳+bF3s4ɍvrc(Cm1l}ñ.꘢ MQŌY㜋,'d_;#[ FMM̯(x@\l#kL^ _PKO'ĸPKLEConfigurations2/popupmenu/PKLEConfigurations2/toolbar/PKLEConfigurations2/menubar/PKLEConfigurations2/floater/PKLE'Configurations2/accelerator/current.xmlPKPKLEConfigurations2/images/Bitmaps/PKLEConfigurations2/toolpanel/PKLEConfigurations2/progressbar/PKLEConfigurations2/statusbar/PKLEMETA-INF/manifest.xmlTMo WDI{颦u"Tԏijl`>Zf%/X\-_ii%Ug'K0\šEA P\8- U뫑Svk>_ ### Terms and Conditions for use, reproduction, and distribution #### 1. Definitions “License” shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. “Licensor” shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. “Legal Entity” shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, “control” means **(i)** the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or **(ii)** ownership of fifty percent (50%) or more of the outstanding shares, or **(iii)** beneficial ownership of such entity. “You” (or “Your”) shall mean an individual or Legal Entity exercising permissions granted by this License. “Source” form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. “Object” form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. “Work” shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). “Derivative Works” shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. “Contribution” shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, “submitted” means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as “Not a Contribution.” “Contributor” shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. #### 2. Grant of Copyright License Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. #### 3. Grant of Patent License Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. #### 4. Redistribution You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: * **(a)** You must give any other recipients of the Work or Derivative Works a copy of this License; and * **(b)** You must cause any modified files to carry prominent notices stating that You changed the files; and * **(c)** You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and * **(d)** If the Work includes a “NOTICE” text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. #### 5. Submission of Contributions Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. #### 6. Trademarks This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. #### 7. Disclaimer of Warranty Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. #### 8. Limitation of Liability In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. #### 9. Accepting Warranty or Additional Liability While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. _END OF TERMS AND CONDITIONS_ ### APPENDIX: How to apply the Apache License to your work To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets `[]` replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same “printed page” as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ippusbxd-1.23/makefile000066400000000000000000000021731257707450500150140ustar00rootroot00000000000000SHELL := /bin/bash ifndef VERBOSE CMD_VERB := @ NOPRINTD := --no-print-directory endif ################################################################################ BuildTargets := all clean configure redep distclean .PHONY: $(BuildTargets) ################################################################################ all: ifeq ($(wildcard exe/Makefile),) $(CMD_VERB) $(MAKE) $(NOPRINTD) configure $(CMD_VERB) $(MAKE) $(NOPRINTD) -C exe else $(CMD_VERB) $(MAKE) $(NOPRINTD) -C exe endif ################################################################################ configure: $(CMD_VERB) rm -rf ./exe ; mkdir -p exe $(CMD_VERB) cd exe/ ; cmake ../src ################################################################################ redep: $(CMD_VERB) cd exe/ ; cmake ../src ; cd .. ################################################################################ clean: $(CMD_VERB) $(MAKE) $(NOPRINTD) -C exe clean ################################################################################ distclean: $(CMD_VERB) if [ -f exe/Makefile ]; \ then $(MAKE) $(NOPRINTD) -C exe clean ;\ fi $(CMD_VERB) rm -rf exe ippusbxd-1.23/readme.md000066400000000000000000000052511257707450500150730ustar00rootroot00000000000000# IPPUSBXD [![Coverity analysis status](https://scan.coverity.com/projects/2634/badge.svg)](https://scan.coverity.com/projects/2634) Version 1.23 About ======= IPPUSBXD is a userland driver for ipp over usb class usb devices. It has been designed for Linux but uses a cross platform usb library allowing eventual porting to Windows and other non-posixs platforms. The ipp over usb standard was ratified by the usb forum in 2012. As of 2014 Mac OSX implemented this standard and with the addition of ippusbxd soon linux shall as well. IPPUSBXD depends on posixs threads, posixs networking, and libusb as developed by the community at libusb.info IPPUSBXD has the following advantages; 1. At runtime links only with libc, pthreads, and libusb. On a typical system these libraries will already be in RAM. This gives ippusbxd a minimal ram footprint. 2. Requires no read access to any files. 3. Ships with a strict apparmor profile. 3. Runs warning & leak free in valgrind 4. Compiles warning free in clang 5. Analyzed warning free in Coverity 6. Can be installed anywhere 7. Near zero cpu usage while idle 8. Low cpu usage while working Building ======= To build ippusbxd you must have the libusb 1.0 development headers installed along with cmake. Under Ubuntu and Debian: sudo apt-get install libusb-1.0-0-dev cmake Under Fedora: sudo yum install libusbx-devel.* cmake Once the dependencies are installed simply run: make That will run a makefile which will inturn run cmake. This makefile also supports several GNU-style make commands such as clean, and redep. Presentation on IPPUSBXD ======= On August 2014 at the Fall Printer Working Group meeting I gave a presentation on ippusbxd and the ipp over usb protocol. Slides from this presentation can be found in the docs folder. IPPUSBXD, the name ======= The original name for this project was ippusbd. Part way through development it came to my attention that ippusbd was the name of the ipp over usb implemented used by Mac OSX. This prompted a rename and Ira of the OpenPrinting group and PWG suggested IPPUSBXD. Either all-caps IPPUSBXD or all-lower-case ippusbxd are valid names. License ======= Copyright 2014 Daniel Dressler Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ippusbxd-1.23/src/000077500000000000000000000000001257707450500141005ustar00rootroot00000000000000ippusbxd-1.23/src/CMakeLists.txt000066400000000000000000000021061257707450500166370ustar00rootroot00000000000000cmake_minimum_required(VERSION 2.6) project(ippusbxd) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -o2 -g -std=c99 -Wall -Wextra -pedantic -pedantic-errors") # Compiler specific configuration if (${CMAKE_C_COMPILER_ID} STREQUAL "GNU") # Nothing elseif( ${CMAKE_C_COMPILER_ID} STREQUAL "Clang") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Weverything -Wno-documentation -Wno-padded") # Compiler cannot detect that our logging functions really do use string # literals. So be careful to never-ever call printf with a user provided # format string since you will not get a warning. Coverity is smart enough # on this matter. set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-format-nonliteral") endif() # Pthreads find_package(Threads REQUIRED) # Libusb set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}) find_package(LIBUSB REQUIRED) include_directories(${LIBUSB_INCLUDE_DIR}) add_executable(ippusbxd ippusbxd.c http.c tcp.c usb.c logging.c options.c ) target_link_libraries(ippusbxd ${CMAKE_THREAD_LIBS_INIT}) target_link_libraries(ippusbxd ${LIBUSB_LIBRARIES}) ippusbxd-1.23/src/FindLIBUSB.cmake000066400000000000000000000023111257707450500166600ustar00rootroot00000000000000# Credit to chdromiumos project # - Try to find the freetype library # Once done this defines # # LIBUSB_FOUND - system has libusb # LIBUSB_INCLUDE_DIR - the libusb include directory # LIBUSB_LIBRARIES - Link these to use libusb # Copyright (c) 2006, 2008 Laurent Montel, # # Redistribution and use is allowed according to the terms of the BSD license. # For details see the accompanying COPYING-CMAKE-SCRIPTS file. if (LIBUSB_INCLUDE_DIR AND LIBUSB_LIBRARIES) # in cache already set(LIBUSB_FOUND TRUE) else (LIBUSB_INCLUDE_DIR AND LIBUSB_LIBRARIES) IF (NOT WIN32) # use pkg-config to get the directories and then use these values # in the FIND_PATH() and FIND_LIBRARY() calls find_package(PkgConfig) pkg_check_modules(PC_LIBUSB libusb-1.0) ENDIF(NOT WIN32) FIND_PATH(LIBUSB_INCLUDE_DIR libusb.h PATHS ${PC_LIBUSB_INCLUDEDIR} ${PC_LIBUSB_INCLUDE_DIRS}) FIND_LIBRARY(LIBUSB_LIBRARIES NAMES usb-1.0 PATHS ${PC_LIBUSB_LIBDIR} ${PC_LIBUSB_LIBRARY_DIRS}) #include(FindPackageHandleStandardArgs) #FIND_PACKAGE_HANDLE_STANDARD_ARGS(LIBUSB DEFAULT_MSG LIBUSB_LIBRARIES LIBUSB_INCLUDE_DIR) MARK_AS_ADVANCED(LIBUSB_INCLUDE_DIR LIBUSB_LIBRARIES) endif (LIBUSB_INCLUDE_DIR AND LIBUSB_LIBRARIES) ippusbxd-1.23/src/apparmor/000077500000000000000000000000001257707450500157215ustar00rootroot00000000000000ippusbxd-1.23/src/apparmor/usr.sbin.ippusbxd000066400000000000000000000010421257707450500212410ustar00rootroot00000000000000# vim:syntax=apparmor # Last Modified: Fri Sep 12 15:52:02 2014 # Author: Daniel Dressler #include /usr/sbin/ippusbxd { #include /usr/sbin/ippusbxd mr, # Scanning for USB devices /dev/bus/usb/ r, /etc/udev/udev.conf r, /sys/bus/ r, /sys/bus/usb/devices/ r, /sys/class/ r, /sys/devices/*/*/*/usb8/busnum r, /sys/devices/*/*/*/usb8/descriptors r, /sys/devices/*/*/*/usb8/devnum r, /sys/devices/*/*/*/usb8/speed r, /sys/devices/*/*/*/usb8/uevent r, } ippusbxd-1.23/src/http.c000066400000000000000000000361541257707450500152340ustar00rootroot00000000000000/* Copyright (C) 2014 Daniel Dressler and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include "http.h" #include "logging.h" #define BUFFER_STEP (1 << 12) struct http_message_t *http_message_new() { struct http_message_t *msg = calloc(1, sizeof(*msg)); if (msg == NULL) { ERR("failed to alloc space for http message"); return NULL; } msg->spare_capacity = 0; msg->spare_filled = 0; msg->spare_buffer = NULL; return msg; } void message_free(struct http_message_t *msg) { free(msg->spare_buffer); free(msg); } static void packet_check_completion(struct http_packet_t *pkt) { struct http_message_t *msg = pkt->parent_message; // Msg full if (msg->claimed_size && msg->received_size >= msg->claimed_size) { msg->is_completed = 1; NOTE("http: Message completed: Received size > claimed size"); // Sanity check if (msg->spare_filled > 0) ERR_AND_EXIT("Msg spare not empty upon completion"); } // Pkt full if (pkt->expected_size && pkt->filled_size >= pkt->expected_size) { pkt->is_completed = 1; NOTE("http: Packet completed: Packet full"); } // Pkt over capacity if (pkt->filled_size > pkt->buffer_capacity) { // Santiy check ERR_AND_EXIT("Overflowed packet buffer"); } } static int doesMatch(const char *matcher, size_t matcher_len, const uint8_t *key, size_t key_len) { for (size_t i = 0; i < matcher_len; i++) if (i >= key_len || matcher[i] != key[i]) return 0; return 1; } static int inspect_header_field(struct http_packet_t *pkt, size_t header_size, char *key, size_t key_size) { // Find key uint8_t *pos = memmem(pkt->buffer, header_size, key, key_size); if (pos == NULL) return -1; // Find first digit size_t number_pos = (size_t) (pos - pkt->buffer) + key_size; while (number_pos < pkt->filled_size && !isdigit(pkt->buffer[number_pos])) ++number_pos; // Find next non-digit size_t number_end = number_pos; while (number_end < pkt->filled_size && isdigit(pkt->buffer[number_end])) ++number_end; // Failed to find next non-digit // header field might be broken if (number_end >= pkt->filled_size) return -1; // Temporary stringification of buffer for atoi() uint8_t original_char = pkt->buffer[number_end]; pkt->buffer[number_end] = '\0'; int val = atoi((const char *)(pkt->buffer + number_pos)); // Restore buffer pkt->buffer[number_end] = original_char; return val; } static void packet_store_excess(struct http_packet_t *pkt) { struct http_message_t *msg = pkt->parent_message; if (msg->spare_buffer != NULL) ERR_AND_EXIT("Do not store excess to non-empty packet"); if (pkt->expected_size >= pkt->filled_size) ERR_AND_EXIT("Do not call packet_store_excess() unless needed"); size_t spare_size = pkt->filled_size - pkt->expected_size; size_t non_spare = pkt->expected_size; NOTE("HTTP: Storing %d bytes of excess", spare_size); // Align to BUFFER_STEP size_t needed_size = 0; needed_size += spare_size / BUFFER_STEP; needed_size += (spare_size % BUFFER_STEP) > 0 ? BUFFER_STEP : 0; if (msg->spare_buffer == NULL) { uint8_t *buffer = calloc(1, needed_size); if (buffer == NULL) ERR_AND_EXIT("Failed to alloc msg spare buffer"); msg->spare_buffer = buffer; } memcpy(msg->spare_buffer, pkt->buffer + non_spare, spare_size); pkt->filled_size = non_spare; msg->spare_capacity = needed_size; msg->spare_filled = spare_size; } static void packet_take_spare(struct http_packet_t *pkt) { struct http_message_t *msg = pkt->parent_message; if (msg->spare_filled == 0) return; if (msg->spare_buffer == NULL) return; if (pkt->filled_size > 0) ERR_AND_EXIT("pkt should be empty when loading msg spare"); // Take message's buffer size_t msg_size = msg->spare_capacity; size_t msg_filled = msg->spare_filled; uint8_t *msg_buffer = msg->spare_buffer; pkt->buffer_capacity = msg_size; pkt->filled_size = msg_filled; pkt->buffer = msg_buffer; msg->spare_capacity = 0; msg->spare_filled = 0; msg->spare_buffer = NULL; } static ssize_t packet_find_chunked_size(struct http_packet_t *pkt) { // NOTE: // chunks can have trailers which are // tacked on http header fields. // NOTE: // chunks may also have extensions. // No one uses or supports them. // Find end of size string if (pkt->filled_size >= SSIZE_MAX) ERR_AND_EXIT("Buffer beyond sane size"); ssize_t max = (ssize_t) pkt->filled_size; ssize_t size_end = -1; ssize_t miniheader_end = -1; ssize_t delimiter_start = -1; for (ssize_t i = 0; i < max; i++) { uint8_t *buf = pkt->buffer; if (size_end < 0) { // No extension if (i + 1 < max && ( buf[i] == '\r' && // CR buf[i + 1] == '\n')// LF ) { size_end = i + 1; miniheader_end = size_end; delimiter_start = i; break; } // No extension if (buf[i] == '\n') // LF { size_end = i; miniheader_end = size_end; delimiter_start = i; break; } // Has extensions if (buf[i] == ';') { size_end = i; continue; } } if (miniheader_end < 0) { if (i + 1 < max && ( buf[i] == '\r' && // CR buf[i + 1] == '\n')// LF ) { miniheader_end = i + 1; delimiter_start = i; break; } if (buf[i] == '\n') // LF { miniheader_end = i; delimiter_start = i; break; } } } if (miniheader_end < 0) { // NOTE: knowing just the size field // is not enough since the extensions // are not included in the size NOTE("failed to find chunk mini-header so far"); return -1; } // Temporary stringification for strtol() uint8_t original_char = *(pkt->buffer + size_end); *(pkt->buffer + size_end) = '\0'; size_t size = strtoul((char *)pkt->buffer, NULL, 16); NOTE("Chunk size raw: %s", pkt->buffer); *(pkt->buffer + size_end) = original_char; if (size > SSIZE_MAX) ERR_AND_EXIT("chunk size is insane"); if (size > 0) { // Regular chunk ssize_t chunk_size = (ssize_t) size; // Chunk body chunk_size += miniheader_end + 1; // Mini-header chunk_size += 2; // Trailing CRLF NOTE("HTTP: Chunk size: %lu", chunk_size); return (ssize_t) chunk_size; } // Terminator chunk // May have trailers in body ssize_t full_size = -1; for (ssize_t i = delimiter_start; i < max; i++) { uint8_t *buf = pkt->buffer; if (i + 3 < max && ( buf[i] == '\r' && // CR buf[i + 1] == '\n' && // LF buf[i + 2] == '\r' && // CR buf[i + 3] == '\n') // LF ) { full_size = i + 4; break; } if (i + 1 < max && buf[i] == '\n' && // LF buf[i + 1] == '\n') // LF { full_size = i + 2; break; } } if (full_size < 0) { NOTE("Chunk miniheader present but body incomplete"); return -1; } NOTE("Found end chunked packet"); pkt->parent_message->is_completed = 1; pkt->is_completed = 1; return full_size; } static ssize_t packet_get_header_size(struct http_packet_t *pkt) { if (pkt->header_size != 0) goto found; /* RFC2616 recomends we match newline on \n despite full * complience requires the message to use only \r\n * http://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html#sec19.3 */ // Find header for (size_t i = 0; i < pkt->filled_size && i < SSIZE_MAX; i++) { // two \r\n pairs if ((i + 3) < pkt->filled_size && '\r' == pkt->buffer[i] && '\n' == pkt->buffer[i + 1] && '\r' == pkt->buffer[i + 2] && '\n' == pkt->buffer[i + 3]) { pkt->header_size = i + 4; goto found; } // two \n pairs if ((i + 1) < pkt->filled_size && '\n' == pkt->buffer[i] && '\n' == pkt->buffer[i + 1]) { pkt->header_size = i + 2; goto found; } } return -1; found: return (ssize_t) pkt->header_size; } enum http_request_t packet_find_type(struct http_packet_t *pkt) { enum http_request_t type = HTTP_UNSET; size_t size = 0; /* Valid methods for determining http request * size are defined by W3 in RFC2616 section 4.4 * link: http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.4 */ /* This function attempts to find what method this * packet would use. This is only possible in specific case: * 1. if the request uses method 1 we can check the http * request type. We must be called on a packet which * has the full header. * 2. if the request uses method 2 we need the full header * but a simple network-byte-order-aware string search * works. This function does not work if called with * a chunked transport's sub-packet. * 3. if the request uses method 3 we again perform the * string search. * * All cases require the packat to contain the full header. */ ssize_t header_size_raw = packet_get_header_size(pkt); if (header_size_raw < 0) { // We don't have the header yet goto do_ret; } size_t header_size = (size_t) header_size_raw; // Try Transfer-Encoding Chunked char xfer_encode_str[] = "Transfer-Encoding: chunked"; size_t xfer_encode_str_size = sizeof(xfer_encode_str) - 1; uint8_t *xfer_encode_pos = memmem(pkt->buffer, header_size, xfer_encode_str, xfer_encode_str_size); if (xfer_encode_pos != NULL) { size = 0; type = HTTP_CHUNKED; goto do_ret; } // Try Content-Length char content_length_str[] = "Content-Length: "; ssize_t contlen_size = inspect_header_field(pkt, header_size, content_length_str, sizeof(content_length_str) - 1); if (contlen_size >= 0) { size = (size_t) contlen_size + header_size; type = HTTP_CONTENT_LENGTH; goto do_ret; } // Get requests if (doesMatch("GET", 3, pkt->buffer, pkt->filled_size)) { size = header_size; type = HTTP_HEADER_ONLY; goto do_ret; } // No size was detectable yet header was found type = HTTP_UNKNOWN; size = 0; do_ret: pkt->parent_message->claimed_size = size; pkt->parent_message->type = type; return type; } size_t packet_pending_bytes(struct http_packet_t *pkt) { struct http_message_t *msg = pkt->parent_message; // Check Cache if (pkt->expected_size > 0) goto pending_known; if (HTTP_UNSET == msg->type) { msg->type = packet_find_type(pkt); if (HTTP_CHUNKED == msg->type) { // Note: this was the packet with the // header of our chunked message. // Save any non-header data we got ssize_t header_size = packet_get_header_size(pkt); // Sanity check if (header_size < 0 || (size_t)header_size > pkt->filled_size) ERR_AND_EXIT("HTTP: Could not find header twice"); NOTE("HTTP: Chunked header size is %ld bytes", header_size); pkt->expected_size = (size_t) header_size; msg->claimed_size = 0; goto pending_known; } } if (HTTP_CHUNKED == msg->type) { if (pkt->filled_size == 0) { // Grab chunk's mini-header goto pending_known; } if (pkt->expected_size == 0) { // Check chunk's mini-header ssize_t size = packet_find_chunked_size(pkt); if (size <= 0) { ERR("============================================="); ERR("Malformed chunk-transport http header receivd"); ERR("Missing chunk's mini-headers in first data"); ERR("Have %d bytes", pkt->filled_size); printf("%.*s\n", (int)pkt->filled_size, pkt->buffer); ERR("Malformed chunk-transport http header receivd"); ERR("============================================="); goto pending_known; } pkt->expected_size = (size_t) size; msg->claimed_size = 0; } goto pending_known; } if (HTTP_HEADER_ONLY == msg->type) { // Note: we can only know it is header only // when the buffer already contains the header. // So this next call cannot fail. pkt->expected_size = (size_t) packet_get_header_size(pkt); msg->claimed_size = pkt->expected_size; goto pending_known; } if (HTTP_CONTENT_LENGTH == msg->type) { // Note: find_header() has // filled msg's claimed_size msg->claimed_size = msg->claimed_size; pkt->expected_size = msg->claimed_size; goto pending_known; } pending_known: // Save excess data if (pkt->expected_size && pkt->filled_size > pkt->expected_size) packet_store_excess(pkt); size_t expected = pkt->expected_size; if (expected == 0) expected = msg->claimed_size; if (expected == 0) expected = pkt->buffer_capacity; // Sanity check if (expected < pkt->filled_size) ERR_AND_EXIT("Expected cannot be larger than filled"); size_t pending = expected - pkt->filled_size; // Expand buffer as needed while (pending + pkt->filled_size > pkt->buffer_capacity) { ssize_t size_added = packet_expand(pkt); if (size_added < 0) { WARN("packet at max allowed size"); return 0; } if (size_added == 0) { ERR("Failed to expand packet"); return 0; } } packet_check_completion(pkt); return pending; } void packet_mark_received(struct http_packet_t *pkt, size_t received) { struct http_message_t *msg = pkt->parent_message; msg->received_size += received; pkt->filled_size += received; if (received) { NOTE("HTTP: got %lu bytes so: pkt has %lu bytes, " "msg has %lu bytes", received, pkt->filled_size, msg->received_size); } packet_check_completion(pkt); if (pkt->filled_size > pkt->buffer_capacity) ERR_AND_EXIT("Overflowed packet's buffer"); if (pkt->expected_size && pkt->filled_size > pkt->expected_size) { // Store excess data packet_store_excess(pkt); } } struct http_packet_t *packet_new(struct http_message_t *parent_msg) { struct http_packet_t *pkt = NULL; uint8_t *buf = NULL; size_t const capacity = BUFFER_STEP; assert(parent_msg != NULL); pkt = calloc(1, sizeof(*pkt)); if (pkt == NULL) { ERR("failed to alloc packet"); return NULL; } pkt->parent_message = parent_msg; pkt->expected_size = 0; // Claim any spare data from prior packets packet_take_spare(pkt); if (pkt->buffer == NULL) { buf = calloc(capacity, sizeof(*buf)); if (buf == NULL) { ERR("failed to alloc space for packet's buffer or space for packet"); free(pkt); return NULL; } // Assemble packet pkt->buffer = buf; pkt->buffer_capacity = capacity; pkt->filled_size = 0; } return pkt; } void packet_free(struct http_packet_t *pkt) { free(pkt->buffer); free(pkt); } #define MAX_PACKET_SIZE (1 << 26) // 64MiB ssize_t packet_expand(struct http_packet_t *pkt) { size_t cur_size = pkt->buffer_capacity; size_t new_size = cur_size * 2; if (new_size > MAX_PACKET_SIZE) { WARN("HTTP: cannot expand packet beyond limit"); return -1; } NOTE("HTTP: doubling packet buffer to %lu", new_size); uint8_t *new_buf = realloc(pkt->buffer, new_size); if (new_buf == NULL) { // If realloc fails the original buffer is still valid WARN("Failed to expand packet"); return 0; } pkt->buffer = new_buf; pkt->buffer_capacity = new_size; size_t diff = new_size - cur_size; if (diff > SSIZE_MAX) ERR_AND_EXIT("Buffer expanded beyond sane limit"); return (ssize_t) diff; } ippusbxd-1.23/src/http.h000066400000000000000000000032001257707450500152230ustar00rootroot00000000000000/* Copyright (C) 2014 Daniel Dressler and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #pragma once #include #include enum http_request_t { HTTP_UNSET, HTTP_UNKNOWN, HTTP_CHUNKED, HTTP_CONTENT_LENGTH, HTTP_HEADER_ONLY }; struct http_message_t { enum http_request_t type; size_t spare_filled; size_t spare_capacity; uint8_t *spare_buffer; size_t unreceived_size; uint8_t is_completed; // Detected from child packets size_t claimed_size; size_t received_size; }; struct http_packet_t { // Cache size_t header_size; size_t filled_size; size_t expected_size; size_t buffer_capacity; uint8_t *buffer; struct http_message_t *parent_message; uint8_t is_completed; }; struct http_message_t *http_message_new(void); void message_free(struct http_message_t *); enum http_request_t packet_find_type(struct http_packet_t *pkt); size_t packet_pending_bytes(struct http_packet_t *); void packet_mark_received(struct http_packet_t *, size_t); struct http_packet_t *packet_new(struct http_message_t *); void packet_free(struct http_packet_t *); ssize_t packet_expand(struct http_packet_t *); ippusbxd-1.23/src/ippusbxd.c000066400000000000000000000231761257707450500161130ustar00rootroot00000000000000/* Copyright (C) 2014 Daniel Dressler and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include #include #include "options.h" #include "logging.h" #include "http.h" #include "tcp.h" #include "usb.h" struct service_thread_param { struct tcp_conn_t *tcp; struct usb_sock_t *usb_sock; pthread_t thread_handle; }; static void *service_connection(void *arg_void) { struct service_thread_param *arg = (struct service_thread_param *)arg_void; // clasify priority while (!arg->tcp->is_closed) { struct usb_conn_t *usb = NULL; struct http_message_t *server_msg = NULL; struct http_message_t *client_msg = NULL; // Client's request client_msg = http_message_new(); if (client_msg == NULL) { ERR("Failed to create client message"); break; } NOTE("M %p: Client msg starting", client_msg); while (!client_msg->is_completed) { struct http_packet_t *pkt; pkt = tcp_packet_get(arg->tcp, client_msg); if (pkt == NULL) { if (arg->tcp->is_closed) { NOTE("M %p: Client closed connection\n", client_msg); goto cleanup_subconn; } ERR("M %p: Got null packet from tcp", client_msg); goto cleanup_subconn; } if (usb == NULL && arg->usb_sock != NULL) { usb = usb_conn_acquire(arg->usb_sock, 1); if (usb == NULL) { ERR("M %p: Failed to acquire usb interface", client_msg); packet_free(pkt); goto cleanup_subconn; } NOTE("M %p: Interface #%d: acquired usb conn", client_msg, usb->interface_index); } NOTE("M %p P %p: Pkt from tcp (buffer size: %d)\n===\n%s===", client_msg, pkt, pkt->filled_size, hexdump(pkt->buffer, (int)pkt->filled_size)); // In no-printer mode we simply ignore passing the // client message on to the printer if (arg->usb_sock != NULL) { usb_conn_packet_send(usb, pkt); NOTE("M %p P %p: Interface #%d: Client pkt done", client_msg, pkt, usb->interface_index); } packet_free(pkt); } if (usb != NULL) NOTE("M %p: Interface #%d: Client msg completed\n", client_msg, usb->interface_index); else NOTE("M %p: Client msg completed\n", client_msg); message_free(client_msg); client_msg = NULL; // Server's response server_msg = http_message_new(); if (server_msg == NULL) { ERR("Failed to create server message"); goto cleanup_subconn; } if (usb != NULL) NOTE("M %p: Interface #%d: Server msg starting", server_msg, usb->interface_index); else NOTE("M %p: Server msg starting", server_msg); while (!server_msg->is_completed) { struct http_packet_t *pkt; if (arg->usb_sock != NULL) { pkt = usb_conn_packet_get(usb, server_msg); if (pkt == NULL) break; } else { // In no-printer mode we "invent" the answer // of the printer, a simple HTML message as // a pseudo web interface pkt = packet_new(server_msg); snprintf((char*)(pkt->buffer), pkt->buffer_capacity - 1, "HTTP/1.1 200 OK\r\nContent-Type: text/html; name=ippusbxd.html; charset=UTF-8\r\n\r\n

ippusbxd

Debug/development mode without connection to IPP-over-USB printer

\r\n"); pkt->filled_size = 183; // End the TCP connection, so that a // web browser does not wait for more data server_msg->is_completed = 1; arg->tcp->is_closed = 1; } NOTE("M %p P %p: Pkt from usb (buffer size: %d)\n===\n%s===", server_msg, pkt, pkt->filled_size, hexdump(pkt->buffer, (int)pkt->filled_size)); tcp_packet_send(arg->tcp, pkt); if (usb != NULL) NOTE("M %p P %p: Interface #%d: Server pkt done", server_msg, pkt, usb->interface_index); else NOTE("M %p P %p: Server pkt done", server_msg, pkt); packet_free(pkt); } if (usb != NULL) NOTE("M %p: Interface #%d: Server msg completed\n", server_msg, usb->interface_index); else NOTE("M %p: Server msg completed\n", server_msg); cleanup_subconn: if (client_msg != NULL) message_free(client_msg); if (server_msg != NULL) message_free(server_msg); if (usb != NULL) usb_conn_release(usb); } tcp_conn_close(arg->tcp); free(arg); return NULL; } static void start_daemon() { // Capture USB device if not in no-printer mode struct usb_sock_t *usb_sock; if (g_options.noprinter_mode == 0) { usb_sock = usb_open(); if (usb_sock == NULL) goto cleanup_usb; } else usb_sock = NULL; // Capture a socket uint16_t desired_port = g_options.desired_port; struct tcp_sock_t *tcp_socket = NULL, *tcp6_socket = NULL; for (;;) { tcp_socket = tcp_open(desired_port); tcp6_socket = tcp6_open(desired_port); if (tcp_socket || tcp6_socket || g_options.only_desired_port) break; // Search for a free port desired_port ++; // We failed with 0 as port number or we reached the max // port number if (desired_port == 1 || desired_port == 0) // IANA recommendation of 49152 to 65535 for ephemeral // ports // https://en.wikipedia.org/wiki/Ephemeral_port desired_port = 49152; NOTE("Access to desired port failed, trying alternative port %d", desired_port); } if (tcp_socket == NULL && tcp6_socket == NULL) goto cleanup_tcp; uint16_t real_port; if (tcp_socket) real_port = tcp_port_number_get(tcp_socket); else real_port = tcp_port_number_get(tcp6_socket); if (desired_port != 0 && g_options.only_desired_port == 1 && desired_port != real_port) { ERR("Received port number did not match requested port number." " The requested port number may be too high."); goto cleanup_tcp; } printf("%u|", real_port); fflush(stdout); NOTE("Port: %d, IPv4 %savailable, IPv6 %savailable", real_port, tcp_socket ? "" : "not ", tcp6_socket ? "" : "not "); // Lose connection to caller uint16_t pid; if (!g_options.nofork_mode && (pid = fork()) > 0) { printf("%u|", pid); exit(0); } // Register for unplug event if (usb_can_callback(usb_sock)) usb_register_callback(usb_sock); for (;;) { struct service_thread_param *args = calloc(1, sizeof(*args)); if (args == NULL) { ERR("Failed to alloc space for thread args"); goto cleanup_thread; } args->usb_sock = usb_sock; // For each request/response round we use the socket (IPv4 or // IPv6) which receives data first args->tcp = tcp_conn_select(tcp_socket, tcp6_socket); if (args->tcp == NULL) { ERR("Failed to open tcp connection"); goto cleanup_thread; } int status = pthread_create(&args->thread_handle, NULL, &service_connection, args); if (status) { ERR("Failed to spawn thread, error %d", status); goto cleanup_thread; } continue; cleanup_thread: if (args != NULL) { if (args->tcp != NULL) tcp_conn_close(args->tcp); free(args); } break; } cleanup_tcp: if (tcp_socket!= NULL) tcp_close(tcp_socket); if (tcp6_socket!= NULL) tcp_close(tcp6_socket); cleanup_usb: if (usb_sock != NULL) usb_close(usb_sock); return; } static uint16_t strto16(const char *str) { unsigned long val = strtoul(str, NULL, 16); if (val > UINT16_MAX) exit(1); return (uint16_t)val; } int main(int argc, char *argv[]) { int c; g_options.log_destination = LOGGING_STDERR; g_options.only_desired_port = 1; while ((c = getopt(argc, argv, "qnhdp:P:s:lv:m:N")) != -1) { switch (c) { case '?': case 'h': g_options.help_mode = 1; break; case 'p': case 'P': { long long port = 0; // Request specific port port = atoi(optarg); if (port < 0) { ERR("Port number must be non-negative"); return 1; } if (port > UINT16_MAX) { ERR("Port number must be %u or less, " "but not negative", UINT16_MAX); return 2; } g_options.desired_port = (uint16_t)port; if (c == 'p') g_options.only_desired_port = 1; else g_options.only_desired_port = 0; break; } case 'l': g_options.log_destination = LOGGING_SYSLOG; break; case 'd': g_options.nofork_mode = 1; g_options.verbose_mode = 1; break; case 'q': g_options.verbose_mode = 1; break; case 'n': g_options.nofork_mode = 1; break; case 'v': g_options.vendor_id = strto16(optarg); break; case 'm': g_options.product_id = strto16(optarg); break; case 's': g_options.serial_num = (unsigned char *)optarg; break; case 'N': g_options.noprinter_mode = 1; break; } } if (g_options.help_mode) { printf( "Usage: %s -v -m -p \n" "Options:\n" " -h Show this help message\n" " -v Vendor ID of desired printer\n" " -m Product ID of desired printer\n" " -s Serial number of desired printer\n" " -p Port number to bind against, error out if port already taken\n" " -P Port number to bind against, use another port if port already\n" " taken\n" " -l Redirect logging to syslog\n" " -q Enable verbose tracing\n" " -d Debug mode for verbose output and no fork\n" " -n No-fork mode\n" " -N No-printer mode, debug/developer mode which makes ippusbxd\n" " run without IPP-over-USB printer\n" , argv[0]); return 0; } start_daemon(); return 0; } ippusbxd-1.23/src/logging.c000066400000000000000000000044211257707450500156730ustar00rootroot00000000000000/* Copyright (C) 2014 Daniel Dressler and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include #include "logging.h" #include "options.h" void BASE_LOG(enum log_level level, const char *fmt, ...) { if (!g_options.verbose_mode && level != LOGGING_ERROR) return; va_list arg; va_start(arg, fmt); if (g_options.log_destination == LOGGING_STDERR) vfprintf(stderr, fmt, arg); else if (g_options.log_destination == LOGGING_SYSLOG) syslog(LOG_ERR, fmt, arg); va_end(arg); } char* hexdump (void *addr, int len) { int i; char linebuf[17]; char *pc = (char*)addr; char *outbuf, *outbufp; outbuf = calloc((len / 16 + 1) * 80, sizeof(char)); if (outbuf == NULL) return "*** Failed to allocate memory for hex dump! ***"; outbufp = outbuf; // Process every byte in the data. for (i = 0; i < len; i++) { if ((i % 16) == 0) { // Multiple of 16 means new line (with line offset). if (i != 0) { // Just don't print ASCII for the zeroth line. sprintf (outbufp, " %s\n", linebuf); outbufp += strlen(linebuf) + 3; } // Output the offset. sprintf (outbufp, " %08x ", i); outbufp += 11; } // Now the hex code for the specific character. sprintf (outbufp, " %02x", pc[i]); outbufp += 3; // And store a printable ASCII character for later. if ((pc[i] < 0x20) || (pc[i] > 0x7e)) linebuf[i % 16] = '.'; else linebuf[i % 16] = pc[i]; linebuf[(i % 16) + 1] = '\0'; } // Pad out last line if not exactly 16 characters. while ((i % 16) != 0) { sprintf (outbufp, " "); outbufp += 3; i++; } // And print the final ASCII bit. sprintf (outbufp, " %s\n", linebuf); return outbuf; } ippusbxd-1.23/src/logging.h000066400000000000000000000043021257707450500156760ustar00rootroot00000000000000/* Copyright (C) 2014 Daniel Dressler and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #pragma once #include // For pthread_self() #define TID() (pthread_self()) enum log_level { LOGGING_ERROR, LOGGING_WARNING, LOGGING_NOTICE, LOGGING_CONFORMANCE, }; #define PP_CAT(x, y) PP_CAT_2(x, y) #define PP_CAT_2(x, y) x##y # define LOG_ARITY(...) LOG_ARITY_2(__VA_ARGS__, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 0) # define LOG_ARITY_2(_15, _14, _13, _12, _11, _10, _9, _8, _7, _6, _5, _4, _3, _2, _1, _0, ...) _0 #define LOG_OVERLOAD(Name, ...) PP_CAT(Name, LOG_ARITY(__VA_ARGS__))(__VA_ARGS__) #define ERR(...) LOG_OVERLOAD(ERR_, __VA_ARGS__) #define ERR_1(msg) BASE_LOG(LOGGING_ERROR, "<%d>Error: " msg "\n", TID()) #define ERR_2(msg, ...) BASE_LOG(LOGGING_ERROR, "<%d>Error: " msg "\n", TID(), __VA_ARGS__) #define WARN(...) LOG_OVERLOAD(WARN_, __VA_ARGS__) #define WARN_1(msg) BASE_LOG(LOGGING_WARNING, "<%d>Warning: " msg "\n", TID()) #define WARN_2(msg, ...) BASE_LOG(LOGGING_WARNING, "<%d>Warning: " msg "\n", TID(), __VA_ARGS__) #define NOTE(...) LOG_OVERLOAD(NOTE_, __VA_ARGS__) #define NOTE_1(msg) BASE_LOG(LOGGING_NOTICE, "<%d>Note: " msg "\n", TID()) #define NOTE_2(msg, ...) BASE_LOG(LOGGING_NOTICE, "<%d>Note: " msg "\n", TID(), __VA_ARGS__) #define CONF(...) LOG_OVERLOAD(CONF_, __VA_ARGS__) #define CONF_1(msg) BASE_LOG(LOGGING_CONFORMANCE, "<%d>Standard Conformance Failure: " msg "\n", TID()) #define CONF_2(msg, ...) BASE_LOG(LOGGING_CONFORMANCE, "<%d>Standard Conformance Failure: " msg "\n", TID(), __VA_ARGS__) #define ERR_AND_EXIT(...) do { ERR(__VA_ARGS__); exit(-1);} while (0) void BASE_LOG(enum log_level, const char *, ...); char* hexdump (void *addr, int len); ippusbxd-1.23/src/options.c000066400000000000000000000012301257707450500157330ustar00rootroot00000000000000/* Copyright (C) 2014 Daniel Dressler and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "options.h" struct options g_options; ippusbxd-1.23/src/options.h000066400000000000000000000020251257707450500157430ustar00rootroot00000000000000/* Copyright (C) 2014 Daniel Dressler and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #pragma once #include enum log_target { LOGGING_STDERR, LOGGING_SYSLOG }; struct options { // Runtime configuration uint16_t desired_port; int only_desired_port; enum log_target log_destination; // Behavior int help_mode; int verbose_mode; int nofork_mode; int noprinter_mode; // Printer indentity unsigned char *serial_num; int vendor_id; int product_id; }; extern struct options g_options; ippusbxd-1.23/src/tcp.c000066400000000000000000000150531257707450500150360ustar00rootroot00000000000000/* Copyright (C) 2014 Daniel Dressler and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include "options.h" #include "logging.h" #include "tcp.h" struct tcp_sock_t *tcp_open(uint16_t port) { struct tcp_sock_t *this = calloc(1, sizeof *this); if (this == NULL) { ERR("IPv4: callocing this failed"); goto error; } // Open [S]ocket [D]escriptor this->sd = -1; this->sd = socket(AF_INET, SOCK_STREAM, 0); if (this->sd < 0) { ERR("IPv4 socket open failed"); goto error; } // Configure socket params struct sockaddr_in addr; memset(&addr, 0, sizeof addr); addr.sin_family = AF_INET; addr.sin_port = htons(port); addr.sin_addr.s_addr = htonl(0x7F000001); // Bind to localhost if (bind(this->sd, (struct sockaddr *)&addr, sizeof addr) < 0) { if (g_options.only_desired_port == 1) ERR("IPv4 bind on port failed. " "Requested port may be taken or require root permissions."); goto error; } // Let kernel over-accept max number of connections if (listen(this->sd, HTTP_MAX_PENDING_CONNS) < 0) { ERR("IPv4 listen failed on socket"); goto error; } return this; error: if (this != NULL) { if (this->sd != -1) { close(this->sd); } free(this); } return NULL; } struct tcp_sock_t *tcp6_open(uint16_t port) { struct tcp_sock_t *this = calloc(1, sizeof *this); if (this == NULL) { ERR("IPv6: callocing this failed"); goto error; } // Open [S]ocket [D]escriptor this->sd = -1; this->sd = socket(AF_INET6, SOCK_STREAM, 0); if (this->sd < 0) { ERR("Ipv6 socket open failed"); goto error; } // Configure socket params struct sockaddr_in6 addr; memset(&addr, 0, sizeof addr); addr.sin6_family = AF_INET6; addr.sin6_port = htons(port); addr.sin6_addr = in6addr_loopback; // Bind to localhost if (bind(this->sd, (struct sockaddr *)&addr, sizeof addr) < 0) { if (g_options.only_desired_port == 1) ERR("IPv6 bind on port failed. " "Requested port may be taken or require root permissions."); goto error; } // Let kernel over-accept max number of connections if (listen(this->sd, HTTP_MAX_PENDING_CONNS) < 0) { ERR("IPv6 listen failed on socket"); goto error; } return this; error: if (this != NULL) { if (this->sd != -1) { close(this->sd); } free(this); } return NULL; } void tcp_close(struct tcp_sock_t *this) { close(this->sd); free(this); } uint16_t tcp_port_number_get(struct tcp_sock_t *sock) { sock->info_size = sizeof sock->info; int query_status = getsockname( sock->sd, (struct sockaddr *) &(sock->info), &(sock->info_size)); if (query_status == -1) { ERR("query on socket port number failed"); goto error; } return ntohs(sock->info.sin6_port); error: return 0; } struct http_packet_t *tcp_packet_get(struct tcp_conn_t *tcp, struct http_message_t *msg) { // Alloc packet ==---------------------------------------------------== struct http_packet_t *pkt = packet_new(msg); if (pkt == NULL) { ERR("failed to create packet for incoming tcp message"); goto error; } size_t want_size = packet_pending_bytes(pkt); if (want_size == 0) { NOTE("TCP: Got %lu from spare buffer", pkt->filled_size); return pkt; } while (want_size != 0 && !msg->is_completed) { NOTE("TCP: Getting %d bytes", want_size); uint8_t *subbuffer = pkt->buffer + pkt->filled_size; ssize_t gotten_size = recv(tcp->sd, subbuffer, want_size, 0); if (gotten_size < 0) { int errno_saved = errno; ERR("recv failed with err %d:%s", errno_saved, strerror(errno_saved)); goto error; } NOTE("TCP: Got %d bytes", gotten_size); if (gotten_size == 0) { tcp->is_closed = 1; if (pkt->filled_size == 0) { // Client closed TCP conn goto error; } else { break; } } packet_mark_received(pkt, (unsigned) gotten_size); want_size = packet_pending_bytes(pkt); NOTE("TCP: Want more %d bytes; Message %scompleted", want_size, msg->is_completed ? "" : "not "); } NOTE("TCP: Received %lu bytes", pkt->filled_size); return pkt; error: if (pkt != NULL) packet_free(pkt); return NULL; } void tcp_packet_send(struct tcp_conn_t *conn, struct http_packet_t *pkt) { size_t remaining = pkt->filled_size; size_t total = 0; while (remaining > 0) { ssize_t sent = send(conn->sd, pkt->buffer + total, remaining, MSG_NOSIGNAL); if (sent < 0) { if (errno == EPIPE) { conn->is_closed = 1; return; } ERR_AND_EXIT("Failed to sent data over TCP"); } size_t sent_ulong = (unsigned) sent; total += sent_ulong; if (sent_ulong >= remaining) remaining = 0; else remaining -= sent_ulong; } NOTE("TCP: sent %lu bytes", total); } struct tcp_conn_t *tcp_conn_select(struct tcp_sock_t *sock, struct tcp_sock_t *sock6) { struct tcp_conn_t *conn = calloc(1, sizeof *conn); if (conn == NULL) { ERR("Calloc for connection struct failed"); goto error; } fd_set rfds; int retval = 0; int nfds = 0; FD_ZERO(&rfds); if (sock) { FD_SET(sock->sd, &rfds); nfds = sock->sd; } if (sock6) { FD_SET(sock6->sd, &rfds); if (sock6->sd > nfds) nfds = sock6->sd; } if (nfds == 0) { ERR("No valid TCP socket supplied."); goto error; } nfds += 1; retval = select(nfds, &rfds, NULL, NULL, NULL); if (retval < 1) { ERR("Failed to open tcp connection"); goto error; } if (sock && FD_ISSET(sock->sd, &rfds)) { conn->sd = accept(sock->sd, NULL, NULL); NOTE ("Using IPv4"); } else if (sock6 && FD_ISSET(sock6->sd, &rfds)) { conn->sd = accept(sock6->sd, NULL, NULL); NOTE ("Using IPv6"); } else { ERR("select failed"); goto error; } if (conn->sd < 0) { ERR("accept failed"); goto error; } return conn; error: if (conn != NULL) free(conn); return NULL; } void tcp_conn_close(struct tcp_conn_t *conn) { close(conn->sd); free(conn); } ippusbxd-1.23/src/tcp.h000066400000000000000000000030041257707450500150340ustar00rootroot00000000000000/* Copyright (C) 2014 Daniel Dressler and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #pragma once #include #include #include #include #include #include "http.h" #define HTTP_MAX_PENDING_CONNS 0 #define BUFFER_STEP (1 << 13) #define BUFFER_STEP_RATIO (2) #define BUFFER_INIT_RATIO (1) #define BUFFER_MAX (1 << 20) struct tcp_sock_t { int sd; struct sockaddr_in6 info; socklen_t info_size; }; struct tcp_conn_t { int sd; int is_closed; }; struct tcp_sock_t *tcp_open(uint16_t); struct tcp_sock_t *tcp6_open(uint16_t); void tcp_close(struct tcp_sock_t *); uint16_t tcp_port_number_get(struct tcp_sock_t *); struct tcp_conn_t *tcp_conn_select(struct tcp_sock_t *sock, struct tcp_sock_t *sock6); void tcp_conn_close(struct tcp_conn_t *); struct http_packet_t *tcp_packet_get(struct tcp_conn_t *, struct http_message_t *); void tcp_packet_send(struct tcp_conn_t *, struct http_packet_t *); ippusbxd-1.23/src/usb.c000066400000000000000000000466241257707450500150510ustar00rootroot00000000000000/* Copyright (C) 2014 Daniel Dressler and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #define _XOPEN_SOURCE 600 #include #include #include #include #include #include #include "options.h" #include "logging.h" #include "http.h" #include "usb.h" #define IGNORE(x) (void)(x) static int bus, dev_addr; static int is_ippusb_interface(const struct libusb_interface_descriptor *interf) { return interf->bInterfaceClass == 0x07 && interf->bInterfaceSubClass == 0x01 && interf->bInterfaceProtocol == 0x04; } static int count_ippoverusb_interfaces(struct libusb_config_descriptor *config) { int ippusb_interface_count = 0; for (uint8_t interface_num = 0; interface_num < config->bNumInterfaces; interface_num++) { const struct libusb_interface *interface = NULL; interface = &config->interface[interface_num]; for (int alt_num = 0; alt_num < interface->num_altsetting; alt_num++) { const struct libusb_interface_descriptor *alt = NULL; alt = &interface->altsetting[alt_num]; // Check for IPP over USB interfaces if (!is_ippusb_interface(alt)) continue; ippusb_interface_count++; break; } } return ippusb_interface_count; } static int is_our_device(libusb_device *dev, struct libusb_device_descriptor desc) { static const int SERIAL_MAX = 1024; unsigned char serial[1024]; if ((g_options.vendor_id && desc.idVendor != g_options.vendor_id) && (g_options.product_id && desc.idProduct != g_options.product_id)) return 0; if (g_options.serial_num == NULL) return 1; libusb_device_handle *handle = NULL; int status = libusb_open(dev, &handle); if (status != 0) { // Device turned off or disconnected, we cannot retrieve its // serial number any more, so we identify it via bus and device // addresses return (bus == libusb_get_bus_number(dev) && dev_addr == libusb_get_device_address(dev)); } else { // Device is turned on and connected, read out its serial number // and use the serial number for identification status = libusb_get_string_descriptor_ascii(handle, desc.iSerialNumber, serial, SERIAL_MAX); libusb_close(handle); if (status <= 0) { WARN("Failed to get serial from device"); return 0; } return strcmp((char *)serial, (char *)g_options.serial_num) == 0; } } struct usb_sock_t *usb_open() { struct usb_sock_t *usb = calloc(1, sizeof *usb); int status = 1; status = libusb_init(&usb->context); if (status < 0) { ERR("libusb init failed with error: %s", libusb_error_name(status)); goto error_usbinit; } libusb_device **device_list = NULL; ssize_t device_count = libusb_get_device_list(usb->context, &device_list); if (device_count < 0) { ERR("failed to get list of usb devices"); goto error; } // Discover device and count interfaces ==---------------------------== int selected_config = -1; unsigned int selected_ipp_interface_count = 0; int auto_pick = !(g_options.vendor_id || g_options.product_id || g_options.serial_num); libusb_device *printer_device = NULL; for (ssize_t i = 0; i < device_count; i++) { libusb_device *candidate = device_list[i]; struct libusb_device_descriptor desc; libusb_get_device_descriptor(candidate, &desc); if (!is_our_device(candidate, desc)) continue; bus = libusb_get_bus_number(candidate); dev_addr = libusb_get_device_address(candidate); NOTE("Printer connected on bus %d device %d", bus, dev_addr); for (uint8_t config_num = 0; config_num < desc.bNumConfigurations; config_num++) { struct libusb_config_descriptor *config = NULL; status = libusb_get_config_descriptor(candidate, config_num, &config); if (status < 0) ERR_AND_EXIT("USB: didn't get config desc %s", libusb_error_name(status)); int interface_count = count_ippoverusb_interfaces(config); libusb_free_config_descriptor(config); if (interface_count >= 2) { selected_config = config_num; selected_ipp_interface_count = (unsigned) interface_count; printer_device = candidate; goto found_device; } // CONFTEST: Two or more interfaces are required if (interface_count == 1) { CONF("usb device has only one ipp interface " "in violation of standard"); goto error; } if (!auto_pick) { ERR_AND_EXIT("No ipp-usb interfaces found"); } } } found_device: if (printer_device == NULL) { if (!auto_pick) { ERR("No printer found by that vid, pid, serial"); } else { ERR("No IPP over USB printer found"); } goto error; } // Open the printer ==-----------------------------------------------== status = libusb_open(printer_device, &usb->printer); if (status != 0) { ERR("failed to open device"); goto error; } // Open every IPP-USB interface ==-----------------------------------== usb->num_interfaces = selected_ipp_interface_count; usb->interfaces = calloc(usb->num_interfaces, sizeof(*usb->interfaces)); if (usb->interfaces == NULL) { ERR("Failed to alloc interfaces"); goto error; } struct libusb_config_descriptor *config = NULL; status = libusb_get_config_descriptor(printer_device, (uint8_t)selected_config, &config); if (status != 0 || config == NULL) { ERR("Failed to acquire config descriptor"); goto error; } unsigned int interfs = selected_ipp_interface_count; for (uint8_t interf_num = 0; interf_num < config->bNumInterfaces; interf_num++) { const struct libusb_interface *interf = NULL; interf = &config->interface[interf_num]; for (int alt_num = 0; alt_num < interf->num_altsetting; alt_num++) { const struct libusb_interface_descriptor *alt = NULL; alt = &interf->altsetting[alt_num]; // Skip non-IPP-USB interfaces if (!is_ippusb_interface(alt)) continue; interfs--; struct usb_interface *uf = usb->interfaces + interfs; uf->interface_number = interf_num; uf->libusb_interface_index = alt->bInterfaceNumber; uf->interface_alt = alt_num; // Store interface's two endpoints for (int end_i = 0; end_i < alt->bNumEndpoints; end_i++) { const struct libusb_endpoint_descriptor *end; end = &alt->endpoint[end_i]; usb->max_packet_size = end->wMaxPacketSize; // High bit set means endpoint // is an INPUT or IN endpoint. uint8_t address = end->bEndpointAddress; if (address & 0x80) uf->endpoint_in = address; else uf->endpoint_out = address; } break; } } libusb_free_config_descriptor(config); libusb_free_device_list(device_list, 1); // Pour interfaces into pool ==--------------------------------------== usb->num_avail = usb->num_interfaces; usb->interface_pool = calloc(usb->num_avail, sizeof(*usb->interface_pool)); if (usb->interface_pool == NULL) { ERR("Failed to alloc interface pool"); goto error; } for (uint32_t i = 0; i < usb->num_avail; i++) { usb->interface_pool[i] = i; } // Stale lock int status_lock = sem_init(&usb->num_staled_lock, 0, 1); if (status_lock != 0) { ERR("Failed to create num_staled lock"); goto error; } // Pool management lock status_lock = sem_init(&usb->pool_manage_lock, 0, 1); if (status_lock != 0) { ERR("Failed to create high priority pool lock"); goto error; } // High priority lock status_lock = sem_init(&usb->pool_high_priority_lock, 0, 1); if (status_lock != 0) { ERR("Failed to create low priority pool lock"); goto error; } // Low priority lock status_lock = sem_init(&usb->pool_low_priority_lock, 0, usb->num_avail - 1); if (status_lock != 0) { ERR("Failed to create high priority pool lock"); goto error; } return usb; error: if (device_list != NULL) libusb_free_device_list(device_list, 1); error_usbinit: if (usb != NULL) { if (usb->context != NULL) libusb_exit(usb->context); if (usb->interfaces != NULL) free(usb->interfaces); if (usb->interface_pool != NULL) free(usb->interface_pool); free(usb); } return NULL; } void usb_close(struct usb_sock_t *usb) { // Release interfaces for (uint32_t i = 0; i < usb->num_interfaces; i++) { int number = usb->interfaces[i].interface_number; libusb_release_interface(usb->printer, number); } libusb_close(usb->printer); libusb_exit(usb->context); sem_destroy(&usb->pool_high_priority_lock); sem_destroy(&usb->pool_low_priority_lock); sem_destroy(&usb->num_staled_lock); free(usb->interfaces); free(usb->interface_pool); free(usb); return; } int usb_can_callback(struct usb_sock_t *usb) { IGNORE(usb); if (!g_options.vendor_id || !g_options.product_id) { NOTE("Exit-on-unplug requires vid & pid"); return 0; } int works = !!libusb_has_capability(LIBUSB_CAP_HAS_HOTPLUG); if (!works) WARN("Libusb cannot tell us when to disconnect"); return works; } static int LIBUSB_CALL usb_exit_on_unplug(libusb_context *context, libusb_device *device, libusb_hotplug_event event, void *call_data) { IGNORE(context); IGNORE(event); IGNORE(call_data); NOTE("Received unplug callback"); struct libusb_device_descriptor desc; libusb_get_device_descriptor(device, &desc); if (is_our_device(device, desc)) exit(0); return 0; } static void *usb_pump_events(void *user_data) { IGNORE(user_data); for (;;) { // NOTE: This is a blocking call so // no need for sleep() libusb_handle_events_completed(NULL, NULL); } return NULL; } void usb_register_callback(struct usb_sock_t *usb) { IGNORE(usb); int status = libusb_hotplug_register_callback( NULL, LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT, // Note: libusb's enum has no default value // a bug has been filled with libusb. // Please switch the below line to 0 // once the issue has been fixed in // deployed versions of libusb // https://github.com/libusb/libusb/issues/35 // 0, LIBUSB_HOTPLUG_ENUMERATE, g_options.vendor_id, g_options.product_id, LIBUSB_HOTPLUG_MATCH_ANY, &usb_exit_on_unplug, NULL, NULL); if (status == LIBUSB_SUCCESS) { pthread_t thread_handle; pthread_create(&thread_handle, NULL, &usb_pump_events, NULL); NOTE("Registered unplug callback"); } else ERR("Failed to register unplug callback"); } static void usb_conn_mark_staled(struct usb_conn_t *conn) { if (conn->is_staled) return; struct usb_sock_t *usb = conn->parent; sem_wait(&usb->num_staled_lock); { usb->num_staled++; } sem_post(&usb->num_staled_lock); conn->is_staled = 1; } static void usb_conn_mark_moving(struct usb_conn_t *conn) { if (!conn->is_staled) return; struct usb_sock_t *usb = conn->parent; sem_wait(&usb->num_staled_lock); { usb->num_staled--; } sem_post(&usb->num_staled_lock); conn->is_staled = 0; } static int usb_all_conns_staled(struct usb_sock_t *usb) { int staled; sem_wait(&usb->num_staled_lock); { sem_wait(&usb->pool_manage_lock); { staled = usb->num_staled == usb->num_taken; } sem_post(&usb->pool_manage_lock); } sem_post(&usb->num_staled_lock); return staled; } struct usb_conn_t *usb_conn_acquire(struct usb_sock_t *usb, int is_high_priority) { int used_high_priority = 0; if (is_high_priority) { // We first take the high priority lock. sem_wait(&usb->pool_high_priority_lock); used_high_priority = 1; // We can then check if a low priority // interface is available. if (!sem_trywait(&usb->pool_low_priority_lock)) { // If a low priority is avail we take that one // then release the high priority interface. // Otherwise we use the high priority interface. used_high_priority = 0; sem_post(&usb->pool_high_priority_lock); } } else { sem_wait(&usb->pool_low_priority_lock); } struct usb_conn_t *conn = calloc(1, sizeof(*conn)); if (conn == NULL) { ERR("failed to aloc space for usb connection"); return NULL; } sem_wait(&usb->pool_manage_lock); { conn->parent = usb; conn->is_high_priority = used_high_priority; usb->num_taken++; uint32_t slot = --usb->num_avail; conn->interface_index = usb->interface_pool[slot]; conn->interface = usb->interfaces + conn->interface_index; struct usb_interface *uf = conn->interface; // Make kernel release interface if (libusb_kernel_driver_active(usb->printer, uf->libusb_interface_index) == 1) { // Only linux supports this // other platforms will fail // thus we ignore the error code // it either works or it does not libusb_detach_kernel_driver(usb->printer, uf->libusb_interface_index); } // Claim the whole interface int status = 0; do { // Spinlock-like // Libusb does not offer a blocking call // so we're left with a spinlock status = libusb_claim_interface( usb->printer, uf->libusb_interface_index); switch (status) { case LIBUSB_ERROR_NOT_FOUND: ERR_AND_EXIT("USB Interface did not exist"); case LIBUSB_ERROR_NO_DEVICE: ERR_AND_EXIT("Printer was removed"); default: break; } } while (status != 0); // Select the IPP-USB alt setting of the interface libusb_set_interface_alt_setting(usb->printer, uf->libusb_interface_index, uf->interface_alt); } sem_post(&usb->pool_manage_lock); return conn; } void usb_conn_release(struct usb_conn_t *conn) { struct usb_sock_t *usb = conn->parent; sem_wait(&usb->pool_manage_lock); { libusb_release_interface(usb->printer, conn->interface->libusb_interface_index); // Return usb interface to pool usb->num_taken--; uint32_t slot = usb->num_avail++; usb->interface_pool[slot] = conn->interface_index; // Release our interface lock if (conn->is_high_priority) sem_post(&usb->pool_high_priority_lock); else sem_post(&usb->pool_low_priority_lock); free(conn); } sem_post(&usb->pool_manage_lock); } void usb_conn_packet_send(struct usb_conn_t *conn, struct http_packet_t *pkt) { int size_sent = 0; const int timeout = 6 * 60 * 60 * 1000; // 6 hours in milliseconds int num_timeouts = 0; size_t sent = 0; size_t pending = pkt->filled_size; while (pending > 0) { int to_send = (int)pending; NOTE("P %p: USB: want to send %d bytes", pkt, to_send); int status = libusb_bulk_transfer(conn->parent->printer, conn->interface->endpoint_out, pkt->buffer + sent, to_send, &size_sent, timeout); if (status == LIBUSB_ERROR_TIMEOUT) { NOTE("P %p: USB: send timed out, retrying", pkt); if (num_timeouts++ > PRINTER_CRASH_TIMEOUT) ERR_AND_EXIT("P %p: Usb send fully timed out", pkt); // Sleep for tenth of a second struct timespec sleep_dur; sleep_dur.tv_sec = 0; sleep_dur.tv_nsec = 100000000; nanosleep(&sleep_dur, NULL); continue; } if (status == LIBUSB_ERROR_NO_DEVICE) ERR_AND_EXIT("P %p: Printer has been disconnected", pkt); if (status < 0) ERR_AND_EXIT("P %p: USB: send failed with status %s", pkt, libusb_error_name(status)); if (size_sent < 0) ERR_AND_EXIT("P %p: Unexpected negative size_sent", pkt); pending -= (size_t) size_sent; sent += (size_t) size_sent; NOTE("P %p: USB: sent %d bytes", pkt, size_sent); } NOTE("P %p: USB: sent %d bytes in total", pkt, sent); } struct http_packet_t *usb_conn_packet_get(struct usb_conn_t *conn, struct http_message_t *msg) { if (msg->is_completed) return NULL; struct http_packet_t *pkt = packet_new(msg); if (pkt == NULL) { ERR("failed to create packet for incoming usb message"); goto cleanup; } // File packet const int timeout = 6 * 60 * 60 * 1000; // 6 hours in milliseconds size_t read_size_ulong = packet_pending_bytes(pkt); if (read_size_ulong == 0) return pkt; uint64_t times_staled = 0; while (read_size_ulong > 0 && !msg->is_completed) { if (read_size_ulong >= INT_MAX) goto cleanup; int read_size = (int)read_size_ulong; // Pad read_size to multiple of usb's max packet size read_size += (512 - (read_size % 512)) % 512; // Expand buffer if needed if (pkt->buffer_capacity < pkt->filled_size + read_size_ulong) if (packet_expand(pkt) < 0) ERR_AND_EXIT("Failed to ensure room for usb pkt"); int gotten_size = 0; int status = libusb_bulk_transfer( conn->parent->printer, conn->interface->endpoint_in, pkt->buffer + pkt->filled_size, read_size, &gotten_size, timeout); if (status == LIBUSB_ERROR_NO_DEVICE) ERR_AND_EXIT("Printer has been disconnected"); if (status != 0 && status != LIBUSB_ERROR_TIMEOUT) { ERR("bulk xfer failed with error code %d", status); ERR("tried reading %d bytes", read_size); goto cleanup; } if (gotten_size < 0) ERR_AND_EXIT("Negative read size unexpected"); if (gotten_size > 0) { times_staled = 0; usb_conn_mark_moving(conn); } else { // Performance Test --------------- // How long we sleep here has a // dramatic affect on how long it // takes to load a page. // Earlier versions waited a tenth // of a second which resulted in // minute long page loads. // On my HP printer the most obvious // bottleneck is the "Unified.js" file // which weighs 517.87KB. My profiling // looked at how shortening this sleep // could improve this file's load times. // The cycle count is from perf and // covers an entire page load. // // Below are my results: // 1 in 100 == 2447ms, 261M cycles // 1 in 1,000 == 483ms, 500M cycles // 5 in 10,000 == 433ms, 800M cycles // 1 in 10,000 == 320ms, 3000M cycles #define TIMEOUT_RATIO (10000 / 5) static uint64_t stale_timeout = CONN_STALE_THRESHHOLD * TIMEOUT_RATIO; static uint64_t crash_timeout = PRINTER_CRASH_TIMEOUT * TIMEOUT_RATIO; static uint64_t skip_timeout = 1000000000 / TIMEOUT_RATIO; struct timespec sleep_dur; sleep_dur.tv_sec = 0; sleep_dur.tv_nsec = skip_timeout; nanosleep(&sleep_dur, NULL); times_staled++; if (times_staled > stale_timeout) { usb_conn_mark_staled(conn); if (usb_all_conns_staled(conn->parent) && times_staled > crash_timeout) { ERR("USB timedout, dropping data"); goto cleanup; } if (pkt->filled_size > 0) NOTE("Packet so far \n===\n%s===\n", hexdump(pkt->buffer, pkt->filled_size)); } } if (gotten_size) { NOTE("USB: Getting %d bytes of %d", read_size, pkt->expected_size); NOTE("USB: Got %d bytes", gotten_size); } packet_mark_received(pkt, (size_t)gotten_size); read_size_ulong = packet_pending_bytes(pkt); } NOTE("USB: Received %d bytes of %d with type %d", pkt->filled_size, pkt->expected_size, msg->type); return pkt; cleanup: if (pkt != NULL) packet_free(pkt); return NULL; } ippusbxd-1.23/src/usb.h000066400000000000000000000035251257707450500150470ustar00rootroot00000000000000/* Copyright (C) 2014 Daniel Dressler and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #pragma once #include #include // In seconds #define PRINTER_CRASH_TIMEOUT (60 * 60 * 6) #define CONN_STALE_THRESHHOLD 6 struct usb_interface { uint8_t interface_number; uint8_t libusb_interface_index; int interface_alt; uint8_t endpoint_in; uint8_t endpoint_out; }; struct usb_sock_t { libusb_context *context; libusb_device_handle *printer; int max_packet_size; uint32_t num_interfaces; struct usb_interface *interfaces; uint32_t num_staled; sem_t num_staled_lock; sem_t pool_manage_lock; uint32_t num_avail; uint32_t num_taken; uint32_t *interface_pool; sem_t pool_low_priority_lock; sem_t pool_high_priority_lock; }; struct usb_conn_t { struct usb_sock_t *parent; struct usb_interface *interface; uint32_t interface_index; int is_high_priority; int is_staled; }; struct usb_sock_t *usb_open(void); void usb_close(struct usb_sock_t *); int usb_can_callback(struct usb_sock_t *); void usb_register_callback(struct usb_sock_t *); struct usb_conn_t *usb_conn_acquire(struct usb_sock_t *, int); void usb_conn_release(struct usb_conn_t *); void usb_conn_packet_send(struct usb_conn_t *, struct http_packet_t *); struct http_packet_t *usb_conn_packet_get(struct usb_conn_t *, struct http_message_t *);