
#include "sockets_reusable_acceptor.h"
#include <sstream>
#include <sys/socket.h>
#include <unistd.h>
#include <exception>
#include <sys/types.h>
namespace sockets {
    
    reusable_acceptor::reusable_acceptor(
        boost::asio::io_service& io_service
    ) :
    io_service(
        io_service
    ),
    reusable(
        NULL
    ),
    sharing(
        NULL
    ) {
    }
    
    boost::asio::io_service& reusable_acceptor::get_io_service(
    ) {
        return io_service;
    }
    
    std::string reusable_acceptor::sharing_socket_name(
        boost::asio::ip::tcp::endpoint const& shared
    ) {
        std::stringstream writing;
        writing << "/tmp/sharing_" << shared.address(
        ) << "_" << shared.port(
        );
        return writing.str(
        );
    }
    
    void reusable_acceptor::handle_sharing_accept(
        boost::system::error_code const& error,
        boost::asio::local::stream_protocol::socket* accepted
    ) {
        if (
            error
        ) {
            delete accepted;
            throw std::exception(
            );
        }
        char dummy = 0;
        struct iovec dummy_storage;
        dummy_storage.iov_base = &dummy;
        dummy_storage.iov_len = sizeof(
            dummy
        );
        struct msghdr descriptors;
        descriptors.msg_name = NULL;
        descriptors.msg_namelen = 0;
        descriptors.msg_iov = &dummy_storage;
        descriptors.msg_iovlen = 1;
        int descriptor_storage[
            2
        ];
        char control_buffer[
            CMSG_SPACE(
                sizeof(
                    descriptor_storage
                )
            )
        ];
        descriptor_storage[
            0
        ] = sharing->native(
        );
        descriptor_storage[
            1
        ] = reusable->native(
        );
        descriptors.msg_control = control_buffer;
        descriptors.msg_controllen = sizeof(
            control_buffer
        );
        descriptors.msg_flags = 0;
        struct cmsghdr* control_data = CMSG_FIRSTHDR(
            &descriptors
        );
        control_data->cmsg_len = CMSG_LEN(
            sizeof(
                descriptor_storage
            )
        );
        control_data->cmsg_level = SOL_SOCKET;
        control_data->cmsg_type = SCM_RIGHTS;
        int* descriptor_passing = reinterpret_cast<
            int*
        >(
            CMSG_DATA(
                 control_data
            )
        );
        descriptor_passing[
            0
        ] = descriptor_storage[
            0
        ];
        descriptor_passing[
            1
        ] = descriptor_storage[
            1
        ];
        descriptors.msg_controllen = control_data->cmsg_len;
        if (
            0 > sendmsg(
                accepted->native(
                ),
                &descriptors,
                0
            )
        ) {
            throw std::exception(
            );
        }
        accepted->close(
        );
        delete accepted;
        boost::asio::local::stream_protocol::socket* new_accepted = new boost::asio::local::stream_protocol::socket(
            io_service
        );
        sharing->async_accept(
            *new_accepted,
            boost::bind(
                &reusable_acceptor::handle_sharing_accept,
                this,
                boost::asio::placeholders::error,
                new_accepted
            )
        );
    }
    
    void reusable_acceptor::handle_provider_connect(
        boost::system::error_code const& error,
        boost::asio::local::stream_protocol::socket* connecting,
        boost::asio::local::stream_protocol::endpoint const& sharing_endpoint,
        boost::asio::ip::tcp::endpoint const& requested_endpoint,
        boost::function<
            void (
                boost::system::error_code const& error
            )
        > handler
    ) {
        if (
            error
        ) {
            delete connecting;
            unlink(
                sharing_endpoint.path(
                ).c_str(
                )
            );
            sharing = new boost::asio::local::stream_protocol::acceptor(
                io_service,
                sharing_endpoint
            );
            reusable = new boost::asio::ip::tcp::acceptor(
                io_service,
                requested_endpoint
            );
        } else {
            char dummy = 0;
            struct iovec dummy_storage;
            dummy_storage.iov_base = &dummy;
            dummy_storage.iov_len = sizeof(
                dummy
            );
            struct msghdr descriptors;
            descriptors.msg_name = NULL;
            descriptors.msg_namelen = 0;
            descriptors.msg_iov = &dummy_storage;
            descriptors.msg_iovlen = 1;
            int descriptor_storage[
                2
            ];
            char control_buffer[
                CMSG_SPACE(
                    sizeof(
                        descriptor_storage
                    )
                )
            ];
            descriptors.msg_control = control_buffer;
            descriptors.msg_controllen = sizeof(
                control_buffer
            );
            descriptors.msg_flags = 0;
            struct cmsghdr* control_data = CMSG_FIRSTHDR(
                &descriptors
            );
            control_data->cmsg_len = CMSG_LEN(
                sizeof(
                    descriptor_storage
                )
            );
            control_data->cmsg_level = SOL_SOCKET;
            control_data->cmsg_type = SCM_RIGHTS;
            int* descriptor_passing = reinterpret_cast<
                int*
            >(
                CMSG_DATA(
                     control_data
                )
            );
            descriptor_passing[
                0
            ] = -1;
            descriptor_passing[
                1
            ] = -1;
            if (
                0 > recvmsg(
                    connecting->native(
                    ),
                    &descriptors,
                    0
                )
            ) {
                throw std::exception(
                );
            }
            sharing = new boost::asio::local::stream_protocol::acceptor(
                io_service,
                boost::asio::local::stream_protocol(
                ),
                descriptor_passing[
                    0
                ]
            );
            reusable = new boost::asio::ip::tcp::acceptor(
                io_service,
                boost::asio::ip::tcp::v4(
                ),
                descriptor_passing[
                    1
                ]
            );
            connecting->close(
            );
            delete connecting;
        }
        boost::asio::local::stream_protocol::socket* accepted = new boost::asio::local::stream_protocol::socket(
            io_service
        );
        sharing->async_accept(
            *accepted,
            boost::bind(
                &reusable_acceptor::handle_sharing_accept,
                this,
                boost::asio::placeholders::error,
                accepted
            )
        );
        handler(
            boost::system::error_code(
            )
        );
    }
}
