/*
    Copyright (C) 2005-2007  Michel de Boer <michel@twinklephone.com>

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

#include "phone_user.h"
#include "log.h"
#include "userintf.h"
#include "util.h"
#include "audits/memman.h"
#include "presence/presence_epa.h"

extern t_phone 		*phone;
extern t_event_queue	*evq_timekeeper;
extern t_event_queue	*evq_sender_udp;
extern string		user_host;

void t_phone_user::cleanup_mwi_dialog(void) {
	if (mwi_dialog && mwi_dialog->get_subscription_state() == SS_TERMINATED) {
		string reason_termination = mwi_dialog->get_reason_termination();
		bool may_resubscribe = mwi_dialog->get_may_resubscribe();
		unsigned long dur_resubscribe = mwi_dialog->get_resubscribe_after();
		
		MEMMAN_DELETE(mwi_dialog);
		delete mwi_dialog;
		mwi_dialog = NULL;
		stun_binding_inuse_mwi = false;
		cleanup_stun_data();
		cleanup_nat_keepalive();
		
		if (mwi_auto_resubscribe) {
			if (may_resubscribe) {
				if (dur_resubscribe > 0) {
					start_resubscribe_mwi_timer(dur_resubscribe * 1000);
				} else {
					subscribe_mwi();
				}
			} else if (reason_termination.empty()) {
				start_resubscribe_mwi_timer(DUR_MWI_FAILURE * 1000);
			}
		}
	}
}

void t_phone_user::cleanup_stun_data(void) {
	if (!use_stun) return;
	
	if (!stun_binding_inuse_registration &&
	    !stun_binding_inuse_mwi && stun_binding_inuse_presence == 0)
	{
		stun_public_ip_sip = 0;
		stun_public_port_sip = 0;
	}
}

void t_phone_user::cleanup_nat_keepalive(void) {
	if (register_ipaddr == 0 && register_port == 0 &&
	    !mwi_dialog)
	{
		if (id_nat_keepalive) phone->stop_timer(PTMR_NAT_KEEPALIVE, this);
	}
}

void t_phone_user::cleanup_registration_data(void) {
	register_ipaddr = 0;
	register_port = 0;
	stun_binding_inuse_registration = false;
	cleanup_stun_data();
	cleanup_nat_keepalive();
}

t_phone_user::t_phone_user(const t_user &profile)
{
	user_config = profile.copy();
	
	service = new t_service(user_config);
	MEMMAN_NEW(service);
	
	buddy_list = new t_buddy_list(this);
	MEMMAN_NEW(buddy_list);
	
	presence_epa = new t_presence_epa(this);
	MEMMAN_NEW(presence_epa);
	
	string err_msg;
	if (buddy_list->load(err_msg)) {
		log_file->write_header("t_phone_user::t_phone_user");
		log_file->write_raw(user_config->get_profile_name());
		log_file->write_raw(": buddy list loaded.\n");
		log_file->write_footer();
	} else {
		log_file->write_header("t_phone_user::t_phone_user", LOG_NORMAL, LOG_CRITICAL);
		log_file->write_raw(user_config->get_profile_name());
		log_file->write_raw(": falied to load buddy list.\n");
		log_file->write_raw(err_msg);
		log_file->write_endl();
		log_file->write_footer();
	}
	
	active = true;

	r_options = NULL;
	r_register = NULL;
	r_deregister = NULL;
	r_query_register = NULL;
	r_message = NULL;
	r_stun = NULL;
	
	// Initialize registration data
	// Call-ID cannot be set here as user_host is not determined yet.
	register_seqnr = NEW_SEQNR;
	is_registered = false;
	register_ipaddr = 0L;
	register_port = 0;
	last_reg_failed = false;
	
	// Initialize STUN data
	stun_public_ip_sip = 0L;
	stun_public_port_sip = 0;
	stun_binding_inuse_registration = false;
	stun_binding_inuse_mwi = false;
	stun_binding_inuse_presence = 0;
	register_after_stun = false;
	mwi_subscribe_after_stun = false;
	presence_subscribe_after_stun = false;
	use_stun = false;
	use_nat_keepalive = false;
	
	// Timers
	id_registration = 0;
	id_nat_keepalive = 0;
	id_resubscribe_mwi = 0;
	
	// MWI
	mwi_dialog = NULL;
	mwi_auto_resubscribe = false;
}

t_phone_user::~t_phone_user() {
	// Stop timers
	if (id_registration) phone->stop_timer(PTMR_REGISTRATION, this);
	if (id_nat_keepalive) phone->stop_timer(PTMR_NAT_KEEPALIVE, this);

	// Delete pointers
	if (r_options) {
		MEMMAN_DELETE(r_options);
		delete r_options;
	}
	if (r_register) {
		MEMMAN_DELETE(r_register);
		delete r_register;
	}
	if (r_deregister) {
		MEMMAN_DELETE(r_deregister);
		delete r_deregister;
	}
	if (r_query_register) {
		MEMMAN_DELETE(r_query_register);
		delete r_query_register;
	}
	if (r_message) {
		MEMMAN_DELETE(r_message);
		delete r_message;
	}
	if (r_stun) {
		MEMMAN_DELETE(r_stun);
		delete r_stun;
	}
	
	for (list<t_request *>::iterator it = pending_messages.begin();
	     it != pending_messages.end(); ++it)
	{
		MEMMAN_DELETE(*it);
		delete *it;
	}
	
	if (mwi_dialog) {
		MEMMAN_DELETE(mwi_dialog);
		delete mwi_dialog;
	}
	
	MEMMAN_DELETE(service);
	delete service;
	MEMMAN_DELETE(presence_epa);
	delete presence_epa;
	MEMMAN_DELETE(buddy_list);
	delete buddy_list;
	buddy_list = NULL;
	MEMMAN_DELETE(user_config);
	delete user_config;
}

t_user *t_phone_user::get_user_profile(void) {
	return user_config;
}

t_buddy_list *t_phone_user::get_buddy_list(void) {
	return buddy_list;
}

t_presence_epa *t_phone_user::get_presence_epa(void) {
	return presence_epa;
}

void t_phone_user::registration(t_register_type register_type, bool re_register,
		unsigned long expires)
{
	// If STUN is enabled, then do a STUN query before registering to
	// determine the public IP address.
	if (register_type == REG_REGISTER && use_stun) {
		if (stun_public_ip_sip == 0) {
			send_stun_request();
			register_after_stun = true;
			registration_time = expires;
			return;
		}
		
		stun_binding_inuse_registration = true;
	}

	// Stop registration timer for non-query request
	if (register_type != REG_QUERY) {
		phone->stop_timer(PTMR_REGISTRATION, this);
	}

	// Create call-id if no call-id is created yet
	if (register_call_id == "") {
		register_call_id = NEW_CALL_ID(user_config);
	}

	// RFC 3261 10.2
	// Construct REGISTER request

	t_request *req = create_request(REGISTER, 
			t_url(string(USER_SCHEME) + ":" + user_config->get_domain()));

	// To
	req->hdr_to.set_uri(user_config->create_user_uri(false));
	req->hdr_to.set_display(user_config->get_display(false));

	//Call-ID
	req->hdr_call_id.set_call_id(register_call_id);

	// CSeq
	req->hdr_cseq.set_method(REGISTER);
	req->hdr_cseq.set_seqnr(++register_seqnr);

	// Contact
        t_contact_param contact;

        switch (register_type) {
        case REG_REGISTER:
                contact.uri.set_url(user_config->create_user_contact(false));
                if (expires > 0) {
			if (user_config->get_registration_time_in_contact()) {
				contact.set_expires(expires);
			} else {
				req->hdr_expires.set_time(expires);
			}
		}
                req->hdr_contact.add_contact(contact);
                break;
        case REG_DEREGISTER:
                contact.uri.set_url(user_config->create_user_contact(false));
 		if (user_config->get_registration_time_in_contact()) {
			contact.set_expires(0);
		} else {
			req->hdr_expires.set_time(0);
		}
                req->hdr_contact.add_contact(contact);
                break;
        case REG_DEREGISTER_ALL:
                req->hdr_contact.set_any();
                req->hdr_expires.set_time(0);
                break;
        default:
                break;
        }

	// Allow
	SET_HDR_ALLOW(req->hdr_allow, user_config);

	// Store request in the proper place
	t_tuid tuid;

        switch(register_type) {
        case REG_REGISTER:
		// Delete a possible pending registration request
		if (r_register) {
			MEMMAN_DELETE(r_register);
			delete r_register;
		}
                r_register = new t_client_request(user_config, req, 0);
		MEMMAN_NEW(r_register);
                tuid = r_register->get_tuid();

                // Store expiration time for re-registration.
                registration_time = expires;
                break;
        case REG_QUERY:
		// Delete a possible pending query registration request
		if (r_query_register) {
			MEMMAN_DELETE(r_query_register);
			delete r_query_register;
		}
                r_query_register = new t_client_request(user_config, req, 0);
		MEMMAN_NEW(r_query_register);
                tuid = r_query_register->get_tuid();
                break;
        case REG_DEREGISTER:
        case REG_DEREGISTER_ALL:
		// Delete a possible pending de-registration request
		if (r_deregister) {
			MEMMAN_DELETE(r_deregister);
			delete r_deregister;
		}
                r_deregister = new t_client_request(user_config, req, 0);
		MEMMAN_NEW(r_deregister);
                tuid = r_deregister->get_tuid();
                break;
        default:
                assert(false);
        }

        // Send REGISTER
        authorizor.set_re_register(re_register);
	ui->cb_register_inprog(user_config, register_type);
        phone->send_request(user_config, req, tuid);
	MEMMAN_DELETE(req);
        delete req;
}

void t_phone_user::options(const t_url &to_uri, const string &to_display) {
	// RFC 3261 11.1
	// Construct OPTIONS request

	t_request *req = create_request(OPTIONS, to_uri);

	// To
	req->hdr_to.set_uri(to_uri);
	req->hdr_to.set_display(to_display);

	// Call-ID
	req->hdr_call_id.set_call_id(NEW_CALL_ID(user_config));

	// CSeq
	req->hdr_cseq.set_method(OPTIONS);
	req->hdr_cseq.set_seqnr(NEW_SEQNR);

	// Accept
	req->hdr_accept.add_media(t_media("application","sdp"));

	// Store and send request
	// Delete a possible pending options request
	if (r_options) {
		MEMMAN_DELETE(r_options);
		delete r_options;
	}
	r_options = new t_client_request(user_config, req, 0);
	MEMMAN_NEW(r_options);
	phone->send_request(user_config, req, r_options->get_tuid());
	MEMMAN_DELETE(req);
	delete req;
}

void t_phone_user::handle_response_out_of_dialog(t_response *r, t_tuid tuid, t_tid tid) {
	t_client_request **current_cr;
	t_request *req;
	bool is_register = false;
	t_buddy *buddy;

	if (r_register && r_register->get_tuid() == tuid) {
		current_cr = &r_register;
		is_register = true;
	} else if (r_deregister && r_deregister->get_tuid() == tuid) {
		current_cr = &r_deregister;
		is_register = true;
	} else if (r_query_register && r_query_register->get_tuid() == tuid) {
		current_cr = &r_query_register;
		is_register = true;
	} else if (r_options && r_options->get_tuid() == tuid) {
		current_cr = &r_options;
	} else if (r_message && r_message->get_tuid() == tuid) {
		current_cr = &r_message;
	} else if (mwi_dialog && mwi_dialog->match_response(r, tuid)) {
		mwi_dialog->recvd_response(r, tuid, tid);
		cleanup_mwi_dialog();
		return;
	} else if (presence_epa && presence_epa->match_response(r, tuid)) {
		presence_epa->recv_response(r, tuid, tid);
		return;
	} else if (buddy_list->match_response(r, tuid, &buddy)) {
		buddy->recvd_response(r, tuid, tid);
		if (buddy->must_delete_now()) buddy_list->del_buddy(*buddy);
		return;
	} else {
		// Response does not match any pending request.
		log_file->write_report("Response does not match any pending request.",
			"t_phone_user::handle_response_out_of_dialog",
			LOG_NORMAL, LOG_WARNING);
		return;
	}

	req = (*current_cr)->get_request();

	// Authentication
	if (r->must_authenticate()) {
		if (authorize(req, r)) {
			resend_request(req, is_register, *current_cr);
			return;
		}

		// Authentication failed
		// Handle the 401/407 as a normal failure response
	}
	
	// RFC 3263 4.3
	// Failover
	if (r->code == R_503_SERVICE_UNAVAILABLE) {
		if (req->next_destination()) {
			log_file->write_report("Failover to next destination.",
				"t_phone_user::handle_response_out_of_dialog");
			resend_request(req, is_register, *current_cr);
			return;
		}			
	}

	// Redirect failed request if there is another destination
	if (r->get_class() > R_2XX && user_config->get_allow_redirection()) {
		// If the response is a 3XX response then add redirection
		// contacts
		if (r->get_class() == R_3XX  &&
		    r->hdr_contact.is_populated())
		{
			(*current_cr)->redirector.add_contacts(
					r->hdr_contact.contact_list);
		}

		// Get next destination
		t_contact_param contact;
		if ((*current_cr)->redirector.get_next_contact(contact)) {
			// Ask user for permission to redirect if indicated
			// by user config
			bool permission = true;
			if (user_config->get_ask_user_to_redirect()) {
				permission = ui->cb_ask_user_to_redirect_request(
							user_config,
							contact.uri, contact.display,
							r->hdr_cseq.method);
			}

			if (permission) {
				req->uri = contact.uri;
				req->calc_destinations(*user_config);
				ui->cb_redirecting_request(user_config, contact);
				resend_request(req, is_register, *current_cr);
				return;
			}
		}
	}

	// REGISTER (register)
	if (r_register && r_register->get_tuid() == tuid) {
		bool re_register;
		handle_response_register(r, re_register);
		MEMMAN_DELETE(r_register);
		delete r_register;
		r_register = NULL;
		if (re_register) registration(REG_REGISTER, authorizor.get_re_register(), 
				registration_time);
		return;
	}

	// REGISTER (de-register)
	if (r_deregister && r_deregister->get_tuid() == tuid) {
		handle_response_deregister(r);
		MEMMAN_DELETE(r_deregister);
		delete r_deregister;
		r_deregister = NULL;
		return;
	}

	// REGISTER (query)
	if (r_query_register && r_query_register->get_tuid() == tuid) {
		handle_response_query_register(r);
		MEMMAN_DELETE(r_query_register);
		delete r_query_register;
		r_query_register = NULL;
		return;
	}


	// OPTIONS
	if (r_options && r_options->get_tuid() == tuid) {
		handle_response_options(r);
		MEMMAN_DELETE(r_options);
		delete r_options;
		r_options = NULL;
		return;
	}
	
	// MESSAGE
	if (r_message && r_message->get_tuid() == tuid) {
		handle_response_message(r);
		MEMMAN_DELETE(r_message);
		delete r_message;
		r_message = NULL;
		
		// Send next pending MESSAGE
		if (!pending_messages.empty()) {
			t_request *req = pending_messages.front();
			pending_messages.pop_front();
			r_message = new t_client_request(user_config, req, 0);
			MEMMAN_NEW(r_message);
			phone->send_request(user_config, req, r_message->get_tuid());
			MEMMAN_DELETE(req);
			delete req;			
		}
		
		return;
	}

	// Response does not match any pending request. Do nothing.
}

void t_phone_user::resend_request(t_request *req, bool is_register, t_client_request *cr) {
	// A new sequence number must be assigned
	if (is_register) {
		req->hdr_cseq.set_seqnr(++register_seqnr);
	} else {
		req->hdr_cseq.seqnr++;
	}

	// Create a new via-header. Otherwise the
	// request will be seen as a retransmission
	req->hdr_via.via_list.clear();
	t_via via(USER_HOST(user_config), PUBLIC_SIP_UDP_PORT(user_config));
	req->hdr_via.add_via(via);

	cr->renew(0);
	phone->send_request(user_config, req, cr->get_tuid());
}

void t_phone_user::handle_response_out_of_dialog(StunMessage *r, t_tuid tuid) {
	if (!r_stun || r_stun->get_tuid() != tuid) {
		// Response does not match pending STUN request
		return;
	}
	
	if (r->msgHdr.msgType == BindResponseMsg && r->hasMappedAddress) {
		// The STUN response contains the public IP.
		stun_public_ip_sip = r->mappedAddress.ipv4.addr;
		stun_public_port_sip = r->mappedAddress.ipv4.port;
                MEMMAN_DELETE(r_stun);
                delete r_stun;
                r_stun = NULL;
                
                if (register_after_stun) {
                	register_after_stun = false;
                	registration(REG_REGISTER, false, registration_time);
                }
                
                if (mwi_subscribe_after_stun) {
                	mwi_subscribe_after_stun = false;
                	subscribe_mwi();
                }
                
                if (presence_subscribe_after_stun) {
                	presence_subscribe_after_stun = false;
                	buddy_list->stun_completed();
                }
                
                return;
	}
	
	if (r->msgHdr.msgType == BindErrorResponseMsg && r->hasErrorCode) {
		// STUN request failed.
                ui->cb_stun_failed(user_config, r->errorCode.errorClass * 100 +
                	r->errorCode.number, r->errorCode.reason);
	} else {	
		// No satisfying STUN response was received.
 	       ui->cb_stun_failed(user_config);
	}
	
        MEMMAN_DELETE(r_stun);
        delete r_stun;
        r_stun = NULL;
	
        if (register_after_stun) {
		// Retry registration later.
		bool first_failure = !last_reg_failed;
		last_reg_failed = true;
		is_registered = false;
		ui->cb_register_stun_failed(user_config, first_failure);
		phone->start_set_timer(PTMR_REGISTRATION, DUR_REG_FAILURE * 1000, this);
		register_after_stun = false;
        }
        
        if (mwi_subscribe_after_stun) {
        	// Retry MWI subscription later
        	start_resubscribe_mwi_timer(DUR_MWI_FAILURE * 1000);
        	mwi_subscribe_after_stun = false;
        }
        
        if (presence_subscribe_after_stun) {
        	buddy_list->stun_failed();
        	presence_subscribe_after_stun = false;
        }
}

void t_phone_user::handle_response_register(t_response *r, bool &re_register) {
	t_contact_param *c;
	unsigned long expires;
	unsigned long e;
	bool first_failure, first_success;

	re_register = false;
	
	// Store the destination IP address/port of the REGISTER message.
	// To this destination the NAT keep alive packets will be sent.
	t_request *req = r_register->get_request();
	req->get_destination(register_ipaddr, register_port, *user_config);

        switch(r->get_class()) {
        case R_2XX:
                last_reg_failed = false;

                // Stop registration timer if one was running
                phone->stop_timer(PTMR_REGISTRATION, this);

                c = r->hdr_contact.find_contact(user_config->create_user_contact(false));
                if (!c) {               	
	               	if (!user_config->get_allow_missing_contact_reg()) {
				is_registered = false;

	              		log_file->write_report(
        	       			"Contact header is missing.",
               				"t_phone_user::handle_response_register",
               				LOG_NORMAL, LOG_WARNING);
				
				ui->cb_invalid_reg_resp(user_config,
					r, "Contact header missing.");
				cleanup_registration_data();
				return;
                        }
                }

                if (c && c->is_expires_present() && c->get_expires() != 0) {
                        expires = c->get_expires();
                }
                else if (r->hdr_expires.is_populated() &&
                         r->hdr_expires.time != 0)
                {
                        expires = r->hdr_expires.time;
                }
                else {	
               		if (!user_config->get_allow_missing_contact_reg()) {
				is_registered = false;
				
               			log_file->write_report(
               				"Expires parameter/header mising.",
               				"t_phone_user::handle_response_register",
               				LOG_NORMAL, LOG_WARNING);
				
				ui->cb_invalid_reg_resp(user_config,
					r, "Expires parameter/header mising.");
				cleanup_registration_data();
				return;
                        }
                        
                        expires = user_config->get_registration_time();
                        
                        // Assume a default expiration of 3600 sec if no expiry
                        // time was returned.
                        if (expires == 0) expires = 3600;
                }

                // Start new registration timer
                // The maximum value of the timer can be 2^32-1 s
                // The maximum timer that we can handle however is 2^31-1 ms
                e = (expires > 2147483 ? 2147483 : expires);
                phone->start_set_timer(PTMR_REGISTRATION, e * 1000, this);
		first_success = !is_registered;
                is_registered = true;
		ui->cb_register_success(user_config, r, expires, first_success);
		
		// Start sending NAT keepalive packets when STUN is used
		// (or in case of symmetric firewall)
		if (use_nat_keepalive && id_nat_keepalive == 0) {
			// Just start the NAT keepalive timer. The REGISTER
			// message itself created the NAT binding. So there is
			// no need to send a NAT keep alive packet now.
			phone->start_timer(PTMR_NAT_KEEPALIVE, this);
		}
		
		// Registration succeeded. If sollicited MWI is provisioned
		// and no MWI subscription is established yet, then subscribe
		// to MWI.
		if (user_config->get_mwi_sollicited() && !mwi_auto_resubscribe) {
			subscribe_mwi();
		}
		
		// Publish presence state if not yet published.
		if (user_config->get_pres_publish_startup() && 
		    presence_epa->get_epa_state() == t_epa::EPA_UNPUBLISHED)
		{
			publish_presence(t_presence_state::ST_BASIC_OPEN);
		}
		
		// Subscribe to buddy list presence if not done so.
		if (!buddy_list->get_is_subscribed()) {
			subscribe_presence();
		}

                break;
        case R_4XX:
                is_registered = false;

                // RFC 3261 10.3
                if (r->code == R_423_INTERVAL_TOO_BRIEF) {
                        if (!r->hdr_min_expires.is_populated()) {
                                // Violation of RFC 3261 10.3 item 7
				log_file->write_report("Min-Expires header missing from 423 response.",
					"t_phone_user::handle_response_register",
					LOG_NORMAL, LOG_WARNING);
                                ui->cb_invalid_reg_resp(user_config, r,
                                        "Min-Expires header missing.");
				cleanup_registration_data();
                                return;
                        }

                        if (r->hdr_min_expires.time <= registration_time) {
                                // Wrong Min-Expires time
                                string s = "Min-Expires (";
                                s += ulong2str(r->hdr_min_expires.time);
                                s += ") is smaller than the requested ";
                                s += "time (";
                                s += ulong2str(registration_time);
                                s += ")";
                                log_file->write_report(s, "t_phone_user::handle_response_register",
                                	LOG_NORMAL, LOG_WARNING);
                                ui->cb_invalid_reg_resp(user_config, r, s);
				cleanup_registration_data();
                                return;
                        }

                        // Automatic re-register with Min-Expires time
                        registration_time = r->hdr_min_expires.time;
                        re_register = true;
                        // No need to cleanup STUN data as a new REGISTER will be
                        // sent immediately.
                        return;
                }

		// If authorization failed, then do not start the continuous
		// re-attempts. When authorization fails the user is asked
		// for credentials (in GUI). So the user cancelled these
		// questions and should not be bothered with the same question
		// again every 30 seconds. The user does not have the
		// credentials.
		if (r->code == R_401_UNAUTHORIZED ||
		    r->code == R_407_PROXY_AUTH_REQUIRED)
		{
			last_reg_failed = true;
			ui->cb_register_failed(user_config, r, true);			
	
			cleanup_registration_data();
			return;
		}

                // fall thru
        default:
		first_failure = !last_reg_failed;
                last_reg_failed = true;
                is_registered = false;
                authorizor.remove_from_cache(""); // Clear credentials cache
		ui->cb_register_failed(user_config, r, first_failure);
                phone->start_set_timer(PTMR_REGISTRATION, DUR_REG_FAILURE * 1000, this);

		cleanup_registration_data();
        }
}

void t_phone_user::handle_response_deregister(t_response *r) {
	is_registered = false;
	last_reg_failed = false;

	if (r->is_success()) {
		ui->cb_deregister_success(user_config, r);
	} else {
		ui->cb_deregister_failed(user_config, r);
	}
	
	cleanup_registration_data();
}

void t_phone_user::handle_response_query_register(t_response *r) {
	if (r->is_success()) {
		ui->cb_fetch_reg_result(user_config, r);
	} else {
		ui->cb_fetch_reg_failed(user_config, r);
	}
}

void t_phone_user::handle_response_options(t_response *r) {
	ui->cb_options_response(r);
}

void t_phone_user::handle_response_message(t_response *r) {
	ui->cb_message_response(user_config, r);
}

void t_phone_user::subscribe_mwi(void) {
	mwi_auto_resubscribe = true;
	
	if (mwi_dialog) {
		// This situation may occur, when an unsubscription is still
		// in progress. The subscibe will be retried after the unsubscription
		// is finished. Note that mwi_auto_resubscribe has been set to true
		// to trigger an automatic subscription.
		log_file->write_header("t_phone_user::subscribe_mwi", LOG_NORMAL, LOG_DEBUG);
		log_file->write_raw("MWI dialog already exists.\n");
		log_file->write_raw("Subscription state: ");
		log_file->write_raw(t_subscription_state2str(mwi_dialog->get_subscription_state()));
		log_file->write_endl();
		log_file->write_footer();

		return;
	}
	
	// If STUN is enabled, then do a STUN query before registering to
	// determine the public IP address.
	if (use_stun) {
		if (stun_public_ip_sip == 0)
		{
			send_stun_request();
			mwi_subscribe_after_stun = true;
			return;
		}
		stun_binding_inuse_mwi = true;
	}
	
	mwi_dialog = new t_mwi_dialog(this);
	MEMMAN_NEW(mwi_dialog);
	
	// RFC 3842 4.1
	// The example flow shows:
	// Request-URI = mail_user@mailbox_server
	// To = user@domain
	mwi_dialog->subscribe(DUR_MWI(user_config), user_config->get_mwi_uri(),
		user_config->create_user_uri(false), user_config->get_display(false));
		
	// Start sending NAT keepalive packets when STUN is used
	// (or in case of symmetric firewall)
	if (use_nat_keepalive && id_nat_keepalive == 0) {
		// Just start the NAT keepalive timer. The SUBSCRIBE
		// message will create the NAT binding. So there is
		// no need to send a NAT keep alive packet now.
		phone->start_timer(PTMR_NAT_KEEPALIVE, this);
	}
		
	cleanup_mwi_dialog();
}

void t_phone_user::unsubscribe_mwi(void) {
	mwi_auto_resubscribe = false;
	stop_resubscribe_mwi_timer();
	mwi.set_status(t_mwi::MWI_UNKNOWN);
	
	if (mwi_dialog) {
		mwi_dialog->unsubscribe();
		cleanup_mwi_dialog();
	}
	
	ui->cb_update_mwi();
}

bool t_phone_user::is_mwi_subscribed(void) const {
	if (mwi_dialog) {
		return mwi_dialog->get_subscription_state() == SS_ESTABLISHED;
	}
	
	return false;
}

bool t_phone_user::is_mwi_terminated(void) const {
	return mwi_dialog == NULL;
}

void t_phone_user::handle_mwi_unsollicited(t_request *r, t_tid tid) {
	if (user_config->get_mwi_sollicited()) {
		// Unsollicited MWI is not supported
		t_response *resp = r->create_response(R_403_FORBIDDEN);
		phone->send_response(resp, 0, tid);
		MEMMAN_DELETE(resp);
		delete resp;
		return;
	}
	
	if (r->body && r->body->get_type() == BODY_SIMPLE_MSG_SUM) {
		t_simple_msg_sum_body *body = dynamic_cast<t_simple_msg_sum_body *>(r->body);
		mwi.set_msg_waiting(body->get_msg_waiting());
		
		t_msg_summary summary;
		if (body->get_msg_summary(MSG_CONTEXT_VOICE, summary)) {
			mwi.set_voice_msg_summary(summary);
		}
		
		mwi.set_status(t_mwi::MWI_KNOWN);
	}
	
	t_response *resp = r->create_response(R_200_OK);
	phone->send_response(resp, 0, tid);
	MEMMAN_DELETE(resp);
	delete resp;
	
	ui->cb_update_mwi();
}

void t_phone_user::subscribe_presence(void) {
	assert(buddy_list);
	buddy_list->subscribe_presence();
}

void t_phone_user::unsubscribe_presence(void) {
	assert(buddy_list);
	buddy_list->unsubscribe_presence();
}

void t_phone_user::publish_presence(t_presence_state::t_basic_state basic_state) {
	assert(presence_epa);
	presence_epa->publish_presence(basic_state);
}

void t_phone_user::unpublish_presence(void) {
	assert(presence_epa);
	presence_epa->unpublish();
}

bool t_phone_user::is_presence_terminated(void) const {
	assert(buddy_list);
	return buddy_list->is_presence_terminated();
}

void t_phone_user::send_message(const t_url &to_uri, const string &to_display, 
		const string &text)
{
	t_request *req = create_request(MESSAGE, to_uri);
	
	// To
	req->hdr_to.set_uri(to_uri);
	req->hdr_to.set_display(to_display);

	// Call-ID
	req->hdr_call_id.set_call_id(NEW_CALL_ID(user_config));

	// CSeq
	req->hdr_cseq.set_method(MESSAGE);
	req->hdr_cseq.set_seqnr(NEW_SEQNR);
	
	// Body
	req->set_body_plain_text(text, MSG_TEXT_CHARSET);
	
	// Store and send request
	// Delete a possible pending options request
	if (r_message) {
		// RFC 3428 8
		// Send only 1 message at a time.
		// Store the message. It will be sent if the previous
		// message transaction is finished.
		pending_messages.push_back(req);
	} else {
		r_message = new t_client_request(user_config, req, 0);
		MEMMAN_NEW(r_message);
		phone->send_request(user_config, req, r_message->get_tuid());
		MEMMAN_DELETE(req);
		delete req;
	}
}

void t_phone_user::recvd_message(t_request *r, t_tid tid) {
	t_response *resp;
	
	if (!r->body ||
	    (r->body->get_type() != BODY_PLAIN_TEXT &&
	     r->body->get_type() != BODY_HTML_TEXT))
	{
		resp = r->create_response(R_415_UNSUPPORTED_MEDIA_TYPE);
		// RFC 3261 21.4.13
		SET_MESSAGE_HDR_ACCEPT(resp->hdr_accept);
		phone->send_response(resp, 0, tid);
		MEMMAN_DELETE(resp);
		delete resp;
		
		return;
	}
	
	bool accepted = ui->cb_message_request(user_config, r);
	if (accepted) {
		resp = r->create_response(R_200_OK);
	} else {
		resp = r->create_response(R_486_BUSY_HERE);
	}
	
	phone->send_response(resp, 0, tid);
	MEMMAN_DELETE(resp);
	delete resp;
}

void t_phone_user::recvd_notify(t_request *r, t_tid tid) {
	bool partial_match = false;
	
	if (r->hdr_to.tag.empty()) {
		// Unsollicited NOTIFY
		handle_mwi_unsollicited(r, tid);
		return;
	}
	
	if (mwi_dialog && mwi_dialog->match_request(r, partial_match)) {
		// Sollicited NOTIFY
		mwi_dialog->recvd_request(r, 0, tid);
		cleanup_mwi_dialog();
		return;
	}
	
	// A NOTIFY may be received before a 2XX on SUBSCRIBE.
	// In this case the NOTIFY will establish the dialog.
	if (partial_match && mwi_dialog->get_remote_tag().empty()) {
		mwi_dialog->recvd_request(r, 0, tid);
		cleanup_mwi_dialog();
		return;
	}
	
	t_buddy *buddy;
	if (buddy_list->match_request(r, &buddy)) {
		buddy->recvd_request(r, 0, tid);
		if (buddy->must_delete_now()) buddy_list->del_buddy(*buddy);
		return;
	}
	
	// RFC 3265 4.4.9
	// A SUBSCRIBE request may have forked. So multiple NOTIFY's
	// can be received. Twinkle simply rejects additional NOTIFY's with
	// a 481. This should terminate the forked dialog, such that only
	// one dialog will remain.
	t_response *resp = r->create_response(R_481_TRANSACTION_NOT_EXIST);
	phone->send_response(resp, 0, tid);
	MEMMAN_DELETE(resp);
	delete resp;
}

void t_phone_user::send_stun_request(void) {
	if (r_stun) {
		log_file->write_report("STUN request already in progress.",
			"t_phone_user::send_stun_request", LOG_NORMAL, LOG_DEBUG);
		return;
	}

	StunMessage req;
	StunAtrString username;
	username.sizeValue = 0;
	stunBuildReqSimple(&req, username, false, false);
	r_stun = new t_client_request(user_config, &req, 0);
	MEMMAN_NEW(r_stun);
	phone->send_request(user_config, &req, r_stun->get_tuid());
	return;
}

// NOTE: The term "NAT keep alive" does not cover all uses. The keep alives will
//       also be sent when there is a symmetric firewall without NAT.
void t_phone_user::send_nat_keepalive(void) {
	// Send keep-alive to registrar/proxy
	if (register_ipaddr != 0 && register_port != 0) {
		evq_sender_udp->push_nat_keepalive(register_ipaddr, register_port);
	}
	
	// Send keep-alive to MWI mailbox if different from registrar/proxy
	if (mwi_dialog) {
		unsigned long mwi_ipaddr = mwi_dialog->get_remote_ipaddr();
		unsigned short mwi_port = mwi_dialog->get_remote_port();
		if (mwi_ipaddr != 0 && mwi_port != 0 &&
		    mwi_ipaddr != register_ipaddr && mwi_port != register_port)
		{
			evq_sender_udp->push_nat_keepalive(mwi_ipaddr, mwi_port);
		}
	}
}

void t_phone_user::timeout(t_phone_timer timer) {
	switch (timer) {
	case PTMR_REGISTRATION:
		id_registration = 0;
		
		// Registration expired. Re-register.
		if (is_registered || last_reg_failed) {
			// Re-register if no register is pending
			if (!r_register) {
				registration(REG_REGISTER, true, registration_time);
			}
		}
		break;
	case PTMR_NAT_KEEPALIVE:
		id_nat_keepalive = 0;
		
		// Send a new NAT keepalive packet
		if (use_nat_keepalive) {
			send_nat_keepalive();
			phone->start_timer(PTMR_NAT_KEEPALIVE, this);
		}
		break;
	default:
		assert(false);
	}
}

void t_phone_user::timeout_sub(t_subscribe_timer timer, t_object_id id_timer) 
{
	t_buddy *buddy;
	
	switch (timer) {
	case STMR_SUBSCRIPTION:
		if (mwi_dialog && mwi_dialog->match_timer(timer, id_timer)) {
			mwi_dialog->timeout(timer);
		} else if (buddy_list->match_timer(timer, id_timer, &buddy)) {
			buddy->timeout(timer, id_timer);
			if (buddy->must_delete_now()) buddy_list->del_buddy(*buddy);
		} else if (id_timer == id_resubscribe_mwi) {
			// Try to subscribe to MWI
			id_resubscribe_mwi = 0;
			subscribe_mwi();
		}
		break;
	default:
		assert(false);
	}
}

void t_phone_user::timeout_publish(t_publish_timer timer, t_object_id id_timer) {
	switch (timer) {
	case PUBLISH_TMR_PUBLICATION:
		if (presence_epa->match_timer(timer, id_timer)) {
			presence_epa->timeout(timer);
		}
		break;
	default:
		assert(false);
	}
}

bool t_phone_user::match_subscribe_timer(t_subscribe_timer timer, t_object_id id_timer) const 
{
	t_buddy *buddy;
	
	if (mwi_dialog && mwi_dialog->match_timer(timer, id_timer)) {
		return true;
	}
	
	t_phone_user *self = const_cast<t_phone_user *>(this);
	if (self->buddy_list->match_timer(timer, id_timer, &buddy)) {
		return true;
	}
	
	return id_timer == id_resubscribe_mwi;
}

bool t_phone_user::match_publish_timer(t_publish_timer timer, t_object_id id_timer) const 
{
	assert(presence_epa);
	return presence_epa->match_timer(timer, id_timer);
}

void t_phone_user::start_resubscribe_mwi_timer(unsigned long duration) {
	t_tmr_subscribe	*t;
	t = new t_tmr_subscribe(duration, STMR_SUBSCRIPTION, 0, 0, SIP_EVENT_MSG_SUMMARY, "");
	MEMMAN_NEW(t);
	id_resubscribe_mwi = t->get_object_id();
	
	evq_timekeeper->push_start_timer(t);
	MEMMAN_DELETE(t);
	delete t;
}

void t_phone_user::stop_resubscribe_mwi_timer(void) {
	if (id_resubscribe_mwi != 0) {
		evq_timekeeper->push_stop_timer(id_resubscribe_mwi);
		id_resubscribe_mwi = 0;
	}
}

t_request *t_phone_user::create_request(t_method m, const t_url &request_uri) const {
	t_request *req = new t_request(m);
	MEMMAN_NEW(req);

	// Via
	t_via via(USER_HOST(user_config), PUBLIC_SIP_UDP_PORT(user_config));
	req->hdr_via.add_via(via);

	// From
	req->hdr_from.set_uri(user_config->create_user_uri(false));
	req->hdr_from.set_display(user_config->get_display(false));
	req->hdr_from.set_tag(NEW_TAG);

	// Max-Forwards header (mandatory)
	req->hdr_max_forwards.set_max_forwards(MAX_FORWARDS);

	// User-Agent
	SET_HDR_USER_AGENT(req->hdr_user_agent);
	
	// Set request URI and calculate destinations. By calculating
	// destinations now, the request can be resend to a next destination
	// if failover is needed.
	req->uri = request_uri;
	req->calc_destinations(*user_config);

	return req;
}

t_response *t_phone_user::create_options_response(t_request *r,
		bool in_dialog) const
{
	t_response *resp;

	// RFC 3261 11.2
	switch(phone->get_state()) {
	case PS_IDLE:
		if (!in_dialog && service->is_dnd_active()) {
			resp = r->create_response(R_480_TEMP_NOT_AVAILABLE);
		} else {
			resp = r->create_response(R_200_OK);
		}
		break;
	case PS_BUSY:
		if (in_dialog) {
			resp = r->create_response(R_200_OK);
		} else {
			resp = r->create_response(R_486_BUSY_HERE);
		}
		break;
	default:
		assert(false);
	}

	SET_HDR_ALLOW(resp->hdr_allow, user_config);
	SET_HDR_ACCEPT(resp->hdr_accept);
	SET_HDR_ACCEPT_ENCODING(resp->hdr_accept_encoding);
	SET_HDR_ACCEPT_LANGUAGE(resp->hdr_accept_language);
	SET_HDR_SUPPORTED(resp->hdr_supported, user_config);

	if (user_config->get_ext_100rel() != EXT_DISABLED) {
		resp->hdr_supported.add_feature(EXT_100REL);
	}

	// TODO: include SDP body if requested (optional)

	return resp;
}

bool t_phone_user::get_is_registered(void) const {
	return is_registered;
}

bool t_phone_user::get_last_reg_failed(void) const {
	return last_reg_failed;
}

string t_phone_user::get_ip_sip(void) const {
	if (stun_public_ip_sip) return h_ip2str(stun_public_ip_sip);
	if (user_config->get_use_nat_public_ip()) return user_config->get_nat_public_ip();
	return LOCAL_IP;
}

unsigned short t_phone_user::get_public_port_sip(void) const {
	if (stun_public_port_sip) return stun_public_port_sip;
	return sys_config->get_sip_udp_port();
}

bool t_phone_user::match(t_response *r, t_tuid tuid) const {
	t_buddy *dummy;

	if (r_register && r_register->get_tuid() == tuid) {
		return true;
	} else if (r_deregister && r_deregister->get_tuid() == tuid) {
		return true;
	} else if (r_query_register && r_query_register->get_tuid() == tuid) {
		return true;
	} else if (r_options && r_options->get_tuid() == tuid) {
		return true;
	} else if (r_message && r_message->get_tuid() == tuid) {
		return true;
	} else if (mwi_dialog && mwi_dialog->match_response(r, tuid)) {
		return true;
	} else if (presence_epa && presence_epa->match_response(r, tuid)) {
		return true;
	} else if (buddy_list && buddy_list->match_response(r, tuid, &dummy)) {
		return true;
	} else {
		// Response does not match any pending request.
		return false;
	}
}

bool t_phone_user::match(t_request *r) const {
	if (!r->hdr_to.tag.empty()) {
		// Match in-dialog requests
		if (mwi_dialog) {
			bool partial_match = false;
			if (mwi_dialog->match_request(r, partial_match)) return true;
			if (partial_match) return true;
		} else if (buddy_list) {
			t_buddy *dummy;
			if (buddy_list->match_request(r, &dummy)) return true;
		} else {
			return false;
		}
	}

	// Match on contact URI
	// NOTE: the host-part is not matched with the IP address to avoid
	//       NAT traversal problems. Some providers, using hosted NAT
	//       traversal, send an INVITE to username@<public_ip>. Twinkle
	//       only knows the <private_ip> in this case though. This is a
	//       fault on the provider side.
	if (r->uri.get_user() == user_config->get_contact_name()) {
		return true;
	}
	
	// Match on user URI
	if (r->uri.get_user() == user_config->get_name() &&
	    r->uri.get_host() == user_config->get_domain())
	{
		return true;
	}
	
	return false;
}

bool t_phone_user::match(StunMessage *r, t_tuid tuid) const {
	if (r_stun && r_stun->get_tuid() == tuid) return true;
	return false;
}

bool t_phone_user::authorize(t_request *r, t_response *resp) {
	if (authorizor.authorize(user_config, r, resp)) {
		return true;
	}
	return false;
}

void t_phone_user::resend_request(t_request *req, t_client_request *cr) {
	return resend_request(req, false, cr);
}

void t_phone_user::remove_cached_credentials(const string &realm) {
	authorizor.remove_from_cache(realm);
}

bool t_phone_user::is_active(void) const {
	return active;
}

void t_phone_user::activate(const t_user &user) {
	// Replace old user config with the new passed user config, because
	// the user config might have been edited while this phone user was
	// inactive.
	delete user_config;
	MEMMAN_DELETE(user_config);
	user_config = user.copy();
	
	// Initialize registration data
	register_seqnr = NEW_SEQNR;
	is_registered = false;
	register_ipaddr = 0L;
	register_port = 0;
	last_reg_failed = false;
	
	// Initialize STUN data
	stun_public_ip_sip = 0L;
	stun_public_port_sip = 0;
	use_stun = false;
	use_nat_keepalive = false;
	
	active = true;
}

void t_phone_user::deactivate(void) {
	// Stop timers
	if (id_registration) phone->stop_timer(PTMR_REGISTRATION, this);
	if (id_nat_keepalive) phone->stop_timer(PTMR_NAT_KEEPALIVE, this);
	if (id_resubscribe_mwi) stop_resubscribe_mwi_timer();

	// Clear MWI
	if (mwi_dialog) {
		MEMMAN_DELETE(mwi_dialog);
		delete mwi_dialog;
		mwi_dialog = NULL;
	}
	mwi.set_status(t_mwi::MWI_UNKNOWN);
	
	// Clear presence state
	// presence_epa->clear();
	buddy_list->clear_presence();
	
	// Clear STUN
	stun_binding_inuse_registration = false;
	stun_binding_inuse_mwi = false;
	stun_binding_inuse_presence = 0;
	cleanup_registration_data();
	cleanup_stun_data();
	
	active = false;
}


syntax highlighted by Code2HTML, v. 0.9.1