/* 
 | 
 * BlueALSA - ctl-client.c 
 | 
 * Copyright (c) 2016-2018 Arkadiusz Bokowy 
 | 
 * 
 | 
 * This file is a part of bluez-alsa. 
 | 
 * 
 | 
 * This project is licensed under the terms of the MIT license. 
 | 
 * 
 | 
 */ 
 | 
  
 | 
#include "shared/ctl-client.h" 
 | 
  
 | 
#include <errno.h> 
 | 
#include <fcntl.h> 
 | 
#include <stdlib.h> 
 | 
#include <unistd.h> 
 | 
#include <sys/socket.h> 
 | 
#include <sys/stat.h> 
 | 
#include <sys/types.h> 
 | 
#include <sys/un.h> 
 | 
  
 | 
#include "shared/log.h" 
 | 
  
 | 
  
 | 
/** 
 | 
 * Convert BlueALSA status message into the POSIX errno value. */ 
 | 
static int bluealsa_status_to_errno(const struct ba_msg_status *status) { 
 | 
    switch (status->code) { 
 | 
    case BA_STATUS_CODE_SUCCESS: 
 | 
        return 0; 
 | 
    case BA_STATUS_CODE_ERROR_UNKNOWN: 
 | 
        return EIO; 
 | 
    case BA_STATUS_CODE_DEVICE_NOT_FOUND: 
 | 
        return ENODEV; 
 | 
    case BA_STATUS_CODE_STREAM_NOT_FOUND: 
 | 
        return ENXIO; 
 | 
    case BA_STATUS_CODE_DEVICE_BUSY: 
 | 
        return EBUSY; 
 | 
    case BA_STATUS_CODE_FORBIDDEN: 
 | 
        return EACCES; 
 | 
    default: 
 | 
        /* some generic error code */ 
 | 
        return EINVAL; 
 | 
    } 
 | 
} 
 | 
  
 | 
#if DEBUG 
 | 
/** 
 | 
 * Convert Bluetooth address into a human-readable string. 
 | 
 * 
 | 
 * In order to convert Bluetooth address into the human-readable string, one 
 | 
 * might use the ba2str() from the bluetooth library. However, it would be the 
 | 
 * only used function from this library. In order to avoid excessive linking, 
 | 
 * we are providing our own implementation of this function. 
 | 
 * 
 | 
 * @param ba Pointer to the Bluetooth address structure. 
 | 
 * @param str Pointer to buffer big enough to contain string representation 
 | 
 *   of the Bluetooth address. 
 | 
 * @return Pointer to the destination string str. */ 
 | 
static char *ba2str_(const bdaddr_t *ba, char str[18]) { 
 | 
    sprintf(str, "%2.2X:%2.2X:%2.2X:%2.2X:%2.2X:%2.2X", 
 | 
            ba->b[5], ba->b[4], ba->b[3], ba->b[2], ba->b[1], ba->b[0]); 
 | 
    return str; 
 | 
} 
 | 
#endif 
 | 
  
 | 
/** 
 | 
 * Send request to the BlueALSA server. 
 | 
 * 
 | 
 * @param fd Opened socket file descriptor. 
 | 
 * @param req An address to the request structure. 
 | 
 * @return Upon success this function returns 0. Otherwise, -1 is returned 
 | 
 *   and errno is set appropriately. */ 
 | 
static int bluealsa_send_request(int fd, const struct ba_request *req) { 
 | 
  
 | 
    struct ba_msg_status status = { 0xAB }; 
 | 
  
 | 
    if (send(fd, req, sizeof(*req), MSG_NOSIGNAL) == -1) 
 | 
        return -1; 
 | 
    if (read(fd, &status, sizeof(status)) == -1) 
 | 
        return -1; 
 | 
  
 | 
    errno = bluealsa_status_to_errno(&status); 
 | 
    return errno != 0 ? -1 : 0; 
 | 
} 
 | 
  
 | 
/** 
 | 
 * Open BlueALSA connection. 
 | 
 * 
 | 
 * @param interface HCI interface to use. 
 | 
 * @return On success this function returns socket file descriptor. Otherwise, 
 | 
 *   -1 is returned and errno is set to indicate the error. */ 
 | 
int bluealsa_open(const char *interface) { 
 | 
  
 | 
    const uint16_t ver = BLUEALSA_CRL_PROTO_VERSION; 
 | 
    int fd, err; 
 | 
  
 | 
    struct sockaddr_un saddr = { .sun_family = AF_UNIX }; 
 | 
    snprintf(saddr.sun_path, sizeof(saddr.sun_path) - 1, 
 | 
            BLUEALSA_RUN_STATE_DIR "/%s", interface); 
 | 
  
 | 
    if ((fd = socket(PF_UNIX, SOCK_SEQPACKET | SOCK_CLOEXEC, 0)) == -1) 
 | 
        return -1; 
 | 
  
 | 
    debug("Connecting to socket: %s", saddr.sun_path); 
 | 
    if (connect(fd, (struct sockaddr *)(&saddr), sizeof(saddr)) == -1) { 
 | 
        err = errno; 
 | 
        close(fd); 
 | 
        errno = err; 
 | 
        return -1; 
 | 
    } 
 | 
  
 | 
    if (send(fd, &ver, sizeof(ver), MSG_NOSIGNAL) == -1) 
 | 
        return -1; 
 | 
  
 | 
    return fd; 
 | 
} 
 | 
  
 | 
/** 
 | 
 * Subscribe for notifications. 
 | 
 * 
 | 
 * @param fd Opened socket file descriptor. 
 | 
 * @param mask Bit-mask with events for which client wants to be subscribed. 
 | 
 *   In order to cancel subscription, use empty event mask. 
 | 
 * @return Upon success this function returns 0. Otherwise, -1 is returned 
 | 
 *   and errno is set appropriately. */ 
 | 
int bluealsa_subscribe(int fd, enum ba_event mask) { 
 | 
    const struct ba_request req = { 
 | 
        .command = BA_COMMAND_SUBSCRIBE, 
 | 
        .events = mask, 
 | 
    }; 
 | 
    debug("Subscribing for events: %B", mask); 
 | 
    return bluealsa_send_request(fd, &req); 
 | 
} 
 | 
  
 | 
/** 
 | 
 * Get the list of connected Bluetooth devices. 
 | 
 * 
 | 
 * @param fd Opened socket file descriptor. 
 | 
 * @param devices An address where the device list will be stored. 
 | 
 * @return Upon success this function returns the number of connected devices 
 | 
 *   and the `devices` address is modified to point to the devices list array, 
 | 
 *   which should be freed with the free(). On error, -1 is returned and errno 
 | 
 *   is set to indicate the error. */ 
 | 
ssize_t bluealsa_get_devices(int fd, struct ba_msg_device **devices) { 
 | 
  
 | 
    const struct ba_request req = { .command = BA_COMMAND_LIST_DEVICES }; 
 | 
    struct ba_msg_device *_devices = NULL; 
 | 
    struct ba_msg_device device; 
 | 
    size_t i = 0; 
 | 
  
 | 
    if (send(fd, &req, sizeof(req), MSG_NOSIGNAL) == -1) 
 | 
        return -1; 
 | 
  
 | 
    while (recv(fd, &device, sizeof(device), 0) == sizeof(device)) { 
 | 
        _devices = realloc(_devices, (i + 1) * sizeof(*_devices)); 
 | 
        memcpy(&_devices[i], &device, sizeof(*_devices)); 
 | 
        i++; 
 | 
    } 
 | 
  
 | 
    *devices = _devices; 
 | 
    return i; 
 | 
} 
 | 
  
 | 
/** 
 | 
 * Get the list of available PCM transports. 
 | 
 * 
 | 
 * @param fd Opened socket file descriptor. 
 | 
 * @param transports An address where the transport list will be stored. 
 | 
 * @return Upon success this function returns the number of available PCM 
 | 
 *   transports and the `transports` address is modified to point to the 
 | 
 *   transport list array, which should be freed with the free(). On error, 
 | 
 *   -1 is returned and errno is set to indicate the error. */ 
 | 
ssize_t bluealsa_get_transports(int fd, struct ba_msg_transport **transports) { 
 | 
  
 | 
    const struct ba_request req = { .command = BA_COMMAND_LIST_TRANSPORTS }; 
 | 
    struct ba_msg_transport *_transports = NULL; 
 | 
    struct ba_msg_transport transport; 
 | 
    size_t i = 0; 
 | 
  
 | 
    if (send(fd, &req, sizeof(req), MSG_NOSIGNAL) == -1) 
 | 
        return -1; 
 | 
  
 | 
    while (recv(fd, &transport, sizeof(transport), 0) == sizeof(transport)) { 
 | 
        _transports = realloc(_transports, (i + 1) * sizeof(*_transports)); 
 | 
        memcpy(&_transports[i], &transport, sizeof(*_transports)); 
 | 
        i++; 
 | 
    } 
 | 
  
 | 
    *transports = _transports; 
 | 
    return i; 
 | 
} 
 | 
  
 | 
/** 
 | 
 * Get PCM transport. 
 | 
 * 
 | 
 * @param fd Opened socket file descriptor. 
 | 
 * @param addr MAC address of the Bluetooth device. 
 | 
 * @param type PCM type to get. 
 | 
 * @param stream Stream direction to get, e.g. playback or capture. 
 | 
 * @param transport An address where the transport will be stored. 
 | 
 * @return Upon success this function returns 0. Otherwise, -1 is returned 
 | 
 *   and errno is set appropriately. */ 
 | 
int bluealsa_get_transport(int fd, bdaddr_t addr, enum ba_pcm_type type, 
 | 
        enum ba_pcm_stream stream, struct ba_msg_transport *transport) { 
 | 
  
 | 
    struct ba_msg_status status = { 0xAB }; 
 | 
    struct ba_request req = { 
 | 
        .command = BA_COMMAND_TRANSPORT_GET, 
 | 
        .addr = addr, 
 | 
        .type = type, 
 | 
        .stream = stream, 
 | 
    }; 
 | 
    ssize_t len; 
 | 
  
 | 
#if DEBUG 
 | 
    char addr_[18]; 
 | 
    ba2str_(&req.addr, addr_); 
 | 
    debug("Getting transport for %s type %d", addr_, type); 
 | 
#endif 
 | 
  
 | 
    if (send(fd, &req, sizeof(req), MSG_NOSIGNAL) == -1) 
 | 
        return -1; 
 | 
    if ((len = read(fd, transport, sizeof(*transport))) == -1) 
 | 
        return -1; 
 | 
  
 | 
    /* in case of error, status message is returned */ 
 | 
    if (len != sizeof(*transport)) { 
 | 
        memcpy(&status, transport, sizeof(status)); 
 | 
        errno = bluealsa_status_to_errno(&status); 
 | 
        return -1; 
 | 
    } 
 | 
  
 | 
    if (read(fd, &status, sizeof(status)) == -1) 
 | 
        return -1; 
 | 
  
 | 
    return 0; 
 | 
} 
 | 
  
 | 
/** 
 | 
 * Get PCM transport delay. 
 | 
 * 
 | 
 * @param fd Opened socket file descriptor. 
 | 
 * @param transport Address to the transport structure with the addr, type 
 | 
 *   and stream fields set - other fields are not used by this function. 
 | 
 * @param delay An address where the transport delay will be stored. 
 | 
 * @return Upon success this function returns 0. Otherwise, -1 is returned 
 | 
 *   and errno is set appropriately. */ 
 | 
int bluealsa_get_transport_delay(int fd, const struct ba_msg_transport *transport, 
 | 
        unsigned int *delay) { 
 | 
  
 | 
    struct ba_msg_transport t; 
 | 
    int ret; 
 | 
  
 | 
    if ((ret = bluealsa_get_transport(fd, transport->addr, 
 | 
                    transport->type, transport->stream, &t)) == 0) 
 | 
        *delay = t.delay; 
 | 
  
 | 
    return ret; 
 | 
} 
 | 
  
 | 
/** 
 | 
 * Set PCM transport delay. 
 | 
 * 
 | 
 * @param fd Opened socket file descriptor. 
 | 
 * @param transport Address to the transport structure with the addr, type 
 | 
 *   and stream fields set - other fields are not used by this function. 
 | 
 * @param delay Transport delay. 
 | 
 * @return Upon success this function returns 0. Otherwise, -1 is returned 
 | 
 *   and errno is set appropriately. */ 
 | 
int bluealsa_set_transport_delay(int fd, const struct ba_msg_transport *transport, 
 | 
        unsigned int delay) { 
 | 
  
 | 
    struct ba_request req = { 
 | 
        .command = BA_COMMAND_TRANSPORT_SET_DELAY, 
 | 
        .addr = transport->addr, 
 | 
        .type = transport->type, 
 | 
        .stream = transport->stream, 
 | 
        .delay = delay, 
 | 
    }; 
 | 
  
 | 
    return bluealsa_send_request(fd, &req); 
 | 
} 
 | 
  
 | 
/** 
 | 
 * Get PCM transport volume. 
 | 
 * 
 | 
 * @param fd Opened socket file descriptor. 
 | 
 * @param transport Address to the transport structure with the addr, type 
 | 
 *   and stream fields set - other fields are not used by this function. 
 | 
 * @param ch1_muted An address where the mute of channel 1 will be stored. 
 | 
 * @param ch1_volume An address where the volume of channel 1 will be stored. 
 | 
 * @param ch2_muted An address where the mute of channel 2 will be stored. 
 | 
 * @param ch2_volume An address where the volume of channel 2 will be stored. 
 | 
 * @return Upon success this function returns 0. Otherwise, -1 is returned 
 | 
 *   and errno is set appropriately. */ 
 | 
int bluealsa_get_transport_volume(int fd, const struct ba_msg_transport *transport, 
 | 
        bool *ch1_muted, int *ch1_volume, bool *ch2_muted, int *ch2_volume) { 
 | 
  
 | 
    struct ba_msg_transport t; 
 | 
    int ret; 
 | 
  
 | 
    if ((ret = bluealsa_get_transport(fd, transport->addr, 
 | 
                    transport->type, transport->stream, &t)) == 0) { 
 | 
        *ch1_muted = t.ch1_muted; 
 | 
        *ch1_volume = t.ch1_volume; 
 | 
        *ch2_muted = t.ch2_muted; 
 | 
        *ch2_volume = t.ch2_volume; 
 | 
    } 
 | 
  
 | 
    return ret; 
 | 
} 
 | 
  
 | 
/** 
 | 
 * Set PCM transport volume. 
 | 
 * 
 | 
 * @param fd Opened socket file descriptor. 
 | 
 * @param transport Address to the transport structure with the addr, type 
 | 
 *   and stream fields set - other fields are not used by this function. 
 | 
 * @param ch1_muted If true, mute channel 1. 
 | 
 * @param ch1_volume Channel 1 volume in range [0, 127]. 
 | 
 * @param ch2_muted If true, mute channel 2. 
 | 
 * @param ch2_volume Channel 2 volume in range [0, 127]. 
 | 
 * @return Upon success this function returns 0. Otherwise, -1 is returned 
 | 
 *   and errno is set appropriately. */ 
 | 
int bluealsa_set_transport_volume(int fd, const struct ba_msg_transport *transport, 
 | 
        bool ch1_muted, int ch1_volume, bool ch2_muted, int ch2_volume) { 
 | 
  
 | 
    struct ba_request req = { 
 | 
        .command = BA_COMMAND_TRANSPORT_SET_VOLUME, 
 | 
        .addr = transport->addr, 
 | 
        .type = transport->type, 
 | 
        .stream = transport->stream, 
 | 
        .ch1_muted = ch1_muted, 
 | 
        .ch1_volume = ch1_volume, 
 | 
        .ch2_muted = ch2_muted, 
 | 
        .ch2_volume = ch2_volume, 
 | 
    }; 
 | 
  
 | 
    return bluealsa_send_request(fd, &req); 
 | 
} 
 | 
  
 | 
/** 
 | 
 * Open PCM transport. 
 | 
 * 
 | 
 * @param fd Opened socket file descriptor. 
 | 
 * @param transport Address to the transport structure with the addr, type 
 | 
 *   and stream fields set - other fields are not used by this function. 
 | 
 * @return PCM FIFO file descriptor, or -1 on error. */ 
 | 
int bluealsa_open_transport(int fd, const struct ba_msg_transport *transport) { 
 | 
  
 | 
    struct ba_msg_status status = { 0xAB }; 
 | 
    struct ba_request req = { 
 | 
        .command = BA_COMMAND_PCM_OPEN, 
 | 
        .addr = transport->addr, 
 | 
        .type = transport->type, 
 | 
        .stream = transport->stream, 
 | 
    }; 
 | 
    char buf[256] = ""; 
 | 
    struct iovec io = { 
 | 
        .iov_base = &status, 
 | 
        .iov_len = sizeof(status), 
 | 
    }; 
 | 
    struct msghdr msg = { 
 | 
        .msg_iov = &io, 
 | 
        .msg_iovlen = 1, 
 | 
        .msg_control = buf, 
 | 
        .msg_controllen = sizeof(buf), 
 | 
    }; 
 | 
    ssize_t len; 
 | 
  
 | 
#if DEBUG 
 | 
    char addr_[18]; 
 | 
    ba2str_(&req.addr, addr_); 
 | 
    debug("Requesting PCM open for %s", addr_); 
 | 
#endif 
 | 
  
 | 
    if (send(fd, &req, sizeof(req), MSG_NOSIGNAL) == -1) 
 | 
        return -1; 
 | 
    if ((len = recvmsg(fd, &msg, MSG_CMSG_CLOEXEC)) == -1) 
 | 
        return -1; 
 | 
  
 | 
    struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg); 
 | 
    if (cmsg == NULL || 
 | 
            cmsg->cmsg_level == IPPROTO_IP || 
 | 
            cmsg->cmsg_type == IP_TTL) { 
 | 
        /* in case of error, status message is returned */ 
 | 
        errno = bluealsa_status_to_errno(&status); 
 | 
        return -1; 
 | 
    } 
 | 
  
 | 
    if (read(fd, &status, sizeof(status)) == -1) 
 | 
        return -1; 
 | 
  
 | 
    return *((int *)CMSG_DATA(cmsg)); 
 | 
} 
 | 
  
 | 
/** 
 | 
 * Close PCM transport. 
 | 
 * 
 | 
 * @param fd Opened socket file descriptor. 
 | 
 * @param transport Address to the transport structure with the addr, type 
 | 
 *   and stream fields set - other fields are not used by this function. 
 | 
 * @return Upon success this function returns 0. Otherwise, -1 is returned. */ 
 | 
int bluealsa_close_transport(int fd, const struct ba_msg_transport *transport) { 
 | 
  
 | 
    struct ba_request req = { 
 | 
        .command = BA_COMMAND_PCM_CLOSE, 
 | 
        .addr = transport->addr, 
 | 
        .type = transport->type, 
 | 
        .stream = transport->stream, 
 | 
    }; 
 | 
  
 | 
#if DEBUG 
 | 
    char addr_[18]; 
 | 
    ba2str_(&req.addr, addr_); 
 | 
    debug("Closing PCM for %s", addr_); 
 | 
#endif 
 | 
  
 | 
    return bluealsa_send_request(fd, &req); 
 | 
} 
 | 
  
 | 
/** 
 | 
 * Pause/resume PCM transport. 
 | 
 * 
 | 
 * @param fd Opened socket file descriptor. 
 | 
 * @param transport Address to the transport structure with the addr, type 
 | 
 *   and stream fields set - other fields are not used by this function. 
 | 
 * @param pause If non-zero, pause transport, otherwise resume it. 
 | 
 * @return Upon success this function returns 0. Otherwise, -1 is returned. */ 
 | 
int bluealsa_pause_transport(int fd, const struct ba_msg_transport *transport, bool pause) { 
 | 
  
 | 
    struct ba_request req = { 
 | 
        .command = pause ? BA_COMMAND_PCM_PAUSE : BA_COMMAND_PCM_RESUME, 
 | 
        .addr = transport->addr, 
 | 
        .type = transport->type, 
 | 
        .stream = transport->stream, 
 | 
    }; 
 | 
  
 | 
#if DEBUG 
 | 
    char addr_[18]; 
 | 
    ba2str_(&req.addr, addr_); 
 | 
    debug("Requesting PCM %s for %s", pause ? "pause" : "resume", addr_); 
 | 
#endif 
 | 
  
 | 
    return bluealsa_send_request(fd, &req); 
 | 
} 
 | 
  
 | 
/** 
 | 
 * Drain PCM transport. 
 | 
 * 
 | 
 * @param fd Opened socket file descriptor. 
 | 
 * @param transport Address to the transport structure with the addr, type 
 | 
 *   and stream fields set - other fields are not used by this function. 
 | 
 * @return Upon success this function returns 0. Otherwise, -1 is returned. */ 
 | 
int bluealsa_drain_transport(int fd, const struct ba_msg_transport *transport) { 
 | 
  
 | 
    struct ba_request req = { 
 | 
        .command = BA_COMMAND_PCM_DRAIN, 
 | 
        .addr = transport->addr, 
 | 
        .type = transport->type, 
 | 
        .stream = transport->stream, 
 | 
    }; 
 | 
  
 | 
#if DEBUG 
 | 
    char addr_[18]; 
 | 
    ba2str_(&req.addr, addr_); 
 | 
    debug("Requesting PCM drain for %s", addr_); 
 | 
#endif 
 | 
  
 | 
    return bluealsa_send_request(fd, &req); 
 | 
} 
 | 
  
 | 
/** 
 | 
 * Send RFCOMM message. 
 | 
 * 
 | 
 * @param fd Opened socket file descriptor. 
 | 
 * @param addr MAC address of the Bluetooth device. 
 | 
 * @param command NULL-terminated command string. 
 | 
 * @return Upon success this function returns 0. Otherwise, -1 is returned. */ 
 | 
int bluealsa_send_rfcomm_command(int fd, bdaddr_t addr, const char *command) { 
 | 
  
 | 
    struct ba_request req = { 
 | 
        .command = BA_COMMAND_RFCOMM_SEND, 
 | 
        .addr = addr, 
 | 
    }; 
 | 
  
 | 
    /* snprintf() guarantees terminating NULL character */ 
 | 
    snprintf(req.rfcomm_command, sizeof(req.rfcomm_command), "%s", command); 
 | 
  
 | 
#if DEBUG 
 | 
    char addr_[18]; 
 | 
    ba2str_(&req.addr, addr_); 
 | 
    debug("Sending RFCOMM command to %s: %s", addr_, req.rfcomm_command); 
 | 
#endif 
 | 
  
 | 
    return bluealsa_send_request(fd, &req); 
 | 
} 
 |