1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146
|
# Extending the Ice interface
The server component supports RPC via [ZeroC Ice](https://zeroc.com/products/ice). This document describes how the Ice interface of the server can be
extended.
Note: If not stated otherwise all referenced files live in `src/murmur/`.
The files involved in extending the Ice interface are
| **File** | **Description** |
| -------- | --------------- |
| `MumbleServer.ice` | Contains the formal definition of the interface |
| `MumbleServer.h` | Contains the C++ interface definition (abstract base classes). This file is generated based on `MumbleServer.ice` when invoking cmake (via `slice2cpp`). |
| `MumbleServer.cpp` | Contains some boilerplate and Ice-internal implementation code. This file is automatically generated. |
| `MumbleServerI.h` | Contains the definition of the actually implemented API classes (`ServerI` and `MetaI`). These extend the abstract base classes from `MumbleServer.h` |
| `MumbleServerWrapper.cpp` | Contains wrapper implementation of the `*I` API classes. This file is automatically generated. |
| `MumbleServere.h` | Contains the definition of a statically used helper class |
| `MumbleServere.cpp` | Contains the implementation of that helper class **and** _static_ functions used to actually implement the server-side functionality of the Ice API functions |
| `RPC.cpp` | Contains the implementations of the `Server` (the Mumble server, _not_ the Ice API type) class's member functions that are required to make certain functionality accessible to the static functions in `MumbleServerIce.cpp` |
All auto-generated functions will end up in the corresponding directory inside the `build` directory.
## Overview
The steps are
1. Add new function declarations to `MumbleServerI.h`
2. Write impl function in `MumbleServerIce.cpp`
3. Potentially write new public API functions (declare in `Server.h` and define in `RPC.cpp`
## Details
The `MumbleServerIceWrapper.cpp` file contains the `*_async` versions of the Ice callbacks that handle the async nature of these callbacks and also
contain the boilerplate for e.g. verification of the caller and things like this. Most importantly though these functions call the `impl_*` functions
defined in `MumbleServerIce.cpp`. For instance a function called `updateCertificates` inside the `Server` class will call
`impl_Server_updateCertificate` which has to be defined as a `static` function inside `MumbleServerIce.cpp`.
The declarations of the async functions generated this way are contained inside `MumbleServerI.h`. You have to manually add the function's declaration
into there. The easiest way to do this is to let the implementation be auto-generated and then copy the function signature from there into the
`MumbleServer.h` file (make the declaration `virtual` though).
The impl function's signature is always
```cpp
static void impl_<className>_<functionName>(const ::MumbleServer::AMD_<className>_<functionName>Ptr cb [, int server_id] [, <function arguments>]) {
// Implementation goes here
cb->ice_response([<function return value>]);
}
```
- `<className>`: Name of the class the function is declared in (e.g. `Server` or `Meta`)
- `<functionName>`: Name of the function as declared in the `MumbleServer.ice` file
- `[, int server_id]`: Only needed when extending the `Server` API class (the brackets are not part of what needs to be written in code)
- `[, <function arguments>]`: To be replaced by the list of arguments the function takes
- `[<function return value>]`: To be replaced with the value this function returns or to be removed if the function does not return anything.
If you have used non-default types that are declared in `MumbleServer.ice` (e.g. `IdList`), you can reference them here as
`::MumbleServer::<typeName>` (e.g. `::MumbleServer::IdList`).
Error reporting works via the `cb->ice_exception` function and if everything went well, the function must end by calling `cb->ice_response`
(potentially passing a value to that function that shall be returned to the caller of the function).
In general it is a good idea to have a look at the existing implementation inside `MumbleServerIce.cpp` and take inspiration from those.
Note that the implementations make heavy use of macros (e.g. `NEED_SERVER`, `NEED_CHANNEL`, etc.). These will initialize the corresponding variables
(`server`, `channel`, etc.) based in the parameters fed into the function (In order to obtain the channel, user, etc. you always have to initialize
the `server` variable first). For this to work it is essential that you are using the same parameter names as the existing implementations (e.g.
`server_id` for the server's ID). (For historical reasons the macro to obtain the `user` variable is called `NEED_PLAYER`)
If the function requires action on the server's side (beyond its public API), you have to declare a new public function in the `Server` class (this
time the Mumble server though; not the Ice server class) defined in `Server.h` (the definitions belong to the group of other RPC functions in there -
section marked by a comment). The implementation of this new function should then be written in `RPC.cpp`.
An example of when this is needed is for instance if you have to access the list of connected clients (e.g. because you want to send them a message).
While in the current state of the code it would be possible to access this list from the outside (public visibility), you should prefer creating a
public API function in the Mumble `Server` class that has the implementation in `RPC.cpp`.
## Testing Ice interface changes
So far, you've used `MumbleServer.ice` to modify and generate **server-side** code. The same file can be used to create Ice **clients**, which then
interact with the server. A small amount of configuration is required, namely:
| **Setting** | **Example** | **Description** |
| --- | --- | --- |
| `host` | `127.0.0.1` | The IP address (or domain) to which MumbleServer's Ice interface is bound. (Check [`murmur.ini`'s `ice` property `-h` flag](../../scripts/murmur.ini#L65).) |
| `port` | `6502` | The TCP port on which Ice's interface is listening. (Check [`murmur.ini`'s `ice` property `-p` flag](../../scripts/murmur.ini#L65).) |
| `secret` | `ice_pa55word` | A clear-text "password" used to authorize with the Ice server. (This will either be [`icesecretread`](../../scripts/murmur.ini#L79) or [`icesecretwrite`](../../scripts/murmur.ini#L80) from [`murmur.ini`](../../scripts/murmur.ini), with read-only or read-write privileges respectively.) |
| `slicefile` | `MumbleServer.ice` | The [`MumbleServer.ice`](../../src/murmur/MumbleServer.ice) file, containing any changes you intend to test. (This can be dynamically fetched from the Mumble server, provided it's running, has Ice exposed, and was built with the updated `MumbleServer.ice` file.) |
> :warning: Since the server's Ice interface is clear-text, there are security factors to consider. Use a strong-ish, unique secret, not used for
> any other case.
An existing Python Ice client is [`mice.py`](https://github.com/mumble-voip/mumble-scripts/blob/master/Helpers/mice.py), which simply creates
necessary Ice objects and then drops you into an interactive Python shell.
```python
# Make sure the Mumble server is running (in a separate terminal)
# $ ./mumble-server ...
# Grab mice.py
$ wget --quiet https://raw.githubusercontent.com/mumble-voip/mumble-scripts/master/Helpers/mice.py
# Either modify mice.py directly, or create mice_config.py with your proper settings
$ cat << EOF > mice_config.py
host = "127.0.0.1"
port = 6502
secret = "ice_pa55word"
prxstr = "Meta:tcp -h {} -p {} -t 1000".format(host, port)
slicefile = "MumbleServer.ice"
EOF
# Invoke Python and drop into interactive mode
$ ipython -i mice.py # IPython is very handy!
...
Import ice... Done
Trying to retrieve slice dynamically from server... Success
Import dynamically compiled murmur class... Done
Establish ice connection... [protected]... Done
MumbleServer object accessible via 'murmur' or 'm'
1 booted servers in 'sl', 's' contains 's/1 -t -e 1.0:tcp -h 127.0.0.1 -p 6502 -t 60000'
--- Reached interactive mode ---
In [1]: m, s # Represents "Meta" and (virtual) "Server" objects
Out[1]:
(Meta -t -e 1.0:tcp -h 127.0.0.1 -p 6502 -t 1000,
s/1 -t -e 1.0:tcp -h 127.0.0.1 -p 6502 -t 60000)
# Tab-complete to find interesting functions and play with Ice struct properties
In [2]: [(user.session, user.name) for user in s.getUsers().values()]
Out[2]: [(8L, 'Alice'), (7L, 'Bob')]
# IPython supports various inspection -- showing what it takes to kick a user
In [3]: s.kickUser?
Signature: s.kickUser(session, reason, context=None)
Docstring: <no docstring>
File: /tmp/tmpB0n19F.ice
Type: instancemethod
In [4]: s.kickUser(8, "Bye bye, Alice!")
In [5]: [(user.session, user.name) for user in s.getUsers().values()]
Out[5]: [(7L, 'Bob')]
```
|