1%% This Source Code Form is subject to the terms of the Mozilla Public 2%% License, v. 2.0. If a copy of the MPL was not distributed with this 3%% file, You can obtain one at https://mozilla.org/MPL/2.0/. 4%% 5%% Copyright (c) 2007-2021 VMware, Inc. or its affiliates. All rights reserved. 6%% 7 8-module(rabbit_exchange_type_headers). 9-include_lib("rabbit_common/include/rabbit.hrl"). 10-include_lib("rabbit_common/include/rabbit_framing.hrl"). 11 12-behaviour(rabbit_exchange_type). 13 14-export([description/0, serialise_events/0, route/2]). 15-export([validate/1, validate_binding/2, 16 create/2, delete/3, policy_changed/2, add_binding/3, 17 remove_bindings/3, assert_args_equivalence/2]). 18-export([info/1, info/2]). 19 20-rabbit_boot_step({?MODULE, 21 [{description, "exchange type headers"}, 22 {mfa, {rabbit_registry, register, 23 [exchange, <<"headers">>, ?MODULE]}}, 24 {requires, rabbit_registry}, 25 {enables, kernel_ready}]}). 26 27info(_X) -> []. 28info(_X, _) -> []. 29 30description() -> 31 [{description, <<"AMQP headers exchange, as per the AMQP specification">>}]. 32 33serialise_events() -> false. 34 35route(#exchange{name = Name}, 36 #delivery{message = #basic_message{content = Content}}) -> 37 Headers = case (Content#content.properties)#'P_basic'.headers of 38 undefined -> []; 39 H -> rabbit_misc:sort_field_table(H) 40 end, 41 rabbit_router:match_bindings( 42 Name, fun (#binding{args = Spec}) -> headers_match(Spec, Headers) end). 43 44validate_binding(_X, #binding{args = Args}) -> 45 case rabbit_misc:table_lookup(Args, <<"x-match">>) of 46 {longstr, <<"all">>} -> ok; 47 {longstr, <<"any">>} -> ok; 48 {longstr, Other} -> {error, 49 {binding_invalid, 50 "Invalid x-match field value ~p; " 51 "expected all or any", [Other]}}; 52 {Type, Other} -> {error, 53 {binding_invalid, 54 "Invalid x-match field type ~p (value ~p); " 55 "expected longstr", [Type, Other]}}; 56 undefined -> ok %% [0] 57 end. 58%% [0] spec is vague on whether it can be omitted but in practice it's 59%% useful to allow people to do this 60 61parse_x_match({longstr, <<"all">>}) -> all; 62parse_x_match({longstr, <<"any">>}) -> any; 63parse_x_match(_) -> all. %% legacy; we didn't validate 64 65%% Horrendous matching algorithm. Depends for its merge-like 66%% (linear-time) behaviour on the lists:keysort 67%% (rabbit_misc:sort_field_table) that route/1 and 68%% rabbit_binding:{add,remove}/2 do. 69%% 70%% !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 71%% In other words: REQUIRES BOTH PATTERN AND DATA TO BE SORTED ASCENDING BY KEY. 72%% !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 73%% 74 75-spec headers_match 76 (rabbit_framing:amqp_table(), rabbit_framing:amqp_table()) -> 77 boolean(). 78 79headers_match(Args, Data) -> 80 MK = parse_x_match(rabbit_misc:table_lookup(Args, <<"x-match">>)), 81 headers_match(Args, Data, true, false, MK). 82 83% A bit less horrendous algorithm :) 84headers_match(_, _, false, _, all) -> false; 85headers_match(_, _, _, true, any) -> true; 86 87% No more bindings, return current state 88headers_match([], _Data, AllMatch, _AnyMatch, all) -> AllMatch; 89headers_match([], _Data, _AllMatch, AnyMatch, any) -> AnyMatch; 90 91% Delete bindings starting with x- 92headers_match([{<<"x-", _/binary>>, _PT, _PV} | PRest], Data, 93 AllMatch, AnyMatch, MatchKind) -> 94 headers_match(PRest, Data, AllMatch, AnyMatch, MatchKind); 95 96% No more data, but still bindings, false with all 97headers_match(_Pattern, [], _AllMatch, AnyMatch, MatchKind) -> 98 headers_match([], [], false, AnyMatch, MatchKind); 99 100% Data key header not in binding, go next data 101headers_match(Pattern = [{PK, _PT, _PV} | _], [{DK, _DT, _DV} | DRest], 102 AllMatch, AnyMatch, MatchKind) when PK > DK -> 103 headers_match(Pattern, DRest, AllMatch, AnyMatch, MatchKind); 104 105% Binding key header not in data, false with all, go next binding 106headers_match([{PK, _PT, _PV} | PRest], Data = [{DK, _DT, _DV} | _], 107 _AllMatch, AnyMatch, MatchKind) when PK < DK -> 108 headers_match(PRest, Data, false, AnyMatch, MatchKind); 109 110%% It's not properly specified, but a "no value" in a 111%% pattern field is supposed to mean simple presence of 112%% the corresponding data field. I've interpreted that to 113%% mean a type of "void" for the pattern field. 114headers_match([{PK, void, _PV} | PRest], [{DK, _DT, _DV} | DRest], 115 AllMatch, _AnyMatch, MatchKind) when PK == DK -> 116 headers_match(PRest, DRest, AllMatch, true, MatchKind); 117 118% Complete match, true with any, go next 119headers_match([{PK, _PT, PV} | PRest], [{DK, _DT, DV} | DRest], 120 AllMatch, _AnyMatch, MatchKind) when PK == DK andalso PV == DV -> 121 headers_match(PRest, DRest, AllMatch, true, MatchKind); 122 123% Value does not match, false with all, go next 124headers_match([{PK, _PT, _PV} | PRest], [{DK, _DT, _DV} | DRest], 125 _AllMatch, AnyMatch, MatchKind) when PK == DK -> 126 headers_match(PRest, DRest, false, AnyMatch, MatchKind). 127 128 129validate(_X) -> ok. 130create(_Tx, _X) -> ok. 131delete(_Tx, _X, _Bs) -> ok. 132policy_changed(_X1, _X2) -> ok. 133add_binding(_Tx, _X, _B) -> ok. 134remove_bindings(_Tx, _X, _Bs) -> ok. 135assert_args_equivalence(X, Args) -> 136 rabbit_exchange:assert_args_equivalence(X, Args). 137