summaryrefslogtreecommitdiffstats
path: root/recipes-wam/wam/wam_git.bb
blob: f80433c4a01d7f99e21d11ddcc1ac047e174f64a (plain)
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
SUMMARY = "WAM"
AUTHOR = "Jani Hautakangas <jani.hautakangas@lge.com>"
LICENSE = "Apache-2.0"
LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/Apache-2.0;md5=89aea4e17d99a7cacdbeed46a0096b10"

DEPENDS = "glib-2.0 jsoncpp boost protobuf protobuf-native grpc grpc-native"

SRC_URI = "\
    git://github.com/igalia/${BPN}.git;branch=@58.agl;protocol=https \
    file://WebAppMgrCli \
    file://WebAppMgr.service \
    file://WebAppMgr.env \
    file://WebAppMgr-cef.env \
"

SRCREV = "4fbd6e648913bcf0fba63e4460eb44242c11f71b"

PV = "ose58.agl"

S = "${WORKDIR}/git"

inherit cmake pkgconfig systemd

# Disable some of security flags
# Disable D_FORTIFY_SOURCE=2 and -fstack-protector-strong
# Refer conf/distro/include/security_flags.inc in meta-webos/conf/distro/include/webos.inc
lcl_maybe_fortify = ""
SECURITY_STACK_PROTECTOR = ""

SYSTEMD_SERVICE:${PN} = "WebAppMgr.service"

do_install:append() {
    install -v -d ${D}${sysconfdir}/wam
    install -v -m 644 ${S}/files/launch/security_policy.conf ${D}${sysconfdir}/wam/security_policy.conf
    install -v -D -m 644 ${WORKDIR}/WebAppMgr.service ${D}${systemd_system_unitdir}/WebAppMgr.service
    install -v -D -m 755 ${WORKDIR}/WebAppMgrCli ${D}${bindir}/WebAppMgrCli
}

CXXFLAGS:append:agl-devel = " -DAGL_DEVEL"

do_install:append:agl-devel() {
    # Enable remote inspector and dev mode
    install -d ${D}${localstatedir}/agl-devel/preferences
    touch ${D}${localstatedir}/agl-devel/preferences/debug_system_apps
    touch ${D}${localstatedir}/agl-devel/preferences/devmode_enabled
}

require wam-cef.inc

FILES:${PN} += "${sysconfdir}/init \
                ${sysconfdir}/wam \
                ${bindir} \
                ${libdir}/webappmanager/plugins/*.so"

RDEPENDS:${PN} += " bash grpc-web-proxy"

PROVIDES += "virtual/webruntime"
RPROVIDES:${PN} += "virtual/webruntime"
Literal.Number */ .highlight .s { color: #e6db74 } /* Literal.String */ .highlight .na { color: #a6e22e } /* Name.Attribute */ .highlight .nb { color: #f8f8f2 } /* Name.Builtin */ .highlight .nc { color: #a6e22e } /* Name.Class */ .highlight .no { color: #66d9ef } /* Name.Constant */ .highlight .nd { color: #a6e22e } /* Name.Decorator */ .highlight .ni { color: #f8f8f2 } /* Name.Entity */ .highlight .ne { color: #a6e22e } /* Name.Exception */ .highlight .nf { color: #a6e22e } /* Name.Function */ .highlight .nl { color: #f8f8f2 } /* Name.Label */ .highlight .nn { color: #f8f8f2 } /* Name.Namespace */ .highlight .nx { color: #a6e22e } /* Name.Other */ .highlight .py { color: #f8f8f2 } /* Name.Property */ .highlight .nt { color: #f92672 } /* Name.Tag */ .highlight .nv { color: #f8f8f2 } /* Name.Variable */ .highlight .ow { color: #f92672 } /* Operator.Word */ .highlight .w { color: #f8f8f2 } /* Text.Whitespace */ .highlight .mb { color: #ae81ff } /* Literal.Number.Bin */ .highlight .mf { color: #ae81ff } /* Literal.Number.Float */ .highlight .mh { color: #ae81ff } /* Literal.Number.Hex */ .highlight .mi { color: #ae81ff } /* Literal.Number.Integer */ .highlight .mo { color: #ae81ff } /* Literal.Number.Oct */ .highlight .sa { color: #e6db74 } /* Literal.String.Affix */ .highlight .sb { color: #e6db74 } /* Literal.String.Backtick */ .highlight .sc { color: #e6db74 } /* Literal.String.Char */ .highlight .dl { color: #e6db74 } /* Literal.String.Delimiter */ .highlight .sd { color: #e6db74 } /* Literal.String.Doc */ .highlight .s2 { color: #e6db74 } /* Literal.String.Double */ .highlight .se { color: #ae81ff } /* Literal.String.Escape */ .highlight .sh { color: #e6db74 } /* Literal.String.Heredoc */ .highlight .si { color: #e6db74 } /* Literal.String.Interpol */ .highlight .sx { color: #e6db74 } /* Literal.String.Other */ .highlight .sr { color: #e6db74 } /* Literal.String.Regex */ .highlight .s1 { color: #e6db74 } /* Literal.String.Single */ .highlight .ss { color: #e6db74 } /* Literal.String.Symbol */ .highlight .bp { color: #f8f8f2 } /* Name.Builtin.Pseudo */ .highlight .fm { color: #a6e22e } /* Name.Function.Magic */ .highlight .vc { color: #f8f8f2 } /* Name.Variable.Class */ .highlight .vg { color: #f8f8f2 } /* Name.Variable.Global */ .highlight .vi { color: #f8f8f2 } /* Name.Variable.Instance */ .highlight .vm { color: #f8f8f2 } /* Name.Variable.Magic */ .highlight .il { color: #ae81ff } /* Literal.Number.Integer.Long */ } @media (prefers-color-scheme: light) { .highlight .hll { background-color: #ffffcc } .highlight .c { color: #888888 } /* Comment */ .highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */ .highlight .k { color: #008800; font-weight: bold } /* Keyword */ .highlight .ch { color: #888888 } /* Comment.Hashbang */ .highlight .cm { color: #888888 } /* Comment.Multiline */ .highlight .cp { color: #cc0000; font-weight: bold } /* Comment.Preproc */ .highlight .cpf { color: #888888 } /* Comment.PreprocFile */ .highlight .c1 { color: #888888 } /* Comment.Single */ .highlight .cs { color: #cc0000; font-weight: bold; background-color: #fff0f0 } /* Comment.Special */ .highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */ .highlight .ge { font-style: italic } /* Generic.Emph */ .highlight .gr { color: #aa0000 } /* Generic.Error */ .highlight .gh { color: #333333 } /* Generic.Heading */ .highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */ .highlight .go { color: #888888 } /* Generic.Output */ .highlight .gp { color: #555555 } /* Generic.Prompt */ .highlight .gs { font-weight: bold } /* Generic.Strong */ .highlight .gu { color: #666666 } /* Generic.Subheading */ .highlight .gt { color: #aa0000 } /* Generic.Traceback */ .highlight .kc { color: #008800; font-weight: bold } /* Keyword.Constant */ .highlight .kd { color: #008800; font-weight: bold } /* Keyword.Declaration */ .highlight .kn { color: #008800; font-weight: bold } /* Keyword.Namespace */ .highlight .kp { color: #008800 } /* Keyword.Pseudo */ .highlight .kr { color: #008800; font-weight: bold } /* Keyword.Reserved */ .highlight .kt { color: #888888; font-weight: bold } /* Keyword.Type */ .highlight .m { color: #0000DD; font-weight: bold } /* Literal.Number */ .highlight .s { color: #dd2200; background-color: #fff0f0 } /* Literal.String */ .highlight .na { color: #336699 } /* Name.Attribute */ .highlight .nb { color: #003388 } /* Name.Builtin */ .highlight .nc { color: #bb0066; font-weight: bold } /* Name.Class */ .highlight .no { color: #003366; font-weight: bold } /* Name.Constant */ .highlight .nd { color: #555555 } /* Name.Decorator */ .highlight .ne { color: #bb0066; font-weight: bold } /* Name.Exception */ .highlight .nf { color: #0066bb; font-weight: bold } /* Name.Function */ .highlight .nl { color: #336699; font-style: italic } /* Name.Label */ .highlight .nn { color: #bb0066; font-weight: bold } /* Name.Namespace */ .highlight .py { color: #336699; font-weight: bold } /* Name.Property */ .highlight .nt { color: #bb0066; font-weight: bold } /* Name.Tag */ .highlight .nv { color: #336699 } /* Name.Variable */ .highlight .ow { color: #008800 } /* Operator.Word */ .highlight .w { color: #bbbbbb } /* Text.Whitespace */ .highlight .mb { color: #0000DD; font-weight: bold } /* Literal.Number.Bin */ .highlight .mf { color: #0000DD; font-weight: bold } /* Literal.Number.Float */ .highlight .mh { color: #0000DD; font-weight: bold } /* Literal.Number.Hex */ .highlight .mi { color: #0000DD; font-weight: bold } /* Literal.Number.Integer */ .highlight .mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */ .highlight .sa { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Affix */ .highlight .sb { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Backtick */ .highlight .sc { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Char */ .highlight .dl { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Delimiter */ .highlight .sd { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Doc */ .highlight .s2 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Double */ .highlight .se { color: #0044dd; background-color: #fff0f0 } /* Literal.String.Escape */ .highlight .sh { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Heredoc */ .highlight .si { color: #3333bb; background-color: #fff0f0 } /* Literal.String.Interpol */ .highlight .sx { color: #22bb22; background-color: #f0fff0 } /* Literal.String.Other */ .highlight .sr { color: #008800; background-color: #fff0ff } /* Literal.String.Regex */ .highlight .s1 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Single */ .highlight .ss { color: #aa6600; background-color: #fff0f0 } /* Literal.String.Symbol */ .highlight .bp { color: #003388 } /* Name.Builtin.Pseudo */ .highlight .fm { color: #0066bb; font-weight: bold } /* Name.Function.Magic */ .highlight .vc { color: #336699 } /* Name.Variable.Class */ .highlight .vg { color: #dd7700 } /* Name.Variable.Global */ .highlight .vi { color: #3333bb } /* Name.Variable.Instance */ .highlight .vm { color: #336699 } /* Name.Variable.Magic */ .highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */ }
/*
 * Copyright (C) 2018 "IoT.bzh"
 * Author Fulup Ar Foll <fulup@iot.bzh>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

#define _GNU_SOURCE  // needed for vasprintf

#include "alsa-softmixer.h"
#include <math.h>

// move from vol % to absolute value
#define CONVERT_RANGE(val, min, max) ceil((val) * ((max) - (min)) * 0.01 + (min))
#define CONVERT_VOLUME(val, min, max) (int) CONVERT_RANGE ((double)val, (double)min, (double)max)

// move from volume to percentage (extract from alsa-utils)

STATIC int CONVERT_PERCENT(long val, long min, long max) {
    long range = max - min;
    int tmp;
    if (range == 0)
        return 0;
    val -= min;
    tmp = (int) rint((double) val / (double) range * 100);
    return tmp;
}

typedef enum {
    RVOL_ABS,
    RVOL_ADD,
    RVOL_DEL,

    RVOL_NONE
} volumeT;

typedef struct {
    const char *uid;
    SoftMixerT *mixer;
    AlsaSndPcmT* pcm;
} apiVerbHandleT;

STATIC AlsaPcmChannelT *ProcessOneChannel(SoftMixerT *mixer, const char *uid, json_object *argsJ) {
    AlsaPcmChannelT *channel = calloc(1, sizeof (AlsaPcmChannelT));
    int error = wrap_json_unpack(argsJ, "{ss,si !}", "uid", &channel->uid, "port", &channel->port);
    if (error) goto OnErrorExit;

    channel->uid = strdup(channel->uid);
    return channel;

OnErrorExit:
    AFB_ApiError(mixer->api, "ProcessOneChannel: sndcard=%s channel: missing (uid||port) error=%s json=%s", uid, wrap_json_get_error_string(error), json_object_get_string(argsJ));
    free(channel);
    return NULL;
}

STATIC int PcmAttachOneCtl(SoftMixerT *mixer, AlsaSndCtlT *sndcard, json_object *argsJ, AlsaSndControlT *control) {
    snd_ctl_elem_id_t* elemId = NULL;
    snd_ctl_elem_info_t *elemInfo;
    int numid = 0;
    long value = ALSA_DEFAULT_PCM_VOLUME;
    const char *name;

    int error = wrap_json_unpack(argsJ, "{s?i,s?s,s?i !}"
            , "numid", &numid
            , "name", &name
            , "value", &value
            );
    if (error || (!numid && !name)) {
        AFB_ApiError(mixer->api,
        		     "%s: cardid=%s channel: missing (numid|name|value) error=%s json=%s",
					 __func__, sndcard->cid.name, wrap_json_get_error_string(error), json_object_get_string(argsJ));
        goto OnErrorExit;
    }

    if (numid > 0) {
        elemId = AlsaCtlGetNumidElemId(mixer, sndcard, numid);
        if (!elemId) {
            AFB_ApiError(mixer->api, "PcmAttachOneCtl sndard=%s fail to find control numid=%d", sndcard->cid.cardid, numid);
            goto OnErrorExit;
        }

    } else {
        elemId = AlsaCtlGetNameElemId(mixer, sndcard, name);
        if (!elemId) {
            AFB_ApiError(mixer->api, "PcmAttachOneCtl sndard=%s fail to find control name=%s", sndcard->cid.cardid, name);
            goto OnErrorExit;
        }
    }

    snd_ctl_elem_info_alloca(&elemInfo);
    snd_ctl_elem_info_set_id(elemInfo, elemId);
    control->name = strdup(snd_ctl_elem_info_get_name(elemInfo));
    control->numid = snd_ctl_elem_info_get_numid(elemInfo);

    if (snd_ctl_elem_info(sndcard->ctl, elemInfo) < 0) {
        AFB_ApiError(mixer->api, "PcmAttachOneCtl: sndard=%s numid=%d name='%s' not loadable", sndcard->cid.cardid, control->numid, control->name);
        goto OnErrorExit;
    }

    if (!snd_ctl_elem_info_is_writable(elemInfo)) {
        AFB_ApiError(mixer->api, "PcmAttachOneCtl: sndard=%s numid=%d name='%s' not writable", sndcard->cid.cardid, control->numid, control->name);
        goto OnErrorExit;
    }

    control->count = snd_ctl_elem_info_get_count(elemInfo);
    switch (snd_ctl_elem_info_get_type(elemInfo)) {
        case SND_CTL_ELEM_TYPE_BOOLEAN:
            control->min = 0;
            control->max = 1;
            control->step = 0;
            error = CtlElemIdSetLong(mixer, sndcard, elemId, value);
            break;

        case SND_CTL_ELEM_TYPE_INTEGER:
        case SND_CTL_ELEM_TYPE_INTEGER64:
            control->min = snd_ctl_elem_info_get_min(elemInfo);
            control->max = snd_ctl_elem_info_get_max(elemInfo);
            control->step = snd_ctl_elem_info_get_step(elemInfo);
            error = CtlElemIdSetLong(mixer, sndcard, elemId, (int) CONVERT_VOLUME(value, control->min, control->max));
            break;

        default:
            AFB_ApiError(mixer->api, "PcmAttachOneCtl: sndard=%s numid=%d name='%s' invalid/unsupported type=%d",
            		     sndcard->cid.cardid, control->numid, control->name, snd_ctl_elem_info_get_type(elemInfo));
            goto OnErrorExit;
    }

    if (error) {
        AFB_ApiError(mixer->api, "PcmAttachOneCtl: sndard=%s numid=%d name='%s' not writable", sndcard->cid.cardid, control->numid, control->name);
        goto OnErrorExit;
    }

    free(elemId);

    return 0;

OnErrorExit:
    if (elemId)free(elemId);
    return -1;
}

STATIC int PcmSetControl(SoftMixerT *mixer, AlsaSndCtlT *sndcard, AlsaSndControlT *control, volumeT volType, int *newvol, int *oldval) {
    snd_ctl_elem_id_t* elemId = NULL;
    snd_ctl_elem_info_t *elemInfo;
    int error, value = 0;
    long curval;

    assert(control->numid);

    elemId = AlsaCtlGetNumidElemId(mixer, sndcard, control->numid);
    if (!elemId) {
        AFB_ApiError(mixer->api, "PcmSetControl sndard=%s fail to find control numid=%d", sndcard->cid.cardid, control->numid);
        goto OnErrorExit;
    }

    snd_ctl_elem_info_alloca(&elemInfo);
    snd_ctl_elem_info_set_id(elemInfo, elemId);

    if (snd_ctl_elem_info(sndcard->ctl, elemInfo) < 0) {
        AFB_ApiError(mixer->api, "PcmSetControl: sndard=%s numid=%d name='%s' not loadable", sndcard->cid.cardid, control->numid, control->name);
        goto OnErrorExit;
    }

    if (!snd_ctl_elem_info_is_writable(elemInfo)) {
        AFB_ApiError(mixer->api, "PcmSetControl: sndard=%s numid=%d name='%s' not writable", sndcard->cid.cardid, control->numid, control->name);
        goto OnErrorExit;
    }

    error = CtlElemIdGetLong(mixer, sndcard, elemId, &curval);
    if (error) {
        AFB_ApiError(mixer->api, "PcmSetControl sndard=%s fail to read control numid=%d", sndcard->cid.cardid, control->numid);
        goto OnErrorExit;
    }

    switch (snd_ctl_elem_info_get_type(elemInfo)) {

        case SND_CTL_ELEM_TYPE_BOOLEAN:
            error = CtlElemIdSetLong(mixer, sndcard, elemId, *newvol);
            break;

        case SND_CTL_ELEM_TYPE_INTEGER:
        case SND_CTL_ELEM_TYPE_INTEGER64:

            switch (volType) {
                case RVOL_ADD:
                    value = CONVERT_PERCENT(curval, control->min, control->max) + *newvol;
                    break;
                case RVOL_DEL:
                    value = CONVERT_PERCENT(curval, control->min, control->max) - *newvol;
                    break;
                default:
                    value = *newvol;
            }

            error = CtlElemIdSetLong(mixer, sndcard, elemId, CONVERT_VOLUME(value, control->min, control->max));
            if (error) {
                AFB_ApiError(mixer->api, "PcmSetControl sndard=%s fail to write control numid=%d value=%d", sndcard->cid.cardid, control->numid, value);
                goto OnErrorExit;
            }
            break;

        default:
            AFB_ApiError(mixer->api, "PcmSetControl: sndard=%s numid=%d name='%s' invalid/unsupported type=%d", sndcard->cid.cardid, control->numid, control->name, snd_ctl_elem_info_get_type(elemInfo));
            goto OnErrorExit;
    }

    if (error) {
        AFB_ApiError(mixer->api, "PcmSetControl: sndard=%s numid=%d name='%s' not writable", sndcard->cid.cardid, control->numid, control->name);
        goto OnErrorExit;
    }

    *oldval = CONVERT_PERCENT(curval, control->min, control->max);
    *newvol = value;
    free(elemId);
    return 0;

OnErrorExit:
    if (elemId)free(elemId);
    return -1;
}

STATIC void ApiPcmVerbCB(AFB_ReqT request) {
    apiVerbHandleT *handle = (apiVerbHandleT*) afb_req_get_vcbdata(request);
    int error, verbose = 0, doInfo = 0, doToggle = 0, doMute = -1;
    json_object *volumeJ = NULL;
    json_object *responseJ = NULL;
    json_object *argsJ = afb_req_json(request);

    SoftMixerT *mixer = handle->mixer;
    AlsaSndCtlT *sndcard = handle->pcm->sndcard;
    assert(mixer && sndcard);

    error = wrap_json_unpack(argsJ, "{s?b,s?b,s?b,s?b,s?o !}"
            , "verbose", &verbose
            , "info", &doInfo
            , "mute", &doMute
            , "toggle", &doToggle
            , "volume", &volumeJ
            );
    if (error) {
        AFB_ReqFailF(request, "syntax-error", "Missing 'mute|volume|toggle|quiet' args=%s error=%s", json_object_get_string(argsJ), wrap_json_get_error_string(error));
        goto OnErrorExit;
    }

    if (verbose) responseJ=json_object_new_object();
    
    if (doMute != -1) {
        int mute = (int) doMute;

        error += AlsaCtlNumidSetLong(mixer, sndcard, handle->pcm->mute.numid, mute);
        if (error) {
            AFB_ReqFailF(request, "invalid-numid", "Fail to set pause numid=%d", handle->pcm->mute.numid);
            goto OnErrorExit;
        }

        if (verbose) {
            json_object_object_add(responseJ, "mute", json_object_new_boolean((json_bool) mute));
        }
    }

    if (doToggle) {
        long mute;

        error += AlsaCtlNumidGetLong(mixer, handle->pcm->sndcard, handle->pcm->mute.numid, &mute);
        error += AlsaCtlNumidSetLong(mixer, handle->pcm->sndcard, handle->pcm->mute.numid, !mute);
        if (error) {
            AFB_ReqFailF(request, "invalid-numid", "Fail to toogle pause numid=%d", handle->pcm->mute.numid);
            goto OnErrorExit;
        }

        if (verbose) {
            json_object_object_add(responseJ, "mute", json_object_new_boolean((json_bool)!mute));
        }
    }

    if (volumeJ) {
        volumeT volType;

        int newvol, oldvol;
        const char*volString;

        switch (json_object_get_type(volumeJ)) {
            case json_type_string:
                volString = json_object_get_string(volumeJ);
                switch (volString[0]) {
                    case '+':
                        sscanf(&volString[1], "%d", &newvol);
                        volType = RVOL_ADD;
                        break;

                    case '-':
                        sscanf(&volString[1], "%d", &newvol);
                        volType = RVOL_DEL;
                        break;
                    default:
                        error = sscanf(&volString[0], "%d", &newvol);
                        volType = RVOL_ABS;
                        if (error != 1) {
                            AFB_ReqFailF(request, "not-integer", "relative volume should start by '+|-' value=%s", json_object_get_string(volumeJ));
                            goto OnErrorExit;
                        }
                }
                break;
            case json_type_int:
                volType = RVOL_ABS;
                newvol = json_object_get_int(volumeJ);
                break;
            case json_type_null:
                volType=RVOL_ADD;
                newvol=0;
                break;
            default:
                AFB_ReqFailF(request, "not-integer", "volume should be string or integer value=%s", json_object_get_string(volumeJ));
                goto OnErrorExit;

        }

        error = PcmSetControl(mixer, handle->pcm->sndcard, &handle->pcm->volume, volType, &newvol, &oldvol);
        if (error) {
            AFB_ReqFailF(request, "invalid-ctl", "Fail to set volume hal=%s card=%s numid=%d name=%s value=%d"
                    , handle->uid, handle->pcm->sndcard->cid.cardid, handle->pcm->volume.numid, handle->pcm->volume.name, newvol);
            goto OnErrorExit;
        }
        
        if (verbose) {
            json_object_object_add(responseJ, "volnew", json_object_new_int(newvol));
            json_object_object_add(responseJ, "volold", json_object_new_int(oldvol));
        }
    }

    AFB_ReqSuccess(request, responseJ, handle->uid);
    return;

OnErrorExit:
    return;
}

PUBLIC AlsaPcmHwInfoT * ApiPcmSetParams(SoftMixerT *mixer, const char *uid, json_object * paramsJ) {
    AlsaPcmHwInfoT *params = calloc(1, sizeof (AlsaPcmHwInfoT));
    const char *format = NULL, *access = NULL;

    // some default values
    params->rate = ALSA_DEFAULT_PCM_RATE;
    params->channels = 2;
    params->sampleSize = 0;

    if (paramsJ) {
        int error =
        	wrap_json_unpack(paramsJ, "{s?i,s?i, s?s, s?s !}",
        		             "rate",    &params->rate,
				             "channels",&params->channels,
				             "format",  &format,
				             "access",  &access);
        if (error) {
            AFB_ApiError(mixer->api, "ApiPcmSetParams: sndcard=%s invalid params=%s", uid, json_object_get_string(paramsJ));
            goto OnErrorExit;
        }
    }

    if (!format) {
        params->format = SND_PCM_FORMAT_S16_LE;
        params->formatS = "S16_LE";
        goto check_access;
    }
    params->formatS = strdup(format);
#define FORMAT_CHECK(arg) if (!strcmp(format,#arg)) { params->format = SND_PCM_FORMAT_##arg; goto check_access; }

    FORMAT_CHECK(S16_LE);
    FORMAT_CHECK(S16_BE);
    FORMAT_CHECK(U16_LE);
    FORMAT_CHECK(U16_BE);
    FORMAT_CHECK(S32_BE);
    FORMAT_CHECK(S32_LE);
    FORMAT_CHECK(U32_BE);
    FORMAT_CHECK(U32_LE);
    FORMAT_CHECK(S24_BE);
    FORMAT_CHECK(S24_LE);
    FORMAT_CHECK(U24_BE);
    FORMAT_CHECK(U24_LE);
    FORMAT_CHECK(S8);
    FORMAT_CHECK(U8);
    FORMAT_CHECK(FLOAT_LE);
    FORMAT_CHECK(FLOAT_BE);

    AFB_ApiError(mixer->api, "ApiPcmSetParams:%s(params) unsupported format 'S16_LE|S32_L|...' format=%s", uid, format);
    goto OnErrorExit;

check_access:
    AFB_ApiNotice(mixer->api, "ApiPcmSetParams:%s format set to SND_PCM_FORMAT_%s", uid, params->formatS);

#define ACCESS_CHECK(arg) if (!strcmp(access,#arg)) { params->access = SND_PCM_ACCESS_##arg; goto success;}

    if (!access) {
        params->access = SND_PCM_ACCESS_RW_INTERLEAVED;
        goto success;
    }

    ACCESS_CHECK(MMAP_INTERLEAVED);
    ACCESS_CHECK(MMAP_NONINTERLEAVED);
    ACCESS_CHECK(MMAP_COMPLEX);
    ACCESS_CHECK(RW_INTERLEAVED);
    ACCESS_CHECK(RW_NONINTERLEAVED);

    AFB_ApiNotice(mixer->api, "ApiPcmSetParams:%s(params) unsupported access 'RW_INTERLEAVED|MMAP_INTERLEAVED|MMAP_COMPLEX' access=%s", uid, access);
    goto OnErrorExit;

success:
    AFB_ApiNotice(mixer->api, "ApiPcmSetParams:%s access set to %s", uid, access);
    return params;

OnErrorExit:
    free(params);
    return NULL;
}

PUBLIC AlsaSndPcmT * ApiPcmAttachOne(SoftMixerT *mixer, const char *uid, snd_pcm_stream_t direction, json_object * argsJ) {
    AlsaSndPcmT *pcm = calloc(1, sizeof (AlsaSndPcmT));
    json_object *sourceJ = NULL, *paramsJ = NULL, *sinkJ = NULL, *targetJ = NULL;
    char *apiVerb = NULL, *apiInfo = NULL;
    int error;

    pcm->sndcard = (AlsaSndCtlT*) calloc(1, sizeof (AlsaSndCtlT));
    error = wrap_json_unpack(argsJ, "{ss,s?s,s?s,s?s,s?i,s?i,s?o,s?o,s?o !}"
            , "uid", &pcm->uid
			, "pcmplug_params", &pcm->sndcard->cid.pcmplug_params
            , "path", &pcm->sndcard->cid.devpath
            , "cardid", &pcm->sndcard->cid.cardid
            , "device", &pcm->sndcard->cid.device
            , "subdev", &pcm->sndcard->cid.subdev
            , "sink", &sinkJ
            , "source", &sourceJ
            , "params", &paramsJ
            );
    if (error) {
        AFB_ApiError(mixer->api, "ApiPcmAttachOne: hal=%s missing 'uid|path|cardid|device|sink|source|params' error=%s args=%s", uid, wrap_json_get_error_string(error), json_object_get_string(argsJ));
        goto OnErrorExit;
    }

    // try to open sound card control interface
    pcm->sndcard->ctl = AlsaByPathOpenCtl(mixer, pcm->uid, pcm->sndcard);
    if (!pcm->sndcard->ctl) {
        AFB_ApiError(mixer->api, "ApiPcmAttachOne: hal=%s Fail to open sndcard uid=%s devpath=%s cardid=%s", uid, pcm->uid, pcm->sndcard->cid.devpath, pcm->sndcard->cid.cardid);
        goto OnErrorExit;
    }

    // check sndcard accepts params
    pcm->sndcard->params = ApiPcmSetParams(mixer, pcm->uid, paramsJ);
    if (!pcm->sndcard->params) {
        AFB_ApiError(mixer->api, "ApiPcmAttachOne: hal=%s  Fail to set params sndcard uid=%s params=%s", uid, pcm->uid, json_object_get_string(paramsJ));
        goto OnErrorExit;
    }

    if (direction == SND_PCM_STREAM_PLAYBACK) {
        if (!sinkJ) {
            AFB_ApiError(mixer->api, "ApiPcmAttachOne: hal=%s SND_PCM_STREAM_PLAYBACK require sinks args=%s", uid, json_object_get_string(argsJ));
            goto OnErrorExit;
        }
        targetJ = sinkJ;
    }

    if (direction == SND_PCM_STREAM_CAPTURE) {
        if (!sourceJ) {
            AFB_ApiError(mixer->api, "ApiPcmAttachOne: hal=%s SND_PCM_STREAM_CAPTURE require sources args=%s", uid, json_object_get_string(argsJ));
            goto OnErrorExit;
        }
        targetJ = sourceJ;

        // we may have to register SMIXER_SUBDS_CTLS per subdev (Fulup ToBeDone when sndcard get multiple device/subdev)
        pcm->sndcard->registry = calloc(SMIXER_SUBDS_CTLS + 1, sizeof (RegistryEntryPcmT));
        pcm->sndcard->rcount = SMIXER_SUBDS_CTLS;
    }

    json_object *channelsJ = NULL, *controlsJ = NULL;
    error = wrap_json_unpack(targetJ, "{so,s?o !}"
            , "channels", &channelsJ
            , "controls", &controlsJ
            );
    if (error) {
        AFB_ApiNotice(mixer->api, "ApiPcmAttachOne: hal=%s pcms missing channels|[controls] error=%s paybacks=%s", uid, wrap_json_get_error_string(error), json_object_get_string(argsJ));
        goto OnErrorExit;
    }
    if (channelsJ) {
        switch (json_object_get_type(channelsJ)) {

            case json_type_object:
                pcm->ccount = 1;
                pcm->channels = calloc(2, sizeof (void*));
                pcm->channels[0] = ProcessOneChannel(mixer, pcm->uid, channelsJ);
                if (!pcm->channels[0]) goto OnErrorExit;
                break;
            case json_type_array:
                pcm->ccount = (int) json_object_array_length(channelsJ);
                pcm->channels = calloc(pcm->ccount + 1, sizeof (void*));
                for (int idx = 0; idx < pcm->ccount; idx++) {
                    json_object *channelJ = json_object_array_get_idx(channelsJ, idx);
                    pcm->channels[idx] = ProcessOneChannel(mixer, pcm->uid, channelJ);
                    if (!pcm->channels[idx]) goto OnErrorExit;
                }
                break;
            default:
                AFB_ApiError(mixer->api, "ApiPcmAttachOne:%s invalid pcm=%s", pcm->uid, json_object_get_string(channelsJ));
                goto OnErrorExit;
        }
    }

    if (controlsJ) {
        json_object *volJ = NULL, *muteJ = NULL;
        error = wrap_json_unpack(controlsJ, "{s?o,s?o !}"
                , "volume", &volJ
                , "mute", &muteJ
                );
        if (error) {
            AFB_ApiNotice(mixer->api,
            		      "%s: source missing [volume]|[mute] error=%s control=%s",
            		      __func__, wrap_json_get_error_string(error), json_object_get_string(controlsJ));
            goto OnErrorExit;
        }

        if (volJ) error += PcmAttachOneCtl(mixer, pcm->sndcard, volJ, &pcm->volume);
        if (muteJ) error += PcmAttachOneCtl(mixer, pcm->sndcard, muteJ, &pcm->mute);
        if (error) goto OnErrorExit;

        // create master control for this sink
        if (direction == SND_PCM_STREAM_PLAYBACK) {
            if (asprintf(&apiVerb, "%s:playback", pcm->uid) == -1)
                goto OnErrorExit;
            if (asprintf(&apiInfo, "HAL:%s SND_PCM_STREAM_PLAYBACK", uid) == -1)
                goto OnErrorExit;
        } else {
            if (asprintf(&apiVerb, "%s:capture", pcm->uid) == -1)
                goto OnErrorExit;
            if (asprintf(&apiInfo, "HAL:%s SND_PCM_STREAM_PLAYBACK", uid) == -1)
                goto OnErrorExit;
        }     
        
        apiVerbHandleT *handle = calloc(1, sizeof (apiVerbHandleT));
        handle->uid = uid;
        handle->pcm = pcm;
        handle->mixer = mixer;
        pcm->verb=apiVerb;
        error = afb_api_add_verb(mixer->api, apiVerb, apiInfo, ApiPcmVerbCB, handle, NULL, 0, 0);
        if (error) {
            AFB_ApiError(mixer->api, "ApiPcmAttachOne mixer=%s verb=%s fail to Register Master control ", mixer->uid, apiVerb);
            goto OnErrorExit;
        }
    } else {
    	/* no controls -> put dummy verb */
    	pcm->verb = "none";
    }

    // free useless resource and secure others
    pcm->uid = strdup(pcm->uid);

    return pcm;

OnErrorExit:
    free(pcm);
    free(apiVerb);
    free(apiInfo);
    return NULL;
}