Page: UMI protocol
2019-12-13 17:12
UMI protocol
Download the UMI tool from our cloud storage: 🔗lnd.im/umi
(Preview) Also you can use Docker image
universa/umi
UMI protocol (meaning “Universa Method Invocation”) is used to create Universa API bindings for various programming languages, in an RMI paradigm.
Universa UMI is executed over farcall
protocol calls (https://github.com/sergeych/farcall/wiki) in JSON format. For test/debug purposes, you can access it using the regular netcat
/nc
tools available in many Unix/Linux systems. For example, if the UMI tool is running in TCP socket mode, at localhost
(or 0.0.0.0
) host and port 34567
, you can call the version
command (see below) via
echo '{"serial":0,"cmd":"version"}' | nc 127.0.0.1 34567
and you will receive the response like:
{"result":{"protocol":"0.0.3","system":"UMI","version":"0.8.63"},"ref":0,"serial":0}
See also UMI usage code samples in other languages.
Running UMI tool
You’ll need Java 8 JRE (or newer) to launch UMI.
Extract the contents of the archive and start it in any of 5 available modes. Note that JSON is always used to transmit farcall packets.
Pipe mode
Execute ./bin/umi
and use the process stdin/stdout for communication.
TCP/Socket mode
Using this is not recommended, as the socket connection is relatively hard to protect from unintended connections, comparing to launching in the Pipe mode.
./bin/umi --listen tcp://localhost:$PORT
Connect to the socket and start farcal over the connection.
Unix socket mode
Run as
./bin/umi --listen unix://$path_to_unix_socket
Connect to the socket and start farcal over the connection.
Docker image (preview)
As an alternative to the provided binary, you can use the Docker image (currently in preview stage) of UMI; it is published as universa/umi
.
To launch:
docker run --publish 127.0.0.1:27000:27000/tcp universa/umi --listen tcp://0.0.0.0:27000
The Docker image publishes the internal port 27000; so if you (most likely) want to access the UMI tool using TCP interface, you need to launch it with --listen tcp://0.0.0.0:27000
(not any other port).
To test the UMI tool is running correctly, you can use the netcat
(nc
) program; for example, let’s call the version UMI command after image launch:
echo '{"serial":0,"cmd":"version"}' | nc 127.0.0.1 27000
Check universa/node page at Docker Hub for details.
Commands list
API Conventions
The API functions are specified in the following form:
some_method({foo: <string>,bar: <timestamp>}) -> {result: <string>}
Which means:
- the callable method
some_method
(with websock unichat endpoint also available assomeMethod
) - it takes 2 named arguments,
foo
of typestring
andbar
of type timestamp which all are explained in detail later - it returns single named value,
result
, which is astring
.
UMI API data types
The api is multiplatform and so are its data types.
name | type |
---|---|
<int> |
integer signed value of 32 bits |
<long> |
integer signed value of 64 bits |
<string> |
a string |
<boolean> |
a boolean |
<decimal_string> |
decimal value (like float number but of higher precision) |
<timestamp> |
date and time in |
<simple_type> |
any on above listed types |
<remote_object> |
a structure represending remote object created on UMI tool side { __type : RemoteObject , id: <long> , className: <string> } |
<remote_object_no_type> |
a structure represending remote object created on UMI tool side { __type : RemoteObject , id: <long> , className: <opt_string> } |
<serialized_object> |
a structure represending complex datatype in the serialized form { __type : <string> , ... } |
<invoсation_result> |
is either <simple_type> or <serialized_object> or <remote_object> |
<invoсation_param> |
is either <simple_type> or <serialized_object> or <remote_object_no_type> |
If the type is prepended with opt_
prefix, e.g. <opt_string>
, it means that corresponding parameter may be omitted or missing.
If the type is prepended with vararg_
prefix, e.g. <vararg_string>
, it means that there could be 0 or more parameters of corresponding type.
instantiate
instantiate({objectType: <string>, args: <vararg_invocation_param>})
-> {result: <remote_object>}
Creates a remote object of given type. objectType
must be on the list of instantiable types.
invoke
invoke({remoteObjectId: <long>, methodName: <string>, args: <vararg_invocation_param>})
-> {result: <invocation_result>}
Invokes given method of remote object with given remoteObjectId
.
invoke({objectType: <string>, methodName: <string>, args: <vararg_invocation_param>})
-> {result: <invocation_result>}
Invokes given static method of given type. objectType
must be on the list of instantiable types.
The actual result type is depending on return value of method being invoked:
<simple_type>
for methods returning simple types<remote_object>
for methods returning instance of linkable types<serialized_object>
for methods returning type that serialization is possible for<string>
for other methods. Some kind of string representation of the returned object (java.lang.Object.toString())
get_field
get_field({remoteObjectId: <long>, fieldName: <string>})
-> {result: <invoсation_result>}
Returns value of given field of remote object with given remoteObjectId
.
get_field({objectType: <string>, fieldName: <string>})
-> {result: <invocation_result>}
Returns value of given static field of given type. objectType
must be on the list of instantiable types.
The actual result type is depending on return value of method being invoked:
<simple_type>
for methods returning simple types<remote_object>
for methods returning instance of linkable types<serialized_object>
for methods returning type that serialization is possible for<string>
for other methods. Some kind of string representaion of the returned object (java.lang.Object.toString())
set_field
set_field({remoteObjectId: <long>, fieldName: <string>, value: <invocation_param>})
-> {result: null}
Sets value of given field of remote object with given remoteObjectId
.
set_field({objectType: <string>, fieldName: <string>, value: <invocation_param>})
-> {result: null}
Sets value of given static field of given type. objectType
must be on the list of instantiable types.
get
get({remoteObjectId: <long>}) -> {result: <serialized_object>}
Returns serialized version of remote object. It takes object id as parameter.
drop_objects
drop_objects({ids: [<long>,...]}) -> {result: <int>}
Destroys given remote objects. It takes ids as the paramenters. The number of remote objects left is returned
drop_all
drop_all() -> {result: <int>}
Destroys all remote objects. The number of remote objects left is returned (zero)
version
version() -> {protocol: <string>, system: <string>, version: <string> }
Examples
Create key address from string
Remote object of type KeyAddress is created first
Scala:
val jsc: JsonConnector = new JsonConnector(socket.getInputStream, socket.getOutputStream)
val ofc = new ObjectFarcall(jsc)
ofc.start()
val keyAddress1 = ofc.instantiate("KeyAddress", "YnFjR2pcTK3rFko1z7dP9qnyBRvuAwK9VBpfeR2m6sBgyQhbtb")
Farcall:
command: `instantiate`
params: ["KeyAddress","YnFjR2pcTK3rFko1z7dP9qnyBRvuAwK9VBpfeR2m6sBgyQhbtb"]
result: {"className":"com.icodici.crypto.KeyAddress","__type":"RemoteObject","id":1}
Raw exchange:
➔
{"serial":0,"args":["KeyAddress","YnFjR2pcTK3rFko1z7dP9qnyBRvuAwK9VBpfeR2m6sBgyQhbtb"],"cmd":"instantiate"}
←
{"result":{"className":"com.icodici.crypto.KeyAddress","__type":"RemoteObject","id":1},"ref":0,"serial":0}
Generate private key
Private key of 2048 bits strength is generated then
Scala:
val privateKey1 = ofc.instantiate("PrivateKey", 2048)
Farcall:
command: `instantiate`
params: ["PrivateKey",2048]
result: {"className":"com.icodici.crypto.PrivateKey","__type":"RemoteObject","id":2}
Raw exchange:
➔
{"serial":1,"args":["PrivateKey",2048],"cmd":"instantiate"}
←
{"result":{"className":"com.icodici.crypto.PrivateKey","__type":"RemoteObject","id":2},"ref":1,"serial":1}
Create another key address from string
Scala:
val keyAddress2 = ofc.instantiate("KeyAddress", "Jg8mgpELueFVbtVTbhLZMT3ZzyZSDftgzXhExshjDc5j5EasTjrHd2A9qDyXHJQJj3726Z7t")
Farcall:
command: `instantiate`
params: ["KeyAddress","Jg8mgpELueFVbtVTbhLZMT3ZzyZSDftgzXhExshjDc5j5EasTjrHd2A9qDyXHJQJj3726Z7t"]
result: {"className":"com.icodici.crypto.KeyAddress","__type":"RemoteObject","id":3}
Raw exchange:
➔
{"serial":2,"args":["KeyAddress","Jg8mgpELueFVbtVTbhLZMT3ZzyZSDftgzXhExshjDc5j5EasTjrHd2A9qDyXHJQJj3726Z7t"],"cmd":"instantiate"}
←
{"result":{"className":"com.icodici.crypto.KeyAddress","__type":"RemoteObject","id":3},"ref":2,"serial":2}
Compare two addresses
Invoke isMatchingKeyAddress
method of one remote object passing other remote object as parameter
Scala:
val res =ofc.invoke(keyAddress1,"isMatchingKeyAddress",keyAddress2)
assert(res == false)
Command:
command: `invoke`
params: [1,"isMatchingKeyAddress",{"__type":"RemoteObject","id":3}]
result: false
Raw exchange:
➔
{"serial":3,"args":[1,"isMatchingKeyAddress",{"__type":"RemoteObject","className":"com.icodici.crypto.KeyAddress","id":3}],"cmd":"invoke"}
←
{"result":false,"ref":3,"serial":3}
Get 2nd address (serialized)
Scala:
val address2value = ofc.get(keyAddress2)
Farcall:
command: `get`
params: [3]
result: {"uaddress":{"base64":"EK66czfW9p4Tjt4ObQT1eH9DZ7LKU8sEwohkMV3ATC2gDYBMkyBl0vOxAkDw8B3I5Y6kqCc=","__type":"binary"},"__type":"KeyAddress"}
Raw exchange:
➔
{"serial":4,"args":[3],"cmd":"get"}
←
{"result":{"uaddress":{"base64":"EK66czfW9p4Tjt4ObQT1eH9DZ7LKU8sEwohkMV3ATC2gDYBMkyBl0vOxAkDw8B3I5Y6kqCc=","__type":"binary"},"__type":"KeyAddress"},"ref":4,"serial":4}
Create Contract object
Scala:
val contract = ofc.instantiate("Contract",privateKey1)
Farcall:
command: `instantiate`
params: ["Contract",{"__type":"RemoteObject","id":2}]
result: {"className":"com.icodici.universa.contract.Contract","__type":"RemoteObject","id":4}
Raw exchange:
➔
{"serial":5,"args":["Contract",{"__type":"RemoteObject","className":"com.icodici.crypto.PrivateKey","id":2}],"cmd":"instantiate"}
←
{"result":{"className":"com.icodici.universa.contract.Contract","__type":"RemoteObject","id":4},"ref":5,"serial":5}
Set issuer of a Contract
Two key addresses and private key are passed to setIssuerKeys method. Second address is passed as a value
Scala:
ofc.invoke(contract,"setIssuerKeys",keyAddress1,address2value,privateKey1)
Farcall:
command: `invoke`
params: [4,"setIssuerKeys",{"__type":"RemoteObject","id":1},{"uaddress":{"base64":"EK66czfW9p4Tjt4ObQT1eH9DZ7LKU8sEwohkMV3ATC2gDYBMkyBl0vOxAkDw8B3I5Y6kqCc=","__type":"binary"},"__type":"KeyAddress"},{"__type":"RemoteObject","id":2}]
result: {"className":"com.icodici.universa.contract.roles.SimpleRole","__type":"RemoteObject","id":5}
Raw exchange:
➔
{"serial":6,"args":[4,"setIssuerKeys",{"__type":"RemoteObject","className":"com.icodici.crypto.KeyAddress","id":1},{"__type":"KeyAddress","uaddress":{"__type":"binary","base64":"EK66czfW9p4Tjt4ObQT1eH9DZ7LKU8sEwohkMV3ATC2gDYBMkyBl0vOxAkDw8B3I5Y6kqCc="}},{"__type":"RemoteObject","className":"com.icodici.crypto.PrivateKey","id":2}],"cmd":"invoke"}
←
{"result":{"className":"com.icodici.universa.contract.roles.SimpleRole","__type":"RemoteObject","id":5},"ref":6,"serial":6}
Seal contract and get sealed binary
Scala:
val bytes = ofc.invoke(contract,"seal")
Farcall:
command: `invoke`
params: [4,"seal"]
result: {"base64":"JyNkYXRhxC4D....p7SCiw==","__type":"binary"}
Raw exchange:
➔
{"serial":7,"args":[4,"seal"],"cmd":"invoke"}
←
{"result":{"base64":"JyNkYXRhxC4D....p7SCiw==","__type":"binary"},"ref":7,"serial":7}
Tips
Passing enum to a function
Enums can be easily passed by name. Please note that current implementation does not differ enums arguments from string arguments when matching method that about to invoked.
val lr = ofc.instantiate("ListRole","somerole")
ofc.invoke(lr,"setMode","ANY")
{"serial":0,"args":["ListRole"],"cmd":"instantiate"}
{"result":{"className":"com.icodici.universa.contract.roles.ListRole","__type":"RemoteObject","id":1},"ref":0,"serial":0}
{"serial":1,"args":[1,"setMode","ANY"],"cmd":"invoke"}
{"result":null,"ref":1,"serial":1}
Passing array to a function
Arrays are passed as normal JSArrays in arguments
val conditionsBinder = ofc.invoke("Binder","of","all_of",Seq("ref.state.origin==this.id","ref.state.revision==1"))
val reference = ofc.instantiate("Reference")
ofc.invoke(reference,"setConditions",conditionsBinder)
{"serial":0,"args":["Binder","of","all_of",["ref.state.origin==this.id","ref.state.revision==1"]],"cmd":"invoke"}
{"result":{"className":"net.sergeych.tools.Binder","__type":"RemoteObject","id":1},"ref":0,"serial":0}
{"serial":1,"args":["Reference"],"cmd":"instantiate"}
{"result":{"className":"com.icodici.universa.contract.Reference","__type":"RemoteObject","id":2},"ref":1,"serial":1}
{"serial":2,"args":[2,"setConditions",{"__type":"RemoteObject","className":"net.sergeych.tools.Binder","id":1}],"cmd":"invoke"}
{"result":{"className":"com.icodici.universa.contract.Reference","__type":"RemoteObject","id":3},"ref":2,"serial":2}
Passing set to a function
Set needs to be instantiated on host and passed as RemoteObject argument. Set can be initialized with elements of array passed as a parameter to instantiate command. Elements can be added later with add/addAll methods getting single argument or array as parameter.
val contract =ofc.instantiate("Contract")
ofc.invoke(contract,"seal")
val privateKey1 = ofc.instantiate("PrivateKey", 2048)
val privateKey2 = ofc.instantiate("PrivateKey", 2048)
val privateKey3 = ofc.instantiate("PrivateKey", 2048)
val privateKey4 = ofc.instantiate("PrivateKey", 2048)
val privateKey5 = ofc.instantiate("PrivateKey", 2048)
//instantiate set. initialize with privateKey1 and privateKey2
val set = ofc.instantiate("Set", Seq(privateKey1,privateKey2))
//Add privateKey3 to set
ofc.invoke(set,"add",privateKey3)
//Add two more keys (privateKey4, privateKey5) from array
ofc.invoke(set,"addAll",Seq(privateKey4,privateKey5))
ofc.invoke(contract,"addSignatureToSeal",set)
{"serial":0,"args":["Contract"],"cmd":"instantiate"}
{"result":{"className":"com.icodici.universa.contract.Contract","__type":"RemoteObject","id":1},"ref":0,"serial":0}
{"serial":1,"args":[1,"seal"],"cmd":"invoke"}
{"result":{"__type":"binary", ... },"ref":1,"serial":1}
{"serial":2,"args":["PrivateKey",2048],"cmd":"instantiate"}
{"result":{"className":"com.icodici.crypto.PrivateKey","__type":"RemoteObject","id":3},"ref":2,"serial":2}
{"serial":3,"args":["PrivateKey",2048],"cmd":"instantiate"}
{"result":{"className":"com.icodici.crypto.PrivateKey","__type":"RemoteObject","id":4},"ref":3,"serial":3}
{"serial":4,"args":["PrivateKey",2048],"cmd":"instantiate"}
{"result":{"className":"com.icodici.crypto.PrivateKey","__type":"RemoteObject","id":5},"ref":4,"serial":4}
{"serial":5,"args":["PrivateKey",2048],"cmd":"instantiate"}
{"result":{"className":"com.icodici.crypto.PrivateKey","__type":"RemoteObject","id":6},"ref":5,"serial":5}
{"serial":6,"args":["PrivateKey",2048],"cmd":"instantiate"}
{"result":{"className":"com.icodici.crypto.PrivateKey","__type":"RemoteObject","id":7},"ref":6,"serial":6}
{"serial":7,"args":["Set",[{"__type":"RemoteObject","className":"com.icodici.crypto.PrivateKey","id":3},{"__type":"RemoteObject","className":"com.icodici.crypto.PrivateKey","id":4}]],"cmd":"instantiate"}
{"result":{"className":"java.util.HashSet","__type":"RemoteObject","id":8},"ref":7,"serial":7}
{"serial":8,"args":[8,"add",{"__type":"RemoteObject","className":"com.icodici.crypto.PrivateKey","id":5}],"cmd":"invoke"}
{"result":true,"ref":8,"serial":8}
{"serial":9,"args":[8,"addAll",[{"__type":"RemoteObject","className":"com.icodici.crypto.PrivateKey","id":6},{"__type":"RemoteObject","className":"com.icodici.crypto.PrivateKey","id":7}]],"cmd":"invoke"}
{"result":true,"ref":9,"serial":9}
{"serial":10,"args":[1,"addSignatureToSeal",{"__type":"RemoteObject","className":"java.util.HashSet","id":8}],"cmd":"invoke"}
{"result":{"base64":"JyNkYXR...qpNhw==","__type":"binary"},"ref":11,"serial":11}
Getting Object[] elements
For types like Object[] there are only 2 invokable methods: []
and length
. First takes an integer as the only argument, second takes no args.
Currently the only linkable []-type is Contract[] but it can be changed in future.
val key = ofc.instantiate("PrivateKey",2048)
val contract = ofc.instantiate("Contract",key)
ofc.invoke(contract,"seal")
val contracts = ofc.invoke(contract,"split",3).asInstanceOf[ObjectFarcall.RemoteObject]
val size = ofc.invoke(contracts,"length")
val lastPart = ofc.invoke(contracts,"[]",2).asInstanceOf[ObjectFarcall.RemoteObject]
assert(size == 3)
assert(lastPart.className == classOf[Contract].getCanonicalName)
{"serial":0,"args":["PrivateKey",2048],"cmd":"instantiate"}
{"result":{"className":"com.icodici.crypto.PrivateKey","__type":"RemoteObject","id":1},"ref":0,"serial":0}
{"serial":1,"args":["Contract",{"__type":"RemoteObject","className":"com.icodici.crypto.PrivateKey","id":1}],"cmd":"instantiate"}
{"result":{"className":"com.icodici.universa.contract.Contract","__type":"RemoteObject","id":2},"ref":1,"serial":1}
{"serial":2,"args":[2,"seal"],"cmd":"invoke"}
{"result":{"base64":"JyNk...4t4w==","__type":"binary"},"ref":2,"serial":2}
{"serial":3,"args":[2,"split",3],"cmd":"invoke"}
{"result":{"className":"com.icodici.universa.contract.Contract[]","__type":"RemoteObject","id":4},"ref":3,"serial":3}
{"serial":4,"args":[4,"length"],"cmd":"invoke"}
{"result":3,"ref":4,"serial":4}
{"serial":5,"args":[4,"[]",2],"cmd":"invoke"}
{"result":{"className":"com.icodici.universa.contract.Contract","__type":"RemoteObject","id":5},"ref":5,"serial":5}