Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 31 additions & 31 deletions crates/ironposh-client-core/src/runspace/win_rs.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
use base64::Engine;
use ironposh_winrm::{
cores::{Attribute, DesiredStream, Receive, Shell, Tag, Text, Time, tag_name},
cores::{Attribute, DesiredStreamTag, StreamTag, Tag, Text, Time},
rsp::{
commandline::CommandLineValue,
receive::{CommandStateValue, ReceiveValue},
shell_value::ShellValue,
receive::{CommandStateTag, CommandStateValue, ReceiveTag, ReceiveValue},
shell_value::{ShellTag, ShellValue},
},
soap::{SoapEnvelope, body::SoapBody},
ws_management::{OptionSetValue, SelectorSetValue, WsAction, WsMan},
Expand Down Expand Up @@ -67,7 +67,7 @@ impl WinRunspace {
option_set: Option<OptionSetValue>,
open_content: &'a str,
) -> impl Into<Element<'a>> {
let shell = Tag::from_name(Shell)
let shell = Tag::from_name(ShellTag)
.with_attribute(ironposh_winrm::cores::Attribute::ShellId(
self.id.to_string().into(),
))
Expand Down Expand Up @@ -130,7 +130,7 @@ impl WinRunspace {
// Join stream names with spaces as required by Windows Shell schema
let combined_streams = stream_names.join(" ");
let mut tag =
Tag::from_name(DesiredStream).with_value(Text::from(combined_streams));
Tag::from_name(DesiredStreamTag).with_value(Text::from(combined_streams));

if let Some(command_id) = command_id {
tag = tag.with_attribute(Attribute::CommandId(command_id));
Expand All @@ -144,7 +144,7 @@ impl WinRunspace {
.desired_streams(desired_stream_tags)
.build();

let receive_tag = Tag::from_name(Receive)
let receive_tag = Tag::from_name(ReceiveTag)
.with_value(receive)
.with_declaration(ironposh_winrm::cores::Namespace::WsmanShell);

Expand Down Expand Up @@ -172,9 +172,12 @@ impl WinRunspace {

/// Build a Disconnect request targeting this shell (MS-WSMV 3.1.4.13).
pub(crate) fn fire_disconnect<'a>(&'a self, ws_man: &'a WsMan) -> impl Into<Element<'a>> {
use ironposh_winrm::{cores::Namespace, rsp::disconnect::DisconnectValue};
use ironposh_winrm::{
cores::Namespace,
rsp::disconnect::{DisconnectTag, DisconnectValue},
};

let disconnect_tag = Tag::from_name(tag_name::Disconnect)
let disconnect_tag = Tag::from_name(DisconnectTag)
.with_declaration(Namespace::WsmanShell)
.with_value(DisconnectValue::builder().build());

Expand All @@ -197,13 +200,16 @@ impl WinRunspace {
option_set: Option<OptionSetValue>,
connect_payload: &'a str,
) -> impl Into<Element<'a>> {
use ironposh_winrm::{cores::Namespace, rsp::connect::ConnectValue};
use ironposh_winrm::{
cores::Namespace,
rsp::connect::{ConnectTag, ConnectValue},
};

let connect_value = ConnectValue {
connect_xml: Tag::new(connect_payload).with_declaration(Namespace::PowerShellRemoting),
};

let connect_tag = Tag::from_name(tag_name::Connect)
let connect_tag = Tag::from_name(ConnectTag)
.with_declaration(Namespace::WsmanShell)
.with_value(connect_value);

Expand All @@ -218,9 +224,9 @@ impl WinRunspace {

/// Build a Reconnect request targeting this shell (MS-WSMV 3.1.4.14).
pub(crate) fn fire_reconnect<'a>(&'a self, ws_man: &'a WsMan) -> impl Into<Element<'a>> {
use ironposh_winrm::cores::{Empty, Namespace};
use ironposh_winrm::cores::{Empty, Namespace, ReconnectTag};

let reconnect_tag = Tag::from_name(tag_name::Reconnect)
let reconnect_tag = Tag::from_name(ReconnectTag)
.with_declaration(Namespace::WsmanShell)
.with_value(Empty);

Expand Down Expand Up @@ -390,20 +396,17 @@ impl WinRunspace {
data: &'a [String],
) -> Result<impl Into<Element<'a>>, crate::PwshCoreError> {
use ironposh_winrm::{
cores::{
Namespace, Tag,
tag_name::{Send, Stream},
},
rsp::send::SendValue,
cores::{Namespace, StreamTag, Tag},
rsp::send::{SendTag, SendValue},
soap::body::SoapBody,
};

// Create a Stream tag for each fragment
// Each fragment is a base64-encoded PSRP fragment that goes in its own <rsp:Stream> element
let streams: Vec<Tag<Text, Stream>> = data
let streams: Vec<Tag<Text, StreamTag>> = data
.iter()
.map(|fragment| {
Tag::from_name(Stream)
Tag::from_name(StreamTag)
.with_value(Text::from(fragment.as_str()))
.with_attribute(Attribute::Name("stdin".into()))
})
Expand All @@ -413,12 +416,12 @@ impl WinRunspace {

// Add send tag with SendValue containing multiple streams
let send_tag = if let Some(cmd_id) = command_id {
Tag::from_name(Send)
Tag::from_name(SendTag)
.with_value(send_value)
.with_attribute(Attribute::CommandId(cmd_id))
.with_declaration(Namespace::WsmanShell)
} else {
Tag::from_name(Send)
Tag::from_name(SendTag)
.with_value(send_value)
.with_declaration(Namespace::WsmanShell)
};
Expand Down Expand Up @@ -460,18 +463,15 @@ impl WinRunspace {
connection: &'a WsMan,
id: Uuid,
) -> Result<impl Into<Element<'a>>, crate::PwshCoreError> {
use ironposh_winrm::cores::{
Namespace,
tag_name::{Signal, SignalCode},
};
use ironposh_winrm::cores::{Namespace, SignalCodeTag, SignalTag};

// Build <rsp:Code>http://schemas.microsoft.com/wbem/wsman/1/windows/shell/signal/ctrl_c</rsp:Code>
let code = Tag::from_name(SignalCode).with_value(Text::from(
let code = Tag::from_name(SignalCodeTag).with_value(Text::from(
"http://schemas.microsoft.com/wbem/wsman/1/windows/shell/signal/terminate",
));

// Build <w:Signal CommandId="...">...</w:Signal>
let signal = Tag::from_name(Signal)
let signal = Tag::from_name(SignalTag)
.with_attribute(Attribute::CommandId(id))
.with_value(code)
.with_declaration(Namespace::WsmanShell);
Expand Down Expand Up @@ -535,10 +535,10 @@ impl Stream {
}
}

impl<'a> TryFrom<&Tag<'a, Text<'a>, tag_name::Stream>> for Stream {
impl<'a> TryFrom<&Tag<'a, Text<'a>, StreamTag>> for Stream {
type Error = crate::PwshCoreError;

fn try_from(value: &Tag<'a, Text<'a>, tag_name::Stream>) -> Result<Self, Self::Error> {
fn try_from(value: &Tag<'a, Text<'a>, StreamTag>) -> Result<Self, Self::Error> {
let attributes = &value.attributes;
let name = attributes
.iter()
Expand Down Expand Up @@ -577,11 +577,11 @@ pub struct CommandState {
pub exit_code: Option<i32>,
}

impl<'a> TryFrom<&Tag<'a, CommandStateValue<'a>, tag_name::CommandState>> for CommandState {
impl<'a> TryFrom<&Tag<'a, CommandStateValue<'a>, CommandStateTag>> for CommandState {
type Error = crate::PwshCoreError;

fn try_from(
value: &Tag<'a, CommandStateValue<'a>, tag_name::CommandState>,
value: &Tag<'a, CommandStateValue<'a>, CommandStateTag>,
) -> Result<Self, Self::Error> {
let command_id = value
.attributes
Expand Down
25 changes: 14 additions & 11 deletions crates/ironposh-client-core/tests/test_send_roundtrip.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,11 @@ use ironposh_psrp::{
fragmentation::{DefragmentResult, Defragmenter, Fragmenter},
};
use ironposh_winrm::{
cores::{Attribute, ReceiveResponse, Send, Tag, Text, tag_name::Stream},
rsp::{receive::ReceiveResponseValue, send::SendValue},
cores::{Attribute, StreamTag, Tag, Text},
rsp::{
receive::{ReceiveResponseTag, ReceiveResponseValue},
send::{SendTag, SendValue},
},
soap::{SoapEnvelope, body::SoapBody},
};
use ironposh_xml::builder::Element;
Expand Down Expand Up @@ -42,18 +45,18 @@ fn test_send_receive_roundtrip_with_fragmentation() {
.collect();

// 4. Create SendValue with multiple Stream elements (NEW CODE PATH)
let streams: Vec<Tag<Text, Stream>> = base64_fragments
let streams: Vec<Tag<Text, StreamTag>> = base64_fragments
.iter()
.map(|fragment| {
Tag::from_name(Stream)
Tag::from_name(StreamTag)
.with_value(Text::from(fragment.as_str()))
.with_attribute(Attribute::Name("stdin".into()))
})
.collect();

let send_value = SendValue::builder().streams(streams).build();

let send_tag = Tag::from_name(Send)
let send_tag = Tag::from_name(SendTag)
.with_value(send_value)
.with_attribute(Attribute::CommandId(command_id))
.with_declaration(ironposh_winrm::cores::Namespace::WsmanShell);
Expand All @@ -80,10 +83,10 @@ fn test_send_receive_roundtrip_with_fragmentation() {

// 6. Simulate receiving the response - create ReceiveResponse with same streams
// (In real flow, server would echo back or client would receive similar structure)
let receive_streams: Vec<Tag<Text, Stream>> = base64_fragments
let receive_streams: Vec<Tag<Text, StreamTag>> = base64_fragments
.iter()
.map(|fragment| {
Tag::from_name(Stream)
Tag::from_name(StreamTag)
.with_value(Text::from(fragment.as_str()))
.with_attribute(Attribute::Name("stdout".into()))
.with_attribute(Attribute::CommandId(command_id))
Expand All @@ -95,7 +98,7 @@ fn test_send_receive_roundtrip_with_fragmentation() {
.command_state(None)
.build();

let receive_response_tag = Tag::from_name(ReceiveResponse)
let receive_response_tag = Tag::from_name(ReceiveResponseTag)
.with_value(receive_response_value)
.with_declaration(ironposh_winrm::cores::Namespace::WsmanShell);

Expand Down Expand Up @@ -183,7 +186,7 @@ fn create_large_session_capability() -> SessionCapability {
fn test_send_with_no_fragments() {
let send_value = SendValue::builder().streams(vec![]).build();

let send_tag = Tag::from_name(Send)
let send_tag = Tag::from_name(SendTag)
.with_value(send_value)
.with_declaration(ironposh_winrm::cores::Namespace::WsmanShell);

Expand Down Expand Up @@ -222,13 +225,13 @@ fn test_send_with_single_fragment() {

let base64_fragment = base64::engine::general_purpose::STANDARD.encode(&fragments[0]);

let stream = Tag::from_name(Stream)
let stream = Tag::from_name(StreamTag)
.with_value(Text::from(base64_fragment.as_str()))
.with_attribute(Attribute::Name("stdin".into()));

let send_value = SendValue::builder().streams(vec![stream]).build();

let send_tag = Tag::from_name(Send)
let send_tag = Tag::from_name(SendTag)
.with_value(send_value)
.with_attribute(Attribute::CommandId(command_id))
.with_declaration(ironposh_winrm::cores::Namespace::WsmanShell);
Expand Down
79 changes: 21 additions & 58 deletions crates/ironposh-macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1291,10 +1291,10 @@ fn impl_from_xml(input: &DeriveInput) -> TokenStream2 {
.map(|field| {
let field_name = field.ident.as_ref().unwrap().clone();
let is_optional = is_option_type(&field.ty);
let tag_name_type = extract_tag_name_type(&field.ty);
let value_type = inner_value_type(&field.ty);
SimpleFieldEntry {
field_name,
tag_name_type,
value_type,
is_optional,
}
})
Expand All @@ -1305,19 +1305,19 @@ fn impl_from_xml(input: &DeriveInput) -> TokenStream2 {
quote! { let mut #f = None; }
});

// One namespace-correct match per field: identity is (URI, local-name).
let matchers = entries.iter().filter_map(|e| {
// One namespace-correct match per field: identity is (URI, local-name),
// read from the field's tag type via `NamedTag` so it works through aliases.
let matchers = entries.iter().map(|e| {
let f = &e.field_name;
e.tag_name_type.as_ref().map(|n| {
quote! {
if child.is_element_named(
<crate::cores::#n as crate::cores::TagName>::NAMESPACE,
<crate::cores::#n as crate::cores::TagName>::TAG_NAME,
) {
#f = Some(ironposh_xml::mapping::FromXml::from_xml(child)?);
}
let ty = &e.value_type;
quote! {
if child.is_element_named(
<#ty as crate::cores::NamedTag>::NAMESPACE,
<#ty as crate::cores::NamedTag>::TAG_NAME,
) {
#f = Some(ironposh_xml::mapping::FromXml::from_xml(child)?);
}
})
}
});

let construct = entries.iter().map(|e| {
Expand Down Expand Up @@ -1419,7 +1419,7 @@ fn impl_simple_tag_value(input: &DeriveInput) -> TokenStream2 {

struct SimpleFieldEntry {
field_name: Ident,
tag_name_type: Option<Ident>,
value_type: Type,
is_optional: bool,
}

Expand All @@ -1437,55 +1437,18 @@ fn is_option_type(ty: &Type) -> bool {
false
}

fn extract_tag_name_type(ty: &Type) -> Option<Ident> {
// Try to extract TagName from Tag<'a, ValueType, TagName> or Option<Tag<'a, ValueType, TagName>>
if let Type::Path(TypePath { path, .. }) = ty {
for segment in &path.segments {
if segment.ident == "Tag" || segment.ident == "Option" {
if let syn::PathArguments::AngleBracketed(args) = &segment.arguments {
// For Option<Tag<...>>, we need to look at the inner type
for arg in &args.args {
if let syn::GenericArgument::Type(inner_type) = arg {
if let Some(tag_name) = extract_tag_name_from_tag_type(inner_type) {
return Some(tag_name);
}
}
}

// For Tag<'a, ValueType, TagName>, the third argument is TagName
if segment.ident == "Tag" && args.args.len() >= 3 {
if let syn::GenericArgument::Type(Type::Path(TypePath { path, .. })) =
&args.args[2]
{
if let Some(segment) = path.segments.last() {
return Some(segment.ident.clone());
}
}
}
}
}
}
}
None
}

fn extract_tag_name_from_tag_type(ty: &Type) -> Option<Ident> {
/// The value a field carries: `Option<T>` -> `T`, otherwise the type itself.
fn inner_value_type(ty: &Type) -> Type {
if let Type::Path(TypePath { path, .. }) = ty {
for segment in &path.segments {
if segment.ident == "Tag" {
if let Some(segment) = path.segments.last() {
if segment.ident == "Option" {
if let syn::PathArguments::AngleBracketed(args) = &segment.arguments {
if args.args.len() >= 3 {
if let syn::GenericArgument::Type(Type::Path(TypePath { path, .. })) =
&args.args[2]
{
if let Some(segment) = path.segments.last() {
return Some(segment.ident.clone());
}
}
if let Some(syn::GenericArgument::Type(inner)) = args.args.first() {
return inner.clone();
}
}
}
}
}
None
ty.clone()
}
Loading