Inner value of `CWPHYMode` appears to change between integer and bitflags index between macOS versions
In the code below, the inner value of CWPHYMode which is returned by interface.activePHYMode() differs based on the macOS version.
unsafe {
let client = CWWiFiClient::sharedWiFiClient();
let interface = client.interface().unwrap();
let phy_mode = interface.activePHYMode();
}
From observation I've noticed that
- on macOS 14.5 the inner value is represented by an integer that maps well to the
const ... Self(n)constants on the enum - on macOS 15.0 it seems like the value represents the naive conversion from a bitflag to an integer
-
802.11nis returned asCWPHYMode(16)but should beCWPHYMode(4) -
802.11axis returned asCWPHYMode(64)but should beCWPHYMode(6)
-
Cargo.toml for context
objc2 = { version = "0.5", features = ["apple"] }
objc2-foundation = { version = "0.2", features = ["apple"] }
objc2-core-wlan = { version = "0.2", features = [
"CWInterface",
"CWNetwork",
"CWWiFiClient",
"CWChannel",
"CoreWLANTypes",
] }
Huh. This is interesting. I ran the example code. I'm on macOS 15 with an 802.11ac network. I get a CWPHYMode(32) when I presume it should be CWPHYMode(5). I get the same incorrect result mentioned above with an 802.11n network.
The mapping looks like:
-
CWPHYMode(4)is returned asCWPHYMode(16) -
CWPHYMode(5)is returned asCWPHYMode(32) -
CWPHYMode(6)is returned asCWPHYMode(64).
Looks like the pattern is a bitmask rather than an integer?
I'm still on macOS 14.7, and can't test this in a virtual machine (since the virtual machines don't have WiFi), so could I get one of you to test the following:
-
RUSTFLAGS="-Clinker-flavor=ld" cargo run(in case this is dependent on theminos/sdkversion in the binary) - The equivalent Swift code:
import CoreWLAN let client = CWWiFiClient.shared() let interface = client.interface()! let phy_mode = interface.activePHYMode() print(phy_mode.rawValue)
Sorry for the delay in my response, after running all the variants you asked for above, I only realized that the output had been fixed. I'm not sure if it was in this repo or the underlying framework, but it seems right now right?
I'd appreciate understanding if it was the underlying macOS version so I know to expect incorrect values from a specific version of the OS or of this crate, if you know? I'm now on macOS 15.1.1.
fn main() {
use objc2_core_wlan::CWWiFiClient;
unsafe {
let client = CWWiFiClient::sharedWiFiClient();
let interface = client.interface().unwrap();
let phy_mode = interface.activePHYMode();
println!("{:?}", phy_mode);
}
}
Without flag:
❯ cargo run --example madsmtm_objc2_issues_662
Compiling ...
Finished `dev` profile [unoptimized + debuginfo] target(s) in 1m 56s
Running `target/debug/examples/madsmtm_objc2_issues_662`
CWPHYMode(6)
With flag:
❯ RUSTFLAGS="-Clinker-flavor=ld" cargo run --example madsmtm_objc2_issues_662
Compiling ...
Finished `dev` profile [unoptimized + debuginfo] target(s) in 3m 27s
Running `target/debug/examples/madsmtm_objc2_issues_662`
CWPHYMode(6)
Swift:
import CoreWLAN
let client = CWWiFiClient.shared()
let interface = client.interface()!
let phy_mode = interface.activePHYMode()
print(phy_mode.rawValue)
6
Program ended with exit code: 0
Running above Rust code snippet again on macOS 15.0.1 with the same version of objc2-* results in the original CWPHYMode(64) issue, regardless of wether the example was run with the RUSTFLAGS set or not. It seems like this is the an issue with the underlying OS framework, do you have precedent in the codebase to handle things in a specific way given a specific version of the OS?
Running above Rust code snippet again on
macOS 15.0.1with the same version ofobjc2-*results in the originalCWPHYMode(64)issue, regardless of wether the example was run with theRUSTFLAGSset or not.
Damn! Could you check macOS 15.0 and macOS 15.1 too, just to be sure of the full range of (non-beta) versions that this happens on?
It seems like this is the an issue with the underlying OS framework, do you have precedent in the codebase to handle things in a specific way given a specific version of the OS?
Hmm, not really, especially not something like this. But I think you'd be able to handle it with something like:
- Modify
objc2-core-wlan/translation-config.tomlto containclass.CWInterface.methods.activePHYMode.skipped = true, and re-run the header translator (if it's difficult for you to set up, then I can do that part myself). - Add a file that contains something like:
impl CWInterface { #[cfg(feature = "CoreWLANTypes")] fn activePHYMode(&self) -> CWPHYMode { let inner: CWPHYMode = unsafe { msg_send![self, activePHYMode] }; // These versions had a bug [explanation] if available!(macos = 15.0) && !available!(macos = 15.1) { CWPHYMode(inner.0 >> 2) } else { inner } } }
If it affects CWNetwork::supportsPHYMode too, then something similar should be done there as well.
Are you still interested in this @keabarnes?
Personally, I'm leaning towards "do nothing" here, since this was fairly clearly a bug in Apple's code which has since been fixed. I'd rather recommend that if you do want to support those buggy versions of macOS, that you do the conversion in your own code. Again, it can be done with just the available! macro:
use objc2_core_wlan::{CWPHYMode, CWInterface};
use objc2::available;
fn fixed_activePHYMode(interface: &CWInterface) -> CWPHYMode {
let inner = unsafe { interface.activePHYMode() };
// These versions had a bug [explanation]
if available!(macos = 15.0) && !available!(macos = 15.1) {
CWPHYMode(inner.0 >> 2)
} else {
inner
}
}
@madsmtm sorry I dropped off here.
I'm happy to proceed either way, at the time I implemented something similar to what you've suggested, but without using the available! macro. I'll go ahead and change to that now as it's definitely cleaner.
My personal opinion is that this codebase is the logical owner of that kind of code, the objc2 crate should know about these nuances with the bindings to the underlying versions of macOS and handle it under the hood so the exposed types can be trusted as correct. Each person using CWPHYMode (and future instances of this issue) won't know that it has this issue, will need to figure it out and then implement the fix (likely only after finding this issue or the example).
sorry I dropped off here.
No problem!
Each person using
CWPHYMode(and future instances of this issue) won't know that it has this issue, will need to figure it out and then implement the fix (likely only after finding this issue or the example).
For this specific issue, for me to change this in objc2-core-wlan, I'd need someone to test this on macOS 15.0 and macOS 15.1 as well, to be sure that this fix doesn't introduce a new bug (e.g. if the bug was not fixed in macOS 15.1 but only in macOS 15.1.1, the above code is wrong). Even worse, because this specific issue can't be tested in a VM, if I were to investigate it I'd have to reinstall macOS. That'd be a huge time sink for me.
My personal opinion is that this codebase is the logical owner of that kind of code, the objc2 crate should know about these nuances with the bindings to the underlying versions of macOS and handle it under the hood so the exposed types can be trusted as correct.
More generally on the "policy" question here:
I agree that it would be nice if we could magically fix bugs like these in Apple's APIs. However, this repo has literally tens of thousands of APIs, and the only way that is in any way maintainable is with a very high degree of auto-generation. Every manually written API carries a risk, both in correctness (as discussed above), and when evolving the codebase (e.g. if I fixed #309, I'd have to manually go and update the fixed method's API docs).
A nuance here is that it's often very easy for users to upgrade to the next minor/patch release of macOS, and often harder to upgrade the "major" version (macOS 10.12, 10.15, 12, 26, etc.). So if I were to write a policy on this, maybe it could be that the project only officially supports the latest version in each major line of OS versions? I'd be much more amenable to patching this if the bug existed in e.g. macOS 13 and 14, but was fixed in macOS 15.
Another (smaller) problem is that it's hard to define where the boundary lies. At the extreme end of the spectrum, if there's a bug in AppKit's auto layout algorithm, do we bundle our own layout algorithm, and re-implement a lot of NSView's logic? In that sense, it's much easier for me to say "this project has the same amount of bugs as calling Apple's APIs from Objective-C would have" (which, we're not even there yet, if we were, the issue list would be empty ;) ).
All of this is in the end influenced by an estimated seriousness of the bug, vs. the risk it introduces, and the time/resources available to me: I suspect that the amount of people that will be hit by this specific issue is quite small, especially at this point, and that amount grows smaller over time as people upgrade their OS. But if a more widely used API like say -[NSArray new] had a serious bug, I'd be more inclined to carry a fix for it.
Does my reasoning here make sense? If not, I'd love to clarify.
@madsmtm your reasoning makes total sense. My comment was in the ideal world, and even now, I'm not convinced of my argument given what you've said. I agree in reality this won't be an issue often enough, and that's good enough to ignore the "consumer won't reasonably know about this" feeling.