123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521(*
* oBus_match.ml
* -------------
* Copyright : (c) 2009, Jeremie Dimino <jeremie@dimino.org>
* Licence : BSD3
*
* This file is a part of obus, an ocaml implementation of D-Bus.
*)letsection=Lwt_log.Section.make"obus(match)"(* +-----------------------------------------------------------------+
| Types |
+-----------------------------------------------------------------+ *)typeargument_filter=|AF_stringofstring|AF_string_pathofstring|AF_namespaceofstringtypearguments=(int*argument_filter)listtyperule={typ:[`Signal|`Error|`Method_call|`Method_return]option;sender:OBus_name.bus;interface:OBus_name.interface;member:OBus_name.member;path:OBus_path.toption;destination:OBus_name.bus;arguments:arguments;eavesdrop:booloption;}lettype=e.typletsendere=e.senderletinterfacee=e.interfaceletmembere=e.memberletpathe=e.pathletdestinatione=e.destinationletargumentse=e.argumentsleteavesdrope=e.eavesdropletrule?typ?(sender="")?(interface="")?(member="")?path?(destination="")?(arguments=[])?eavesdrop()={typ=typ;sender=sender;interface=interface;member=member;path=path;destination=destination;arguments=arguments;eavesdrop=eavesdrop;}(* +-----------------------------------------------------------------+
| Arguments lists |
+-----------------------------------------------------------------+ *)letrecinsert_sortednumfilter=function|[]->[(num,filter)]|(num',_)aspair::restwhennum'<num->pair::insert_sortednumfilterrest|(num',_)::restwhennum'=num->(num,filter)::rest|((num',_)::rest)asl->(num,filter)::lletmake_argumentslist=List.fold_left(funl(num,filter)->ifnum<0||num>63thenPrintf.ksprintfinvalid_arg"OBus_match.arguments_of_list: invalid argument number '%d': it must be in the rane [1..63]"numelseinsert_sortednumfilterl)[]listexternalcast_arguments:arguments->(int*argument_filter)list="%identity"(* +-----------------------------------------------------------------+
| string <-> rule |
+-----------------------------------------------------------------+ *)letstring_of_rulemr=letbuf=Buffer.create42inletreccoma=ref(fun_->coma:=fun_->Buffer.add_charbuf',')inletaddkeyvalue=!coma();Buffer.add_stringbufkey;Buffer.add_stringbuf"='";Buffer.add_stringbufvalue;Buffer.add_charbuf'\''inletadd_stringkeytest=function|""->()|str->matchteststrwith|Someerror->raise(OBus_string.Invalid_stringerror)|None->addkeystrinbeginmatchmr.typwith|None->()|Somet->add"type"(matchtwith|`Method_call->"method_call"|`Method_return->"method_return"|`Error->"error"|`Signal->"signal")end;add_string"sender"OBus_name.validate_busmr.sender;add_string"interface"OBus_name.validate_interfacemr.interface;add_string"member"OBus_name.validate_membermr.member;beginmatchmr.pathwith|None->()|Some[]->!coma();Buffer.add_stringbuf"path='/'"|Somep->!coma();Buffer.add_stringbuf"path='";List.iter(funelt->matchOBus_path.validate_elementeltwith|Someerror->raise(OBus_string.Invalid_stringerror)|None->Buffer.add_charbuf'/';Buffer.add_stringbufelt)p;Buffer.add_charbuf'\''end;add_string"destination"OBus_name.validate_busmr.destination;List.iter(fun(n,filter)->!coma();matchfilterwith|AF_stringstr->Printf.bprintfbuf"arg%d='%s'"nstr|AF_string_pathstr->Printf.bprintfbuf"arg%dpath='%s'"nstr|AF_namespacestr->Printf.bprintfbuf"arg%dnamespace='%s'"nstr)mr.arguments;beginmatchmr.eavesdropwith|None->()|Sometrue->add"eavesdrop""true"|Somefalse->add"eavesdrop""false"end;Buffer.contentsbufexceptionParse_failureofstring*int*stringlet()=Printexc.register_printer(function|Parse_failure(str,pos,reason)->Some(Printf.sprintf"failed to parse D-Bus matching rule %S, at position %d: %s"strposreason)|_->None)exceptionFail=OBus_match_rule_lexer.Failletrule_of_stringstr=tryletl=matchstrwith|""->[]|_->OBus_match_rule_lexer.match_rules(Lexing.from_stringstr)inletcheckposvalidatevalue=matchvalidatevaluewith|None->()|Someerr->raise(Fail(pos,OBus_string.error_messageerr))inletmr={typ=None;sender="";interface="";member="";path=None;destination="";arguments=[];eavesdrop=None;}inList.fold_leftbeginfunmr(pos,key,value)->matchkeywith|"type"->{mrwithtyp=Some(matchvaluewith|"method_call"->`Method_call|"method_return"->`Method_return|"signal"->`Signal|"error"->`Error|_->raise(Fail(pos,Printf.sprintf"invalid message type (%s)"value)))}|"sender"->checkposOBus_name.validate_busvalue;{mrwithsender=value}|"destination"->checkposOBus_name.validate_busvalue;{mrwithdestination=value}|"interface"->checkposOBus_name.validate_interfacevalue;{mrwithinterface=value}|"member"->checkposOBus_name.validate_membervalue;{mrwithmember=value}|"path"->begintry{mrwithpath=Some(OBus_path.of_stringvalue)}withOBus_string.Invalid_stringerr->raise(Fail(pos,OBus_string.error_messageerr))end|"eavesdrop"->beginmatchvaluewith|"true"->{mrwitheavesdrop=Sometrue}|"false"->{mrwitheavesdrop=Somefalse}|_->raise(Fail(pos,Printf.sprintf"invalid value for eavesdrop (%s)"value))end|_->matchOBus_match_rule_lexer.arg(Lexing.from_stringkey)with|Some(n,kind)->{mrwitharguments=insert_sortedn(matchkindwith|`String->AF_stringvalue|`Path->AF_string_pathvalue|`Namespace->AF_namespacevalue)mr.arguments}|None->raise(Fail(pos,Printf.sprintf"invalid key (%s)"key))endmrlwithFail(pos,msg)->raise(Parse_failure(str,pos,msg))(* +-----------------------------------------------------------------+
| Matching |
+-----------------------------------------------------------------+ *)letmatch_keymatchervalue=matchmatcherwith|None->true|Somevalue'->value=value'letmatch_stringmatchervalue=matchmatcherwith|""->true|value'->value=value'letstarts_withstrprefix=letstr_len=String.lengthstrandprefix_len=String.lengthprefixinletrecloopi=(i=prefix_len)||(i<str_len&&str.[i]=prefix.[i]&&loop(i+1))inloop0letends_with_slashstr=str<>""&&str.[String.lengthstr-1]='/'letrecmatch_argumentsnummatcherarguments=matchmatcherwith|[]->true|(num',filter)::rest->match_arguments_auxnumnum'filterrestargumentsandmatch_arguments_auxnumnum'filtermatcherarguments=matchargumentswith|[]->false|value::restwhennum<num'->match_arguments_aux(num+1)num'filtermatcherrest|OBus_value.V.Basic(OBus_value.V.Stringvalue)::rest->(matchfilterwith|AF_stringstr->str=value|AF_string_pathstr->(str=value)||(ends_with_slashstr&&starts_withvaluestr)||(ends_with_slashvalue&&starts_withstrvalue)|AF_namespacestr->starts_withvaluestr&&(String.lengthvalue=String.lengthstr||value.[String.lengthstr]='.'))&&match_arguments(num+1)matcherrest|OBus_value.V.Basic(OBus_value.V.Object_pathvalue)::rest->(matchfilterwith|AF_stringstr->false|AF_string_pathstr->letvalue=OBus_path.to_stringvaluein(str=value)||(ends_with_slashstr&&starts_withvaluestr)||(ends_with_slashvalue&&starts_withstrvalue)|AF_namespace_->false)&&match_arguments(num+1)matcherrest|_->falseletmatch_valuesfiltersvalues=match_arguments0filtersvaluesletmatch_messagemrmsg=(matchOBus_message.typmsg,mr.typwith|OBus_message.Method_call(path,interface,member),(Some`Method_call|None)->(match_keymr.pathpath)&&(match_stringmr.interfaceinterface)&&(match_stringmr.membermember)|OBus_message.Method_returnserial,(Some`Method_return|None)->true|OBus_message.Signal(path,interface,member),(Some`Signal|None)->(match_keymr.pathpath)&&(match_stringmr.interfaceinterface)&&(match_stringmr.membermember)|OBus_message.Error(serial,name),(Some`Error|None)->true|_->false)&&(match_stringmr.sender(OBus_message.sendermsg))&&(match_stringmr.destination(OBus_message.destinationmsg))&&(match_arguments0mr.arguments(OBus_message.bodymsg))(* +-----------------------------------------------------------------+
| Comparison |
+-----------------------------------------------------------------+ *)typecomparison_result=|More_general|Less_general|Equal|Incomparableletreccompare_argumentsaccl1l2=matchacc,l1,l2with|acc,[],[]->acc|(Less_general|Equal),_::_,[]->Less_general|(More_general|Equal),[],_::_->More_general|acc,(pos1,filter1)::rest1,(pos2,filter2)::rest2->ifpos1=pos2&&filter1=filter2thencompare_argumentsaccrest1rest2elseifpos1<pos2&&(acc=Less_general||acc=Equal)thencompare_argumentsLess_generalrest1l2elseifpos1>pos2&&(acc=More_general||acc=Equal)thencompare_argumentsMore_generall1rest2elseraiseExit|_->raiseExitletcompare_optionaccx1x2=ifx1=x2thenaccelsematchacc,x1,x2with|(Less_general|Equal),Some_,None->Less_general|(More_general|Equal),None,Some_->More_general|_->raiseExitletcompare_stringaccx1x2=ifx1=x2thenaccelsematchacc,x1,x2with|(Less_general|Equal),x,""whenx<>""->Less_general|(More_general|Equal),"",xwhenx<>""->More_general|_->raiseExitletcompare_rulesr1r2=tryifr1.typ=r2.typthenbeginletacc=Equalinletacc=compare_stringaccr1.senderr2.senderinletacc=compare_stringaccr1.destinationr2.destinationinletacc=compare_optionaccr1.pathr2.pathinletacc=compare_stringaccr1.interfacer2.interfaceinletacc=compare_stringaccr1.memberr2.memberinletacc=compare_argumentsaccr1.argumentsr2.argumentsinifr1.eavesdrop=r2.eavesdropthenaccelsematchacc,r1.eavesdrop,r2.eavesdropwith|_,None,Somefalse->acc|_,Somefalse,None->acc|(Less_general|Equal),(None|Somefalse),Sometrue->Less_general|(More_general|Equal),Sometrue,(None|Somefalse)->More_general|_->IncomparableendelseIncomparablewithExit->Incomparable(* +-----------------------------------------------------------------+
| Exporting rules on message buses |
+-----------------------------------------------------------------+ *)moduleString_set=Set.Make(String)(* Informations stored in connections *)typeinfo={mutableexported:String_set.t;(* Rules that are currently exported on the message bus (as strings) *)mutablerules:rulelist;(* The list of all rules we want to export *)connection:OBus_connection.t;(* The connection on which the rules are exported *)mutex:Lwt_mutex.t;(* Mutex to prevent concurrent modifications of rules *)}(* Add a matching rule to a list of incomparable most general rules *)letrecinsert_rulerulerules=matchruleswith|[]->[rule]|rule'::rest->matchcompare_rulesrulerule'with|Incomparable->rule'::insert_rulerulerest|Equal|Less_general->rules|More_general->rule::restletdo_exportinforule_string=let%lwt()=OBus_connection.method_call~connection:info.connection~destination:OBus_protocol.bus_name~path:OBus_protocol.bus_path~interface:OBus_protocol.bus_interface~member:"AddMatch"~i_args:(OBus_value.C.seq1OBus_value.C.basic_string)~o_args:OBus_value.C.seq0rule_stringininfo.exported<-String_set.addrule_stringinfo.exported;Lwt.return()letdo_removeinforule_string=info.exported<-String_set.removerule_stringinfo.exported;try%lwtOBus_connection.method_call~connection:info.connection~destination:OBus_protocol.bus_name~path:OBus_protocol.bus_path~interface:OBus_protocol.bus_interface~member:"RemoveMatch"~i_args:(OBus_value.C.seq1OBus_value.C.basic_string)~o_args:OBus_value.C.seq0rule_stringwithexn->matchOBus_error.nameexnwith|"org.freedesktop.DBus.Error.MatchRuleNotFound"->Lwt_log.info_f~section"rule %S does not exists on the message bus"rule_string|_->Lwt.failexn(* Commits rules changes on the message bus: *)letcommitinfo=Lwt_mutex.with_lockinfo.mutex(fun()->(* Computes the set of most general rules: *)letrules=List.fold_left(funaccrule->insert_ruleruleacc)[]info.rulesin(* Turns them into a set of strings: *)letrules=List.fold_left(funaccrule->String_set.add(string_of_rulerule)acc)String_set.emptyrulesin(* Computes the minimal set of operations to update the rules: *)letnew_rules=String_set.diffrulesinfo.exportedandold_rules=String_set.diffinfo.exportedrulesin(* Does the update of rules on the message bus: *)letthreads=[]inletthreads=String_set.fold(funruleacc->do_exportinforule::acc)new_rulesthreadsinletthreads=String_set.fold(funruleacc->do_removeinforule::acc)old_rulesthreadsinLwt.jointhreads)letkey=OBus_connection.new_key()letrecremove_firstxl=matchlwith|[]->[]|x'::lwhenx=x'->l|x'::l->x'::remove_firstxlletexport?switchconnectionrule=Lwt_switch.checkswitch;letinfo=matchOBus_connection.getconnectionkeywith|Someinfo->info|None->letinfo={exported=String_set.empty;connection=connection;rules=[];mutex=Lwt_mutex.create();}inOBus_connection.setconnectionkey(Someinfo);infoininfo.rules<-rule::info.rules;let%lwt()=commitinfoinlet%lwt()=Lwt_switch.add_hook_or_execswitch(fun()->info.rules<-remove_firstruleinfo.rules;commitinfo)inLwt.return()