add debug logging to ticket generation for debugging

This commit is contained in:
Maple 2026-03-24 23:17:22 +01:00
commit 4ad883a54d
10 changed files with 223 additions and 127 deletions

View file

@ -17,7 +17,7 @@ rand = "0.8.5"
cfg-if = "1.0.4"
hmac = "0.12.1"
md-5 = "^0.10.6"
tokio = { version = "1.43.0", features = ["macros", "rt-multi-thread", "net", "sync", "fs"] }
tokio = { version = "1.43.0", features = ["full"] }
hex = "0.4.3"
macros = { path = "../macros" }

View file

@ -161,7 +161,7 @@ impl TicketInternalData {
}
}
#[derive(Pod, Zeroable, Copy, Clone)]
#[derive(Pod, Zeroable, Debug, Copy, Clone)]
#[repr(C, packed)]
pub struct Ticket {
pub session_key: [u8; SESSION_KEY_LENGTH],

View file

@ -134,6 +134,7 @@ impl AuthHandler {
};
let source_login_data = (pid, &passwd[..]);
println!("{}, {:?}", pid, passwd);
let destination_login_data = self.destination_server_acct.get_login_data();
Ok((
@ -336,3 +337,82 @@ impl Auth for AuthHandler {
Err(ErrorCode::Core_Exception)
}
}
mod test {
use std::io::Cursor;
use rc4::{KeyInit, Rc4, StreamCipher};
use rnex_core::PID;
use rnex_core::kerberos::KerberosDateTime;
use rnex_core::rmc::structures::connection_data::ConnectionData;
use rnex_core::rmc::{
response::ErrorCode,
structures::{RmcSerialize, qresult::QResult},
};
use crate::kerberos::{self, derive_key};
use crate::rmc;
use crate::rmc::message::RMCMessage;
use crate::rmc::response::{RMCResponse, RMCResponseResult};
#[test]
fn test() {
return;
let packet = [
26, 1, 0, 0, 10, 1, 30, 0, 0, 0, 1, 128, 0, 0, 1, 0, 1, 0, 86, 4, 0, 0, 116, 0, 0, 0,
144, 209, 130, 175, 45, 215, 95, 55, 226, 192, 51, 54, 201, 84, 118, 150, 159, 164, 32,
103, 134, 252, 199, 168, 178, 5, 6, 208, 206, 241, 94, 23, 136, 37, 109, 247, 156, 252,
189, 233, 142, 115, 206, 72, 180, 57, 106, 223, 37, 59, 144, 208, 250, 197, 51, 202,
185, 156, 51, 159, 219, 117, 250, 103, 184, 1, 103, 108, 15, 14, 174, 160, 192, 146,
135, 10, 55, 125, 68, 181, 88, 127, 183, 34, 4, 213, 19, 146, 81, 56, 248, 213, 241,
168, 205, 253, 29, 10, 123, 198, 177, 157, 247, 209, 113, 167, 231, 42, 214, 15, 12,
200, 192, 230, 125, 227, 74, 0, 112, 114, 117, 100, 112, 115, 58, 47, 80, 73, 68, 61,
50, 59, 115, 105, 100, 61, 49, 59, 115, 116, 114, 101, 97, 109, 61, 49, 48, 59, 116,
121, 112, 101, 61, 50, 59, 97, 100, 100, 114, 101, 115, 115, 61, 57, 49, 46, 57, 56,
46, 49, 50, 56, 46, 56, 54, 59, 112, 111, 114, 116, 61, 54, 48, 48, 49, 59, 67, 73, 68,
61, 49, 0, 0, 0, 0, 0, 1, 0, 0, 162, 243, 240, 168, 31, 0, 0, 0, 51, 0, 98, 114, 97,
110, 99, 104, 58, 111, 114, 105, 103, 105, 110, 47, 112, 114, 111, 106, 101, 99, 116,
47, 119, 117, 112, 45, 97, 103, 109, 106, 32, 98, 117, 105, 108, 100, 58, 51, 95, 56,
95, 49, 53, 95, 50, 48, 48, 52, 95, 48, 0,
];
let rmc_packet = RMCResponse::new(&mut Cursor::new(&packet)).unwrap();
println!("{:?}", rmc_packet);
let RMCResponseResult::Success {
call_id,
method_id,
data,
} = rmc_packet.response_result
else {
panic!();
};
println!("{}", hex::encode(&data));
let mut data =
<(QResult, PID, Vec<u8>, ConnectionData, String) as RmcSerialize>::deserialize(
&mut Cursor::new(&data[..]),
)
.unwrap();
println!("{:?}", data);
let key = derive_key(1110, "AAAAAAAAAAAAAAAA".as_bytes());
let mut rc4 = Rc4::new((&key).into());
rc4.apply_keystream(&mut data.2);
println!("raw tick: {:?}", data.2);
let tick: &kerberos::Ticket =
bytemuck::from_bytes(&data.2[..size_of::<kerberos::Ticket>()]);
let remainder = &data.2[size_of::<kerberos::Ticket>()..];
println!("tick: {:?}", tick);
let data = <Vec<u8> as RmcSerialize>::deserialize(&mut Cursor::new(remainder)).unwrap();
println!("inner ticket raw: {:?}", data);
println!("{:?}", data);
}
}

View file

@ -2,17 +2,18 @@
// attributes but this gets it to not complain anymore
#![allow(unused_parens)]
use std::io;
use std::io::{Read, Seek, Write};
use std::mem::transmute;
use bytemuck::bytes_of;
use log::error;
use v_byte_helpers::EnumTryInto;
use v_byte_helpers::{ReadExtensions, IS_BIG_ENDIAN};
use crate::rmc::response::ErrorCode::Core_Exception;
use crate::rmc::structures::qresult::ERROR_MASK;
use crate::util::SendingBufferConnection;
use bytemuck::bytes_of;
use log::error;
use std::io;
use std::io::{Read, Seek, Write};
use std::mem::transmute;
use v_byte_helpers::EnumTryInto;
use v_byte_helpers::{IS_BIG_ENDIAN, ReadExtensions};
#[derive(Debug, Clone)]
pub enum RMCResponseResult {
Success {
call_id: u32,
@ -25,13 +26,14 @@ pub enum RMCResponseResult {
},
}
#[derive(Debug, Clone)]
pub struct RMCResponse {
pub protocol_id: u8,
pub response_result: RMCResponseResult,
}
impl RMCResponse {
pub fn new(stream: &mut (impl Seek + Read)) -> io::Result<Self>{
pub fn new(stream: &mut (impl Seek + Read)) -> io::Result<Self> {
// ignore the size for now this will only be used for checking
let size: u32 = stream.read_struct(IS_BIG_ENDIAN)?;
@ -46,7 +48,7 @@ impl RMCResponse {
let is_success: u8 = stream.read_struct(IS_BIG_ENDIAN)?;
let response_result = if is_success == 0x01{
let response_result = if is_success == 0x01 {
let call_id: u32 = stream.read_struct(IS_BIG_ENDIAN)?;
let method_id: u32 = stream.read_struct(IS_BIG_ENDIAN)?;
let method_id = method_id & (!0x8000);
@ -55,11 +57,10 @@ impl RMCResponse {
stream.read(&mut data)?;
RMCResponseResult::Success {
call_id,
method_id,
data
data,
}
} else {
let error_code: u32 = stream.read_struct(IS_BIG_ENDIAN)?;
@ -68,7 +69,7 @@ impl RMCResponse {
RMCResponseResult::Error {
error_code: {
match ErrorCode::try_from(error_code){
match ErrorCode::try_from(error_code) {
Ok(v) => v,
Err(()) => {
error!("invalid error code {:#010x}", error_code);
@ -77,36 +78,35 @@ impl RMCResponse {
}
},
call_id,
}
};
Ok(Self{
Ok(Self {
protocol_id,
response_result
response_result,
})
}
pub fn get_call_id(&self) -> u32{
match &self.response_result{
RMCResponseResult::Success { call_id, ..} => *call_id,
RMCResponseResult::Error { call_id, .. } => *call_id
pub fn get_call_id(&self) -> u32 {
match &self.response_result {
RMCResponseResult::Success { call_id, .. } => *call_id,
RMCResponseResult::Error { call_id, .. } => *call_id,
}
}
pub fn to_data(self) -> Vec<u8> {
generate_response(self.protocol_id, self.response_result).expect("failed to generate response")
generate_response(self.protocol_id, self.response_result)
.expect("failed to generate response")
}
}
pub fn generate_response(protocol_id: u8, response: RMCResponseResult) -> io::Result<Vec<u8>> {
let size = 1 + 1 + match &response {
RMCResponseResult::Success {
data,
..
} => 4 + 4 + data.len(),
RMCResponseResult::Error { .. } => 4 + 4,
};
let size = 1
+ 1
+ match &response {
RMCResponseResult::Success { data, .. } => 4 + 4 + data.len(),
RMCResponseResult::Error { .. } => 4 + 4,
};
let mut data_out = Vec::with_capacity(size + 4);
@ -119,7 +119,7 @@ pub fn generate_response(protocol_id: u8, response: RMCResponseResult) -> io::Re
RMCResponseResult::Success {
call_id,
method_id,
data
data,
} => {
data_out.push(1);
data_out.write_all(bytes_of(&call_id))?;
@ -129,7 +129,7 @@ pub fn generate_response(protocol_id: u8, response: RMCResponseResult) -> io::Re
}
RMCResponseResult::Error {
call_id,
error_code
error_code,
} => {
data_out.push(0);
let error_code_val: u32 = error_code.into();
@ -151,23 +151,21 @@ pub async fn send_result(
method_id: u32,
call_id: u32,
) {
let response_result = match result {
Ok(v) => RMCResponseResult::Success {
call_id,
method_id,
data: v
data: v,
},
Err(e) => RMCResponseResult::Error {
call_id,
error_code: e.into(),
},
Err(e) =>
RMCResponseResult::Error {
call_id,
error_code: e.into()
}
};
let response = RMCResponse{
let response = RMCResponse {
response_result,
protocol_id
protocol_id,
};
send_response(connection, response).await
@ -177,7 +175,6 @@ pub async fn send_response(connection: &SendingBufferConnection, rmcresponse: RM
connection.send(rmcresponse.to_data()).await;
}
//taken from kinnays error list directly
#[allow(nonstandard_style)]
#[repr(u32)]
@ -464,25 +461,23 @@ impl Into<u32> for ErrorCode {
#[cfg(test)]
mod test {
use hmac::digest::consts::U5;
use hmac::digest::KeyInit;
use rc4::{Rc4, StreamCipher};
use crate::rmc::response::ErrorCode;
use hmac::digest::KeyInit;
use hmac::digest::consts::U5;
use rc4::{Rc4, StreamCipher};
#[test]
fn test() {
let data_orig = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 69, 4, 20];
let mut data = data_orig;
let mut rc4: Rc4<U5> =
Rc4::new_from_slice("FUCKE".as_bytes().into()).expect("invalid key");
let mut rc4: Rc4<U5> = Rc4::new_from_slice("FUCKE".as_bytes().into()).expect("invalid key");
rc4.apply_keystream(&mut data);
assert_ne!(data_orig, data);
let mut rc4: Rc4<U5> =
Rc4::new_from_slice("FUCKE".as_bytes().into()).expect("invalid key");
let mut rc4: Rc4<U5> = Rc4::new_from_slice("FUCKE".as_bytes().into()).expect("invalid key");
rc4.apply_keystream(&mut data);
@ -494,4 +489,4 @@ mod test {
let val: u32 = ErrorCode::Core_Unknown.into();
assert_eq!(val, 0x00010001)
}
}
}

View file

@ -1,19 +1,17 @@
use crate::rmc::structures::RmcSerialize;
use bytemuck::bytes_of;
use std::io::{Read, Write};
use std::mem::MaybeUninit;
use bytemuck::bytes_of;
use v_byte_helpers::{IS_BIG_ENDIAN, ReadExtensions};
use crate::rmc::structures::RmcSerialize;
// this is also for implementing `Buffer` this is tecnically not the same as its handled internaly
// this is also for implementing `Buffer` this is tecnically not the same as its handled internaly
// probably but as it has the same mapping it doesn't matter and simplifies things
impl<T: RmcSerialize> RmcSerialize for Vec<T>{
impl<T: RmcSerialize> RmcSerialize for Vec<T> {
fn serialize(&self, writer: &mut impl Write) -> crate::rmc::structures::Result<()> {
let u32_len = self.len() as u32;
writer.write_all(bytes_of(&u32_len))?;
for e in self{
for e in self {
e.serialize(writer)?;
}
@ -21,27 +19,31 @@ impl<T: RmcSerialize> RmcSerialize for Vec<T>{
}
fn deserialize(reader: &mut impl Read) -> crate::rmc::structures::Result<Self> {
println!("reading list");
let len: u32 = reader.read_struct(IS_BIG_ENDIAN)?;
println!("readijg list: {:?}", len);
//let mut vec = Vec::with_capacity(len as usize);
let vec = (0..len).map(|_| T::deserialize(reader)).collect::<Result<Vec<_>, _>>()?;
let vec: Vec<T> = (0..len)
.map(|_| T::deserialize(reader))
.collect::<Result<Vec<_>, _>>()?;
Ok(vec)
}
fn serialize_write_size(&self) -> crate::rmc::structures::Result<u32> {
let mut val = 0u32;
for i in self{
for i in self {
val += i.serialize_write_size()?;
}
Ok(4 + val)
}
}
impl<const LEN: usize, T: RmcSerialize> RmcSerialize for [T; LEN]{
impl<const LEN: usize, T: RmcSerialize> RmcSerialize for [T; LEN] {
fn serialize(&self, writer: &mut impl Write) -> crate::rmc::structures::Result<()> {
for i in 0..LEN{
for i in 0..LEN {
self[i].serialize(writer)?;
}
@ -51,20 +53,20 @@ impl<const LEN: usize, T: RmcSerialize> RmcSerialize for [T; LEN]{
fn deserialize(reader: &mut impl Read) -> crate::rmc::structures::Result<Self> {
let mut arr = [const { MaybeUninit::<T>::uninit() }; LEN];
for i in 0..LEN{
for i in 0..LEN {
arr[i] = MaybeUninit::new(T::deserialize(reader)?);
}
// all of the elements are now initialized so it is safe to assume they are initialized
let arr = arr.map(|v| unsafe{ v.assume_init() });
let arr = arr.map(|v| unsafe { v.assume_init() });
Ok(arr)
}
#[inline(always)]
fn serialize_write_size(&self) -> crate::rmc::structures::Result<u32> {
let mut val = 0u32;
for i in self{
for i in self {
val += i.serialize_write_size()?;
}
Ok(val)

View file

@ -1,12 +1,12 @@
use std::{fmt, io};
use crate::rmc::structures::helpers::DummyWriter;
use std::io::{Read, Write};
use std::string::FromUtf8Error;
use std::{fmt, io};
use thiserror::Error;
use crate::rmc::structures::helpers::DummyWriter;
//ideas for the future: make a proc macro library which allows generation of struct reads
#[derive(Error, Debug)]
pub enum Error{
pub enum Error {
#[error("Io Error: {0}")]
Io(#[from] io::Error),
#[error("UTF8 conversion Error: {0}")]
@ -18,51 +18,52 @@ pub enum Error{
#[error("an error occurred reading the station url")]
StationUrlInvalid,
#[error("error formatting text: {0}")]
FormatError(#[from] fmt::Error)
FormatError(#[from] fmt::Error),
}
pub type Result<T> = std::result::Result<T, Error>;
pub mod string;
pub mod any;
pub mod qresult;
pub mod buffer;
pub mod connection_data;
pub mod rmc_struct;
pub mod list;
pub mod qbuffer;
pub mod primitives;
pub mod matchmake;
pub mod variant;
pub mod ranking;
pub mod networking;
pub mod helpers;
pub mod list;
pub mod matchmake;
pub mod networking;
pub mod primitives;
pub mod qbuffer;
pub mod qresult;
pub mod ranking;
pub mod rmc_struct;
pub mod string;
pub mod variant;
pub trait RmcSerialize{
pub trait RmcSerialize {
fn serialize(&self, writer: &mut impl Write) -> Result<()>;
fn serialize_write_size(&self) -> Result<u32>{
fn serialize_write_size(&self) -> Result<u32> {
let mut dummy = DummyWriter::new();
self.serialize(&mut dummy)?;
Ok(dummy.get_total_len())
}
fn deserialize(reader: &mut impl Read) -> Result<Self> where Self: Sized;
fn deserialize(reader: &mut impl Read) -> Result<Self>
where
Self: Sized;
fn to_data(&self) -> Result<Vec<u8>>{
let mut data = Vec::with_capacity(
self.serialize_write_size()? as usize
);
fn to_data(&self) -> Result<Vec<u8>> {
let expected_size = self.serialize_write_size()?;
let mut data = Vec::with_capacity(expected_size as usize);
self.serialize(&mut data)?;
debug_assert_eq!(self.serialize_write_size().unwrap(), data.len() as u32);
debug_assert_eq!(expected_size, data.len() as u32);
Ok(data)
}
}
impl RmcSerialize for (){
impl RmcSerialize for () {
fn serialize(&self, _writer: &mut impl Write) -> Result<()> {
Ok(())
}
@ -72,6 +73,4 @@ impl RmcSerialize for (){
fn serialize_write_size(&self) -> Result<u32> {
Ok(0)
}
}
}

View file

@ -280,8 +280,8 @@ impl<T: RmcSerialize, U: RmcSerialize, V: RmcSerialize, W: RmcSerialize, X: RmcS
Ok(self.0.serialize_write_size()?
+ self.1.serialize_write_size()?
+ self.2.serialize_write_size()?
+ self.2.serialize_write_size()?
+ self.3.serialize_write_size()?)
+ self.3.serialize_write_size()?
+ self.4.serialize_write_size()?)
}
}

View file

@ -1,37 +1,42 @@
use crate::rmc::response::ErrorCode;
use crate::rmc::structures::{Result, RmcSerialize};
use bytemuck::{Pod, Zeroable, bytes_of};
use std::io::{Read, Write};
use bytemuck::{bytes_of, Pod, Zeroable};
use v_byte_helpers::SwapEndian;
use v_byte_helpers::{IS_BIG_ENDIAN, ReadExtensions};
use crate::rmc::response::ErrorCode;
use crate::rmc::structures::{RmcSerialize, Result};
pub const ERROR_MASK: u32 = 1 << 31;
pub const ERROR_MASK: u32 = 1 << 31;
#[derive(Pod, Zeroable, Copy, Clone, SwapEndian, Debug)]
#[repr(transparent)]
pub struct QResult(u32);
impl QResult{
pub fn success(error_code: ErrorCode) -> Self{
impl QResult {
pub fn success(error_code: ErrorCode) -> Self {
let val: u32 = error_code.into();
Self(val & (!ERROR_MASK))
}
pub fn error(error_code: ErrorCode) -> Self{
pub fn error(error_code: ErrorCode) -> Self {
let val: u32 = error_code.into();
Self(val | ERROR_MASK)
}
}
impl RmcSerialize for QResult{
impl RmcSerialize for QResult {
fn serialize(&self, writer: &mut impl Write) -> Result<()> {
writer.write(bytes_of(self))?;
Ok(())
}
fn deserialize(reader: &mut impl Read) -> Result<Self> {
Ok(reader.read_struct(IS_BIG_ENDIAN)?)
let result: Self = reader.read_struct(IS_BIG_ENDIAN)?;
println!("reading qresult: {:x}", result.0);
Ok(result)
}
}
fn serialize_write_size(&self) -> Result<u32> {
Ok(4)
}
}

View file

@ -1,15 +1,13 @@
use crate::rmc::structures::Result;
use std::cmp::max;
use std::fmt::Arguments;
use std::io;
use std::io::{ErrorKind, IoSlice, Read, Write};
use crate::rmc::structures::Result;
#[repr(C, packed)]
struct StructureHeader{
struct StructureHeader {
version: u8,
length: u32
length: u32,
}
#[cfg(feature = "rmc_struct_header")]
@ -38,7 +36,12 @@ impl Write for OnlyWriteVec<'_> {
}
#[cfg(feature = "rmc_struct_header")]
pub fn write_struct<T: Write>(writer: &mut T, version: u8, inner_size: u32, pred: impl FnOnce(&mut T) -> Result<()> ) -> Result<()> {
pub fn write_struct<T: Write>(
writer: &mut T,
version: u8,
inner_size: u32,
pred: impl FnOnce(&mut T) -> Result<()>,
) -> Result<()> {
use bytemuck::bytes_of;
writer.write_all(&[version])?;
@ -50,29 +53,31 @@ pub fn write_struct<T: Write>(writer: &mut T, version: u8, inner_size: u32, pred
Ok(())
}
#[cfg(not(feature = "rmc_struct_header"))]
pub fn write_struct<T: Write>(writer: &mut T, _version: u8, _inner_size: u32, pred: impl FnOnce(&mut T) -> Result<()> ) -> Result<()> {
pub fn write_struct<T: Write>(
writer: &mut T,
_version: u8,
_inner_size: u32,
pred: impl FnOnce(&mut T) -> Result<()>,
) -> Result<()> {
pred(writer)
}
pub struct SubRead<'a, T: Read>{
pub struct SubRead<'a, T: Read> {
left_to_read: usize,
origin: &'a mut T
origin: &'a mut T,
}
impl<'a, T: Read> SubRead<'a, T>{
pub const fn new(origin: &'a mut T, left_to_read: usize) -> Self{
Self{
impl<'a, T: Read> SubRead<'a, T> {
pub const fn new(origin: &'a mut T, left_to_read: usize) -> Self {
Self {
left_to_read,
origin
origin,
}
}
}
impl<T: Read> Read for SubRead<'_, T>{
impl<T: Read> Read for SubRead<'_, T> {
#[inline(always)]
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
let max_read = max(self.left_to_read, buf.len());
@ -83,8 +88,11 @@ impl<T: Read> Read for SubRead<'_, T>{
#[inline(always)]
fn read_exact(&mut self, buf: &mut [u8]) -> io::Result<()> {
if buf.len() > self.left_to_read{
return Err(io::Error::new(ErrorKind::UnexpectedEof, "Would run over end of SubRead"));
if buf.len() > self.left_to_read {
return Err(io::Error::new(
ErrorKind::UnexpectedEof,
"Would run over end of SubRead",
));
}
self.left_to_read -= buf.len();
self.origin.read_exact(buf)
@ -92,10 +100,14 @@ impl<T: Read> Read for SubRead<'_, T>{
}
#[cfg(feature = "rmc_struct_header")]
pub fn read_struct<T: Sized, R: Read>(reader: &mut R, version: u8, pred: impl FnOnce(&mut SubRead<R>) -> Result<T>) -> Result<T> {
pub fn read_struct<T: Sized, R: Read>(
reader: &mut R,
version: u8,
pred: impl FnOnce(&mut SubRead<R>) -> Result<T>,
) -> Result<T> {
use crate::rmc::structures::Error::VersionMismatch;
use v_byte_helpers::ReadExtensions;
use v_byte_helpers::IS_BIG_ENDIAN;
use v_byte_helpers::ReadExtensions;
let ver: u8 = reader.read_struct(IS_BIG_ENDIAN)?;
if ver != version {
@ -108,6 +120,10 @@ pub fn read_struct<T: Sized, R: Read>(reader: &mut R, version: u8, pred: impl Fn
}
#[cfg(not(feature = "rmc_struct_header"))]
pub fn read_struct<T: Sized, R: Read>(mut reader: &mut R, _version: u8, pred: impl FnOnce(&mut R) -> Result<T>) -> Result<T> {
pub fn read_struct<T: Sized, R: Read>(
mut reader: &mut R,
_version: u8,
pred: impl FnOnce(&mut R) -> Result<T>,
) -> Result<T> {
Ok(pred(&mut reader)?)
}

View file

@ -1,18 +1,18 @@
use std::io::{Read, Write};
use super::{Result, RmcSerialize};
use bytemuck::bytes_of;
use log::error;
use std::io::{Read, Write};
use v_byte_helpers::{IS_BIG_ENDIAN, ReadExtensions};
use super::{Result, RmcSerialize};
impl RmcSerialize for String{
impl RmcSerialize for String {
fn deserialize(reader: &mut impl Read) -> Result<Self> {
let len: u16 = reader.read_struct(IS_BIG_ENDIAN)?;
if len == 0{
if len == 0 {
return Ok("".to_string());
}
let mut data = vec![0; len as usize];
reader.read_exact(&mut data)?;
if *data.last().unwrap() != 0{
if *data.last().unwrap() != 0 {
error!("unable to find null terminator... continuing anyways");
}
data.pop();
@ -27,7 +27,7 @@ impl RmcSerialize for String{
}
}
impl RmcSerialize for &str{
impl RmcSerialize for &str {
fn deserialize(_reader: &mut impl Read) -> Result<Self> {
panic!("cannot serialize to &str")
}
@ -45,4 +45,3 @@ impl RmcSerialize for &str{
Ok(2 + self.as_bytes().len() as u32 + 1)
}
}