Issues with IOBluetooth - swift

I have a few questions regarding IOBluetooth framework which are listed below.
I currently have one MacBook Pro (13-inch, 2018) running Big Sur v11.1 and when I call inquiries and attempt to find nearby devices it often fails and does not work consistently. On the other hand, I have a second MacBook Pro (13-inch, 2015) running Mojave v10.14.6 which does the exact same functions and always works each time. I have also been testing using blueutil command line tool: https://github.com/toy/blueutil as well as PyBluez: https://github.com/pybluez/pybluez and find that my second MacBook running Mojave always finds nearby devices while the MacBook running Big Sur has trouble doing so. Do you know if this is because of potential updates to the framework or is there something wrong with my laptop running Big Sur?
I am trying to open L2CAPChannel from my first laptop to the second and vice versa but calling openL2CAPChannelSync on the IOBluetoothDevice object (which I have instantiated properly) seems to never return kIOReturnSuccess. Am I doing something wrong here as well? I attached a snippet of the code (in which I removed the addressString of my other device) I am using below.
import IOBluetooth
import PlaygroundSupport
class ChannelDelegate : IOBluetoothL2CAPChannelDelegate {
func l2capChannelOpenComplete(_ l2capChannel: IOBluetoothL2CAPChannel!, status error: IOReturn) {
print("Channel Opened!")
}
}
var remoteDevice = IOBluetoothDevice(addressString: ***deviceString***)
print((remoteDevice?.name ?? "nil") as String)
remoteDevice?.openConnection()
var connection = remoteDevice?.isConnected()
print(connection!)
var channelPtr: AutoreleasingUnsafeMutablePointer<IOBluetoothL2CAPChannel?>?
var success = remoteDevice?.openL2CAPChannelSync(channelPtr, withPSM: 0x0000, delegate: ChannelDelegate())
print(success == kIOReturnSuccess)
PlaygroundPage.current.needsIndefiniteExecution = true

In regards to the second point, I fixed the code and is pasted below. The issue was that Apple's documentation stated that the object is instantiated using the function call openL2CAPChannelSync but that is not the case. You need to instantiate the object first then pass a reference to the object you instantiated. Hope this saves people some time given how little examples there are on IOBluetooth API.
import IOBluetooth
import PlaygroundSupport
class ChannelDelegate : IOBluetoothL2CAPChannelDelegate {
func l2capChannelOpenComplete(_ l2capChannel: IOBluetoothL2CAPChannel!, status error: IOReturn) {
print("Channel Opened!")
}
}
var remoteDevice = IOBluetoothDevice(addressString: ***deviceString***)
print((remoteDevice?.name ?? "nil") as String)
remoteDevice?.openConnection()
var connection = remoteDevice?.isConnected()
print(connection!)
var channel: IOBluetoothL2CAPChannel? = IOBluetoothL2CAPChannel()
var success = remoteDevice?.openL2CAPChannelSync(&channel, withPSM: 0x0000, delegate: ChannelDelegate())
print(success == kIOReturnSuccess)
PlaygroundPage.current.needsIndefiniteExecution = true

Related

channelRead not being called in Swift-NIO Datagram's ChannelInboundHandler

I am trying to capture a UDP video stream within a (fresh) vapor application running in Xcode. The data is being streamed by ffmpeg and I can successfully view the stream on the target machine using VLC, which is also the one running the vapor application, using udp://0.0.0.0:5000. I have used various bits of Apple documentation to get to the code below. When I run it, I get these lines of output on the console log, but I wonder if they are not relevant:
2021-07-07 17:59:27.102681+0100 Run[10550:2494617] [si_destination_compare] send failed: Invalid argument
2021-07-07 17:59:27.104056+0100 Run[10550:2494617] [si_destination_compare] send failed: Undefined error: 0
In configure.swift:
try setupClient()
This is the client code:
final class FrameHandler : ChannelInboundHandler {
typealias InboundIn = AddressedEnvelope<ByteBuffer>
typealias OutboundOut = AddressedEnvelope<ByteBuffer>
func channelRead(ctx: ChannelHandlerContext, data: NIOAny) {
// minimal for question
}
func errorCaught(ctx: ChannelHandlerContext, error: Error) {
// minimal for question
}
}
func setupClient() throws {
let group = MultiThreadedEventLoopGroup(numberOfThreads: 1)
let bootstrap = DatagramBootstrap(group: group)
.channelOption(ChannelOptions.socketOption(.so_reuseaddr), value: 1)
.channelInitializer { channel in
channel.pipeline.addHandler(FrameHandler())
}
defer {
try! group.syncShutdownGracefully()
}
let channel = try bootstrap.bind(host: "0.0.0.0", port: 5000).wait()
try channel.closeFuture.wait()
}
The problem is that although channelRegistered and channelActive are called, followed by a never-ending stream of readComplete, the important one channelRead never gets called - neither does errorCaught. If I comment out the call to setupClient then there is no network activity, however, if it runs then Xcode's network monitor shows activity consistent with the levels in ffmpeg. So, I believe the connection is being set up.
I wonder if the problem is in the way I am setting the handler up? All the examples use echo or reflecting chat examples, so the inbound handler is set up in the closure of the data-writing function using the context rather than adding it in the initialiser (although, the outbound handler is set up in this way).
I'm assuming you're using Vapor 4 which is based on SwiftNIO 2. And in NIO 2, the ChannelHandlerContext variable is called context and not ctx. So if you just rename all your ctx to context, I'd assume it'll work.

NETunnelProvider stop receiving packet on iOS 14?

I'm having a Local VPN app that using "NETunnelProvider / NetworkExtentsion", In my solution, I created a split tunnel on the device itself to track the DNS request, using NEKit I was able to peek inside the packets and filter the ongoing request based on the destination address (let's call ita UDP listener for DNS requests).
This solution was working fine on iOS 13.7 and less, recently apple release iOS 14, and my solution stop working, VPN connection still established but the user can't access any webSite, I debugged the code and found out the networkExtision does not receive any packets from user activity only.
I'm using the CocoaAsyncSocket library.
func udpSocket(_ sock: GCDAsyncUdpSocket, didReceive data: Data, fromAddress address: Data, withFilterContext filterContext: Any?) {
let host = GCDAsyncUdpSocket.host(fromAddress: address)
guard let message = DNSMessage(payload: data) else {
return
}
guard let session = pendingSession.removeValue(forKey: message.transactionID) else {
return
}
session.realResponseMessage = message
session.realIP = message.resolvedIPv4Address
let domain = session.requestMessage.queries[0].name
let udpParser = UDPProtocolParser()
udpParser.sourcePort = Port(port: dnsServerPort)
udpParser.destinationPort = (session.requestIPPacket!.protocolParser as! UDPProtocolParser).sourcePort
udpParser.payload = session.realResponseMessage!.payload
let ipPacket = IPPacket()
ipPacket.sourceAddress = IPAddress(fromString: dnsServerAddress)
ipPacket.destinationAddress = session.requestIPPacket!.sourceAddress
ipPacket.protocolParser = udpParser
ipPacket.transportProtocol = .udp
ipPacket.buildPacket()
packetFlow.writePackets([ipPacket.packetData], withProtocols: [NSNumber(value: AF_INET as Int32)])
}
let dummyTunnelAddress = "127.0.0.1"
let dnsServerAddress = "8.8.4.4"
let dnsServerPort: UInt16 = 53
// Tunnel confg.
let tunnelAddress = "192.168.0.1"
let tunnelSubnetMask = "255.255.255.0"
Regarding triggering"Local Network permissions" which is not the issue here (I don't think my solution need to have this permission), Based on the apple document some apps need to request local network permissions, I added the permission to the info.plist but local network permissions are not triggered.
==========================
Update #1
============================
I found out that I was able to capture the packets and do my own things then write packets back to the packetFlow packetFlow.writePackets, But on iOS 14 browsers not loading the websites and keep loading until show time out.
I have an idea, and maybe a solution for you. Starting in iOS version 14, the C function connect() started failing in network extensions, where VPNs have to run, with the following log message from the kernel:
Sandbox: VPN Extensio(8335) deny(1) network-outbound*:<port #>
However, it does work in the app, which is next to useless if you need it in the extension. GCDAsyncUdpSocket is a thin layer that right under the covers is calling socket() and connect().
NWConnection does work in a network extension, and it should work for you if it is feasible to port your code and you don't need the actual socket descriptor. But you will have to conditionally compile if you have to support devices < ios 12.

Why is my swift app "leaking" memory in AVAsset

While retrieving metadata from media files, I've run into a memory issue I cannot figure out.
I want to retrieve metadata for media files either stored in the local app storage or in the iTunes area. For this I use AVAsset. While looping through these files I can see the memory consumption rising constantly. And not just a little. It is significant and end up stalling the app when I enumerate my iTunes library on the phone.
The problem seems to be accessing the metadata property on the AVAsset class. I've narrowed in down to one line of code: 'let meta = ass.metadata'. Having that line of code (without any references) makes the app consume memory. I've included an example of my code structure.
func processFiles(_ files:Array)
{
var lastalbum : String = ""
var i : Int = 0
for file in files
{
i += 1
view.setProgressPosition(CGFloat(i)/CGFloat(files.count))
lastalbum = updateFile(file.url,lastalbum,
{ (album,title,artist,composer) in
view.setProgressNote(album,title,artist+" / "+composer)
})
}
}
func updateFile(_ url:URL,_ lastalbum:String,iPod:Bool=false,
_ progress:(String,String,String,String) -> Void) -> String
{
let ass = AVAsset(url:url)
let meta = ass.metadata
for item in meta
{
// Examine metadata
}
// Use metadata
// Callback with status
}
It seems that memory allocated in the updateFile method, is kept, even when the function is ended. However, once the processFile function completes and the app returns to a normal state, all memory is released again.
So in conclusion, this is not a real leak, but still a significant problem. Any good ideas as to what goes wrong? Is there any way I can force the memory management to run a cleanup?
As suggested in the comment on the post, the solution for this is to wrap the specific code in a 'autoreleasepool' block. I've tested this both with a small set of local media files and also with my rather large iTunes media library (70GB). After implementing the 'autoreleasepool' the memory buildup is eliminated.
func updateFile(_ url:URL,_ lastalbum:String,iPod:Bool=false,
_ progress:(String,String,String,String) -> Void) -> String
{
autoreleasepool
{
let ass = AVAsset(url:url)
let meta = ass.metadata
for item in meta
{
// Examine metadata
}
// Use metadata
// Callback with status
}
}

What impact does changing a IReliableQueue to a IReliableConcurrentQueue have in an existing deployment?

I am working in a Service Fabric application that uses IReliableQueue. For the uses cases of this system, the IReliableConcurrentQueue makes sense to use and some local testing (i.e. basically by just changing the code to use IReliableConcurrentQueue instead of IReliableQueue - queue name does not change) shows great performance improvements. However, I am worried about the impact of changing this in a production system (i.e. upgrading). I can't find any docs or online questions (unless I just missed them) about these considerations. For example, in this system, the existing IReliableQueue will almost always have items. So what happens to that data when I upgrade the SF application? Will it be available to dequeue in the IReliableConcurrentQueue? Or would data be lost? I know I can "just try it" but wanted to see if someone out there had done the same or could offer pointers to existing resources. Thanks!
Sorry for a late answer (that you probably don't need anymore but still).
When we calling GetOrAddAsync method on IReliableStateManager we aren't retrieving the interface to store values - we actually creating an instance of reliable collection. This basically means that type of the interface we specify is very important.
Taking this into account if we do this:
Service v. 1.0
// Somewhere in RunAsync for example
await this.StateManager.GetOrAddAsync<IReliableQueue<long>>("MyCollection")
Then doing this in the next version:
Service v. 1.1
// Somewhere in RunAsync for example
await this.StateManager.GetOrAddAsync<IReliableConcurrentQueue<long>>("MyCollection")
will throw an exception:
Returned reliable object of type Microsoft.ServiceFabric.Data.Collections.DistributedQueue`1[System.Int64] cannot be casted to requested type Microsoft.ServiceFabric.Data.Collections.IReliableConcurrentQueue`1[System.Int64]
and then:
System.ExecutionEngineException: 'Exception of type 'System.ExecutionEngineException' was thrown.'
The above exception looks like a bug so I have filled one.
UPDATE 2019.06.28
It turned out that appearance of System.ExecutionEngineException isn't a bug but rather an undocumented behavior of Environment.FailFast method in combination with Visual Studio debugger.
Please see my comment to the above issue.
This is what would happen.
There are plenty ways to overcome this.
Here is the most obvious one:
Example
var migrate = false; // This flag indicates whether the migration was already done.
var migrateValues = new List<long>();
var applicationFlags = await this.StateManager
.GetOrAddAsync<IReliableDictionary<string, bool>>("application-flags");
using (var transaction = this.StateManager.CreateTransaction())
{
var flag = await applicationFlags
.TryGetValueAsync(transaction, "queue-to-concurrent-queue-migration");
if (!flag.HasValue || !flag.Value)
{
var queue = await this.StateManager
.GetOrAddAsync<IReliableQueue<long>>("value-collection");
for (;;)
{
var c = await queue.TryDequeueAsync(transaction);
if (!c.HasValue)
{
break;
}
migrateValues.Add(c.Value);
}
migrate = true;
}
}
if (migrate)
{
await this.StateManager.RemoveAsync("value-collection");
using (var transaction = this.StateManager.CreateTransaction())
{
var concurrentQueue = await this.StateManager
.GetOrAddAsync<IReliableConcurrentQueue<long>>("value-collection");
foreach (var i in migrateValues)
{
await concurrentQueue.EnqueueAsync(transaction, i);
}
await applicationFlags.AddOrUpdateAsync(
transaction,
"queue-to-concurrent-queue-migration",
true,
(s, b) => true);
}
await transaction.CommitAsync();
}
Please note that this code is just an illustrative example and should be properly tested before applying it to real life application.

Write to HID with Chip Selection with .NET Console App

Hi I am writing a simple console app that needs to write bytes to MCP2210 USB to SPI Master
I found this library over here, seems to do good job with connecting the device and reading the metadata.
I am writing message to the board as below
public static byte[] Talk()
{
var device = DeviceList.Local.GetHidDevices(1240, 222).FirstOrDefault();
if (device == null)
{
Console.WriteLine($"Could not find a device with Vendor Id:1240, Product Id:222 ");
return null;
}
var reportDescriptor = device.GetReportDescriptor();
foreach (var deviceItem in reportDescriptor.DeviceItems)
{
Console.WriteLine("Opening device for 20 seconds...");
if (!device.TryOpen(out var hidStream))
{
Console.WriteLine("Failed to open device.");
continue;
}
Console.WriteLine("Opened device.");
hidStream.ReadTimeout = Timeout.Infinite;
hidStream.Write(new byte[3] {60, 00, 00});
}
Not sure If I am writing it correctly.
While writing I need to do a chip selection as displayed in this other terminal
Any help is greatly appreciated
Here is the MC I am using https://www.microchip.com/wwwproducts/en/MCP2210
I do not see a closing of your stream. This may cause your data to not even being sent (at least not in time).
Consider using blocks with streams.
But with out parameters not possible.