Improve the program a bit, and include a protocol documentation
This commit is contained in:
parent
48be34ff5c
commit
a124a26d36
4 changed files with 101 additions and 10 deletions
46
README.md
Normal file
46
README.md
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
# Protocol Format
|
||||||
|
|
||||||
|
All data-types bigger than 1 byte are stored in network endian (big-endian) unless stated otherwise.
|
||||||
|
|
||||||
|
## Request Base
|
||||||
|
| Name | Size (bytes) | Description |
|
||||||
|
|-----------|--------------|--------------------------------------|
|
||||||
|
|opcode | 1 | The operation code to perform, A list of operations currently supported (and their data) can be found in the **Operations** chapter |
|
||||||
|
|request_id | 4 | The ID for the current request, Used to distinguish responses in the current connection |
|
||||||
|
|
||||||
|
The data afterwards depends on the supplied opcode, Please consult the **Operations** chapter for more information.
|
||||||
|
|
||||||
|
## Response Base
|
||||||
|
| Name | Size (bytes) | Description |
|
||||||
|
|------------|--------------|---------------------------------------|
|
||||||
|
|request_id | 4 | The ID for the request that this response is meant for |
|
||||||
|
|
||||||
|
The data afterwards depends on the supplied opcode, Please consult the **Operations** chapter for more information.
|
||||||
|
|
||||||
|
## Operations
|
||||||
|
### `FORCE_UPDATE` (0x00)
|
||||||
|
Forces the server to re-fetch the YouTube player, and extract the necessary components from it (`nsig` function code, `sig` function code, signature timestamp).
|
||||||
|
|
||||||
|
#### Request
|
||||||
|
*No additional data required*
|
||||||
|
|
||||||
|
#### Response
|
||||||
|
| Name | Size (bytes) | Description |
|
||||||
|
|------|--------------|-------------|
|
||||||
|
|status| 4 | The status code of the request: `0xF44F` if successful, `0xFFFF` if no updating is required (YouTube's player ID is equal to the server's current player ID), `0x0000` if an error occurred |
|
||||||
|
|
||||||
|
### `DECRYPT_N_SIGNATURE` (0x01)
|
||||||
|
Decrypt a provided `n` signature using the server's current `nsig` function code, and return the result (or an error).
|
||||||
|
|
||||||
|
#### Request
|
||||||
|
| Name | Size (bytes) | Description |
|
||||||
|
|------|--------------|-------------------------------------|
|
||||||
|
|size | 2 | The size of the encrypted signature |
|
||||||
|
|string| *`size`* | The encrypted signature |
|
||||||
|
|
||||||
|
#### Response
|
||||||
|
| Name | Size (bytes) | Description |
|
||||||
|
|------|--------------|------------------------------------------------------------------|
|
||||||
|
|size | 2 | The size of the decrypted signature, `0x0000` if an error occurred |
|
||||||
|
|string| *`size`* | The decrypted signature |
|
||||||
|
|
|
@ -8,5 +8,11 @@ pub static REGEX_PLAYER_ID: &Lazy<Regex> = regex!("\\/s\\/player\\/([0-9a-f]{8})
|
||||||
pub static NSIG_FUNCTION_ARRAY: &Lazy<Regex> = regex!(
|
pub static NSIG_FUNCTION_ARRAY: &Lazy<Regex> = regex!(
|
||||||
"\\.get\\(\"n\"\\)\\)&&\\([a-zA-Z0-9$_]=([a-zA-Z0-9$_]+)(?:\\[(\\d+)])?\\([a-zA-Z0-9$_]\\)"
|
"\\.get\\(\"n\"\\)\\)&&\\([a-zA-Z0-9$_]=([a-zA-Z0-9$_]+)(?:\\[(\\d+)])?\\([a-zA-Z0-9$_]\\)"
|
||||||
);
|
);
|
||||||
|
pub static REGEX_SIGNATURE_TIMESTAMP: &Lazy<Regex> = regex!("signatureTimestamp[=:](\\d+)");
|
||||||
|
|
||||||
|
pub static REGEX_SIGNATURE_FUNCTION: &Lazy<Regex> =
|
||||||
|
regex!("\\bc&&\\(c=([a-zA-Z0-9$]{2,})\\(decodeURIComponent\\(c\\)\\)");
|
||||||
|
pub static REGEX_HELPER_OBJ_NAME: &Lazy<Regex> = regex!(";([A-Za-z0-9_\\$]{2,})\\...\\(");
|
||||||
|
|
||||||
pub static NSIG_FUNCTION_NAME: &str = "decrypt_nsig";
|
pub static NSIG_FUNCTION_NAME: &str = "decrypt_nsig";
|
||||||
|
pub static SIG_FUNCTION_NAME: &str = "decrypt_sig";
|
||||||
|
|
56
src/jobs.rs
56
src/jobs.rs
|
@ -15,6 +15,8 @@ use crate::consts::{NSIG_FUNCTION_ARRAY, NSIG_FUNCTION_NAME, REGEX_PLAYER_ID, TE
|
||||||
pub enum JobOpcode {
|
pub enum JobOpcode {
|
||||||
ForceUpdate,
|
ForceUpdate,
|
||||||
DecryptNSignature,
|
DecryptNSignature,
|
||||||
|
DecryptSignature,
|
||||||
|
GetSignatureTimestamp,
|
||||||
UnknownOpcode,
|
UnknownOpcode,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,6 +25,8 @@ impl std::fmt::Display for JobOpcode {
|
||||||
match self {
|
match self {
|
||||||
Self::ForceUpdate => write!(f, "ForceUpdate"),
|
Self::ForceUpdate => write!(f, "ForceUpdate"),
|
||||||
Self::DecryptNSignature => write!(f, "DecryptNSignature"),
|
Self::DecryptNSignature => write!(f, "DecryptNSignature"),
|
||||||
|
Self::DecryptSignature => write!(f, "DecryptSignature"),
|
||||||
|
Self::GetSignatureTimestamp => write!(f, "GetSignatureTimestamp"),
|
||||||
Self::UnknownOpcode => write!(f, "UnknownOpcode"),
|
Self::UnknownOpcode => write!(f, "UnknownOpcode"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -32,9 +36,8 @@ impl From<u8> for JobOpcode {
|
||||||
match value {
|
match value {
|
||||||
0x00 => Self::ForceUpdate,
|
0x00 => Self::ForceUpdate,
|
||||||
0x01 => Self::DecryptNSignature,
|
0x01 => Self::DecryptNSignature,
|
||||||
|
0x02 => Self::DecryptSignature,
|
||||||
// make debugging easier
|
0x03 => Self::GetSignatureTimestamp,
|
||||||
b'a' => Self::ForceUpdate,
|
|
||||||
_ => Self::UnknownOpcode,
|
_ => Self::UnknownOpcode,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -94,19 +97,40 @@ impl GlobalState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub async fn process_fetch_update(state: Arc<GlobalState>) {
|
|
||||||
|
macro_rules! write_failure {
|
||||||
|
($s:ident, $r:ident) => {
|
||||||
|
$s.write_u32($r).await;
|
||||||
|
$s.write_u16(0x0000).await;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn process_fetch_update<W>(
|
||||||
|
state: Arc<GlobalState>,
|
||||||
|
stream: Arc<Mutex<W>>,
|
||||||
|
request_id: u32,
|
||||||
|
) where
|
||||||
|
W: tokio::io::AsyncWrite + Unpin + Send,
|
||||||
|
{
|
||||||
|
let cloned_writer = stream.clone();
|
||||||
|
let mut writer = cloned_writer.lock().await;
|
||||||
|
|
||||||
let global_state = state.clone();
|
let global_state = state.clone();
|
||||||
let response = match reqwest::get(TEST_YOUTUBE_VIDEO).await {
|
let response = match reqwest::get(TEST_YOUTUBE_VIDEO).await {
|
||||||
Ok(req) => req.text().await.unwrap(),
|
Ok(req) => req.text().await.unwrap(),
|
||||||
Err(x) => {
|
Err(x) => {
|
||||||
println!("Could not fetch the test video: {}", x);
|
println!("Could not fetch the test video: {}", x);
|
||||||
|
write_failure!(writer, request_id);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let player_id_str = match REGEX_PLAYER_ID.captures(&response).unwrap().get(1) {
|
let player_id_str = match REGEX_PLAYER_ID.captures(&response).unwrap().get(1) {
|
||||||
Some(result) => result.as_str(),
|
Some(result) => result.as_str(),
|
||||||
None => return,
|
None => {
|
||||||
|
write_failure!(writer, request_id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let player_id: u32 = u32::from_str_radix(player_id_str, 16).unwrap();
|
let player_id: u32 = u32::from_str_radix(player_id_str, 16).unwrap();
|
||||||
|
@ -118,6 +142,8 @@ pub async fn process_fetch_update(state: Arc<GlobalState>) {
|
||||||
|
|
||||||
if player_id == current_player_id {
|
if player_id == current_player_id {
|
||||||
// Player is already up to date
|
// Player is already up to date
|
||||||
|
writer.write_u32(request_id).await;
|
||||||
|
writer.write_u16(0xFFFF).await;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -130,6 +156,7 @@ pub async fn process_fetch_update(state: Arc<GlobalState>) {
|
||||||
Ok(req) => req.text().await.unwrap(),
|
Ok(req) => req.text().await.unwrap(),
|
||||||
Err(x) => {
|
Err(x) => {
|
||||||
println!("Could not fetch the player JS: {}", x);
|
println!("Could not fetch the player JS: {}", x);
|
||||||
|
write_failure!(writer, request_id);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -152,6 +179,7 @@ pub async fn process_fetch_update(state: Arc<GlobalState>) {
|
||||||
Ok(x) => x,
|
Ok(x) => x,
|
||||||
Err(x) => {
|
Err(x) => {
|
||||||
println!("Error: nsig regex compilation failed: {}", x);
|
println!("Error: nsig regex compilation failed: {}", x);
|
||||||
|
write_failure!(writer, request_id);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -186,10 +214,19 @@ pub async fn process_fetch_update(state: Arc<GlobalState>) {
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.as_str();
|
.as_str();
|
||||||
|
|
||||||
|
// Extract signature function name
|
||||||
|
|
||||||
current_player_info = global_state.player_info.lock().await;
|
current_player_info = global_state.player_info.lock().await;
|
||||||
current_player_info.player_id = player_id;
|
current_player_info.player_id = player_id;
|
||||||
current_player_info.nsig_function_code = nsig_function_code;
|
current_player_info.nsig_function_code = nsig_function_code;
|
||||||
println!("Successfully updated the player")
|
|
||||||
|
writer.write_u32(request_id).await;
|
||||||
|
// sync code to tell the client the player had updated
|
||||||
|
writer.write_u16(0xF44F).await;
|
||||||
|
|
||||||
|
writer.flush().await;
|
||||||
|
|
||||||
|
println!("Successfully updated the player");
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn process_decrypt_n_signature<W>(
|
pub async fn process_decrypt_n_signature<W>(
|
||||||
|
@ -200,6 +237,8 @@ pub async fn process_decrypt_n_signature<W>(
|
||||||
) where
|
) where
|
||||||
W: tokio::io::AsyncWrite + Unpin + Send,
|
W: tokio::io::AsyncWrite + Unpin + Send,
|
||||||
{
|
{
|
||||||
|
let cloned_writer = stream.clone();
|
||||||
|
let mut writer = cloned_writer.lock().await;
|
||||||
let global_state = state.clone();
|
let global_state = state.clone();
|
||||||
|
|
||||||
println!("Signature to be decrypted: {}", sig);
|
println!("Signature to be decrypted: {}", sig);
|
||||||
|
@ -219,6 +258,7 @@ pub async fn process_decrypt_n_signature<W>(
|
||||||
} else {
|
} else {
|
||||||
println!("JavaScript interpreter error (nsig code): {}", n);
|
println!("JavaScript interpreter error (nsig code): {}", n);
|
||||||
}
|
}
|
||||||
|
write_failure!(writer, request_id);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -240,13 +280,11 @@ pub async fn process_decrypt_n_signature<W>(
|
||||||
} else {
|
} else {
|
||||||
println!("JavaScript interpreter error (nsig code): {}", n);
|
println!("JavaScript interpreter error (nsig code): {}", n);
|
||||||
}
|
}
|
||||||
|
write_failure!(writer, request_id);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let cloned_writer = stream.clone();
|
|
||||||
let mut writer = cloned_writer.lock().await;
|
|
||||||
|
|
||||||
writer.write_u32(request_id).await;
|
writer.write_u32(request_id).await;
|
||||||
writer.write_u16(u16::try_from(decrypted_string.len()).unwrap()).await;
|
writer.write_u16(u16::try_from(decrypted_string.len()).unwrap()).await;
|
||||||
writer.write_all(decrypted_string.as_bytes()).await;
|
writer.write_all(decrypted_string.as_bytes()).await;
|
||||||
|
|
|
@ -86,8 +86,9 @@ async fn process_socket(state: Arc<GlobalState>, socket: UnixStream) -> Result<(
|
||||||
match opcode {
|
match opcode {
|
||||||
JobOpcode::ForceUpdate => {
|
JobOpcode::ForceUpdate => {
|
||||||
let cloned_state = state.clone();
|
let cloned_state = state.clone();
|
||||||
|
let cloned_stream = cloned_writestream.clone();
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
process_fetch_update(cloned_state).await;
|
process_fetch_update(cloned_state, cloned_stream, request_id).await;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
JobOpcode::DecryptNSignature => {
|
JobOpcode::DecryptNSignature => {
|
||||||
|
|
Loading…
Add table
Reference in a new issue