Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Path: blob/master/lib/rex/proto/iax2/call.rb
Views: 11703
# -*- coding: binary -*-1module Rex2module Proto3module IAX24class Call56attr_accessor :client7attr_accessor :oseq, :iseq8attr_accessor :scall, :dcall9attr_accessor :codec, :state10attr_accessor :ring_start, :ring_finish11attr_accessor :itime12attr_accessor :queue13attr_accessor :audio_hook14attr_accessor :audio_buff15attr_accessor :time_limit16attr_accessor :busy1718attr_accessor :caller_name19attr_accessor :caller_number20attr_accessor :dtmf212223def initialize(client, src_id)24self.client = client25self.scall = src_id26self.dcall = 027self.iseq = 028self.oseq = 029self.state = nil3031self.itime = ::Time.now32self.queue = ::Queue.new3334self.audio_buff = []3536self.busy = false37self.dtmf = ''38end394041def dprint(msg)42self.client.dprint(msg)43end4445def wait_for(*stypes)46begin47::Timeout.timeout( Constants::IAX_DEFAULT_TIMEOUT ) do48while (res = self.queue.pop )49if stypes.include?(res[1])50return res51end52end53end54rescue ::Timeout::Error55return nil56end57end5859# Register with the IAX endpoint60def register61self.client.send_regreq(self)62res = wait_for( Constants::IAX_SUBTYPE_REGAUTH, Constants::IAX_SUBTYPE_REGREJ )63return if not res6465if res[1] == Constants::IAX_SUBTYPE_REGREJ66reason = res[2][Constants::IAX_IE_REGREJ_CAUSE] || "Unknown Reason"67dprint("REGREJ: #{reason}")68# Acknowledge the REGREJ69self.client.send_ack(self)70return71end7273chall = nil7475# Look for IAX_AUTH_MD5 (2) as an available auth method76if res[2][14].unpack("n")[0] & 2 <= 077dprint("REGAUTH: MD5 authentication is not enabled on the server")78return79end8081if res[2][Constants::IAX_IE_CHALLENGE_DATA]82self.dcall = res[0][0]83chall = res[2][Constants::IAX_IE_CHALLENGE_DATA]84end8586if chall.nil?87dprint("REGAUTH: No challenge data received")88return89end9091self.client.send_regreq_chall_response(self, chall)92res = wait_for( Constants::IAX_SUBTYPE_REGACK, Constants::IAX_SUBTYPE_REGREJ )93return if not res9495if res[1] == Constants::IAX_SUBTYPE_REGREJ96reason = res[2][Constants::IAX_IE_REGREJ_CAUSE] || "Unknown Reason"97dprint("REGREJ: #{reason}")98return99end100101if res[2][Constants::IAX_IE_APPARENT_ADDR]102r_fam, r_port, r_addr = res[2][Constants::IAX_IE_APPARENT_ADDR].unpack('nnA4')103r_addr = r_addr.unpack("C*").map{|x| x.to_s }.join(".")104dprint("REGACK: Registered from address #{r_addr}:#{r_port}")105end106107# Acknowledge the REGACK108self.client.send_ack(self)109110self.state = :registered111112true113end114115def dial(number)116self.client.send_new(self, number)117res = wait_for(Constants::IAX_SUBTYPE_AUTHREQ, Constants::IAX_SUBTYPE_ACCEPT)118return if not res119120# Handle authentication if its requested121if res[1] == Constants::IAX_SUBTYPE_AUTHREQ122chall = nil123124# Look for IAX_AUTH_MD5 (2) as an available auth method125if res[2][14].unpack("n")[0] & 2 <= 0126dprint("REGAUTH: MD5 authentication is not enabled on the server")127return128end129130if res[2][Constants::IAX_IE_CHALLENGE_DATA]131self.dcall = res[0][0]132chall = res[2][Constants::IAX_IE_CHALLENGE_DATA]133end134135if chall.nil?136dprint("REGAUTH: No challenge data received")137return138end139140self.client.send_authrep_chall_response(self, chall)141res = wait_for( Constants::IAX_SUBTYPE_ACCEPT)142return if not res143end144145self.codec = res[2][Constants::IAX_IE_DESIRED_CODEC].unpack("N")[0]146self.state = :ringing147self.ring_start = ::Time.now.to_i148self.client.send_ack(self)149true150end151152def hangup153self.client.send_hangup(self)154self.state = :hangup155true156end157158def ring_time159(self.ring_finish || ::Time.now).to_i - self.ring_start.to_i160end161162def timestamp163(( ::Time.now - self.itime) * 1000.0 ).to_i & 0xffffffff164end165166def process_elements(data,off=0)167res = {}168while( off < data.length )169ie_type = data[off ,1].unpack("C")[0]170ie_len = data[off + 1,2].unpack("C")[0]171res[ie_type] = data[off + 2, ie_len]172off += ie_len + 2173end174res175end176177# Handling incoming control packets178# TODO: Enforce sequence order to prevent duplicates from breaking our state179def handle_control(pkt)180src_call, dst_call, tstamp, out_seq, inp_seq, itype = pkt.unpack('nnNCCC')181182# Scrub the high bits out of the call IDs183src_call ^= 0x8000 if (src_call & 0x8000 != 0)184dst_call ^= 0x8000 if (dst_call & 0x8000 != 0)185186phdr = [ src_call, dst_call, tstamp, out_seq, inp_seq, itype ]187188info = nil189stype = pkt[11,1].unpack("C")[0]190info = process_elements(pkt, 12) if [Constants::IAX_TYPE_IAX, Constants::IAX_TYPE_CONTROL].include?(itype)191192if dst_call != self.scall193dprint("Incoming packet to inactive call: #{dst_call} vs #{self.scall}: #{phdr.inspect} #{stype.inspect} #{info.inspect}")194return195end196197# Increment the received sequence number198self.iseq = (self.iseq + 1) & 0xff199200if self.state == :hangup201dprint("Packet received after hangup, replying with invalid")202self.client.send_invalid(self)203return204end205206# Technically these all require an ACK reply207# NEW, HANGUP, REJECT, ACCEPT, PONG, AUTHREP, REGREL, REGACK, REGREJ, TXREL208209case itype210when Constants::IAX_TYPE_DTMF_BEGIN211self.dprint("DTMF BEG: #{pkt[11,1]}")212self.dtmf << pkt[11,1]213214when Constants::IAX_TYPE_DTMF_END215self.dprint("DTMF END: #{pkt[11,1]}")216217when Constants::IAX_TYPE_CONTROL218case stype219when Constants::IAX_CTRL_HANGUP220dprint("HANGUP")221self.client.send_ack(self)222self.state = :hangup223224when Constants::IAX_CTRL_RINGING225dprint("RINGING")226self.client.send_ack(self)227228when Constants::IAX_CTRL_BUSY229dprint("BUSY")230self.busy = true231self.state = :hangup232self.client.send_ack(self)233234when Constants::IAX_CTRL_ANSWER235dprint("ANSWER")236if self.state == :ringing237self.state = :answered238self.ring_finish = ::Time.now.to_i239end240self.client.send_ack(self)241242when Constants::IAX_CTRL_PROGRESS243dprint("PROGRESS")244245when Constants::IAX_CTRL_PROCEED246dprint("PROCEED")247248when 255249dprint("STOP SOUNDS")250end251# Acknowledge all control packets252# self.client.send_ack(self)253254when Constants::IAX_TYPE_IAX255256dprint( ["RECV", phdr, stype, info].inspect )257case stype258when Constants::IAX_SUBTYPE_HANGUP259self.state = :hangup260self.client.send_ack(self)261when Constants::IAX_SUBTYPE_LAGRQ262# Lagrps echo the timestamp263self.client.send_lagrp(self, tstamp)264when Constants::IAX_SUBTYPE_ACK265# Nothing to do here266when Constants::IAX_SUBTYPE_PING267# Pongs echo the timestamp268self.client.send_pong(self, tstamp)269when Constants::IAX_SUBTYPE_PONG270self.client.send_ack(self)271else272dprint( ["RECV-QUEUE", phdr, stype, info].inspect )273self.queue.push( [phdr, stype, info ] )274end275276when Constants::IAX_TYPE_VOICE277v_codec = stype278if self.state == :answered279handle_audio(pkt)280end281self.client.send_ack(self)282283when nil284dprint("Invalid control packet: #{pkt.unpack("H*")[0]}")285end286end287288289# Encoded audio from the client290def handle_audio(pkt)291# Ignore audio received before the call is answered (ring ring)292return if self.state != :answered293294# Extract the data from the packet (full or mini)295data = audio_packet_data(pkt)296297# Decode the data into linear PCM frames298buff = decode_audio_frame(data)299300# Call the caller-provided hook if its exists301if self.audio_hook302self.audio_buff(buff)303# Otherwise append the frame to the buffer304else305self.audio_buff << buff306end307end308309def each_audio_frame(&block)310self.audio_buff.each do |frame|311block.call(frame)312end313end314315def decode_audio_frame(buff)316case self.codec317318# Convert u-law into signed PCM319when Constants::IAX_CODEC_G711_MULAW320Rex::Proto::IAX2::Codecs::MuLaw.decode(buff)321322# Convert a-law into signed PCM323when Constants::IAX_CODEC_G711_ALAW324Rex::Proto::IAX2::Codecs::ALaw.decode(buff)325326# Linear little-endian signed PCM is our native format327when Constants::IAX_CODEC_LINEAR_PCM328buff329330# Unsupported codec, return empty331else332dprint("UNKNOWN CODEC: #{self.codec.inspect}")333''334end335end336337def audio_packet_data(pkt)338(pkt[0,1].unpack("C")[0] & 0x80 == 0) ? pkt[4,pkt.length-4] : pkt[12,pkt.length-12]339end340341end342end343end344end345346347