Subject: [icnrg] RPC Piublish Method in NDN

From: "Thomas C. Schmidt" <[email-masked]>
Date: Sun, 18 Mar 2018 17:09:14 +0000

Hi Christian,

today you mentioned (without reference) a "new Publish method" build on 
primitives from HIP.

Can you give any further reference for this so that we can actually 
lookup the details?

Thanks,
  Thomas
-- 

Prof. Dr. Thomas C. Schmidt
° Hamburg University of Applied Sciences                  Berliner Tor 7 °
° Dept. Informatik, Internet Technologies Group   20099 Hamburg, Germany °
° http://inet.haw-hamburg.de/members/schmidt      Fon: +49-40-42875-8452 °

---

From: [email-masked]
Date: Mon, 19 Mar 2018 13:02:06 +0100 (CET)

Hi Thomas,

re your question for a reference: there are notes, and running code, on this laptop..

So I wrote down a description here - let's do a protocol review in the open!

Starting with HIP (RFC 7401), this is figure 1:

---
       Initiator                              Responder

                    I1: DH list
                  -------------------------->
                                              select precomputed R1
                    R1: puzzle, DH, key, sig
                  <-------------------------
    check sig                                 remain stateless
    solve puzzle
                  I2: solution, DH, {key}, sig
                  -------------------------->
    compute DH                                check puzzle
                                              check sig
                            R2: sig
                  <--------------------------
    check sig                                 compute DH

                                  Figure 1
---

The mechanics of these four messages (I1,R1,I2,R2) is described in 
section "4.1. Creating a HIP Association". Puzzle is a proof-of-work 
task for protecting against DoS attacks, it can be made trivial for 
trusted sensors or controlled environments.

Below follows how I mapped the HIP handshake to the Interest/Data 
exchange. First comes a sketch of the full RPC protocol implementation 
pattern. A more detailed description of the handshake phase follows 
afterwards.

Note that "args" is not multiple args for the function to be executed: 
instead this is a stream of args leading to repeated invocations of the 
same function. gRPC makes a strong point that RPC functions should only 
have one parameter, which also simplifies the RPC implementation in our 
case.

# handshake:

   caller            callee

     --> Interest(I1)
        <-- Interest(R1)
        --> Data(I2)   # including name of fct to be executed
     <-- Data(R2)

# arg fetching:
     <-- Interest(Arg)
     --> Data(ArgVal)

# result delivery (small results)
     <-- Interest(ResVal)
     --> Data(ResAck)

# result notification (large results)
     <-- Interest(Res,where)
       --> Interest(where)
       <-- Data(where, result)
     --> Data(ResAck)  # we can remove the data kept at "where"

# ping
     --> Interest(Alive?)
     <-- Data(ETA)

# abort
     --> Interest(Abort!)
     <-- Data(OK)

# close
     <-- Interest(Close)
     --> Data(CloseAck)

Arg fetching as well as result delivery or notification are "receiver 
driven": Only the RPC server knows when it is ready to consume a next 
arg for fct invocation, or when a result is ready to be delivered.

Zooming in. In my notes I used symbolic expressions to describe the I1 
etc msgs that have to be mangled into e.g. NDN's fields, and I also 
applying it to the NDN packets themselves. The first uppercase letter in 
a list is the type of the sexpr.

   "(N /a/b/d)" is a name,
   "(I (N /a/b/c) (P '\x9876ab'))    is an interest with a payload,
   "(D (N /a/b/c) (P 'my answer'))"  is a matching data packet,
   "(S (D...) sig)" is a signed data packet.

I Interest
D Data
N Name
P Payload tuple
S Signed
E Encrypted tuple

i_id is a (random) id of the initiator/caller
r_id is a (random) id of the responder/callee
the (i_id,r_id) tuple serves as a session id

---
   caller                                                                callee

     # RPC handshake:

     (I (N /rpc/pfx) (P (I1 i_id 0 (DH_list1 ...) (N /my/pfx))))
     ---------------------------------------------------------->            (a)

     (I (N /my/pfx) (P (S (R1 r_id i_id (DH_list2 ...) puzz DH_1) sig)))
     <------------------------------------------------------------------    (b)

     (S (D (N /my/pfx) (P (I2 (i_id r_id solu DH_2 (E key fctNm arg0))))) sig)
     ---------------------------------------------------------------------> (c)

     (S (D (N /rpc/pfx) (P (R2 r_id i_id (E ...)))) sig)
     <--------------------------------------------------
---

A note on the event points (a) and (b), which in HIP are labeled with 
"stateless":

In order to be stateless, at point (a), the callee REMOVEs the PIT entry 
of the incoming msg: The subsequent I2 event (3rd msg) will carry enough 
information to reconstruct the PIT state for replying with msg 4.

Similarily, at point (b), the RPC server does NOT create PIT state for 
the outgoing interest. The I2 event will carry all that is needed to 
create the RPC state as well as the reply details for sending back 
Data(R2). If the 2nd message is lost (we did not keep PIT state for R1), 
the caller will retransmit based on its PIT entry for I1.

At (c), after having checked the solution to the puzzle and having 
verified the signature, RPC state is finally created.

In your email you asked about "publish". I prefer to call it the 
"persist" procedure that RPC should execute. In gRPC it would look like 
this:

   service ICNnode {
     rpc persist(stream DataObj)   returns (stream Retcode);
   }

The unfolded message exchange:

   caller            callee

     # handshake
     --> Interest(I1)
        <-- Interest(R1)
        --> Data(I2)    # this msg contains the "persist" function name
     <-- Data(R2)

     # fetch the items to persist (until getting an end-of-arg response)
     <-- Interest(Arg)  # encrypted
     --> Data(ArgVal)   # encrypted

     # return AKC/Fail for each arg
     <-- Interest(ResVal)
     --> Data(ResAck)

     # close
     <-- Interest(Close)
     --> Data(CloseAck)

A useful second RPC method is "persistByName" for objects having MTU 
size. Using the L2 data in the PIT, the RPC server will do a callback to 
the caller.

   service ICNnode {
     rpc persistByName(stream Name)   returns (stream Retcode);
   }

   # fetch the name of an item to persist (until getting an end-of-arg response),
   # then do a callback for that name
   <-- Interest(Arg)      # encrypted
   --> Data(ArgObjName)   # encrypted
   <-- Interest(ObjName)  # send to caller's L2 addr, unencrypted
   --> Data(Obj)          # unencrypted

One could consider to collapse the many phases into a minimal single 
phase if there is only one item and it fits into the 3rd I2 message:

     # combined "handshake, RPC request and close":
     Interest(I1) -->
        <-- Interest(R1)
        --> Data(I2)       # fctNm="persistAndClose", arg0=data
     Data(R2) <--

Summary: The "persist" method and its variants run inside the RPC 
bracket. They do not need to do authentication themselves: the RPC 
handshake handles the signature verification and does DH for a session 
encryption key. A persist method then serves multiple requests in 
streaming mode, within that same session.

I hope this provides more color, happy to clarify more and getting 
comments.

christian

On Sun, 18 Mar 2018, Thomas C. Schmidt wrote:

> Can you give any further reference for this so that we can actually lookup 
> the details?
>
> Thanks,
> Thomas
> -- 
>

---

From: "Thomas C. Schmidt" <[email-masked]>
Date: Mon, 19 Mar 2018 14:05:19 +0000

Hi Christian,

On 19/03/2018 12:02, [email-masked] wrote:

>Oopsi - I didn't know I was supposed to break into your laptop ;)

> So I wrote down a description here - let's do a protocol review in the open!
Many thanks! I'll check the details and will be back soonish.

Cheers,
  Thomas

--