Discard call in case of an error.

Also add a couple of call error messages.
This commit is contained in:
John Preston 2017-05-09 15:06:21 +03:00
parent 061bd109d2
commit c78cc331d1
4 changed files with 65 additions and 35 deletions

View File

@ -1141,6 +1141,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
"lng_call_error_not_available" = "Sorry, {user} doesn't accept calls."; "lng_call_error_not_available" = "Sorry, {user} doesn't accept calls.";
"lng_call_error_incompatible" = "{user}'s app is using an incompatible protocol. They need to update their app before you can call them."; "lng_call_error_incompatible" = "{user}'s app is using an incompatible protocol. They need to update their app before you can call them.";
"lng_call_error_outdated" = "{user}'s app does not support calls. They need to update their app before you can call them."; "lng_call_error_outdated" = "{user}'s app does not support calls. They need to update their app before you can call them.";
"lng_call_error_audio_io" = "There seems to be a problem with audio playback on your computer. Please make sure that your computer's speakers and microphone are working and try again.";
"lng_call_bar_info" = "Show call info"; "lng_call_bar_info" = "Show call info";
"lng_call_bar_hangup" = "End call"; "lng_call_bar_hangup" = "End call";

View File

@ -93,7 +93,7 @@ void Call::generateModExpFirst(base::const_byte_span randomSeed) {
auto first = MTP::CreateModExp(_dhConfig.g, _dhConfig.p, randomSeed); auto first = MTP::CreateModExp(_dhConfig.g, _dhConfig.p, randomSeed);
if (first.modexp.empty()) { if (first.modexp.empty()) {
LOG(("Call Error: Could not compute mod-exp first.")); LOG(("Call Error: Could not compute mod-exp first."));
setState(State::Failed); finish(FinishType::Failed);
return; return;
} }
@ -121,7 +121,7 @@ void Call::start(base::const_byte_span random) {
t_assert(!_dhConfig.p.empty()); t_assert(!_dhConfig.p.empty());
generateModExpFirst(random); generateModExpFirst(random);
if (_state != State::Failed) { if (_state != State::Failed && _state != State::FailedHangingUp) {
if (_type == Type::Outgoing) { if (_type == Type::Outgoing) {
startOutgoing(); startOutgoing();
} else { } else {
@ -136,11 +136,14 @@ void Call::startOutgoing() {
setState(State::Requesting); setState(State::Requesting);
request(MTPphone_RequestCall(_user->inputUser, MTP_int(rand_value<int32>()), MTP_bytes(_gaHash), MTP_phoneCallProtocol(MTP_flags(MTPDphoneCallProtocol::Flag::f_udp_p2p | MTPDphoneCallProtocol::Flag::f_udp_reflector), MTP_int(kMinLayer), MTP_int(kMaxLayer)))).done([this](const MTPphone_PhoneCall &result) { request(MTPphone_RequestCall(_user->inputUser, MTP_int(rand_value<int32>()), MTP_bytes(_gaHash), MTP_phoneCallProtocol(MTP_flags(MTPDphoneCallProtocol::Flag::f_udp_p2p | MTPDphoneCallProtocol::Flag::f_udp_reflector), MTP_int(kMinLayer), MTP_int(kMaxLayer)))).done([this](const MTPphone_PhoneCall &result) {
Expects(result.type() == mtpc_phone_phoneCall); Expects(result.type() == mtpc_phone_phoneCall);
setState(State::Waiting);
auto &call = result.c_phone_phoneCall(); auto &call = result.c_phone_phoneCall();
App::feedUsers(call.vusers); App::feedUsers(call.vusers);
if (call.vphone_call.type() != mtpc_phoneCallWaiting) { if (call.vphone_call.type() != mtpc_phoneCallWaiting) {
LOG(("Call Error: Expected phoneCallWaiting in response to phone.requestCall()")); LOG(("Call Error: Expected phoneCallWaiting in response to phone.requestCall()"));
setState(State::Failed); finish(FinishType::Failed);
return; return;
} }
@ -148,10 +151,12 @@ void Call::startOutgoing() {
auto &waitingCall = phoneCall.c_phoneCallWaiting(); auto &waitingCall = phoneCall.c_phoneCallWaiting();
_id = waitingCall.vid.v; _id = waitingCall.vid.v;
_accessHash = waitingCall.vaccess_hash.v; _accessHash = waitingCall.vaccess_hash.v;
setState(State::Waiting); if (_finishAfterRequestingCall != FinishType::None) {
if (_finishAfterRequestingCall == FinishType::Failed) {
if (_finishAfterRequestingCall) { finish(_finishAfterRequestingCall);
} else {
hangup(); hangup();
}
return; return;
} }
@ -188,7 +193,7 @@ void Call::answer() {
App::feedUsers(call.vusers); App::feedUsers(call.vusers);
if (call.vphone_call.type() != mtpc_phoneCallWaiting) { if (call.vphone_call.type() != mtpc_phoneCallWaiting) {
LOG(("Call Error: Expected phoneCallWaiting in response to phone.acceptCall()")); LOG(("Call Error: Expected phoneCallWaiting in response to phone.acceptCall()"));
setState(State::Failed); finish(FinishType::Failed);
return; return;
} }
@ -218,7 +223,7 @@ void Call::hangup() {
auto declined = (_state == State::WaitingIncoming); auto declined = (_state == State::WaitingIncoming);
auto reason = missed ? MTP_phoneCallDiscardReasonMissed() : auto reason = missed ? MTP_phoneCallDiscardReasonMissed() :
declined ? MTP_phoneCallDiscardReasonBusy() : MTP_phoneCallDiscardReasonHangup(); declined ? MTP_phoneCallDiscardReasonBusy() : MTP_phoneCallDiscardReasonHangup();
finish(reason); finish(FinishType::Ended, reason);
} }
} }
@ -282,7 +287,7 @@ bool Call::handleUpdate(const MTPPhoneCall &call) {
} }
if (AuthSession::CurrentUserId() != data.vparticipant_id.v) { if (AuthSession::CurrentUserId() != data.vparticipant_id.v) {
LOG(("Call Error: Wrong call participant_id %1, expected %2.").arg(data.vparticipant_id.v).arg(AuthSession::CurrentUserId())); LOG(("Call Error: Wrong call participant_id %1, expected %2.").arg(data.vparticipant_id.v).arg(AuthSession::CurrentUserId()));
setState(State::Failed); finish(FinishType::Failed);
return true; return true;
} }
_id = data.vid.v; _id = data.vid.v;
@ -291,7 +296,7 @@ bool Call::handleUpdate(const MTPPhoneCall &call) {
auto gaHashBytes = bytesFromMTP(data.vg_a_hash); auto gaHashBytes = bytesFromMTP(data.vg_a_hash);
if (gaHashBytes.size() != _gaHash.size()) { if (gaHashBytes.size() != _gaHash.size()) {
LOG(("Call Error: Wrong g_a_hash size %1, expected %2.").arg(gaHashBytes.size()).arg(_gaHash.size())); LOG(("Call Error: Wrong g_a_hash size %1, expected %2.").arg(gaHashBytes.size()).arg(_gaHash.size()));
setState(State::Failed); finish(FinishType::Failed);
return true; return true;
} }
base::copy_bytes(gsl::make_span(_gaHash), gaHashBytes); base::copy_bytes(gsl::make_span(_gaHash), gaHashBytes);
@ -303,7 +308,7 @@ bool Call::handleUpdate(const MTPPhoneCall &call) {
return false; return false;
} }
LOG(("Call Error: phoneCallEmpty received.")); LOG(("Call Error: phoneCallEmpty received."));
setState(State::Failed); finish(FinishType::Failed);
} return true; } return true;
case mtpc_phoneCallWaiting: { case mtpc_phoneCallWaiting: {
@ -358,7 +363,7 @@ bool Call::handleUpdate(const MTPPhoneCall &call) {
} }
if (_type != Type::Outgoing) { if (_type != Type::Outgoing) {
LOG(("Call Error: Unexpected phoneCallAccepted for an incoming call.")); LOG(("Call Error: Unexpected phoneCallAccepted for an incoming call."));
setState(State::Failed); finish(FinishType::Failed);
} else if (checkCallFields(data)) { } else if (checkCallFields(data)) {
confirmAcceptedCall(data); confirmAcceptedCall(data);
} }
@ -375,7 +380,7 @@ void Call::confirmAcceptedCall(const MTPDphoneCallAccepted &call) {
auto computedAuthKey = MTP::CreateAuthKey(firstBytes, _randomPower, _dhConfig.p); auto computedAuthKey = MTP::CreateAuthKey(firstBytes, _randomPower, _dhConfig.p);
if (computedAuthKey.empty()) { if (computedAuthKey.empty()) {
LOG(("Call Error: Could not compute mod-exp final.")); LOG(("Call Error: Could not compute mod-exp final."));
setState(State::Failed); finish(FinishType::Failed);
return; return;
} }
@ -389,7 +394,7 @@ void Call::confirmAcceptedCall(const MTPDphoneCallAccepted &call) {
App::feedUsers(call.vusers); App::feedUsers(call.vusers);
if (call.vphone_call.type() != mtpc_phoneCall) { if (call.vphone_call.type() != mtpc_phoneCall) {
LOG(("Call Error: Expected phoneCall in response to phone.confirmCall()")); LOG(("Call Error: Expected phoneCall in response to phone.confirmCall()"));
setState(State::Failed); finish(FinishType::Failed);
return; return;
} }
@ -405,7 +410,7 @@ void Call::startConfirmedCall(const MTPDphoneCall &call) {
auto firstBytes = bytesFromMTP(call.vg_a_or_b); auto firstBytes = bytesFromMTP(call.vg_a_or_b);
if (_gaHash != openssl::Sha256(firstBytes)) { if (_gaHash != openssl::Sha256(firstBytes)) {
LOG(("Call Error: Wrong g_a hash received.")); LOG(("Call Error: Wrong g_a hash received."));
setState(State::Failed); finish(FinishType::Failed);
return; return;
} }
_ga = base::byte_vector(firstBytes.begin(), firstBytes.end()); _ga = base::byte_vector(firstBytes.begin(), firstBytes.end());
@ -413,7 +418,7 @@ void Call::startConfirmedCall(const MTPDphoneCall &call) {
auto computedAuthKey = MTP::CreateAuthKey(firstBytes, _randomPower, _dhConfig.p); auto computedAuthKey = MTP::CreateAuthKey(firstBytes, _randomPower, _dhConfig.p);
if (computedAuthKey.empty()) { if (computedAuthKey.empty()) {
LOG(("Call Error: Could not compute mod-exp final.")); LOG(("Call Error: Could not compute mod-exp final."));
setState(State::Failed); finish(FinishType::Failed);
return; return;
} }
@ -504,7 +509,7 @@ void Call::handleControllerStateChange(tgvoip::VoIPController *controller, int s
template <typename T> template <typename T>
bool Call::checkCallCommonFields(const T &call) { bool Call::checkCallCommonFields(const T &call) {
auto checkFailed = [this] { auto checkFailed = [this] {
setState(State::Failed); finish(FinishType::Failed);
return false; return false;
}; };
if (call.vaccess_hash.v != _accessHash) { if (call.vaccess_hash.v != _accessHash) {
@ -530,7 +535,7 @@ bool Call::checkCallFields(const MTPDphoneCall &call) {
} }
if (call.vkey_fingerprint.v != _keyFingerprint) { if (call.vkey_fingerprint.v != _keyFingerprint) {
LOG(("Call Error: Wrong call fingerprint.")); LOG(("Call Error: Wrong call fingerprint."));
setState(State::Failed); finish(FinishType::Failed);
return false; return false;
} }
return true; return true;
@ -541,6 +546,12 @@ bool Call::checkCallFields(const MTPDphoneCallAccepted &call) {
} }
void Call::setState(State state) { void Call::setState(State state) {
if (_state == State::Failed) {
return;
}
if (_state == State::FailedHangingUp && state != State::Failed) {
return;
}
if (_state != state) { if (_state != state) {
_state = state; _state = state;
_stateChanged.notify(state, true); _stateChanged.notify(state, true);
@ -583,31 +594,37 @@ void Call::setState(State state) {
} }
} }
void Call::finish(const MTPPhoneCallDiscardReason &reason) { void Call::finish(FinishType type, const MTPPhoneCallDiscardReason &reason) {
Expects(type != FinishType::None);
auto finalState = (type == FinishType::Ended) ? State::Ended : State::Failed;
auto hangupState = (type == FinishType::Ended) ? State::HangingUp : State::FailedHangingUp;
if (_state == State::Requesting) { if (_state == State::Requesting) {
_finishByTimeoutTimer.call(kHangupTimeoutMs, [this] { setState(State::Ended); }); _finishByTimeoutTimer.call(kHangupTimeoutMs, [this, finalState] { setState(finalState); });
_finishAfterRequestingCall = true; _finishAfterRequestingCall = type;
return; return;
} }
if (_state == State::HangingUp || _state == State::Ended) { if (_state == State::HangingUp
|| _state == State::FailedHangingUp
|| _state == State::Ended
|| _state == State::Failed) {
return; return;
} }
if (!_id) { if (!_id) {
setState(State::Ended); setState(finalState);
return; return;
} }
setState(State::HangingUp); setState(hangupState);
auto duration = getDurationMs() / 1000; auto duration = getDurationMs() / 1000;
auto connectionId = _controller ? _controller->GetPreferredRelayID() : 0; auto connectionId = _controller ? _controller->GetPreferredRelayID() : 0;
_finishByTimeoutTimer.call(kHangupTimeoutMs, [this] { setState(State::Ended); }); _finishByTimeoutTimer.call(kHangupTimeoutMs, [this, finalState] { setState(finalState); });
request(MTPphone_DiscardCall(MTP_inputPhoneCall(MTP_long(_id), MTP_long(_accessHash)), MTP_int(duration), reason, MTP_long(connectionId))).done([this](const MTPUpdates &result) { request(MTPphone_DiscardCall(MTP_inputPhoneCall(MTP_long(_id), MTP_long(_accessHash)), MTP_int(duration), reason, MTP_long(connectionId))).done([this, finalState](const MTPUpdates &result) {
// This could be destroyed by updates, so we set Ended after // This could be destroyed by updates, so we set Ended after
// updates being handled, but in a guarded way. // updates being handled, but in a guarded way.
InvokeQueued(this, [this] { setState(State::Ended); }); InvokeQueued(this, [this, finalState] { setState(finalState); });
App::main()->sentUpdatesReceived(result); App::main()->sentUpdatesReceived(result);
}).fail([this](const RPCError &error) { }).fail([this, finalState](const RPCError &error) {
setState(State::Ended); setState(finalState);
}).send(); }).send();
} }
@ -627,14 +644,16 @@ void Call::handleRequestError(const RPCError &error) {
} else if (error.type() == qstr("CALL_PROTOCOL_LAYER_INVALID")) { } else if (error.type() == qstr("CALL_PROTOCOL_LAYER_INVALID")) {
Ui::show(Box<InformBox>(lng_call_error_incompatible(lt_user, App::peerName(_user)))); Ui::show(Box<InformBox>(lng_call_error_incompatible(lt_user, App::peerName(_user))));
} }
setState(State::Failed); finish(FinishType::Failed);
} }
void Call::handleControllerError(int error) { void Call::handleControllerError(int error) {
if (error == TGVOIP_ERROR_INCOMPATIBLE) { if (error == TGVOIP_ERROR_INCOMPATIBLE) {
Ui::show(Box<InformBox>(lng_call_error_incompatible(lt_user, App::peerName(_user)))); Ui::show(Box<InformBox>(lng_call_error_incompatible(lt_user, App::peerName(_user))));
} else if (error == TGVOIP_ERROR_AUDIO_IO) {
Ui::show(Box<InformBox>(lang(lng_call_error_audio_io)));
} }
setState(State::Failed); finish(FinishType::Failed);
} }
void Call::destroyController() { void Call::destroyController() {

View File

@ -87,6 +87,7 @@ public:
WaitingInit, WaitingInit,
WaitingInitAck, WaitingInitAck,
Established, Established,
FailedHangingUp,
Failed, Failed,
HangingUp, HangingUp,
Ended, Ended,
@ -127,9 +128,14 @@ public:
~Call(); ~Call();
private: private:
enum class FinishType {
None,
Ended,
Failed,
};
void handleRequestError(const RPCError &error); void handleRequestError(const RPCError &error);
void handleControllerError(int error); void handleControllerError(int error);
void finish(const MTPPhoneCallDiscardReason &reason); void finish(FinishType type, const MTPPhoneCallDiscardReason &reason = MTP_phoneCallDiscardReasonDisconnect());
void startOutgoing(); void startOutgoing();
void startIncoming(); void startIncoming();
void startWaitingTrack(); void startWaitingTrack();
@ -154,7 +160,7 @@ private:
gsl::not_null<UserData*> _user; gsl::not_null<UserData*> _user;
Type _type = Type::Outgoing; Type _type = Type::Outgoing;
State _state = State::Starting; State _state = State::Starting;
bool _finishAfterRequestingCall = false; FinishType _finishAfterRequestingCall = FinishType::None;
base::Observable<State> _stateChanged; base::Observable<State> _stateChanged;
TimeMs _startTime = 0; TimeMs _startTime = 0;
base::DelayedCallTimer _finishByTimeoutTimer; base::DelayedCallTimer _finishByTimeoutTimer;

View File

@ -694,7 +694,10 @@ void Panel::stateChanged(State state) {
updateStatusText(state); updateStatusText(state);
if (_call) { if (_call) {
if ((state != State::HangingUp) && (state != State::Ended) && (state != State::Failed)) { if ((state != State::HangingUp)
&& (state != State::Ended)
&& (state != State::FailedHangingUp)
&& (state != State::Failed)) {
auto toggleButton = [this](auto &&button, bool visible) { auto toggleButton = [this](auto &&button, bool visible) {
if (isHidden()) { if (isHidden()) {
button->toggleFast(visible); button->toggleFast(visible);
@ -765,6 +768,7 @@ void Panel::updateStatusText(State state) {
} }
return lang(lng_call_status_ended); return lang(lng_call_status_ended);
} break; } break;
case State::FailedHangingUp:
case State::Failed: return lang(lng_call_status_failed); case State::Failed: return lang(lng_call_status_failed);
case State::HangingUp: return lang(lng_call_status_hanging); case State::HangingUp: return lang(lng_call_status_hanging);
case State::Ended: return lang(lng_call_status_ended); case State::Ended: return lang(lng_call_status_ended);