w:200

Context Management via JSON-RPC

Sirenia – Oct 2020

© 2020 SIRENIA — CONFIDENTIAL

Outline

  • CCOW interfaces
  • Transport protocols
    • Named Pipes
    • Netstring/TCP
  • Development setup
  • Demo
  • Example code
© 2020 SIRENIA — CONFIDENTIAL

CCOW interfaces

Functionality is split into interfaces from the CCOW standard itself.

Some interfaces, like ContextParticipant, are implemented by the applications (participants) while the majority are implemented by Manatee.

© 2020 SIRENIA — CONFIDENTIAL

ContextManager

The ContextManager interface contains methods for the lifecycle of context transactions and for managing participants.

It is the “entrypoint” for all communication with the context management system is the only interface that is required to use.

© 2020 SIRENIA — CONFIDENTIAL

ContextManager

type ContextManager interface
{
  MostRecentContextCoupon() int64

  StartContextChanges(participantCoupon int64) int64
  UndoContextChanges(contextCoupon int64)
  EndContextChanges(contextCoupon int64) (decision, reason string) 
  PublishChangesDecision(contextCoupon int64, decision string)

  JoinCommonContext(applicationName, componentId string, survey, wait bool) int64
  LeaveCommonContext(participantCoupon int64)
  SuspendParticipation(participantCoupon int64)
  ResumeParticipation(participantCoupon int64)
}
© 2020 SIRENIA — CONFIDENTIAL

ContextData

For extracting values and manipulating the contents of the common context.

type ContextData interface
{
  GetItemNames(contextCoupon int64) []string
  SetItemValues(participantCoupon, contextCoupon int64, names, values []string) []string
  GetItemValues(contextCoupon int64, itemNames []string, onlyChanges bool) []string
}
© 2020 SIRENIA — CONFIDENTIAL

ContextAction

This interface enables the context manager to receive requests from context participants to perform a context action. The context manager relays the request to perform the action to the appropriate participant and returns the resulting output.

It consists of a single Perform method:

type ContextAction interface {
  Perform(
    actionIdentifier string,
    participantCoupon int64, 
    inputNames, inputValues []string
  ) (decision, reason string, outputNames, outputValues []string)
}
© 2020 SIRENIA — CONFIDENTIAL

ContextParticipant

The ContextParticipant interface is expected to be implemented by the CP joining the common context. It contains methods that will be invoked by the CM on the CP.

type ContextParticipant interface {
  ContextChangesPending(contextCoupon int64) (decision, reason string)
  ContextChangesAccepted(contextCoupon int64)
  ContextChangesCancelled(contextCoupon int64)
  CommonContextTerminated()
  Ping() any
}
© 2020 SIRENIA — CONFIDENTIAL

ContextAgent

Context participants using the JSON-RPC can also implement the ContextAgent interface and respond to ContextAgent.ContextChangesPending requests to allow other participants to invoke its actions.

type ContextAgent interface {
  ContextChangesPending(
    actionIdentifier string,
    itemNames,
    itemValues []string,
    agentCoupon,
    contextCoupon int64,
    principal string
  ) (decision, reason string, itemNames, itemValues []string)
}
© 2020 SIRENIA — CONFIDENTIAL

ParticipantMonitor

This method is also optional to implement. It can be used by the participant to monitor certain events regarding any other participants.

type ParticipantMonitor interface {
  ParticipantsChanged(applicationName, event string)
  ApplicationLaunched(applicationName string, hwnd int64, pid int)
}
© 2020 SIRENIA — CONFIDENTIAL

JSON-RPC 2.0

JSON-RPC is a simple and light-weight remote procedure call protocol. It is transport agnostic and Manatee has built-in support for three transports; WebSockets, NamedPipes and Netstring/Tcp.

JSON-RPC messages are divided into four types: Request, Response, Notification and Error.

Each type places specific requirements on content and behavioural semantics:

  • a Request must always be followed by a Response.
  • a Reponse must contain either an Error or a Result.
  • a Notification and an Error (not in a Response) can be sent at any time.
© 2020 SIRENIA — CONFIDENTIAL

Request

The Request object contains:

  • jsonrpc indicating the version of JSON-RPC supported – always "2.0"
  • id is an identifier (string) used to correlate the request with the following response
  • method is a string identifying which invocation the requests represents
  • params which is an object with named parameters for the method to be invoked
{
  "jsonrpc":  "2.0",
  "id":       "123",
  "method":   "Plus",
  "params":   { "A": 200, "B": 100 },
}
© 2020 SIRENIA — CONFIDENTIAL

Response

The Response object must contain:

  • jsonrpc indicating the version of JSON-RPC supported – always "2.0"
  • id is the id of the Request object that this is a response to

Furthermore it must contain either:

  • result to indicate a successful invocation, containing an object with the result, or
  • error containing an Error to indicate a failed invocation.
© 2020 SIRENIA — CONFIDENTIAL

Response

For a succesful invocation;

{
  "jsonrpc":  "2.0",
  "id":       "123",
  "result":   { "A+B": 300  },
}

or in case of failure;

{
  "jsonrpc":  "2.0",
  "id":       "123",
  "error":    { "code": 9000, "message": "A is not a number" },
}
© 2020 SIRENIA — CONFIDENTIAL

Notification

A Notification is similar to a Response except it must not contain an id property.

An example could be;

{
  "jsonrpc":  "2.0",
  "method":   "UserLoggedIn",
  "params":   { "User": "John Doe" } 
}
© 2020 SIRENIA — CONFIDENTIAL

Error

An Error object must contain the following properties:

  • code an integer denoting the type of the error
  • message a string describing the error

Furthermore the following are optional:

  • data an object with further details about the error

An example error could be:

{
  "code":     -32601, // Method not found
  "message":  "The method Minus is not found" 
}
© 2020 SIRENIA — CONFIDENTIAL

Named Pipes

The NamedPipes transport uses two bi-directional pipes.

The first pipe is used for the interfaces in which the client/CP sends requests to the server/CM. Those are

  • ContextManager
  • ContextData
  • ContextAction
  • Registry
© 2020 SIRENIA — CONFIDENTIAL

Named Pipes

The pipe is named JSON-RPC v1/Context{Manager,Data,Action} and a server endpoint is created by the CM. The client must connect to it with

  • PipeDirection = InOut
  • TransmissionMode = Message

Once the CP issues a JoinCommonContext request and receives a ParticipantCoupon the CP must now connect to the second pipe in order for the CM to use the ContextParticipant (and ContextAgent and ParticipantMonitor) interface.

© 2020 SIRENIA — CONFIDENTIAL

Named Pipes

This second pipe must be created as a client pipe with the same properties as the first and the CM will initiate the server end after the JoinCommonContext invocation successfully completes.

This pipe is named JSON-RPC v1/ContextParticipant/<ParticipantCoupon>.

The reason why two named pipes are needed is such that the strict request/response sequence can be maintained and that the client and server may agree on this sequence based on the which requests/notifications/errors are issued by either.

The encoding for both pipes is UTF-8.

© 2020 SIRENIA — CONFIDENTIAL

Netstring/TCP

The Netstring transport is a raw TCP stream over which JSON-RPC messages are encoded using the Netstring scheme. Netstring is a simple encoding scheme in which the format is:

<byte-count>:<data>,

Where

  • <byte-count> are the number of bytes in <data>
  • <data> are the bytes of an UTF-8 encoded string
  • : is the delimeter between <byte-count> and <data>
  • , is the terminating char
© 2020 SIRENIA — CONFIDENTIAL

Netstring/TCP

{
  "jsonrpc":  "2.0",
  "id":       "1",
  "method":   "ContextParticipant.Ping"
}

would be encoded as:

61:{“jsonrpc”:“2.0”,“id”:“1”,“method”:“ContextParticipant.Ping”},

or the raw bytes:

36 31 3A 7B 22 6A 73 6F 6E 72 70 63 22 3A 22 32 2E 30 22 2C 22 69 64 22 3A 22 31 22 2C 22 6D 65 74 68 6F 64 22 3A 22 43 6F 6E 74 65 78 74 50 61 72 74 69 63 69 70 61 6E 74 2E 50 69 6E 67 22 7D 2C

© 2020 SIRENIA — CONFIDENTIAL

Netstring/TCP

The client should connect to the localhost:<port> where <port> can be found in the registry at

  • HKCU\Software\Sirenia\Manatee\Ports\netstringtcp or
  • HKCU\Software\Sirenia\Manatee\Ports\netstringtcpsecure.

The TCP stream is encrypted using SSL for the secure connection and the server certificate is written to disk at

  • %appdata%/Sirenia/Manatee/certs and the registry at
  • HKCU\Software\Sirenia\Manatee\Certs\netstringtcpsecure

if the client wishes to use it for validation.

© 2020 SIRENIA — CONFIDENTIAL

Development setup

Requirements for a dev setup is;

© 2020 SIRENIA — CONFIDENTIAL

Settings

Key Default value
IdentifyingSubjectToShowInBar "cpr"
ShowSessionIndicator true
MaxNumberOfSessions 10
SessionColors "#0E73CE,#D0021B,#369E2C"
ContextParticipantResponseTimeoutInSeconds 10
MaxContextTransactionDurationInSeconds 10
PublishChangesDecisionMaxWaitInSeconds 60
ContextManagerPingDelayInSeconds 30
ContextManagerTimeoutInSeconds 5
© 2020 SIRENIA — CONFIDENTIAL

Configuration

© 2020 SIRENIA — CONFIDENTIAL

Configuration

© 2020 SIRENIA — CONFIDENTIAL

Configuration

Or edit the %appdata%/Sirenia/Manatee/manatee.ini file directly and restart Manatee.

© 2020 SIRENIA — CONFIDENTIAL

Ports

Manatee will find usable ports when it starts its services. The port selection is somewhat randomized to allow for multiple Manatees to run on the same machine. The ports can be found in the registry at HKCU\Software\Sirenia\Manatee\Ports\ under their respective names, e.g.

  • netstringtpc for the regular Netstring/TCP port
  • netstringtcpsecure for the SSL encrypted Netstring/TCP port
  • websocketserver for the Websocket server
  • etc etc

The ports are reused s.t. if Manatee has decided on port N for a service it will try to use N first before trying another random choice. But you should always check the registry before connecting to ensure you have the correct port.

© 2020 SIRENIA — CONFIDENTIAL

Debugging

It is useful to connect with the DotNetContextParticipant to have another participant to verify and set values for a context. Important notes to remember:

  • the DotNetContextParticipant configuration needs to have the same subjects as the application under test
  • there are timeouts for many parts of the protocol, consider increasing their values if you are using a debugger and breaking the application under test
© 2020 SIRENIA — CONFIDENTIAL

Logs

For Manatee can be found at %appdata%/Sirenia/Manatee/logs/.

Default setting for LogLevel setting is set to "Debug" for more verbose logs.

© 2020 SIRENIA — CONFIDENTIAL

Demo

Source available at https://gitlab.com/sirenia/open/dotnetcontextparticipant

© 2020 SIRENIA — CONFIDENTIAL

Questions etc going forward

Emails with questions etc are welcome.

We’re also available for questions, help with debugging etc at the #cambio channel on the Sirenia / Open slack instance.

Email jonathan@sirenia.eu for invites.

© 2020 SIRENIA — CONFIDENTIAL

--- ```cs JoinCommonContext().ContinueWith(result => { _participantCoupon = (long)result.Result.GetValue("ParticipantCoupon"); var sessionColor = result.Result.GetValue("Color")?.ToString(); return GetItemNames().ContinueWith(ginTask => { var names = ginTask.Result.GetValue("Names") as JArray; GetItemValues(-1, names?.Select(n => n.ToString()) ?? new string[0]) .ContinueWith(givTask => { var contextKeyValues = new List<ContextKeyValue>(); for (var i = 1; i < jValues.Count; i += 2) { contextKeyValues.Add( new ContextKeyValue(jValues[i - 1].ToString(), jValues[i].ToString(), this) ); } ValuesReceived?.Invoke(this, contextKeyValues); }); }); }); ```