Rust में Base64 डिकोडिंग: एक सम्पूर्ण गाइड
आपके Rust प्रोग्राम में एक स्ट्रिंग उतर आती है: अक्षर और अंक, कभी-कभार एक प्लस या स्लैश, और आख़िर में लटके एक-दो शकनाक =। यह Base64 है, और यह गाइड उसी के बारे में है कि मूल बाइट्स बिना किसी झटके के वापस कैसे पाए जाएं। इस साइट का होम पेज इस फ़ॉर्मैट को पूरा-पूरा गहराई से समझाता है, इसलिए यहाँ बस सौदे की शक्ल दोहरानी है: चार वर्णमाला चिह्नों का मतलब तीन इनपुट बाइट्स हैं, और पूंछ में एक या दो = चर बताते हैं कि असली डेटा कहाँ ख़त्म हुआ। डिकोडिंग वही सौदा उल्टी दिशा में चलाती है, और नीचे की हर बात इसी को जान-बूझ कर करने के बारे में है।
Rust को ज़्यादातर भाषाओं से अलग कर देने वाला एक मोड़: स्टैंडर्ड लाइब्रेरी में Base64 की बात ही नहीं है। std में छिपा कोई base64_decode() नहीं है, और कोई use std::... भी नहीं जो किसी को बुला लाए। एकोसिस्टम ने बस एक ही क्रेट पर फैसला किया, जिसका नाम सिर्फ़ base64 है, और वह पूरे एकोसिस्टम की रीढ़ बन गया: वर्ज़न 0.23.1 4 अगस्त 2026 को रिलीज़ हुआ, दिसंबर 2015 की पहली रिलीज़ से अब तक इस क्रेट ने 45 वर्ज़न पब्लिश किए हैं, और उसका डाउनलोड काउंटर 1.5 अरब के आसपास बैठा है। आप लगभग पक्के तौर पर पहले से इसी क्रेट के रास्ते Base64 डिकोड कर रहे हैं, सीधे या jsonwebtoken, pem या serde_with जैसे किसी के ज़रिए, जो सब इसी पर निर्भर हैं।
वह एक क्रेट और उसकी मंडली
अगर मशीन पर अभी Rust खुद नहीं है, तो आपका ऑपरेटिंग सिस्टम इसे साथ देता है: Debian और Ubuntu पर rustc और cargo, macOS और Windows पर एक पैकेज या इंस्टॉलर, या ऑफिशियल इंस्टॉलर जो rustup सेटअप करता है:
# Debian / Ubuntu
sudo apt install rustc cargo
# या ऑफिशियल इंस्टॉलर, जो rustup और cargo सेटअप करता है
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
फिर क्रेट, किसी भी cargo प्रोजेक्ट के अंदर। यह एक ही लाइन पूरा इंस्टॉल है, और इससे बिल्कुल ज़ीरो डिपेंडेंसीज़ आती हैं:
cargo new my-app
cd my-app
cargo add base64
तीन वैकल्पिक फ़ीचर्स बिल्ड का रूप तय करते हैं। std डिफ़ॉल्ट रूप से on रहता है और std::io स्ट्रीमिंग टाइप्स, स्टैंडर्ड Error इम्प्लीमेंटेशन और हीप अलोकेशन चालू करता है। alloc पूरी स्टैंडर्ड लाइब्रेरी के बिना इम्बेडेड no_std बिल्ड्स के लिए अलोकेशन वाले APIs देता है। simd-unsafe डिफ़ॉल्ट रूप से on रहता है और उन वेक्टराइज़्ड इंजनों को गेट करता है जो आप आगे देखेंगे। कम से कम सपोर्टेड Rust वर्ज़न 1.71.0 है, इसलिए कोई भी हालिया वर्ज़न इसे चला लेगा। मूल क्रेट के चारों ओर एक छोटी विशेषज्ञ मंडली कक्षा में चलती है, हर एक उस किनारे के लिए जो मूल क्रेट जान-बूझ कर आपको छोड़ देता है:
| क्रेट | वर्ज़न (2026) | यह क्या लाता है | इसे कब चुनें |
|---|---|---|---|
base64ct |
1.8 | RustCrypto प्रोजेक्ट का कन्स्टेंट-टाइम डिकोडिंग; हीप APIs alloc फीचर के पीछे बंद हैं |
जो बाइट्स आप डिकोड करते हैं वे टाइमिंग के रास्ते जानकारी लीक कर सकती हैं, जैसे कुंजी का मटेरियल |
data-encoding |
2.11 | base32, hex और साथियों के साथ Base64, मिलनसार MIME वेरिएंट्स के साथ और स्लाइस स्तर पर एन्कोड/डिकोड | एक कंपोनेंट को गंदे, रैप्ड या मल्टी-प्रोटोकॉल इनपुट को पार्स करना है |
base64-turbo |
0.3 | एक नया कोडेक जो 100 GiB/s से ऊपर तक पहुँचता है, AVX512, AVX2 और NEON कर्नेल के साथ, साथ ही एक सुरक्षित स्केलर फ़ॉलबैक | थ्रूपुट ही पूरा मक़सद है और स्टैंडर्ड इंजन साइकल्स मेज़ पर छोड़कर रह जाता है |
इनमें से कोई भी दैनिक काम का विकल्प नहीं है। ज़्यादातर Rust प्रोग्राम्स के लिए, base64 अकेला ही सही और पूरा जवाब है, और इस लेख का बाक़ी हिस्सा डिकोडिंग खुद के लिए बस उसी एक क्रेट का इस्तेमाल करता है, और मंडली के विशेषज्ञों को तभी बुलाता है जब काम base64 से बड़ा हो।
तीन पंक्तियों में आपके बाइट्स तक
डिकोडिंग की ज़िंदगी का नब्बे प्रतिशत तीन पंक्तियों में समा जाता है। मानक स्मोक टेस्ट मशहूर TWFu स्ट्रिंग का इस्तेमाल करता है:
use base64::prelude::*;
fn main() {
let packed = "TWFu";
let bytes = BASE64_STANDARD.decode(packed).expect("valid base64");
println!("{}", String::from_utf8(bytes).expect("valid utf-8"));
}
आउटपुट Man है, और उस छोटी सी रस्म में तीन बातें याद रखने लायक हैं। पहली, decode() हमेशा आपको Vec<u8> सौंपता है, कभी स्ट्रिंग नहीं। यह एक फ़ीचर है, ग़लती नहीं: Base64 एक वाक्य, एक JPEG या एक सर्टिफ़िकेट ला सकता है, और यह पता चलने से पहले कि आपके पास क्या है, इनमें से किसी को अलग-तरह से नहीं समझना चाहिए। दूसरी, बाइट्स से टेक्स्ट की छलांग एक अलग, जान-बूझ कर लिया गया कदम है, String::from_utf8() के रास्ते, और बस उसी कदम पर कैरेक्टर सेट का फैसला बसता है। तीसरी, prelude मॉड्यूल चुपचाप एक साथ दो चीज़ें सौंप देता है: BASE64_STANDARD इंजन और Engine ट्रेट, जिसके मेटोड्स आप कॉल कर रहे हैं। अगर आपको एक्सप्लिसिट इम्पोर्ट्स पसंद हैं, तो use base64::engine::general_purpose::STANDARD; के साथ use base64::Engine; वही दरवाज़ा है, बस नमप्लेट लगा हुआ।
और क्योंकि आप आख़िरकार वह डिकोड करेंगे जो आपने खुद एन्कोड किया था, यहाँ है वह राउंड ट्रिप जो साबित करता है कि दोनों दिशाएँ एकमत हैं। एन्कोडिंग को sister साइट पर अपना पूरा गाइड मिलता है; वह यहाँ बस टेस्ट डेटा बनाने के लिए दिखा है:
use base64::prelude::*;
fn main() {
let packed = BASE64_STANDARD.encode("Hello, world!");
println!("{packed}"); // SGVsbG8sIHdvcmxkIQ==
let back = BASE64_STANDARD.decode(packed).unwrap();
println!("{}", String::from_utf8(back).unwrap()); // Hello, world!
}
TWFu को अपनी पीछे की जेब में रखिए, अपने लिखे हर डिकोड पाथ का स्मोक टेस्ट: अगर वह TWFu को Man में बदल दे, तो मशीन ईमानदार है।
ना बोलने के चार तरीके
यही वह सेक्शन है जो दो बजे रात को आपकी जान बचाएगा, क्योंकि जब प्रोडक्शन की कोई स्ट्रिंग फट जाए, तो आप जानना चाहेंगे कि क्रेट ठीक-ठीक किस चीज़ की शिकायत कर रहा है। अच्छी ख़बर: वह ज़ोर से और बिल्कुल सटीक शिकायत करता है। DecodeError में बिल्कुल चार वेरिएंट्स हैं, और यहाँ है कि हर एक आम दोषियों के परिवार पर कैसे बोलता है, सब को सख़्त स्टैंडर्ड इंजन के रास्ते खिलाया:
| इनपुट | क्या ग़लती है | बिल्कुल एरर |
|---|---|---|
"SGVs bG8s" |
एक स्पेस छुपकर आ गया | Invalid symbol 32, offset 4. |
"SGVs\nbG8s" |
एक लाइन ब्रेक छुपकर आ गया | Invalid symbol 10, offset 4. |
"SG=VsbG8="" |
स्ट्रिंग के बीच में पैडिंग | Invalid symbol 61, offset 2. |
"SGVsbG8sIHdvcmxkIQ==xx" |
पैडिंग के आगे कचरा | Invalid symbol 61, offset 18. |
"S" |
एक चिह्न से बाइट बन ही नहीं सकती | Invalid input length: 1 |
"SGV" |
तीन चिह्न, बिना उस पैडिंग के जो ज़रूर फॉलो करनी चाहिए | Invalid padding |
"SGVs$bG8="" |
$ वर्णमाला में नहीं है |
Invalid symbol 36, offset 4. |
ध्यान दीजिए कि Invalid symbol मैसेज आपको दोनों चीज़ें बताता है - दोषी बाइट की वैल्यू और उसका ऑफ़सेट - ताकि आप सीधे घटनास्थल पर कूद सकें। InvalidLength वेरिएंट सबसे चुनेबाज़ है: वर्ज़न 0.22.0 से यह खास तभी फायर होता है जब वैलिड चिह्नों की संख्या असंभव हो, यानी एक ऐसी लंबाई जो चार के गुणज से बिल्कुल एक ज़्यादा हो, जबकि दूसरी ख़राब लंबाइयाँ पैडिंग एरर के रूप में सतह पर आती हैं। यहाँ है पूरा match, उन दिनों के लिए जब आप हर असफलता को अलग-अलग हँडल करना चाहें:
use base64::DecodeError;
use base64::prelude::*;
fn triage(dirty: &str) {
match BASE64_STANDARD.decode(dirty) {
Ok(_) => println!("{dirty:?} sailed through"),
Err(DecodeError::InvalidByte(offset, byte)) =>
println!("{dirty:?}: symbol {byte} at {offset} is not in the alphabet"),
Err(DecodeError::InvalidLength(symbols)) =>
println!("{dirty:?}: {symbols} valid symbols is impossible"),
Err(DecodeError::InvalidLastSymbol { offset, .. }) =>
println!("{dirty:?}: trailing bits at {offset} suggest truncation"),
Err(DecodeError::InvalidPadding) =>
println!("{dirty:?}: padding is wrong or missing"),
}
}
यह जान-बूझ कर सख़्ताई ख़ुद स्टैंडर्ड तक लौटती है। RFC 4648 सेक्शन 12 चेतावनी देता है कि वर्णमाला से बाहर के चरों का दुरुपयोग छुपा हुआ चैनल की तरह हो सकता है - आउट-ऑफ़-बैंड जानकारी चुपचाप बाहर भेजने के लिए, या लापरवाह पार्सर में बग्स छेदने के लिए - और यह सुझाव देता है कि डिकोडर उन्हें ठुकरा दें। MIME स्पेक वह मशहूर अपवाद है, जो डिकोडरों को साफ़ कहता है कि बिखरे हुए चरों को अनदेखा करें, और वही इनपुट शक्ल है जिससे निपटना नीचे का ईमेल सेक्शन आपको दिखाएगा।
पैडिंग पर तीन रुखें
दुनिया की हर Base64 स्ट्रिंग पैडिंग के बारे में एक चुपचाप वचन देती है, और वर्ज़न 0.23 आपको DecodePaddingMode enum के रास्ते चुनने देता है कि कौन-सा वचन लागू करना है। तीन मोड्स हैं, और व्यवहार का फ़र्क़ याद करने लायक है। यहाँ है Zm8 का स्कोरकार्ड, जो fo शब्द है बिना उसके = के:
| मोड | "Zm8", बिना पैडिंग |
"Zm8=", पैडेड |
इसे कब इस्तेमाल करें |
|---|---|---|---|
RequireCanonical, डिफ़ॉल्ट |
Err(Invalid padding) |
Ok([102, 111]) |
आप डेटा ख़ुद बनाते हैं और ख़ुद इस्तेमाल भी करते हैं |
Indifferent |
Ok([102, 111]) |
Ok([102, 111]) |
मिक्स्ड सोर्स से डेटा मिल रहा हो |
RequireNone |
Ok([102, 111]) |
Err(Invalid padding) |
बिना पैडिंग वाला प्रोटोकॉल चला रहे हों |
use base64::engine::general_purpose::{GeneralPurpose, GeneralPurposeConfig, STANDARD_PAD_INDIFFERENT};
use base64::engine::DecodePaddingMode;
use base64::prelude::*;
let strict = BASE64_STANDARD; // RequireCanonical ही डिफ़ॉल्ट है
let flexible = STANDARD_PAD_INDIFFERENT;
let bare = GeneralPurpose::new(
&base64::alphabet::STANDARD,
GeneralPurposeConfig::new().with_decode_padding_mode(DecodePaddingMode::RequireNone),
);
println!("{:?}", strict.decode("Zm8")); // Err(Invalid padding)
println!("{:?}", flexible.decode("Zm8")); // Ok([102, 111])
println!("{:?}", flexible.decode("Zm8=")); // Ok([102, 111])
println!("{:?}", bare.decode("Zm8=")); // Err(Invalid padding)
डिफ़ॉल्ट स्टैंडर्ड के पीछे चलते हैं: RFC 4648 सेक्शन 3.2 कहता है कि इम्प्लीमेंटेशन को एन्कोडेड डेटा के अंत में उचित पैड चरों शामिल करने होंगे, जब तक कि चारों ओर का स्पेसिफिकेशन कुछ और न कहता हो, और इसीलिए स्टॉक STANDARD इंजन उन्हें ज़रूरी मानता है। और यह चुनाव बस बारीकियों की ज़िद के लिए नहीं, सुरक्षा के लिए भी मायने रखता है। एक ही डेटा की दोनों स्पेलिंग्स - पैडेड और बिना पैडिंग - को स्वीकार करना Base64 को लचीला बना देता है: एक ही लॉजिकल पेलोड को दो तरह से लिखा जा सकता है, और कोई भी कोड जो किसी वैल्यू की एक कैनोनिकल स्पेलिंग मानती है, उसकी आँख कड़की जा सकती है। 2022 का पेपर "अमल में Base64 की लचिली" (Chatzigiannis और Chalkias, ePrint 2022/361) असली नतीजों का दस्तावेज़ीकरण करता है, और क्रेट की अपनी डॉक्यूमेंटेशन इस पेपर को लिंक करती है। प्रैक्टिकल नियम: हर प्रोटोकॉल के लिए एक मोड चुनें, और हर बाउंडरी पर सख़्त रहें जहाँ प्रोड्यूसर आपकी निगरानी में नहीं है।
आख़िरी चिह्न के छिपे बिट्स
यहाँ वह ख़राबी है जो हर चर चेक से बचकर निकल जाती है। हर Base64 चिह्न 6 बिट्स लेकर आता है, और 3 इनपुट बाइट्स (24 बिट्स) बिल्कुल 4 चिह्नों बन जाते हैं। जब इनपुट में बस 1 या 2 बाइट्स हों, तो आख़िरी चिह्न में खाली बिट्स बचते हैं, और RFC साफ़ है: कंपलायंट एन्कोडर को उन खाली बिट्स को ज़ीरो करना ज़रूरी है। ख़राब या दुष्ट एन्कोडर वहाँ कचरा छोड़ सकता है, और नतीजा फिर भी वर्णमाला टेस्ट, लंबाई टेस्ट और पैडिंग टेस्ट से गुज़र जाता है, जबकि चुपचाप ख़राब पूंछ लेकर चलता है। सख़्त इंजन आपकी पीठ थपथपाता है, एक अनोखे तरह से विस्तृत एरर के साथ जो आपको शक भरे बिट्स तक दिखा देता है:
use base64::engine::general_purpose::{GeneralPurpose, GeneralPurposeConfig};
use base64::prelude::*;
println!("{:?}", BASE64_STANDARD.decode("MT=="));
// Err(Invalid last symbol 0x54 ('T') at offset 1, decoded as 0b00010011.)
let lenient = GeneralPurpose::new(
&base64::alphabet::STANDARD,
GeneralPurposeConfig::new().with_decode_allow_trailing_bits(true),
);
println!("{}", String::from_utf8_lossy(&lenient.decode("MT==").unwrap()));
// 1
उस एरर वाला 0b00010011 दोषी चिह्न का डिकोडेड वैल्यू है, अमान्य हाई बिट्स सहित, और वर्ज़न 0.23.0 ने वह बात उजागर की बस इसलिए क्योंकि वरना उसे डीबग करना बहुत मुश्किल है। अगर आपको पता है कि आपके प्रोड्यूसर लापरवाह हैं, तो with_decode_allow_trailing_bits(true) कचरा ठुकराने की जगह उसे निगल लेता है। ब्राउज़रों ने उल्टा दांव खेला: WHATWG का forgiving-base64 अल्गोरिथम, जो JavaScript के atob() के पीछे है, ट्रेलिंग बिट्स के सवाल में जाहिर तौर पर दयालु है, जबकि Rust का डिफ़ॉल्ट फोरेंसिक एग्ज़ामिनर है। जानिए कि आप मेज़ के किस तरफ़ बैठे हैं।
Base64url: वह वर्णमाला जो सफ़र करती है
स्टैंडर्ड Base64 अपनी वर्णमाला के अंतिम दो स्थान + और / पर खर्च करता है, और बिल्कुल वही जगह है जहाँ URL उन्हें नहीं चाहते: क्वरी स्ट्रिंग में प्लस का मतलब स्पेस होता है, स्लैश एक नया पाथ सेगमेंट शुरू करता है, और लटका हुआ = किसी सेपरेटर जैसा पढ़ा जाता है। इसीलिए RFC 4648 सेक्शन 5 URL- और फ़ाइलनेम-सुरक्षित वर्णमाला की परिभाषा देता है, जो दो मुसीबतख़ोरों को - और _ से बदल देता है और, लंबाई आमतौर पर बहाल होने की वजह से, आमतौर पर पैडिंग भी गिरा देता है। RFC तो चेतावनी तक देता है कि इस एन्कोडिंग को स्टैंडर्ड Base64 के बराबर नहीं मानना चाहिए, और इंजन के नाम भी एकमत हैं:
use base64::engine::general_purpose::URL_SAFE_NO_PAD;
use base64::Engine;
let packed = URL_SAFE_NO_PAD.encode(b"\xfb\xef\xbe");
println!("{packed}"); // ----
let back = URL_SAFE_NO_PAD.decode(packed).unwrap();
println!("{back:02x?}"); // [fb, ef, be]
// स्टैंडर्ड इंजन वही इनपुट ठुकरा देता है,
// क्योंकि dash उसकी वर्णमाला में ही नहीं है
println!("{:?}", base64::prelude::BASE64_STANDARD.decode("----"));
// Err(Invalid symbol 45, offset 0.)
और अब वह वजह जिससे ज़्यादातर डेवलपर्स base64url से मिलते हैं: JSON Web Tokens। JWT तीन base64url हिस्से हैं जो बिंदुओं से जुड़े होते हैं, और उसमें झाँकना पाँच पंक्तियों का काम है:
use base64::engine::general_purpose::URL_SAFE_NO_PAD;
use base64::Engine;
let jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c";
let parts: Vec<&str> = jwt.split('.').collect();
let header = String::from_utf8(URL_SAFE_NO_PAD.decode(parts[0]).unwrap()).unwrap();
let payload = String::from_utf8(URL_SAFE_NO_PAD.decode(parts[1]).unwrap()).unwrap();
println!("{header}");
println!("{payload}");
// {"alg":"HS256","typ":"JWT"}
// {"sub":"1234567890","name":"John Doe","iat":1516239022}
दो ईमानदार चेतावनियाँ। JWT को डिकोड करना झाँकना है, भरोसा नहीं: तीसरा हिस्सा सिग्नेचर है, और वह तब तक कुछ भी नहीं बोलता जब तक उसे किसी कुंजी के ख़िलाफ़ जाँचा न जाए, और यही jsonwebtoken क्रेट (2026 में वर्ज़न 11) का काम है। वर्ज़न 11 का एक तेज़ किनारा है: Cargo.toml में rust_crypto या aws_lc_rs फ़ीचर्स में से बिल्कुल एक के चालू होना ज़रूरी है, वरना पहली बार टोकन साइन या वैरिफ़ाई करते ही वह पैनिक कर जाएगा, और उसका Validation बिल्डर exp क्लेम को डिफ़ॉल्ट रूप में ज़रूरी मानता है, इसलिए दूसरी लाइब्रेरीज़ के लिए बने टोकन को एडजस्टेड वैलिडेशन की ज़रूरत हो सकती है। और डिकोडिंग ही वह जगह है जहाँ वर्णमाला का चुनाव कस जाता है: URL-सुरक्षित स्ट्रिंग BASE64_STANDARD को खिला दीजिए, या उल्टा, और आपको रिजेक्शन मिलेगा, क्योंकि -, _ और मिसिंग पैडिंग तीनों दूसरे इंजन के लिए अमान्य हैं। हर बार इंजन को प्रोटोकॉल से मिलाइए।
बाइट्स शब्द नहीं होते
इस लेख का हर डिकोडर जान-बूझ कर बाइट्स पर ही रुकता है, और Rust में यह ज़्यादातर भाषाओं से आसान है, क्योंकि कोई छिपा हुआ कैरेक्टर सेट स्टेप नहीं है जो ग़लत हो सके। Base64 एक बाइट फ़ॉर्मैट है, बस। "वह क्या टेक्स्ट था?" का सवाल आपका है, और आधुनिक वेब का डिफ़ॉल्ट जवाब UTF-8 है, जिसे आप स्टैंडर्ड लाइब्रेरी की एक पंक्ति से गेट कर सकते हैं:
use base64::prelude::*;
let packed = "Y2Fmw6k="; // एक्सेंट वाला cafe शब्द, पैक किया हुआ
let bytes = BASE64_STANDARD.decode(packed).unwrap();
match std::str::from_utf8(&bytes) {
Ok(text) => println!("{text}"),
Err(_) => eprintln!("not utf-8: {bytes:02x?}"),
}
मल्टीबाइट वाला ख़ुशनुमा रास्ता नेटवर्क पर मिलने वाली हर चीज़ को कवर करता है:
| मूल टेक्स्ट | Base64 | वापस डिकोड |
|---|---|---|
café |
Y2Fmw6k= |
हाँ |
日本語 |
5pel5pys6Kqe |
हाँ |
naïve résumé |
bmHDr3ZlIHLDqXN1bcOp |
हाँ |
😀 |
8J+YgA== |
हाँ |
π ≈ 3.14159 |
z4Ag4omIIDMuMTQxNTk= |
हाँ |
और जब पेलोड बिल्कुल टेक्स्ट ही न हो, तो वही कोड बस अलग अंत पा लेता है। यहाँ है PNG फ़ाइल का मैजिक नंबर, चार बाइट्स 89 50 4E 47 और उसके बाद आने वाली CRLF जोड़ी:
use base64::prelude::*;
let packed = "iVBORw0KGgo=";
let bytes = BASE64_STANDARD.decode(packed).unwrap();
println!("{bytes:02x?}"); // [89, 50, 4e, 47, 0d, 0a, 1a, 0a]
assert!(std::str::from_utf8(&bytes).is_err());
std::fs::write("sprite.png", &bytes).unwrap(); // बाइट्स बाहर, टेक्स्ट नहीं
अनुभव-नियम छोटा है: UTF-8 मानिए, std::str::from_utf8() से जाँचिए, और जो भी फ़ेल हो उसे fs::write, डेटाबेस ब्लॉब या वह सिंक जहाँ से आया हो, उसके लिए बाइट पेलोड समझिए। असली कैरेक्टर सेट की तरफ़ हाथ तभी बढ़ाना है जब डेटा लेगासी हो और कभी माइग्रेट न हुआ हो। encoding_rs क्रेट (वर्ज़न 0.8) पुरानी एन्कोडिंग्स का नाम जानता है और बदल भी देता है:
use base64::prelude::*;
use encoding_rs::Encoding;
let packed = "Y2Fm6Q=="; // एक्सेंट वाला cafe, Latin-1 बाइट्स से पैक किया हुआ
let bytes = BASE64_STANDARD.decode(packed).unwrap();
let (text, _, _) = Encoding::for_label(b"windows-1252").unwrap().decode(&bytes);
println!("{text}"); // एक्सेंट वाला cafe शब्द, UTF-8 के रूप में
base64 क्रेट के अंदर कोई "Latin-1 के रूप में डिकोड" मोड नहीं है जिसे ग़लत सेट कर दिया जाए, क्योंकि वह आपके लिए कभी अंदाज़ा नहीं लगाता। यही वह अनुशासन है जिसे आप बरकरार रखें: क्रेट आपको बाइट्स देता है, और उनके मतलब आप तय करते हैं।
ईमेल से बचकर आया इनपुट
मेल सिस्टम से बचकर निकला Base64 लाइन ब्रेक्स के साथ आता है: MIME हर लाइन 76 चरों पर रैप करता है (PEM ब्लॉक्स 64 पर), और MIME स्पेक कंपलायंट डिकोडर को साफ़ तौर पर कहता है कि वर्णमाला से बाहर के चरों को अनदेखा करें, लाइन ब्रेक्स समेत। हमारा इंजन MIME-कंपलायंट का बिल्कुल उल्टा है: वह पहली ही लाइन ब्रेक को ठुकरा देता है, और स्ट्रीमिंग रीडर उस अस्वीकृति की रिपोर्ट एक I/O एरर के रूप में करता है, जिसमें वही सटीक DecodeError रैप्ड है:
use std::io::Read;
use base64::prelude::*;
use base64::read::DecoderReader;
let wrapped_in = "SGVs\nbG8s";
let mut reader = DecoderReader::new(wrapped_in.as_bytes(), &BASE64_STANDARD);
let mut out = Vec::new();
println!("{:?}", reader.read_to_end(&mut out).map(|_| out));
// Err(Custom { kind: InvalidData, error: Invalid symbol 10, offset 4. })
दोनों रुखें उसी RFC तक लौटते हैं, जो यह चुनाव चारों ओर के स्पेसिफिकेशन के सौंपता है, और base64 क्रेट ने सख़्त वाला चुना। यह पहली बार नहीं है जब उसने राय बदली: वर्ज़न 0.5.0 बिल्ट-इन MIME लाइन रैपिंग और व्हाइटस्पेस हैंडलिंग के साथ रिलीज़ हुआ, और वर्ज़न 0.10.0 ने दोनों हटा दिए, इस तर्क के साथ कि रैपिंग किसी जनरल लाइब्रेरी के लिए ज़्यादा राय-ख़ास थी और no_std की कहानी मुश्किल बना देती थी। इसलिए असल दुनिया के रैप्ड इनपुट की रेसिपी वही है जो क्रेट की अपनी डॉक्यूमेंटेशन सुझाती है: पहले गैर-वर्णमाला चर हटाइए, फिर डिकोड कीजिए। इन-मेमोरी स्ट्रिंग के लिए यह एक फ़िल्टर है:
use base64::prelude::*;
fn strip_non_b64(input: &[u8]) -> Vec<u8> {
input.iter().copied().filter(|b| !b" \n\r\t\x0b\x0c".contains(b)).collect()
}
fn main() {
let wrapped = "SGVs\nbG8s\r\nIHN0\nYW5kYXJk";
let clean = strip_non_b64(wrapped.as_bytes());
let bytes = BASE64_STANDARD.decode(clean).unwrap();
println!("{}", String::from_utf8_lossy(&bytes)); // Hello, standard
}
अगर आप एक ऐसा डिकोडर चाहते हैं जो ब्रेक्स के ऊपर सीधे देख ले, तो data-encoding क्रेट का BASE64_MIME_PERMISSIVE कॉन्स्टेंट बस वही है: यह "SGVsbG8s\r\nd29ybGQh\r\n" को Hello,world! में डिकोड कर देता है बिना आपके किसी पंक्ति को छुए। उन स्ट्रीम्स के लिए जहाँ आप पूरा इनपुट थामे नहीं रख सकते, क्रेट की FAQ iter_read क्रेट की तरफ़ इशारा करती है बाइट स्ट्रीम फ़िल्टर करने के लिए, या एक छोटे से Read रैपर की तरफ़ जो अनचाहे बाइट्स को आते ही गिरा दे। "दयालु डिकोडर" हाथ से बनाने से पहले एक चेतावनी: गैर-वर्णमाला चरों को चुपचाप अनदेखा करना वही व्यवहार है जो RFC 4648 सेक्शन 12 छुपा हुआ चैनल के रूप में चिह्नित करता है, इसलिए बस उम्मीद की गई व्हाइटस्पेस हटाइए, और बाक़ी सब ठुकरा दीजिए।
जब पूरा मैसेज हाथ में हो, तो मेल पार्सर base64 का काम आपके लिए कर देता है। mail-parser क्रेट (वर्ज़न 0.11) पार्स करते समय हर Content-Transfer-Encoding: base64 पार्ट को डिकोड करता है, इसलिए अटैचमेंट्स रॉ बाइट्स के रूप में वापस आते हैं, पहले से अन-रैप्ड और डिकोड हो चुके:
use mail_parser::MessageParser;
let email = br#"From: art@vandelay.com
To: jane@example.com
Subject: gift
MIME-Version: 1.0
Content-Type: multipart/mixed; boundary="festivus"
--festivus
Content-Type: text/plain; charset="us-ascii"
Content-Transfer-Encoding: base64
SGVsbG8gZnJvbSBlbWFpbA==
--festivus
Content-Type: image/gif
Content-Transfer-Encoding: Base64
Content-Disposition: attachment; filename="tiny.gif"
R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7
--festivus--
"#;
let message = MessageParser::default().parse(email).unwrap();
for part in message.attachments() {
let name = part
.headers()
.iter()
.find(|h| h.name().eq_ignore_ascii_case("content-disposition"))
.and_then(|h| h.value().clone().unwrap_content_type()
.attribute("filename").map(|n| n.to_string()));
let bytes = part.contents().to_vec();
println!("{name:?}: {} bytes", bytes.len());
}
// Some("tiny.gif"): 42 bytes
वह GIF अटैचमेंट 42 बाइट्स में डिकोड होता है, शुरुआत चार बाइट्स 47 49 46 38 से, जो ASCII के अक्षर GIF8 हैं। आपने एक पंक्ति भी Base64 कोड नहीं लिखी, और यही पार्सर इस्तेमाल करने का मक़सद है: एन्कोडिंग की बारीकियाँ लाइब्रेरी का सिरदर्द हैं।
बड़ा पेलोड
स्ट्रिंग आसान हैं; फ़ाइलें वही जगह है जहाँ Base64 अपना ख़र्चा कमाता है, और क्रेट Rust के बाक़ी io जैसा ही स्ट्रीमिंग फिलसोफी से जवाब देता है। read::DecoderReader किसी भी रीडर को रैप करता है और आप पढ़ते ही उसे डिकोडेड बाइट्स बिना किसी रुकावट के सौंपता है, इसलिए मल्टी-गिगाबाइट एन्कोडेड फ़ाइल को कभी भी मेमोरी में पूरा फिट होने की ज़रूरत नहीं। नीचे के उदाहरण के लिए, स्ट्रिंग dGhlIHF1aWNrIGJyb3duIGZveCBqdW1wcyBvdmVyIHRoZSBsYXp5IGRvZw== को fox.b64 नाम की साधारण टेक्स्ट फ़ाइल में सेव कीजिए:
use std::io::Read;
use base64::prelude::*;
use base64::read::DecoderReader;
fn main() {
let packed = std::fs::read("fox.b64").unwrap();
let mut decoder = DecoderReader::new(&packed[..], &BASE64_STANDARD);
let mut plain = Vec::new();
decoder.read_to_end(&mut plain).unwrap();
println!("{}", String::from_utf8_lossy(&plain));
// the quick brown fox jumps over the lazy dog
}
वही विचार io::copy के साथ एक पंक्ति तक सिकोड़ जाता है: फ़ाइल के चारों ओर DecoderReader बनाइए और उसे किसी भी राइटर में कॉपी कीजिए, और डिकोडिंग रास्ते में ही हो जाती है। आधिकारिक डॉक्यूमेंटेशन में पेलोड को कन्स्टेंट स्पेस में वैलिडेट करने का एक प्यारी सी ट्रिक भी है, स्टेटिकली साइज़ेड बफ़र का इस्तेमाल करके और डिकोडेड डेटा की बिल्कुल कोई अलोकेशन किए बिना:
use std::io::Cursor;
use std::io::Read;
use base64::prelude::*;
use base64::read::DecoderReader;
fn is_valid_base64(input: &str) -> bool {
let mut cursor = Cursor::new(input.as_bytes());
let mut decoder = DecoderReader::new(&mut cursor, &BASE64_STANDARD);
let mut buf = [0u8; 128];
loop {
match decoder.read(&mut buf) {
Ok(0) => return true, // एरर के बिना अंत तक पढ़ा
Ok(_) => continue,
Err(_) => return false, // कुछ base64 नहीं था
}
}
}
fn main() {
println!("{}", is_valid_base64("SGVsbG8sIHdvcmxkIQ==")); // true
println!("{}", is_valid_base64("dt==")); // false
}
उन पेलोड के लिए जो बड़े हों लेकिन आपके संभाले बफ़र में फिट हों, स्लाइस API बिना किसी झटके वाला विकल्प है: base64::decoded_len_estimate(len) len चिह्नों के लिए कन्ज़रवेटिव अधिकतम डिकोडेड साइज़ देता है, और decode_slice() सीधे आपके प्री-अलोकेटेड बफ़र में लिखता है, बिल्कुल सही बताता है कि कितनी बाइट्स लिखीं:
use base64::prelude::*;
let packed = "SGVsbG8sIHdvcmxkIQ==";
let cap = base64::decoded_len_estimate(packed.len()); // 15, सुरक्षित अधिकतम
let mut buf = vec![0u8; cap];
let written = BASE64_STANDARD.decode_slice(packed, &mut buf).unwrap();
buf.truncate(written);
println!("{}", std::str::from_utf8(&buf).unwrap()); // Hello, world!
अगर आपका बफ़र बहुत छोटा है, तो आपको पैनिक के बजाय साफ़ DecodeSliceError::OutputSliceTooSmall मिलता है, और decode_slice_unchecked() नाम का एक वेरिएंट भी है जो डिज़ाइन के हिसाब से पैनिक करता है, उन जगहों के लिए जहाँ "बहुत छोटा" एक प्रोग्रामर एरर है जिसे आप क्रैश करके पकड़ना पसंद करेंगे, घुमा-फिराकर छिपाने के बजाय। 0.22.0 से स्लाइस चेक आपकी पक्ष में कन्ज़रवेटिव है: यह तभी फ़ेल होता है जब आउटपुट सच में फिट न हो सके, इसलिए बिल्कुल सही-साइज़ का बफ़र काम करता है।
डिकोडेड बाइट्स कहाँ जाते हैं
Base64 Rust प्रोजेक्ट्स में "किसी रैंडम स्ट्रिंग" की तुलना में बहुत ज़्यादा बार दिखता है:
- API रिस्पॉन्सेस और वेबहुक्स जो अपने पेलोड्स में बायनेरी या नेस्टेड JSON को Base64 टेक्स्ट के रूप में एम्बेड करते हैं, क्लासिक फ़ाइल-अपलोड-जैसे-JSON वाला पैटर्न।
- JWT जाँच, जहाँ आप क्लेम्स पर नज़र डालने हेतु हेडर और पेलोड को डिकोड करते हैं और सिग्नेचर को
jsonwebtokenको सौपते हैं, जो असल में मायने रखने वाला हिस्सा है। - ईमेल-मंज़र वाला डेटा: MIME अटैचमेंट्स और जो कुछ भी किसी मेल सिस्टम से गुज़रा है, यही वजह है कि व्हाइटस्पेस सेक्शन मौजूद ही है।
- सर्टिफ़िकेट्स और कुंजीयों में PEM ब्लॉक्स,
-----BEGIN CERTIFICATE-----वाले सेक्शन जो हर TLS स्टैक चबाता है;pemक्रेट उन्हें आपके लिए पार्स करता है और, मुनासिब ही, अंदर सेbase64क्रेट पर बना है। - Data URIs जो आपके स्क्रैप या रेंडर किए हुए HTML और CSS में छिपे हैं,
data:image/png;base64,...वाली किस्म। - HTTP Basic ऑथ हेडर्स, जहाँ
Basic TWFuOnBhc3M=बसMan:passहै, छद्म रूप पहने हुए। - डेटाबेस और कॉन्फ़िग फ़ाइलें, जहाँ किसी को टेक्स्ट कॉलम या एनवायरनमेंट वेरिएबल के अंदर बायनेरी चाहिए था।
- क्रॉस-लैंग्वेज हैंडऑफ़्स: एक Python सर्विस ब्लॉब को पैक करती है, Rust उसे अनपैक करता है, और परिभाषा के हिसाब से दोनों तरफ़ वही वर्णमाला बोलती है।
JSON केस के लिए एक शॉर्टकट जानने लायक है: serde_with क्रेट (वर्ज़न 3) struct फील्ड को एनोटेट कर सकता है ताकि serde Base64 को दोनों दिशाओं में संभाले - बाहर जाते समय Vec<u8> फील्ड्स को टेक्स्ट में एन्कोड करके और अंदर आते समय उन्हें वापस डिकोड करके - और URL-सुरक्षित वेरिएंट सिर्फ़ एक पैरामीटर दूर है:
use serde::{Deserialize, Serialize};
use serde_with::serde_as;
#[serde_as]
#[derive(Debug, PartialEq, Serialize, Deserialize)]
struct Config {
#[serde_as(as = "serde_with::base64::Base64")]
blob: Vec<u8>,
}
let cfg = Config { blob: b"stored in a database".to_vec() };
let json = serde_json::to_string(&cfg).unwrap();
// {"blob":"c3RvcmVkIGluIGEgZGF0YWJhc2U="}
let back: Config = serde_json::from_str(&json).unwrap();
assert_eq!(back, cfg);
Data URIs दो-कदमी स्ट्रिंग ऑपरेशन के बाद एक साधारण डिकोड लेते हैं: कॉमा ढूँढिए, उसके आगे की चीज़ रखिए, और जाँचिए कि मेटाडेटा base64 शब्द पर ख़त्म हो:
use base64::prelude::*;
let uri = "data:image/png;base64,iVBORw0KGgo=";
let comma = uri.find(',').unwrap();
let meta = &uri[..comma];
let payload = &uri[comma + 1..];
let is_b64 = meta.rsplit(';').next().unwrap() == "base64";
let bytes = BASE64_STANDARD.decode(payload).unwrap();
println!("{is_b64}: {} bytes from {meta}", bytes.len());
// true: 8 bytes from data:image/png;base64
और वही एक नियम जो सब पर राज़ करेगा, दोहराने लायक है क्योंकि यह आज भी लोगों को फँसाता है: Base64 पैकिंग टेप है, लॉक नहीं। यह एनक्रिप्शन नहीं है और कंप्रेशन नहीं है - यह कंप्रेशन का उल्टा है - और यह लेख पढ़ने वाला कोई भी इसके हर काम को पलट सकता है। बिना रुके डिकोड कीजिए, ट्रस्ट चुन-छान कर।
सावधानी भरे कदमों का दशक
क्रेट की अपनी कहानी धीमे-धीमे नटों को कसने जैसी लगती है। वह दिसंबर 2015 में crates.io पर पहली बार दिखा, और वर्ज़न 0.5.0 ने गर्व से MIME सपोर्ट जोड़ा, कॉन्फ़िगर करने लायक लाइन एंडिंग्स और रैपिंग के साथ। फिर 2018 में वर्ज़न 0.10.0 ने रैपिंग और व्हाइटस्पेस हैंडलिंग हटा दी, लाइब्रेरी का फैसला था कि सामान्य-उद्देश्यीय क्रेट को डिकोड करना चाहिए और कविता ऐप्लिकेशन लेयर को छोड़नी चाहिए; वही रिलीज़ स्ट्रीमिंग एन्कोडर और इनवलैड ट्रेलिंग चिह्नों की पहचान भी जोड़ता है। 2022 में वर्ज़न 0.20.0 ने इंजन अब्स्ट्रैक्शन पेश की और पैडिंग डिफ़ॉल्ट पलट दी, ताकि स्टॉक इंजन कैनोनिकल पैडिंग ज़रूरी माने; 0.21.0 ने पुराने फ्री फ़ंक्शन्स जैसे base64::decode() को इंजन मेटोड्स के पक्ष में डेप्रिकेटेड कर दिया, कंपाइलर नोट "Use Engine::decode" के साथ (वे अब भी चलते हैं, और इसीलिए बहुत सा लेगासी कोड ख़ुशी-ख़ुशी कंपाइल होता है)। 2024 में वर्ज़न 0.22.0 ने एरर सेमेंटिक्स को तेज़ किया, InvalidLength के मतलब को साफ़ किया, और डिकोडिंग को 5 से 10 प्रतिशत तेज़ किया। और जुलाई 2026 में वर्ज़न 0.23.0 SIMD इंजन, कस्टम पैडिंग चिह्नों, साफ़ InvalidLastSymbol मैसेज और 1.71 तक MSRV बढ़ाकर आया, और 4 अगस्त के 0.23.1 पैच ने नॉन-SIMD आर्किटेक्चर्स के लिए टेस्ट स्यूट ठीक की। सावधानी भरे छोटे-छोटे कदमों का दशक, और वह क्रेट जो "यह तो बस base64 है, और और क्या चाहता है कोई?" से शुरू हुआ था, आज वेक्टराइज़्ड कर्नेल के साथ आता है।
फ़ॉर्मैट वेब से पुराना है, और इसीलिए सख़्ताई व्यक्तिगत लगती है। 1987 में Privacy-Enhanced Mail प्रोटोकॉल (RFC 989) को 7-बिट मेल चैनल्स पर बायनेरी डेटा ले जाने की ज़रूरत थी, और उसने इस एन्कोडिंग को 64-चरों की लाइन्स के साथ स्टैंडरडाइज़ किया; आपकी TLS स्टैक का जो भी -----BEGIN CERTIFICATE----- ब्लॉक कभी भरोसा कर चुकी है, वह उस फैसले का सीधा वंशज है। 1996 में MIME स्पेक (RFC 2045) ने स्कीम को अपन लिया, इसे उसके 64-चरों की वर्णमाला के नाम पर "base64" कहा, और 76-चरों की लाइन लंबाई तय की जो आज भी आपके ईमेल अटैचमेंट्स को रैप करती है। 2006 में RFC 4648 वह स्टैंडर्ड बन गया जिसे सब क्वोट करते हैं: वर्णमाला की टेबल्स, सेक्शन 5 का base64url वेरिएंट, और सख़्ताई के नियम जिन्हें यह क्रेट इतने खुलकर, मज़ा लेते हुए इम्प्लीमेंट करता है।
मज़ेदार तथ्य
क्योंकि एक पूरा गाइड मुस्कान पर ख़त्म होना चाहिए:
- शब्द "base64" का एन्कोड
YmFzZTY0होता है। खुद को डिस्क्राइब करता हुआ फ़ॉर्मैट बस मोर्स में बोलने वाले आईने का तकनीकी संस्करण है। - ख़ाली स्ट्रिंग ज़ीरो बाइट्स में डिकोड होती है, लेकिन
"AA=="एक बाइट में डिकोड होती है: NUL। Base64 में "कुछ नहीं" और "एक ज़ीरो" अलग-अलग जीव हैं। - आपकी देखी गई हर Base64-एन्कोडेड PNG
iVBORw0Kसे शुरू होती है। वह PNG मैजिक नंबर छद्म रूप में है, और यह इंटरनेट के सबसे पहचाने जाने वाले प्रीफिक्स में से एक है। - YouTube वीडियो IDs बिना पैडिंग वाला base64url हैं: 8-बाइट की वैल्यू बारह Base64 चर देती है, और ट्रेलिंग पैडिंग गिरा देने से वह परिचित 11-चरों का ID बच जाता है जिसे आप URL के किसी भी हिस्से में पेस्ट कर सकते हैं। पूरे इंटरनेट पर बिना पैडिंग वाले मोड के सबसे दिखने वाले इस्तेमालों में से एक।
- Bash सालों से बेस 64 में गिनती कर रहा है:
$((64#...))अरिथमेटिक लिटरल अपने अंकों को इस क्रम में लेता है -0-9,a-z,A-Z- और आख़िर में वैल्यूज़ 62 और 63 के लिए@और_, इसलिए आपका शेल खुलकर 64-चरों की वर्णमाला लेकर चलता है। - पुराने
crypt(3)पासवर्ड हैश एक Base64 वेरिएंट का इस्तेमाल करते थे जिसकी वर्णमाला./से शुरू होती है, और उसकी एक प्यारी सी ख़ासियत है: एन्कोडेड स्ट्रिंग्स को सॉर्ट करने से वही क्रम मिलता है जो मूल बाइट्स को सॉर्ट करने से मिलता है। वंशावली फ़ाइलों ने इम्बेडेड मल्टीमीडिया के लिए वही वर्णमाला इस्तेमाल की (GEDCOM 5.5; 5.5.1 रिवीज़न ने उसे गिरा दिया), औरbase64क्रेट उसेalphabet::CRYPTके रूप में देता है। - MIME गणित, पुराने अनुभव-नियम के हिसाब से जैसे अभी भी गिना जाता है: रैप्ड ईमेल पेलोड की कीमत अपने मूल आकार की लगभग 1.37 गुना होती है, साथ में कुछ सौ बाइट्स के बराबर हेडर्स। 1990s की मेल इंफ्रास्ट्रक्चर ने सच में हर अटैचमेंट पर वह शुल्क लिया।
- पूरा क्रेट
#![forbid(unsafe_code)]है, सिर्फ़ डिफ़ॉल्ट-onsimd-unsafeफीचर को छोड़कर, जिससे आप बाहर निकलते हैं, अंदर नहीं। एक शब्द, "unsafe", और वह किसी फीचर फ्लैग का नाम है। - Base64 इतना लचीला है कि सिक्योरिटी रिसर्चर्स को डर लगता है: वही बाइट्स पैडिंग के साथ या बिना लिखे जा सकते हैं, ट्रेलिंग बिट्स में कचरे के साथ भी, और दयालु डिकोडर उसे नोटिस भी नहीं करेंगे। 2022 के पेपर ने असली-दुनिया नतीजे दिखाए, और इसीलिए इस क्रेट का सख़्त डिफ़ॉल्ट किसी बॉडीगार्ड जैसा लगता है।
- Base64 एनक्रिप्शन नहीं है। अगर होती, तो आप इस लेख के किसी भी उदाहरण का आउटपुट पढ़ ही नहीं पाते। यह खिड़की वाली सीट है, तहखाना नहीं।
निष्कर्ष
अपने संग-सथ के हिसाब से इंजन चुनिए: BASE64_STANDARD हर चीज़ के लिए जिसे आप ख़ुद बनाते और संभालते हैं, STANDARD_PAD_INDIFFERENT मिक्स्ड-सोर्स सीमा के लिए, URL_SAFE_NO_PAD टोकन और URLs के लिए, और प्रोटोकॉल अपने खुद के नियमों की मांग करे तब हाथ से बना GeneralPurpose। चारों DecodeError वेरिएंट्स को अपनी सटीक शिकायत करने दीजिए, उन्हें टेक्स्ट कहने से पहले बाइट्स को std::str::from_utf8() के रास्ते गेट कीजिए, बड़ी चीज़ें DecoderReader से स्ट्रीम कीजिए, बस उम्मीद की गई व्हाइटस्पेस हटाइए, और टाइमिंग ख़तरा बन जाए तब base64ct की तरफ़ हाथ बढ़ाइए। सब कुछ डिकोड कीजिए, सिर्फ़ वही ट्रस्ट कीजिए जो वैरिफ़ाई हो। और अगर एक दिन आपको उल्टी दिशा में जाने की ज़रूरत हो - बाइट्स को सफ़र के लिए स्ट्रिंग में पैक करना, अनपैक करने की बजाय - तो sister लेख Rust में एन्कोडिंग कवर करता है, साइज़ की गणित से स्ट्रीमिंग के अंत तक।
अंतिम अपडेट: 2026-09-08
संबंधित लेख: Rust में Base64 एन्कोडिंग: एक सम्पूर्ण गाइड