<?xml version="1.0" encoding="latin1" ?>
<!DOCTYPE chapter SYSTEM "chapter.dtd">

<chapter>
   <header>
    <copyright>
      <year>2001</year><year>2009</year>
      <holder>Ericsson AB. All Rights Reserved.</holder>
    </copyright>
    <legalnotice>
      The contents of this file are subject to the Erlang Public License,
      Version 1.1, (the "License"); you may not use this file except in
      compliance with the License. You should have received a copy of the
      Erlang Public License along with this software. If not, it can be
      retrieved online at http://www.erlang.org/.
    
      Software distributed under the License is distributed on an "AS IS"
      basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
      the License for the specific language governing rights and limitations
      under the License.
    
    </legalnotice>

    <title>Orber Interceptors</title>
    <prepared>Nick</prepared>
    <docno></docno>
    <date>2001-08-16</date>
    <rev></rev>
    <file>ch_interceptors.xml</file>
  </header>

  <section>
    <title>Using Interceptors</title>
    <p>For Inter-ORB communication, e.g., via <c>IIOP</c>, it is possible
      to intercept requests and replies. To be able to use <c>Interceptors</c>
      Orber the configuration parameter <c>interceptors</c> must be defined.</p>

    <section>
      <title>Configure Orber to Use Interceptors</title>
      <p>The configuration parameter <c>interceptors</c> must be defined, e.g.,
        as command line option:</p>
      <code type="none">
erl -orber interceptors "{native, ['myInterceptor']}"
      </code>
      <p>It is possible to use more than one interceptor; simply add them to the
        list and they will be invoked in the same order as they appear in the list.</p>
      <p>One can also active and deactivate an interceptor during
        run-time, but this will only affect currently existing connections. 
        For more information, consult Orber's Reference Manual regarding the
        operations <c>orber:activate_audit_trail/0/1</c> and
        <c>orber:activate_audit_trail/0/1.</c></p>
    </section>

    <section>
      <title>Creating Interceptors</title>
      <p>Each supplied interceptor <em>must</em> export the following functions:</p>
      <list type="bulleted">
        <item><em>new_out_connection/3/5</em> - one of these operations is called when
         a client application calls an object residing on remote ORB.
         If an interceptor exports both versions, arity 3 and 5, which
         operation that will be invoked is Orber internal.</item>
        <item><em>new_in_connection/3/5</em> - one of these operations is invoked
         when a client side ORB tries to set up a connection to the target ORB.
         If an interceptor exports both versions, arity 3 and 5, which
         operation that will be invoked is Orber internal.</item>
        <item><em>out_request/6</em> - supplies all request data on the client side
         ORB.</item>
        <item><em>out_request_encoded/6</em> - similar to <c>out_request</c>
         but the request body is encode.</item>
        <item><em>in_request_encoded/6</em> - after a new request arrives at the
         target ORB the request data is passed to the interceptor in
         encoded format.</item>
        <item><em>in_request/6</em> - prior to invoking the operation on the
         target object, the interceptor <c>in_request</c> is called.</item>
        <item><em>out_reply/6</em> - after the target object replied the
        <c>out_reply</c> operation is called with the result of the object
         invocation.</item>
        <item><em>out_reply_encoded/6</em> - before sending a reply back to the
         client side ORB this operation is called with the result in 
         encoded format.</item>
        <item><em>in_reply_encoded/6</em> - after the client side ORB receives
         a reply this function is called with the reply in encoded 
         format.</item>
        <item><em>in_reply/6</em> - before delivering the reply to the client
         this operation is invoked.</item>
        <item><em>closed_in_connection/1</em> - when a connection is terminated
         on the client side this function is called.</item>
        <item><em>closed_out_connection/1</em> - if an outgoing connection is
         terminated this operation will be invoked.</item>
      </list>
      <p>The operations <c>new_out_connection</c>, <c>new_in_connection</c>, 
        <c>closed_in_connection</c> and <c>closed_out_connection</c> operations
        are only invoked <em>once</em> per connection. The remaining operations
        are called, as shown below, for every Request/Reply to/from remote
        CORBA Objects.</p>
      <marker id="interceptor_operations"></marker>
      <image file="interceptor_operations.gif">
        <icaption>
The Invocation Order of Interceptor Functions.</icaption>
      </image>
    </section>
  </section>

  <section>
    <title>Interceptor Example</title>
    <p>Assume we want to create a simple access service which purpose is to:</p>
    <list type="bulleted">
      <item>Only allow incoming request from ORB's residing on a certain set of
       nodes.</item>
      <item>Restrict the objects any client may invoke operations on.</item>
      <item>Only allow outgoing requests to call a limited set of external
       ORB's.</item>
      <item>Add a checksum to each binary request/reply body.</item>
    </list>
    <p>To restricts the access we use a <c>protected</c> and <c>named</c> ets-table
      holding all information. How the ets-table is initiated and maintained
      is implementation specific, but it contain 
      <c>{Node, ObjectTable, ChecksumModule}</c> where <c>Node</c> is used as 
      ets-key, <c>ObjectTable</c> is a reference to another ets-table in which
      we store which objects the clients are allowed to invoke operations on
      and <c>ChecksumModule</c> determines which module we should use to handle
      the checksums. </p>
    <code type="none">
new_in_connection(Arg, Host, Port) ->
    %% Since we only use one interceptor we do not care about the
    %% input Arg since it is set do undefined by Orber.
    case ets:lookup(in_access_table, Host) of
         [] ->
            %% We may want to log the Host/Port to see if someone tried
            %% to hack in to our system.
            exit("Access not granted");
         [{Host, ObjTable, ChecksumModule}] ->
            {ObjTable, ChecksumModule}
    end.
    </code>
    <p>The returned tuple, i.e., {ObjTable, ChecksumModule}, will be passed
      as the first argument whenever invoking one of the interceptor functions.
      Unless the connection attempt did not fail we are now ready for receiving
      requests from the client side ORB.</p>
    <p>When a new request comes in the first interceptor function to be invoked is
      <c>in_request_encoded</c>. We will remove the checksum from the coded
      request body in the following way:</p>
    <code type="none">
in_request_encoded({ObjTable, ChecksumModule}, ObjKey, Ctx, Op, Bin, Extra) ->
    NewBin = ChecksumModule:remove_checksum(Bin),
    {NewBin, Extra}.
    </code>
    <p>If the checksum check fails the <c>ChecksumModule</c> should invoke exit/1.
      But if the check succeeded we are now ready to check if the client-ORB 
      objects are allowed to invoke operations on the target object. Please note,
      it is possible to run both checks in <c>in_request_encoded</c>. Please
      note, the checksum calculation must be relatively fast to ensure a
      good throughput.</p>
    <p>If we want to we can restrict any clients to only use a subset of operations
      exported by a server:</p>
    <code type="none">
in_request({ObjTable, ChecksumModule}, ObjKey, Ctx, Op, Params, Extra) ->
    case ets:lookup(ObjTable, {ObjKey, Op}) of
         [] ->
            exit("Client tried to invoke illegal operation");
         [SomeData] ->
            {Params, Extra}
    end.
    </code>
    <p>At this point Orber are now ready to invoke the operation on the target 
      object. Since we do not care about what the reply is the <c>out_reply</c>
      function do nothing, i.e.:</p>
    <code type="none">
out_reply(_, _, _, _, Reply, Extra) ->
    {Reply, Extra}.
    </code>
    <p>If the client side ORB expects a checksum to be added to the reply we
      add it by using:</p>
    <code type="none">
out_reply_encoded({ObjTable, ChecksumModule}, ObjKey, Ctx, Op, Bin, Extra) ->
    NewBin = ChecksumModule:add_checksum(Bin),
    {NewBin, Extra}.
    </code>
    <warning>
      <p>If we manipulate the binary as above the behavior <em>must</em>
        be <c>Bin == remove_checksum(add_checksum(Bin))</c>.</p>
    </warning>
    <p>For outgoing requests the principle is the same. Hence, it is not further
      described here. The complete interceptor module would look like:</p>
    <code type="none">

-module(myInterceptor).

%% Interceptor functions.
-export([new_out_connection/3,
     new_in_connection/3,
     closed_in_connection/1,
     closed_out_connection/1,
     in_request_encoded/6,
     in_reply_encoded/6,
     out_reply_encoded/6,
     out_request_encoded/6,
     in_request/6,
     in_reply/6,
     out_reply/6,
     out_request/6]).

new_in_connection(Arg, Host, Port) ->
    %% Since we only use one interceptor we do not care about the
    %% input Arg since it is set do undefined by Orber.
    case ets:lookup(in_access_table, Host) of
         [] ->
            %% We may want to log the Host/Port to see if someone tried
            %% to hack in to our system.
            exit("Access not granted");
         [{Host, ObjTable, ChecksumModule}] ->
            {ObjTable, ChecksumModule}
    end.

new_out_connection(Arg, Host, Port) ->
    case ets:lookup(out_access_table, Host) of
         [] ->
            exit("Access not granted");
         [{Host, ObjTable, ChecksumModule}] ->
            {ObjTable, ChecksumModule}
    end.

in_request_encoded({_, ChecksumModule}, ObjKey, Ctx, Op, Bin, Extra) ->
    NewBin = ChecksumModule:remove_checksum(Bin),
    {NewBin, Extra}.

in_request({ObjTable, _}, ObjKey, Ctx, Op, Params, Extra) ->
    case ets:lookup(ObjTable, {ObjKey, Op}) of
         [] ->
            exit("Client tried to invoke illegal operation");
         [SomeData] ->
            {Params, Extra}
    end.

out_reply(_, _, _, _, Reply, Extra) ->
    {Reply, Extra}.

out_reply_encoded({_, ChecksumModule}, ObjKey, Ctx, Op, Bin, Extra) ->
    NewBin = ChecksumModule:add_checksum(Bin),
    {NewBin, Extra}.

out_request({ObjTable, _}, ObjKey, Ctx, Op, Params, Extra) ->
    case ets:lookup(ObjTable, {ObjKey, Op}) of
         [] ->
            exit("Client tried to invoke illegal operation");
         [SomeData] ->
            {Params, Extra}
    end.

out_request_encoded({_, ChecksumModule}, ObjKey, Ctx, Op, Bin, Extra) ->
    NewBin = ChecksumModule:add_checksum(Bin),
    {NewBin, Extra}.

in_reply_encoded({_, ChecksumModule}, ObjKey, Ctx, Op, Bin, Extra) ->
    NewBin = ChecksumModule:remove_checksum(Bin),
    {NewBin, Extra}.

in_reply(_, _, _, _, Reply, Extra) ->
    {Reply, Extra}.

closed_in_connection(Arg) ->
    %% Nothing to clean up.
    Arg.

closed_out_connection(Arg) ->
    %% Nothing to clean up.
    Arg.
    </code>
    <note>
      <p>One can also use interceptors for debugging purposes, e.g.,
        print which objects and operations are invoked with which arguments
        and the outcome of the operation. In conjunction with the configuration
        parameter <c>orber_debug_level</c> it is rather easy to find out what
        went wrong or just to log the traffic.  </p>
    </note>
  </section>
</chapter>

