Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fail when installing big ipa file #4

Open
jessehardy opened this issue May 18, 2021 · 9 comments
Open

Fail when installing big ipa file #4

jessehardy opened this issue May 18, 2021 · 9 comments

Comments

@jessehardy
Copy link

jessehardy commented May 18, 2021

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.

@electricbubble
Copy link
Owner

Is the device locked when an error is reported?

@jessehardy
Copy link
Author

No, it will fail without the added d.lockdown = nil, whether locked or not.

@electricbubble
Copy link
Owner

Was it an error at about 30s?

Maybe I made a mistake here 👇

if innerConn, err = d.NewConnect(LockdownPort); err != nil {


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 🥳

@jessehardy
Copy link
Author

jessehardy commented May 18, 2021

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?

@electricbubble
Copy link
Owner

🤔 Maybe AFCService needs a separate connection?

@jessehardy
Copy link
Author

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
}

@electricbubble
Copy link
Owner

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
}

@jessehardy
Copy link
Author

jessehardy commented May 21, 2021

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?

@electricbubble
Copy link
Owner

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

  1. like Fail when installing big ipa file #4 (comment)
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)
}
  1. 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
}

ZhouYixun referenced this issue in SonicCloudOrg/sonic-gidevice Nov 27, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants