Skip to content

Instantly share code, notes, and snippets.

@stek29
Last active April 11, 2022 17:59
Show Gist options
  • Save stek29/9ced63a52199faf2d0b2fd9e99b49109 to your computer and use it in GitHub Desktop.
Save stek29/9ced63a52199faf2d0b2fd9e99b49109 to your computer and use it in GitHub Desktop.
proof of concept
git clone https://github.com/grishka/libtgvoip.git
cd libtgvoip
# Build openssl-1.0.1 and opus-1.1 to prefix libraries/
# Save CMakeLists.txt here (in repo root)
mkdir build
cd build
# Save CallMakefile and main.cpp here
cmake ..
make # libtgvoip.a should be in .

now go to webogram:

  • Sideload webogram-calls.js
  • Run callsInit() in console
  • Call user with id X with callMeMaybe(getInputUserByID(X))
  • All calls are accepted automatically

Wait for textarea to pop up. Focus in it. Now your pasteboard has keys and stuff.

Get back to terminal and run make -f CallMakefile. Call will start soon.

all: pastekey build run
pastekey:
pbpaste | tee keys.h
build:
clang -std=c++1z main.cpp -I{..,../webrtc_dsp,../libraries/include,../libraries/include/opus} ../build/liblibtgvoip.a -L../libraries/lib -lcrypto -lopenal -lopus -lc++ -framework CoreAudio -lc++ -framework AudioToolbox -framework Foundation
run:
./a.out
cmake_minimum_required(VERSION 3.6)
project(libtgvoip)
set(CMAKE_CXX_STANDARD 14)
set(SOURCES
BlockingQueue.cpp
BufferInputStream.cpp
BufferOutputStream.cpp
BufferPool.cpp
CongestionControl.cpp
EchoCanceller.cpp
JitterBuffer.cpp
logging.cpp
MediaStreamItf.cpp
OpusDecoder.cpp
OpusEncoder.cpp
VoIPController.cpp
VoIPServerConfig.cpp
audio/AudioInput.cpp
audio/AudioOutput.cpp
audio/Resampler.cpp
NetworkSocket.cpp
os/darwin/AudioInputAudioUnit.cpp
os/darwin/AudioOutputAudioUnit.cpp
os/darwin/AudioInputAudioUnitOSX.cpp
os/darwin/AudioOutputAudioUnitOSX.cpp
os/darwin/AudioUnitIO.cpp
os/darwin/DarwinSpecific.mm
os/posix/NetworkSocketPosix.cpp
webrtc_dsp/webrtc/base/checks.cc
webrtc_dsp/webrtc/base/stringutils.cc
webrtc_dsp/webrtc/common_audio/audio_util.cc
webrtc_dsp/webrtc/common_audio/channel_buffer.cc
webrtc_dsp/webrtc/common_audio/fft4g.c
webrtc_dsp/webrtc/common_audio/ring_buffer.c
webrtc_dsp/webrtc/common_audio/signal_processing/auto_corr_to_refl_coef.c
webrtc_dsp/webrtc/common_audio/signal_processing/auto_correlation.c
webrtc_dsp/webrtc/common_audio/signal_processing/complex_bit_reverse.c
webrtc_dsp/webrtc/common_audio/signal_processing/complex_fft.c
webrtc_dsp/webrtc/common_audio/signal_processing/copy_set_operations.c
webrtc_dsp/webrtc/common_audio/signal_processing/cross_correlation.c
webrtc_dsp/webrtc/common_audio/signal_processing/cross_correlation_neon.c
webrtc_dsp/webrtc/common_audio/signal_processing/division_operations.c
webrtc_dsp/webrtc/common_audio/signal_processing/dot_product_with_scale.c
webrtc_dsp/webrtc/common_audio/signal_processing/downsample_fast.c
webrtc_dsp/webrtc/common_audio/signal_processing/downsample_fast_neon.c
webrtc_dsp/webrtc/common_audio/signal_processing/energy.c
webrtc_dsp/webrtc/common_audio/signal_processing/filter_ar.c
webrtc_dsp/webrtc/common_audio/signal_processing/filter_ar_fast_q12.c
webrtc_dsp/webrtc/common_audio/signal_processing/filter_ma_fast_q12.c
webrtc_dsp/webrtc/common_audio/signal_processing/get_hanning_window.c
webrtc_dsp/webrtc/common_audio/signal_processing/get_scaling_square.c
webrtc_dsp/webrtc/common_audio/signal_processing/ilbc_specific_functions.c
webrtc_dsp/webrtc/common_audio/signal_processing/levinson_durbin.c
webrtc_dsp/webrtc/common_audio/signal_processing/lpc_to_refl_coef.c
webrtc_dsp/webrtc/common_audio/signal_processing/min_max_operations.c
webrtc_dsp/webrtc/common_audio/signal_processing/min_max_operations_neon.c
webrtc_dsp/webrtc/common_audio/signal_processing/randomization_functions.c
webrtc_dsp/webrtc/common_audio/signal_processing/real_fft.c
webrtc_dsp/webrtc/common_audio/signal_processing/refl_coef_to_lpc.c
webrtc_dsp/webrtc/common_audio/signal_processing/resample.c
webrtc_dsp/webrtc/common_audio/signal_processing/resample_48khz.c
webrtc_dsp/webrtc/common_audio/signal_processing/resample_by_2.c
webrtc_dsp/webrtc/common_audio/signal_processing/resample_by_2_internal.c
webrtc_dsp/webrtc/common_audio/signal_processing/resample_fractional.c
webrtc_dsp/webrtc/common_audio/signal_processing/spl_init.c
webrtc_dsp/webrtc/common_audio/signal_processing/spl_inl.c
webrtc_dsp/webrtc/common_audio/signal_processing/spl_sqrt.c
webrtc_dsp/webrtc/common_audio/signal_processing/spl_sqrt_floor.c
webrtc_dsp/webrtc/common_audio/signal_processing/splitting_filter_impl.c
webrtc_dsp/webrtc/common_audio/signal_processing/sqrt_of_one_minus_x_squared.c
webrtc_dsp/webrtc/common_audio/signal_processing/vector_scaling_operations.c
webrtc_dsp/webrtc/common_audio/sparse_fir_filter.cc
webrtc_dsp/webrtc/common_audio/wav_file.cc
webrtc_dsp/webrtc/common_audio/wav_header.cc
webrtc_dsp/webrtc/modules/audio_processing/aec/aec_core.cc
webrtc_dsp/webrtc/modules/audio_processing/aec/aec_core_neon.cc
webrtc_dsp/webrtc/modules/audio_processing/aec/aec_core_sse2.cc
webrtc_dsp/webrtc/modules/audio_processing/aec/aec_resampler.cc
webrtc_dsp/webrtc/modules/audio_processing/aec/echo_cancellation.cc
webrtc_dsp/webrtc/modules/audio_processing/aecm/aecm_core.cc
webrtc_dsp/webrtc/modules/audio_processing/aecm/aecm_core_c.cc
webrtc_dsp/webrtc/modules/audio_processing/aecm/aecm_core_neon.cc
webrtc_dsp/webrtc/modules/audio_processing/aecm/echo_control_mobile.cc
webrtc_dsp/webrtc/modules/audio_processing/agc/legacy/analog_agc.c
webrtc_dsp/webrtc/modules/audio_processing/agc/legacy/digital_agc.c
webrtc_dsp/webrtc/modules/audio_processing/logging/apm_data_dumper.cc
webrtc_dsp/webrtc/modules/audio_processing/ns/noise_suppression.c
webrtc_dsp/webrtc/modules/audio_processing/ns/noise_suppression_x.c
webrtc_dsp/webrtc/modules/audio_processing/ns/ns_core.c
webrtc_dsp/webrtc/modules/audio_processing/ns/nsx_core.c
webrtc_dsp/webrtc/modules/audio_processing/ns/nsx_core_c.c
webrtc_dsp/webrtc/modules/audio_processing/ns/nsx_core_neon.c
webrtc_dsp/webrtc/modules/audio_processing/splitting_filter.cc
webrtc_dsp/webrtc/modules/audio_processing/three_band_filter_bank.cc
webrtc_dsp/webrtc/modules/audio_processing/utility/block_mean_calculator.cc
webrtc_dsp/webrtc/modules/audio_processing/utility/delay_estimator.cc
webrtc_dsp/webrtc/modules/audio_processing/utility/delay_estimator_wrapper.cc
webrtc_dsp/webrtc/modules/audio_processing/utility/ooura_fft.cc
webrtc_dsp/webrtc/modules/audio_processing/utility/ooura_fft_neon.cc
webrtc_dsp/webrtc/modules/audio_processing/utility/ooura_fft_sse2.cc
webrtc_dsp/webrtc/system_wrappers/source/cpu_features.cc
)
add_library(tgvoip STATIC ${SOURCES})
target_link_libraries(tgvoip -L libraries/lib -lcrypto -lopenal -lopus)
target_compile_definitions(tgvoip PRIVATE TGVOIP_USE_DESKTOP_DSP WEBRTC_POSIX WEBRTC_MAC WEBRTC_APM_DEBUG_DUMP=0)
target_include_directories(tgvoip PRIVATE libraries/include libraries/include/opus webrtc_dsp)
#include <iostream>
#include <string>
#include <map>
#include "VoIPController.h"
#include "VoIPServerConfig.h"
using namespace std;
using namespace tgvoip;
static voip_config_t config {
/*init_timeout*/3000,
/*recv_timeout*/3000,
/*data_saving*/0,
/*logFilePath*/"tgvoip.log",
/*statsDumpFilePath*/NULL,
/*enableAEC*/0,
/*enableNS*/0,
/*enableAGC*/0,
};
static map<string, string> server_conf = {
{"audio_frame_size", "60"},
{"jitter_min_delay_20", "6"},
{"jitter_min_delay_40", "4"},
{"jitter_min_delay_60", "2"},
{"jitter_max_delay_20", "25"},
{"jitter_max_delay_40", "15"},
{"jitter_max_delay_60", "10"},
{"jitter_max_slots_20", "50"},
{"jitter_max_slots_40", "30"},
{"jitter_max_slots_60", "20"},
{"jitter_losses_to_reset", "20"},
{"jitter_resync_threshold", "0.5"},
{"audio_congestion_window", "1024"},
{"audio_max_bitrate", "20000"},
{"audio_max_bitrate_edge", "16000"},
{"audio_max_bitrate_gprs", "8000"},
{"audio_max_bitrate_saving", "8000"},
{"audio_init_bitrate", "16000"},
{"audio_init_bitrate_edge", "8000"},
{"audio_init_bitrate_gprs", "8000"},
{"audio_init_bitrate_saving", "8000"},
{"audio_bitrate_step_incr", "1000"},
{"audio_bitrate_step_decr", "1000"},
{"use_system_ns", "true"},
{"use_system_aec", "true"}
};
int main(int argc, char**argv) {
//const idx_is_outgoing=1;
char *key; // '\0', because lazy to fill not from cstring
vector<Endpoint> endpoints;
bool is_outgoing;
#include "keys.h"
ServerConfig::GetSharedInstance()->Update(server_conf);
VoIPController *cnt = new VoIPController();
cnt->SetConfig(&config);
cnt->SetEncryptionKey(key, /*argv[idx_is_outgoing][0]=='1'*/ is_outgoing);
cnt->SetRemoteEndpoints(endpoints, false);
cnt->Start();
cnt->Connect();
while(1) {
sleep(10);
};
}
var callsInit = function() {
const $inj = angular.element(document).injector();
window.getInputPeerByID = $inj.get('AppPeersManager').getInputPeerByID;
window.invokeApi = $inj.get('MtpApiManager').invokeApi;
window.getInputUserByID = id => ([getInputPeerByID(id)]).map(p=>((p||{})._='inputUser',p))[0];
window.secureRand = new SecureRandom();
window.calls = {};
window.dhConfig = null;
// inject our update handler
$inj.get('$rootScope').$on('apiUpdate', (u,v)=>processUpdate(v))
}
var getDhConfig = function(force) {
if (dhConfig != null && !force) return Promise.resolve(dhConfig);
return invokeApi('messages.getDhConfig', {}).then(function(dhConfigR) {
dhConfig = {};
dhConfig.p_int = new BigInteger(dhConfigR.p);
dhConfig.g_int = new BigInteger(dhConfigR.g.toString(10), 10);
dhConfig.resp = dhConfigR;
return dhConfig;
})
}
var getRandBytes = function(len) {
len = len || 256;
//return getDhConfig().then(function(dhConfig) {
var bytes1 = new Uint8Array(len);
secureRand.nextBytes(bytes1);
return bytesXor(bytes1, dhConfig.resp.random);
//})
}
var bytesEqual = function(arr1, arr2) {
if (arr1.length != arr2.length) return false;
for (let i = arr1.length - 1; i >= 0; i--) {
if (arr1[i] != arr2[i]) return false;
}
return true;
}
processPhoneCall = function(call) {
switch(call._) {
case 'phoneCallEmpty':
break;
case 'phoneCallWaiting':
console.log(`[CALL] Waiting for ${call.participant_id} to answer...`);
break;
case 'phoneCallRequested':
acceptCall(call);
break;
case 'phoneCallDiscarded':
let state = calls[call.id];
let participant_id = state.incoming ? state.admin_id : state.user_id.user_id;
console.log(`[CALL] ${participant_id} discarded your call because of ${call.reason._}`);
//delete calls[call.id];
break;
case 'phoneCall':
processFullCall(call);
break;
case 'phoneCallAccepted':
console.log(`[CALL] Call accepted by ${call.participant_id}! Doing stage 2!`);
callStageTwo(call);
break;
default:
break;
}
}
processUpdate = function(update) {
switch(update._) {
case 'updatePhoneCall':
console.log('[CALL] Phone call update:', update.phone_call);
processPhoneCall(update.phone_call)
break;
}
}
acceptCall = function(call) {
const PROTOCOL = {'_':'phoneCallProtocol', flags: 1, min_layer: 65, max_layer: 65};
return getDhConfig().then(function(dhc) {
state = {};
state.incoming = true;
state.peer = {
_: 'inputPhoneCall',
id: call.id,
access_hash: call.access_hash,
}
state.user_id = getInputUserByID(call.admin_id);
state.random_id = parseInt(0x7fffffff * Math.random());
state.g_int = dhc.g_int;
state.p_int = dhc.p_int;
state.b_int = new BigInteger(getRandBytes());
state.g_b_int = state.g_int.modPow(state.b_int, state.p_int);
state.g_b = bytesFromHex(state.g_b_int.toString(16));
state.g_a_hash = call.g_a_hash;
state.my_proto = PROTOCOL;
return state;
}).then(function(state) {
calls[call.id] = state;
return invokeApi('phone.acceptCall', {
peer: state.peer,
g_b: state.g_b,
protocol: state.my_proto
}).then(function (phoneCall) {
console.warn(`[CALL] Call confirmed:`, phoneCall);
return processPhoneCall(phoneCall.phone_call);
})
});
}
var calcFingerprint = function (key) {
return (new BigInteger(sha1BytesSync(key).slice(-8).reverse())).toString(10);
}
processFullCall = function(call) {
state = calls[call.id];
if (state.incoming) {
console.log(`[CALL] Got more info about call from ${call.admin_id} we've accepted.`);
state.g_a = call.g_a_or_b;
state.g_a_int = new BigInteger(state.g_a);
state.pt_g_a_hash = sha256HashSync(state.g_a);
if (!bytesEqual(state.pt_g_a_hash, state.g_a_hash)) {
console.error(`[CALL] HASH(G_A) != G_A_HASH!`, state.pt_g_a_hash, state.g_a_hash);
}
state.key_int = state.g_a_int.modPow(state.b_int, state.p_int);
state.key = bytesFromHex(state.key_int.toString(16));
state.key_fingerprint = calcFingerprint(state.key);
state.pt_key_fingerprint = call.key_fingerprint;
if (state.pt_key_fingerprint != state.key_fingerprint) {
console.error(`[CALL] Fingerprint mismatch! Got ${state.pt_key_fingerprint}, expected ${state.key_fingerprint}!`);
}
}
state.connection = call.connection;
state.alternative_connections = call.alternative_connections;
calls[call.id] = state;
console.info(`[CALL] Call #${call.id} ready!`);
copyToClipboard(dumpCallForCLI(state));
}
callMeMaybe = function(inputUser) {
const PROTOCOL = {'_':'phoneCallProtocol', flags: 1, min_layer: 65, max_layer: 65};
return getDhConfig().then(function(dhc) {
state = {};
state.incoming = false;
state.user_id = inputUser;
state.random_id = parseInt(0x7fffffff * Math.random());
state.g_int = dhc.g_int;
state.p_int = dhc.p_int;
state.a_int = new BigInteger(getRandBytes());
state.g_a_int = state.g_int.modPow(state.a_int, state.p_int);
state.g_a = bytesFromHex(state.g_a_int.toString(16));
state.g_a_hash = sha256HashSync(state.g_a);
state.my_proto = PROTOCOL;
return state;
}).then(function(state) {
return invokeApi('phone.requestCall', {
user_id: state.user_id,
random_id: state.random_id,
g_a_hash: state.g_a_hash,
protocol: state.my_proto
}).then(function(phoneCall) {
phoneCall = phoneCall.phone_call;
state.peer = {
_: 'inputPhoneCall',
id: phoneCall.id,
access_hash: phoneCall.access_hash,
}
calls[phoneCall.id] = state;
return processPhoneCall(phoneCall);
});
});
};
callStageTwo = function(call) {
var state = calls[call.id];
state.prt_proto = call.protocol;
state.g_b = call.g_b;
state.g_b_int = new BigInteger(state.g_b);
state.key_int = state.g_b_int.modPow(state.a_int, state.p_int);
state.key = bytesFromHex(state.key_int.toString(16));
state.key_fingerprint = calcFingerprint(state.key);
calls[call.id] = state;
return invokeApi('phone.confirmCall', {
peer: {
_: 'inputPhoneCall',
id: call.id,
access_hash: call.access_hash
},
g_a: state.g_a,
key_fingerprint: state.key_fingerprint,
protocol: state.my_proto
}).then(function(phoneCall) {
console.info(`[CALL] Call confirmed:`, phoneCall);
return processPhoneCall(phoneCall.phone_call);
});
};
dumpCallForCLI = function(state) {
let dumpBytes = bytes => Array.prototype.slice.call(bytes).map(i=>`\\x${i<16?'0':''}${i.toString(16)}`).join(''); // to cstring
let dumpEndpoint = conn =>
['{',
`auto ipv4 = IPv4Address("${conn.ip }");`,
`auto ipv6 = IPv6Address("${conn.ipv6}");`,
`endpoints.emplace_back(${conn.id}, ${conn.port}, ipv4, ipv6, (char) EP_TYPE_UDP_RELAY, (unsigned char*) "${dumpBytes(conn.peer_tag)}");`,
'};'].join('\n');
ret = ['\n\n\n\n'];
ret.push(`is_outgoing = ${!state.incoming};`);
ret.push(`key = "${dumpBytes(state.key)}";`);
ret.push(dumpEndpoint(state.connection));
for (conn of state.alternative_connections)
ret.push(dumpEndpoint(conn));
ret.push('\n\n\n\n');
return ret.join('\n');
}
copyToClipboard = function(text) {
var copyFrom = document.createElement('textarea');
copyFrom.style.position = "absolute";
copyFrom.style.left = "10px";
copyFrom.style.top = "50px";
copyFrom.value = text;
document.body.append(copyFrom);
document.execCommand('copy');
copyFrom.onfocus=()=>(copyFrom.select(),document.execCommand('copy'),document.body.removeChild(copyFrom));
}
@devxpy
Copy link

devxpy commented Mar 29, 2018

when i try to build, i get the following error

...
[ 17%] Building CXX object CMakeFiles/tgvoip.dir/NetworkSocket.cpp.o
[ 18%] Building CXX object CMakeFiles/tgvoip.dir/os/darwin/AudioInputAudioUnit.cpp.o
In file included from /libtgvoip/os/darwin/AudioInputAudioUnit.cpp:10:0:
/libtgvoip/os/darwin/AudioUnitIO.h:10:33: fatal error: AudioUnit/AudioUnit.h: No such file or directory
 #include <AudioUnit/AudioUnit.h>
                                 ^
compilation terminated.
CMakeFiles/tgvoip.dir/build.make:470: recipe for target 'CMakeFiles/tgvoip.dir/os/darwin/AudioInputAudioUnit.cpp.o' failed
make[2]: *** [CMakeFiles/tgvoip.dir/os/darwin/AudioInputAudioUnit.cpp.o] Error 1
CMakeFiles/Makefile2:67: recipe for target 'CMakeFiles/tgvoip.dir/all' failed
make[1]: *** [CMakeFiles/tgvoip.dir/all] Error 2
make: *** [all] Error 2
Makefile:83: recipe for target 'all' failed
The command '/bin/sh -c make' returned a non-zero code: 2

Here is my Dockerfile

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment