module RubySMB
  # Contains all the RubySMB specific Error classes.
  module Error
    # Base class for RubySMB errors
    class RubySMBError < StandardError; end

    # Raised when there is a length or formatting issue with an ASN1-encoded string
    # @see https://en.wikipedia.org/wiki/Abstract_Syntax_Notation_One
    # @todo Find an SMB-specific link for ASN1 above
    class ASN1Encoding < RubySMBError; end

    # Raised when there is a problem with communication over NetBios Session Service
    # @see https://wiki.wireshark.org/NetBIOS/NBSS
    class NetBiosSessionService < RubySMBError; end

    # Raised when trying to parse raw binary into a Packet and the data
    # is invalid.
    class InvalidPacket < RubySMBError
      attr_reader :status_code
      def initialize(args = nil)
        if args.nil?
          super
        elsif args.is_a? String
          super(args)
        elsif args.is_a? Hash
          expected_proto = args[:expected_proto] ? translate_protocol(args[:expected_proto]) : '???'
          expected_cmd = args[:expected_cmd] || '???'
          received_proto = args[:packet]&.packet_smb_version || '???'
          received_cmd = get_cmd(args[:packet]) || '???'
          @status_code = args[:packet]&.status_code
          super(
            "Expecting #{expected_proto} protocol "\
            "with command=#{expected_cmd}"\
            "#{(" (" + args[:expected_custom] + ")") if args[:expected_custom]}, "\
            "got #{received_proto} protocol "\
            "with command=#{received_cmd}"\
            "#{(" (" + args[:received_custom] + ")") if args[:received_custom]}"\
            "#{(", Status: #{@status_code}") if @status_code}"
          )
        else
          raise ArgumentError, "InvalidPacket expects a String or a Hash, got a #{args.class}"
        end
      end

      def translate_protocol(proto)
        case proto
        when RubySMB::SMB1::SMB_PROTOCOL_ID
          'SMB1'
        when RubySMB::SMB2::SMB2_PROTOCOL_ID
          'SMB2'
        else
          raise ArgumentError, 'Unknown SMB protocol'
        end
      end
      private :translate_protocol

      def get_cmd(packet)
        return nil unless packet
        case packet.packet_smb_version
        when 'SMB1'
          packet.smb_header.command
        when 'SMB2'
          packet.smb2_header.command
        else
          nil
        end
      end
      private :get_cmd
    end

    # Raised when a response packet has a NTStatus code that was unexpected.
    class UnexpectedStatusCode < RubySMBError
      module Mixin
        attr_reader :status_code

        def status_name
          @status_code.name
        end

        private

        def status_code=(status_code)
          case status_code
          when WindowsError::ErrorCode
            @status_code = status_code
          when Integer
            @status_code = WindowsError::NTStatus.find_by_retval(status_code).first
            if @status_code.nil?
              @status_code = WindowsError::ErrorCode.new("0x#{status_code.to_s(16).rjust(8, '0')}", status_code, "Unknown status: 0x#{status_code.to_s(16).rjust(8, '0')}")
            end
          else
            raise ArgumentError, "Status code must be a WindowsError::ErrorCode or an Integer, got #{status_code.class}"
          end
        end
      end

      include Mixin

      def initialize(status_code)
        self.status_code = status_code
        super
      end

      def to_s
        "The server responded with an unexpected status code: #{status_code.name}"
      end
    end

    # Raised when an error occurs with the underlying socket.
    class CommunicationError < RubySMBError; end

    # Raised when Protocol Negotiation fails, possibly due to an
    # unsupported protocol.
    class NegotiationFailure < RubySMBError; end

    # Raised when Authentication fails, possibly due to an
    # unsupported GSS mechanism type.
    class AuthenticationFailure < RubySMBError; end

    # Raised when trying to parse raw binary into a BitField and the data
    # is invalid.
    class InvalidBitField < RubySMBError; end

    # Raised when an encryption operation fails
    class EncryptionError < RubySMBError; end

    # Raised when an signing operation fails
    class SigningError < RubySMBError; end
  end
end
