# Copyright (c) 2021 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details. # frozen_string_literal: true require 'socket' require 'uri' require 'contrast/api/communication/tcp_socket' require 'contrast/api/communication/unix_socket' require 'contrast/components/logger' module Contrast module Api module Communication # SocketClient acts as a interface between the agent and the service. It instantiates a # service proxy and tracks the state of that proxy. class SocketClient include Contrast::Components::Logger::InstanceMethods def initialize @socket = init_connection end # Wrap the given DTM in a Contrast::Api::Dtm::Message and send it to the # Service for processing # # @param event [Contrast::Api::Dtm] One of the DTMs valid for the event field of # Contrast::Api::Dtm::Message # @return [Contrast::Api::Settings::AgentSettings] def send_one event msg = Contrast::Api::Dtm::Message.build(event) send_message(msg) end private def init_connection log_connection if ::Contrast::CONTRAST_SERVICE.use_tcp? Contrast::Api::Communication::TcpSocket.new( ::Contrast::CONTRAST_SERVICE.host, ::Contrast::CONTRAST_SERVICE.port) else Contrast::Api::Communication::UnixSocket.new(::Contrast::CONTRAST_SERVICE.socket_path) end end def log_connection # The socket is set, if ::Contrast::CONFIG.root.agent.service.socket logger.info('Connecting to the Contrast Service using a UnixSocket socket', socket: ::Contrast::CONTRAST_SERVICE.socket_path) return end # The host & port are set, if ::Contrast::CONFIG.root.agent.service.host && ::Contrast::CONFIG.root.agent.service.port logger.info('Connecting to the Contrast Service using a TCP socket', host: ::Contrast::CONTRAST_SERVICE.host, port: ::Contrast::CONTRAST_SERVICE.port) return end # Or something is not set. logger.warn( log_connection_error_msg, host: ::Contrast::CONTRAST_SERVICE.host, port: ::Contrast::CONTRAST_SERVICE.port) end # If our connection isn't built properly, we need to warn the user. This builds out the context specific # message to provide that warning # # @return [String] def log_connection_error_msg if ::Contrast::CONFIG.root.agent.service.host 'Missing a required connection value to the Contrast Service. ' \ '`agent.service.port` is not set. ' \ 'Falling back to default TCP socket port.' elsif ::Contrast::CONFIG.root.agent.service.port 'Missing a required connection value to the Contrast Service. ' \ '`agent.service.host` is not set. ' \ 'Falling back to default TCP socket host.' else 'Missing a required connection value to the Contrast Service. ' \ 'Neither `agent.service.socket` nor the pair of `agent.service.host` and `agent.service.port` are set. '\ 'Falling back to default TCP socket.' end end def send_message msg return unless msg logger.debug('Sending message.', msg_id: msg.__id__, p_id: msg.pid, msg_count: msg.message_count) to_service = Contrast::Api::Dtm::Message.encode(msg) from_service = send_marshaled(to_service) response = Contrast::Api::Settings::AgentSettings.decode(from_service) logger.debug('Received response.', msg_id: msg.__id__, p_id: msg.pid, msg_count: msg.message_count, response_id: response&.__id__) response rescue StandardError => e logger.error('Sending failed for message.', e, msg_id: msg.__id__, p_id: msg.pid, msg_count: msg.message_count, response_id: response&.__id__) raise e # reraise to let Speedracer manage the connection end def send_marshaled marshaled @socket.send_marshaled(marshaled) end end end end end