Fail when installing big ipa file
When installing a big ipa file (3.3G), it will report error after uploading to PublicStaging:
device_test.go:104: receive packet: read tcp 127.0.0.1:15119->127.0.0.1:27015: wsarecv: An established connection was aborted by the software in your host machine.
It seems like lockdown connection has been closed by the phone. A simple fix would work: `
func (d *device) AppInstall(ipaPath string) (err error) { if _, err = d.AfcService(); err != nil { return err }
stagingPath := "PublicStaging"
if _, err = d.afc.Stat(stagingPath); err != nil {
if err != ErrAfcStatNotExist {
return err
}
if err = d.afc.Mkdir(stagingPath); err != nil {
return fmt.Errorf("app install: %w", err)
}
}
var info map[string]interface{}
if info, err = ipa.Info(ipaPath); err != nil {
return err
}
bundleID, ok := info["CFBundleIdentifier"]
if !ok {
return errors.New("can't find 'CFBundleIdentifier'")
}
installationPath := path.Join(stagingPath, fmt.Sprintf("%s.ipa", bundleID))
var data []byte
if data, err = os.ReadFile(ipaPath); err != nil {
return err
}
if err = d.afc.WriteFile(installationPath, data, AfcFileModeWr); err != nil {
return err
}
d.lockdown = nil
if _, err = d.installationProxyService(); err != nil {
return err
}
return d.installationProxy.Install(fmt.Sprintf("%s", bundleID), installationPath)
}
`
Add a d.lockdown = nil before loading installation proxy service. But I'm not sure that's the best way of fixing.
Is the device locked when an error is reported?
No, it will fail without the added d.lockdown = nil, whether locked or not.
Was it an error at about 30s?
Maybe I made a mistake here 👇
https://github.com/electricbubble/gidevice/blob/8c797a42a78b7c0ff7aff753dd87da4590eb00b1/device.go#L158
Maybe that's right 👇
if innerConn, err = d.NewConnect(LockdownPort, 0); err != nil {
Can you give it a try?
If it works as expected, PR welcomed 🥳
if innerConn, err = d.NewConnect(LockdownPort, 0); err != nil {
I changed all NewConnect() calls to include a 0 timeout. But it didn't work. I think that the timeout here, is for the action of connecting itself, and read/write timeout, but not for how long iOS allows the connection to live. So maybe we have to reconnect?
🤔 Maybe AFCService needs a separate connection?
Tried another method, sending a 'ping' message to lockdown service, and it worked.
func (c *afc) WriteFile(filename string, data []byte, perm AfcFileMode, l Lockdown) (err error) {
var file *AfcFile
if file, err = c.Open(filename, perm); err != nil {
return err
}
defer func() {
err = file.Close()
}()
chunk_size := 1 << 26
buf := bytes.NewBuffer(data)
for buf.Len() > 0 {
if _, err = file.Write(buf.Next(chunk_size)); err != nil {
return err
}
log.Println(buf.Len())
l.QueryType()
}
return
}
You seem to have found another mistake I made
I may not have written all the data correctly
Can you try this? Will it still report an error?
func (c *afc) WriteFile(filename string, data []byte, perm AfcFileMode) (err error) {
var file *AfcFile
if file, err = c.Open(filename, perm); err != nil {
return err
}
defer func() {
err = file.Close()
}()
if _, err = io.Copy(file, bytes.NewReader(data)); err != nil {
return err
}
return
}
This still don't work. Actually the data was written correctly in my case. I added some log to see the problem:
2021/05/21 10:47:51 Exit lockdown._startService
2021/05/21 10:47:51 PublicStaging
2021/05/21 10:47:52 Enter afc.WriteFile
2021/05/21 10:47:52 afc.Open
2021/05/21 10:47:52 Begin Copy
2021/05/21 10:49:40 End Copy
2021/05/21 10:49:40 file.Close
2021/05/21 10:49:40 Exit afc.WriteFile
2021/05/21 10:49:40 Enter installationProxyService
2021/05/21 10:49:40 Reuse d.lockdown
2021/05/21 10:49:40 lockdown.InstallationProxyService()
2021/05/21 10:49:40 Enter lockdown._startService
2021/05/21 10:49:40 handshake
2021/05/21 10:49:40 safeConn.Read Error read tcp 127.0.0.1:28673->127.0.0.1:27015: wsarecv: An established connection was aborted by the software in your host machine.
2021/05/21 10:49:40 handshake Error: receive packet: read tcp 127.0.0.1:28673->127.0.0.1:27015: wsarecv: An established connection was aborted by the software in your host machine.
2021/05/21 10:49:40 Exit installationProxyService
--- FAIL: Test_device_AppInstall (109.08s)
device_test.go:105: receive packet: read tcp 127.0.0.1:28673->127.0.0.1:27015: wsarecv: An established connection was aborted by the software in your host machine.
FAIL
Call stack:
github.com/electricbubble/gidevice/pkg/libimobiledevice.(*safeConn).Read (f:\Trunk\gidevice\pkg\libimobiledevice\usbmux.go:345)
github.com/electricbubble/gidevice/pkg/libimobiledevice.(*servicePacketClient).ReceivePacket (f:\Trunk\gidevice\pkg\libimobiledevice\client_servicepacket.go:50)
github.com/electricbubble/gidevice/pkg/libimobiledevice.(*LockdownClient).ReceivePacket (f:\Trunk\gidevice\pkg\libimobiledevice\lockdown.go:104)
github.com/electricbubble/gidevice.(*lockdown).QueryType (f:\Trunk\gidevice\lockdown.go:55)
github.com/electricbubble/gidevice.(*lockdown).handshake (f:\Trunk\gidevice\lockdown.go:143)
github.com/electricbubble/gidevice.(*lockdown)._startService (f:\Trunk\gidevice\lockdown.go:476)
github.com/electricbubble/gidevice.(*lockdown).InstallationProxyService (f:\Trunk\gidevice\lockdown.go:367)
github.com/electricbubble/gidevice.(*device).installationProxyService (f:\Trunk\gidevice\device.go:283)
github.com/electricbubble/gidevice.(*device).AppInstall (f:\Trunk\gidevice\device.go:418)
github.com/electricbubble/gidevice.Test_device_AppInstall (f:\Trunk\gidevice\device_test.go:103)
testing.tRunner (c:\Program Files\Go\src\testing\testing.go:1193)
runtime.goexit (c:\Program Files\Go\src\runtime\asm_amd64.s:1371)
There are 3 connections: a physical connection to usbmuxd, a tunneled connection to lockdown service, and a tunneled connection to afc service. The connection to installation service was never established. So the problem is with the tunneled connection to lockdown service. After applying afc service, we have never talked to the lockdown service, for a long time. So maybe the phone has closed the tunneled connection. When we want to talk to the lockdown service again, applying installation service, the error is reported.
So we have to keep the connection alive, or reconnect when appropriate. But I didn't find a way to check if the connection was alive. So maybe we should alway reconnect to lockdown service again before talking to it, or reconnect when we waited too long?
Nice!
I quite agree with you 👍
2021/05/21 10:47:52 Begin Copy
2021/05/21 10:49:40 End Copy
2mins
Maybe we have two ways to choose
Looking forward to your PR
- like https://github.com/electricbubble/gidevice/issues/4#issuecomment-843970622
func (d *device) AppInstall(ipaPath string) (err error) {
if _, err = d.AfcService(); err != nil {
return err
}
stagingPath := "PublicStaging"
if _, err = d.afc.Stat(stagingPath); err != nil {
if err != ErrAfcStatNotExist {
return err
}
if err = d.afc.Mkdir(stagingPath); err != nil {
return fmt.Errorf("app install: %w", err)
}
}
var info map[string]interface{}
if info, err = ipa.Info(ipaPath); err != nil {
return err
}
bundleID, ok := info["CFBundleIdentifier"]
if !ok {
return errors.New("can't find 'CFBundleIdentifier'")
}
installationPath := path.Join(stagingPath, fmt.Sprintf("%s.ipa", bundleID))
chUploaded := make(chan bool)
go func() {
for {
select {
case <-chUploaded:
return
default:
if _, err := newLockdown(d).QueryType(); err != nil {
debugLog(fmt.Sprintf("AppInstall 'ping': %s", err))
}
time.Sleep(time.Second * 5)
}
}
}()
var data []byte
if data, err = os.ReadFile(ipaPath); err != nil {
return err
}
if err = d.afc.WriteFile(installationPath, data, AfcFileModeWr); err != nil {
chUploaded <- true
return err
}
chUploaded <- true
if _, err = d.installationProxyService(); err != nil {
return err
}
return d.installationProxy.Install(fmt.Sprintf("%s", bundleID), installationPath)
}
- Ensure that other functions do not have similar situations
https://github.com/electricbubble/gidevice/blob/main/device.go#L152
func (d *device) lockdownService() (lockdown Lockdown, err error) {
if d.lockdown != nil {
return d.lockdown, nil
}
var innerConn InnerConn
if innerConn, err = d.NewConnect(LockdownPort, 0); err != nil {
return nil, err
}
d.lockdownClient = libimobiledevice.NewLockdownClient(innerConn)
d.lockdown = newLockdown(d)
_, err = d.lockdown._getProductVersion()
lockdown = d.lockdown
go func() {
for {
if _, err := lockdown.QueryType(); err != nil {
if strings.Contains(err.Error(), io.EOF.Error()) {
return
}
debugLog(fmt.Sprintf("lockdownService 'ping': %s", err))
}
time.Sleep(time.Second * 10)
}
}()
return
}