How does a 3rd party (Go/Golang) app call a service?

Hi,

I am trying to access the positioning service from a Go app and I’m stuck on starting the positioning service. Generally, how do you use Legato services from 3rd party apps (in my case a Go app)?

I am using bundles to package the app or just copy the binary to the mangOH green to see whether it works at all.

When I run the app I get le_posCtrl_ConnectService() not called for current thread. I’m assuming that I need to start the positioning service somehow and that this is achieved through the bindings section in the .adef file.

$ cat Makefile
CC=/opt/swi/y17-ext/sysroots/x86_64-pokysdk-linux/usr/libexec/arm-poky-linux-gnueabi/gcc/arm-poky-linux-gnueabi/4.9.1/gcc
build: clean
	CC=$(CC) GOOS=linux GOARCH=arm GOARM=7 CGO_ENABLED=1 go build

scp: build
	scp gps root@192.168.2.2:

instapp: build
	mkapp -v -t wp750x gps.adef
	instapp gps.wp750x.update 192.168.2.2

ifgen:
	ifgen  --gen-interface --gen-client --gen-local $(LEGATO_ROOT)/interfaces/positioning/le_pos.api
	ifgen  --gen-interface --gen-client --gen-local $(LEGATO_ROOT)/interfaces/positioning/le_posCtrl.api

clean:
	rm -f *.o
	rm -f gps
$ cat gps.adef
sandboxed: false

version: 1.0.0

bundles:
{
	file:
	{
		[x] gps /bin/gps
	}
}

processes:
{
    run:
    {
        ( gps )
    }
}
$ cat /go/src/gps/main.go

package main

/*
#cgo CFLAGS: -I/legato/framework/c/inc
#cgo LDFLAGS: -L/legato/build/wp750x/framework/lib -llegato

#include <stdio.h>
#include "legato.h"
#include "le_pos_interface.h"
#include "le_posCtrl_interface.h"
*/
import "C"

import "log"
import "time"

func main() {
	log.Print("Starting gps")

	// this never returns
	// log.Print("Calling le_posCtrl_ConnectService()")
	// C.le_posCtrl_ConnectService()

	log.Print("Calling le_posCtrl_Requst()")
	ref := C.le_posCtrl_Request()
	if ref == nil {
		log.Fatal("Cannot activate positioning service")
	}
	defer C.le_posCtrl_Release(ref)

	var lat, lon, acc C.int
	for {
		log.Println("Calling le_pos_Get2DLocation")
		res2 := C.le_pos_Get2DLocation(&lat, &lon, &acc)
		log.Printf("le_pos_Get2DLocation: res: %d lat: %d lon: %d acc: %d\n", res2, lat, lon, acc)
		time.Sleep(500*time.Millisecond)
	}
}

Running make and copying the file to 192.168.2.2 and running it returns

Jun  9 22:30:00 swi-mdm9x15 user.emerg Legato: *EMR* | _UNKNOWN_[8337]/framework T=main | LE_FILENAME GetCurrentSessionRef() 320 | le_posCtrl_ConnectService() not called for current thread

Adding a call to le_posCtrl_ConnectService() at the start never returns.

I’ve then tried adding generic bindings to the gps.adef file but with no success.

...
bindings:
{
    *.le_posCtrl -> positioningService.le_posCtrl
    *.le_pos -> positioningService.le_pos
}

mkapp -v -t wp750x gps.adef returns

/go/src/gps/gps.adef:27:6: error: No such client-side pre-built interface 'le_posCtrl'.

I’m probably missing something obvious or this isn’t possible. Any help is greatly appreciated.

Update 1: I am using Legato 16.10.3 on a mangOH green with a WP750x

Seems like you are on the right track, the main issue being that your app somehow doesn’t handshake properly since:

	// this never returns
	// log.Print("Calling le_posCtrl_ConnectService()")
	// C.le_posCtrl_ConnectService()

I’m not sure exactly what’s happening, but in any case you would need to call C.le_posCtrl_ConnectService or the IPC won’t be initiated as not much will happen (except for errors).

Now the hanging part can be caused by a lot of different things … Try to look at the output of sdir list while your app is starting to see if the hash from le_posCtrl is the same as the one provided by the positioning app.

Otherwise that an interesting usage, the C interfaces through go. Could you please post the app somewhere on GitHub (or GitLab :wink: ) ?

Thanks

Can do once I have something working. I’d prefer to have a native Go client and I can work on that but first I need to have something working.

I’ve trimmed the example to the /legato/apps/sample/helloIpc example and get the same behavior. The native printClient works but the Go one doesn’t. However, both the C and the Go hang the same way when not run in the sandbox. Does that ring a bell?

root@swi-mdm9x15:~# strace /legato/systems/current/appsWriteable/printClient/bin/client
execve("/legato/systems/current/appsWriteable/printClient/bin/client", ["/legato/systems/current/appsWrit"...], [/* 12 vars */]) = 0
brk(0)                                  = 0x11000
uname({sys="Linux", node="swi-mdm9x15", ...}) = 0
...
socket(PF_LOCAL, SOCK_SEQPACKET, 0)     = 5
connect(5, {sa_family=AF_LOCAL, sun_path="/tmp/legato/serviceDirectoryClient"}, 110) = 0
sendmsg(5, {msg_name(0)=NULL, msg_iov(1)=[{"P\4\0\0007605c59739c69c22b22125e5a912"..., 264}], msg_controllen=0, msg_flags=0}, 0) = 264
recvmsg(5,

@CoRfr sdir list returns this

The printServer is from the helloIpc example

        printServer.printer  (protocol ID = 'e2533dc76a5bf9ba6b2d3e74d8f95bd6', max message size = 116 bytes)

WAITING CLIENTS

        [pid   791] goprint.printer UNBOUND  (protocol ID = 'e2533dc76a5bf9ba6b2d3e74d8f95bd6')

Testing now with 17.10.0 and the virt now. I have the following .adef file

sandboxed: true
version: 1.0.2

bundles:
{
    file:
    {
        [x] goprint /bin/goprint
    }
}

processes:
{
    run:
    {
        (goprint)
    }
}

bindings:
{
    goprint.printer -> printServer.printer
}

my suspicion is that the bindings: section is mandatory in order for this to work but that this either doesn’t work with 3rd party apps or that I’m doing it wrong. The next thing I’m going to try is to make the Go app a component since in .cdef files I can apis as requirement without referring to an app.

Also, I have approval to open source the code if you help me to get it to work :slight_smile:

Can you try adding the following lines to your gps.adef?
Extern is needed because: Application Definition .adef - Legato Docs

extern:
{
    requires:
    {
    	le_pos = $LEGATO_ROOT/interfaces/positioning/le_pos.api
        le_posCtrl = $LEGATO_ROOT/interfaces/positioning/le_posCtrl.api
    }
}

bindings:
{
    *.le_pos -> positioningService.le_pos
    *.le_posCtrl -> positioningService.le_posCtrl
}

I modified your main.go to the following:

func main() {
	log.Print("Starting gps")
	
	// this never returns
	log.Print("Calling le_pos_ConnectService()")
	C.le_pos_ConnectService()

	log.Print("Calling le_posCtrl_ConnectService()")
	C.le_posCtrl_ConnectService()

	log.Print("Calling le_posCtrl_Requst()")
	ref := C.le_posCtrl_Request()
	if ref == nil {
		log.Fatal("Cannot activate positioning service")
	}
	defer C.le_posCtrl_Release(ref)

	var lat, lon, acc C.int32_t
	for {
		log.Println("Calling le_pos_Get2DLocation")
		res2 := C.le_pos_Get2DLocation(&lat, &lon, &acc)
		log.Printf("le_pos_Get2DLocation: res: %d lat: %d lon: %d acc: %d\n", res2, lat, lon, acc)
		time.Sleep(500*time.Millisecond)
	}
}

Logs:

an  1 00:54:23 swi-mdm9x15 user.err Legato: =ERR= | gps[7821] | 1970/01/01 00:54:23 Starting gps
Jan  1 00:54:23 swi-mdm9x15 user.err Legato: =ERR= | gps[7821] | 1970/01/01 00:54:23 Calling le_pos_ConnectService()
Jan  1 00:54:23 swi-mdm9x15 user.err Legato: =ERR= | gps[7821] | 1970/01/01 00:54:23 Calling le_posCtrl_ConnectService()
Jan  1 00:54:23 swi-mdm9x15 user.err Legato: =ERR= | gps[7821] | 1970/01/01 00:54:23 Calling le_posCtrl_Requst()
Jan  1 00:54:23 swi-mdm9x15 authpriv.info dropbear[7789]: Exit (root): Disconnect received
Jan  1 00:54:23 swi-mdm9x15 user.warn Legato: -WRN- | posDaemon[775]/le_pa_gnss T=main | pa_gnss_qmi.c pa_gnss_Start() 3320 | EngineState ON
Jan  1 00:54:23 swi-mdm9x15 user.err Legato: =ERR= | gps[7821] | 1970/01/01 00:54:23 Calling le_pos_Get2DLocation
Jan  1 00:54:23 swi-mdm9x15 user.err Legato: =ERR= | gps[7821] | 1970/01/01 00:54:23 le_pos_Get2DLocation: res: -3 lat: 50000002 lon: -97000002 acc: 2147483647
Jan  1 00:54:24 swi-mdm9x15 user.err Legato: =ERR= | gps[7821] | 1970/01/01 00:54:24 Calling le_pos_Get2DLocation
Jan  1 00:54:24 swi-mdm9x15 user.err Legato: =ERR= | gps[7821] | 1970/01/01 00:54:24 le_pos_Get2DLocation: res: -3 lat: 50000002 lon: -97000002 acc: 2147483647
Jan  1 00:54:24 swi-mdm9x15 user.err Legato: =ERR= | gps[7821] | 1970/01/01 00:54:24 Calling le_pos_Get2DLocation
Jan  1 00:54:24 swi-mdm9x15 user.err Legato: =ERR= | gps[7821] | 1970/01/01 00:54:24 le_pos_Get2DLocation: res: -3 lat: 50000002 lon: -97000002 acc: 2147483647
Jan  1 00:54:25 swi-mdm9x15 user.err Legato: =ERR= | gps[7821] | 1970/01/01 00:54:25 Calling le_pos_Get2DLocation
Jan  1 00:54:25 swi-mdm9x15 user.err Legato: =ERR= | gps[7821] | 1970/01/01 00:54:25 le_pos_Get2DLocation: res: -3 lat: 50000002 lon: -97000002 acc: 2147483647
Jan  1 00:54:25 swi-mdm9x15 user.err Legato: =ERR= | gps[7821] | 1970/01/01 00:54:25 Calling le_pos_Get2DLocation
Jan  1 00:54:25 swi-mdm9x15 user.err Legato: =ERR= | gps[7821] | 1970/01/01 00:54:25 le_pos_Get2DLocation: res: -3 lat: 50000002 lon: -97000002 acc: 2147483647
Jan  1 00:54:26 swi-mdm9x15 user.err Legato: =ERR= | gps[7821] | 1970/01/01 00:54:26 Calling le_pos_Get2DLocation
Jan  1 00:54:26 swi-mdm9x15 user.err Legato: =ERR= | gps[7821] | 1970/01/01 00:54:26 le_pos_Get2DLocation: res: -3 lat: 50000002 lon: -97000002 acc: 2147483647
Jan  1 00:54:26 swi-mdm9x15 user.err Legato: =ERR= | gps[7821] | 1970/01/01 00:54:26 Calling le_pos_Get2DLocation
Jan  1 00:54:26 swi-mdm9x15 user.err Legato: =ERR= | gps[7821] | 1970/01/01 00:54:26 le_pos_Get2DLocation: res: -3 lat: 50000002 lon: -97000002 acc: 2147483647
Jan  1 00:54:27 swi-mdm9x15 user.err Legato: =ERR= | gps[7821] | 1970/01/01 00:54:27 Calling le_pos_Get2DLocation
Jan  1 00:54:27 swi-mdm9x15 user.err Legato: =ERR= | gps[7821] | 1970/01/01 00:54:27 le_pos_Get2DLocation: res: -3 lat: 50000002 lon: -97000002 acc: 2147483647

Following the example from: Port Legacy C App - Legato Docs

Note: My coordinates are not valid because I do not have a GPS antenna.

Thanks

This fixes it… and the docs clearly state why. Feeling grateful and a bit embarrassed.

Thanks @esun

Amazing! I’ve been looking into support for other languages in Legato for a while. I love C but my fingers are a little fatigued these days. I was thinking Node (or deno :wink:) with a TypeScript build system since it fits well with Legato’s event based architecture. This is much easier though since Go can bind to C so easily. Node does support binding to C/C++ but I suspect it would be easier to just re-implement the IPC messaging in Node. I’ll have to try this out in the meantime (step one, learn Go), thanks so much for sharing this!

I should have some code ready for open sourcing soon. Watch this space.

I’ll start with the C bindings to have something working but in the longer run it would be better to port the IPC code to Go and drop the dependency on C.

I agree. I think it would make sense to define some general specification (e.g what needs to be sent over the socket) for language implementors to follow such it’s approachable in almost any language.

Is there a spec of the protocol or do I have to reverse engineer it from the C code?

I believe you would be the pioneer on this one, so to my knowledge yes. Alternatively, you could reverse engineer some of the tools that generate this code, but that’s probably more work.

P.S I was about to ask how you got the Go runtime on the hardware… but then it hit me.

A pure Go implementation would make things so much easier. The cross-compilation toolchain setup is quite complex to get right. I’ve looked at the code generators for the APIs and that shouldn’t be too hard. I might just wrap the low-level messaging part with CGO for now and build everything else as pure Go on top.

I would think that having a native Go implementation for the Legato IPC would be nicer as it avoid the C dependency.
There already is some other languages supported by:

The Python support is in an early state and was merged quite recently. Still tested internally and not necessarily ready for prime time …

The Java support is a bit older and more robust, but was taken out of the documentation a few months ago. [Docs] Minor 17.05 Fixes · legatoproject/legato-af@cbd9c5b · GitHub

Anyway, both tool could probably be adapted to work with go :stuck_out_tongue:
And any contribution (through GitHub for now) is very much appreciated :slight_smile:

Thanks!

I’m trying to get a minimal handshake with the serviceDirectory working in Go which is the first part in creating the session. I’ve recreated this from OpenSessionSync legato-af/messagingSession.c at master · legatoproject/legato-af · GitHub

I’m testing this with Legato 17.10.0 for the virt target. You build it with GOOS=linux GOARCH=386 go build

Edit: I’ve removed the code here since I have a simpler version which has the same behavior below.


~~~After starting run `strace -s 512 -p $(pgrep -f socket)` and `strace -s 512 -p $(pgrep -f serviceDirectory)`~~~

This works now. The `binding` definition was referring to the wrong interface (printerService instead of printService).

#### $GOPATH/src/socket/main.go

```go
package main

import (
	"bytes"
	"encoding/binary"
	"fmt"
	"log"
	"os"
	"time"

	"golang.org/x/sys/unix"
)

const (
	ProtocolID          = "e2533dc76a5bf9ba6b2d3e74d8f95bd6" // /legato/apps/sample/helloIpc/printer.api
	ServiceInstanceName = "printer"
	MaxMsgSize          = 112
	msgID_printer_Print = 0
)

type OpenMsg struct {
	MaxSize  uint32
	ProtoID  [128]byte
	IntfName [128]byte
	Wait     bool
	_        [3]byte // padding
}

const sock = "/tmp/legato/serviceDirectoryClient"

func main() {
	fmt.Println("strace -s 512 -p ", os.Getpid())
	time.Sleep(10 * time.Second)

	os.MkdirAll("/data/le_fs/", 0777)

	fd, err := unix.Socket(unix.AF_UNIX, unix.SOCK_SEQPACKET, 0)
	if err != nil {
		log.Fatal(err)
	}

	sa := &unix.SockaddrUnix{Name: sock}
	if err := unix.Connect(fd, sa); err != nil {
		log.Fatal(err)
	}

	openMsg := &OpenMsg{
		MaxSize: MaxMsgSize + 4,
		Wait:    true,
	}
	copy(openMsg.ProtoID[:], []byte(ProtocolID))
	copy(openMsg.IntfName[:], []byte(ServiceInstanceName))

	var b bytes.Buffer
	if err := binary.Write(&b, binary.LittleEndian, openMsg); err != nil {
		log.Fatal(err)
	}
	_, err = unix.SendmsgN(fd, b.Bytes(), nil, nil, 0)
	if err != nil {
		log.Fatal(err)
	}

	buf := make([]byte, 4)
	n, _, _, _, err := unix.Recvmsg(fd, buf, nil, 0)
	if err != nil {
		log.Fatal(err)
	}
	buf = buf[:n]
	log.Print("resp: buf: %#v", buf)
	log.Print("Done")
}
```

#### $GOPATH/src/socket/Makefile
```
build:
	GOOS=linux GOARCH=386 go build

pkg: build
	mkapp -v -t virt socket.adef

install: build
	scp -P 10022 socket socket.*.update root@localhost:
```

#### $GOPATH/src/socket/socket.adef
```
sandboxed: true
version: 1.0.2

bundles:
{
    file:
    {
        [x] socket /bin/
    }
}

processes:
{
    run:
    {
        ( socket )
    }
}

extern:
{
    requires:
    {
        printer = $LEGATO_ROOT/apps/sample/helloIpc/printer.api
    }
}

bindings:
{
    *.printer -> printServer.printer
}
```

@magiconair, if you want to try with a more recent version of virt I updated GitHub - CoRfr/legato-virt: (WIP) Documentation for the Legato virtual target.

The linux version, LXSWI2.2-6.0, is the latest release to date which is associated with Legato 18.05.

One thing as well: some of this non-responding stuff makes me think that it could be SMACK issues.
SMACK is disabled on virt in 18.05 (unfortunately in a sense), so that would remove one thing from the equation.

@CoRfr the new image doesn’t make a difference. Also it doesn’t seem to have strace installed.

I’ve straced the printClient from the helloIpc app and the socket app and from what I can tell they are making identical calls to the directory service. They are also both running in a sandbox as a normal user.

printClient.strace:

socket(PF_LOCAL, SOCK_SEQPACKET, 0)     = 5
connect(5, {sa_family=AF_LOCAL, sun_path="/tmp/legato/serviceDirectoryClient"}, 110) = 0
sendmsg(5, {msg_name(0)=NULL, msg_iov(1)=[{"t\0\0\0e2533dc76a5bf9ba6b2d3e74d8f95bd6\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0client.printClient.printer\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\1\0\0\0", 264}], msg_controllen=0, msg_flags=0}, 0) = 264
recvmsg(5, {msg_name(0)=NULL, msg_iov(1)=[{"\0\0\0\0", 4}], msg_controllen=0, msg_flags=0}, 0) = 4

socket.strace:

socket(PF_LOCAL, SOCK_SEQPACKET, 0)     = 3
connect(3, {sa_family=AF_LOCAL, sun_path="/tmp/legato/serviceDirectoryClient"}, 37) = 0
futex(0x81633b8, FUTEX_WAKE, 1)         = 1
futex(0x8163330, FUTEX_WAKE, 1)         = 1
sendmsg(3, {msg_name(0)=NULL, msg_iov(1)=[{"t\0\0\0e2533dc76a5bf9ba6b2d3e74d8f95bd6\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0printer\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\1\0\0\0", 264}], msg_controllen=0, msg_flags=0}, 0) = 264
futex(0x81633b8, FUTEX_WAKE, 1)         = 1
futex(0x8163330, FUTEX_WAKE, 1)         = 1
recvmsg(3,

Update:

I’ve updated the minimal Go app above to the latest version. Used this doc to do strace debugging which was helpful. However, the app runProc socket --exe=strace -- -s 512 socket trick did not work with the Go app since all values in the system calls were 0. That’s why the app how waits 10 seconds during startup so that I can attach to it.

Yes strace was removed from the image and is now part of devMode.
Cf Developer Mode - Legato Docs

Main reason for that are:

  • weight
  • potential security issues
  • license issues (gdbserver is GPLv3 for instance)

So the overall the idea that we are following is to trim down the linux to a bare minimum, and package a ‘toolbox’ as part of this Legato app. I really like the CoreOS - Container Linux approach for instance, so if anything that very roughly shows what we are trying to accomplish, but applied to the IoT world.

Could you try to run them out of a sandbox, just in case?
I also wonder what’s happening on the supervisor and serviceDirectory side.