mirror of
https://github.com/MengMengCode/VoCat.git
synced 2026-08-21 23:33:42 +08:00
fix: stabilize OpenStick 410 VoWiFi startup (#74)
* fix: stabilize OpenStick 410 VoWiFi startup * fix: recover OpenStick 410 eUICC channel allocation --------- Co-authored-by: MengMengCode <[email protected]>
This commit is contained in:
+63
-1
@@ -684,7 +684,18 @@ func configureVoWiFiRuntime(
|
||||
)
|
||||
}
|
||||
}
|
||||
if _, err := manager.RequestEnabled(deviceConfig.ID, true); err != nil {
|
||||
requestEnable := func() error {
|
||||
_, requestErr := manager.RequestEnabled(deviceConfig.ID, true)
|
||||
return requestErr
|
||||
}
|
||||
if err := requestVoWiFiStartup(
|
||||
ctx,
|
||||
logger,
|
||||
deviceConfig.DeviceType,
|
||||
deviceConfig.ID,
|
||||
wifi410VoWiFiStartupDelay,
|
||||
requestEnable,
|
||||
); err != nil {
|
||||
_ = manager.Close(context.Background())
|
||||
return nil, fmt.Errorf("start device %q VoWiFi policy: %w", deviceConfig.ID, err)
|
||||
}
|
||||
@@ -696,8 +707,55 @@ func configureVoWiFiRuntime(
|
||||
const (
|
||||
vowifiStartupRadioAttempts = 3
|
||||
vowifiStartupRadioDelay = time.Second
|
||||
wifi410VoWiFiStartupDelay = 80 * time.Second
|
||||
)
|
||||
|
||||
// requestVoWiFiStartup delays only the persisted startup policy for OpenStick
|
||||
// 410 devices. Their Qualcomm UIM and Vodafone ePDG path need a short quiet
|
||||
// period after a cold boot; user-triggered reconnects and every other device
|
||||
// type continue to execute immediately.
|
||||
func requestVoWiFiStartup(
|
||||
ctx context.Context,
|
||||
logger *slog.Logger,
|
||||
deviceType string,
|
||||
deviceID string,
|
||||
delay time.Duration,
|
||||
request func() error,
|
||||
) error {
|
||||
if deviceType != store.DeviceTypeWiFi410 || delay <= 0 {
|
||||
return request()
|
||||
}
|
||||
if logger == nil {
|
||||
logger = slog.Default()
|
||||
}
|
||||
logger.Info(
|
||||
"OpenStick 410 VoWiFi startup delayed",
|
||||
"device_id", deviceID,
|
||||
"delay", delay,
|
||||
)
|
||||
go func() {
|
||||
timer := time.NewTimer(delay)
|
||||
defer timer.Stop()
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-timer.C:
|
||||
}
|
||||
if err := request(); err != nil {
|
||||
logger.Warn(
|
||||
"OpenStick 410 delayed VoWiFi startup failed",
|
||||
"device_id", deviceID,
|
||||
"error", err,
|
||||
)
|
||||
}
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
func shouldDelayWiFi410VoWiFi(deviceType string, now, notBefore time.Time) bool {
|
||||
return deviceType == store.DeviceTypeWiFi410 && now.Before(notBefore)
|
||||
}
|
||||
|
||||
type flightModeSetter interface {
|
||||
SetFlight(context.Context, string, bool) (device.FlightResult, error)
|
||||
}
|
||||
@@ -1136,6 +1194,7 @@ func reconcileCardPolicies(
|
||||
vowifiManager *vowifiruntime.Manager,
|
||||
) {
|
||||
observedCards := make(map[string]string)
|
||||
wifi410StartupNotBefore := time.Now().Add(wifi410VoWiFiStartupDelay)
|
||||
reconcile := func() {
|
||||
policies, policyListErr := database.ListCardPolicies(ctx)
|
||||
if policyListErr == nil {
|
||||
@@ -1217,6 +1276,9 @@ func reconcileCardPolicies(
|
||||
}
|
||||
switch {
|
||||
case stateErr != nil || !state.Enabled:
|
||||
if shouldDelayWiFi410VoWiFi(config.DeviceType, time.Now(), wifi410StartupNotBefore) {
|
||||
continue
|
||||
}
|
||||
_, _ = vowifiManager.RequestEnabled(config.ID, true)
|
||||
case state.ICCID != "" && !strings.EqualFold(strings.TrimSpace(state.ICCID), iccid):
|
||||
_, _ = vowifiManager.RequestReconnect(config.ID)
|
||||
|
||||
@@ -439,6 +439,9 @@ func (manager *Manager) openQMIEuiccOnceAID(ctx context.Context, id string, cand
|
||||
}
|
||||
const slot uint8 = 1
|
||||
logicalChannel, err := session.OpenLogicalChannel(openContext, slot, aid)
|
||||
if recoverySession, recoveryOK := session.(nativeQMIChannelRecoverySession); recoveryOK && isQMIInsufficientResources(err) {
|
||||
logicalChannel, err = openNativeQMIChannelWithRecovery(openContext, recoverySession, slot, aid)
|
||||
}
|
||||
if err != nil {
|
||||
_ = session.Close()
|
||||
return nil, fmt.Errorf("%w: %v", errNoEUICC, err)
|
||||
|
||||
@@ -6,6 +6,8 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/iniwex5/quectel-qmi-go/pkg/qmi"
|
||||
)
|
||||
|
||||
func (manager *Manager) withNativeQMIVoWiFiSession(ctx context.Context, id string, fn func(nativeQMIVoWiFiSession) error) error {
|
||||
@@ -70,7 +72,7 @@ func (manager *Manager) ProbeNativeQMIApplication(ctx context.Context, id, prefe
|
||||
|
||||
func (manager *Manager) AuthenticateNativeQMI(ctx context.Context, id string, aid, apdu []byte) (response []byte, err error) {
|
||||
err = manager.withNativeQMIVoWiFiSession(ctx, id, func(session nativeQMIVoWiFiSession) error {
|
||||
channel, openErr := session.OpenLogicalChannel(ctx, 1, aid)
|
||||
channel, openErr := openNativeQMIChannelWithRecovery(ctx, session, 1, aid)
|
||||
if openErr != nil {
|
||||
return fmt.Errorf("open QMI UIM logical channel: %w", openErr)
|
||||
}
|
||||
@@ -102,6 +104,60 @@ func (manager *Manager) AuthenticateNativeQMI(ctx context.Context, id string, ai
|
||||
return
|
||||
}
|
||||
|
||||
type nativeQMIChannelRecoverySession interface {
|
||||
OpenLogicalChannel(context.Context, uint8, []byte) (byte, error)
|
||||
PowerOffSIM(context.Context, uint8) error
|
||||
PowerOnSIM(context.Context, uint8) error
|
||||
}
|
||||
|
||||
// OpenStick 410 can leave the physical UICC powered but unable to allocate a
|
||||
// logical channel after a SIM hot-swap. A UIM service reset alone does not
|
||||
// clear that state; cycling the affected physical slot does. Recover only the
|
||||
// precise QMI InsufficientResources response, then retry the original AID once.
|
||||
func openNativeQMIChannelWithRecovery(
|
||||
ctx context.Context,
|
||||
session nativeQMIChannelRecoverySession,
|
||||
slot uint8,
|
||||
aid []byte,
|
||||
) (byte, error) {
|
||||
channel, err := session.OpenLogicalChannel(ctx, slot, aid)
|
||||
if err == nil || !isQMIInsufficientResources(err) {
|
||||
return channel, err
|
||||
}
|
||||
if resetter, ok := session.(nativeQMIUIMResetSession); ok {
|
||||
_ = resetter.ResetUIM(ctx)
|
||||
}
|
||||
if powerErr := session.PowerOffSIM(ctx, slot); powerErr != nil {
|
||||
return 0, errors.Join(err, fmt.Errorf("power off QMI UIM slot %d: %w", slot, powerErr))
|
||||
}
|
||||
if waitErr := waitNativeQMIRecovery(ctx, 3*time.Second); waitErr != nil {
|
||||
return 0, errors.Join(err, waitErr)
|
||||
}
|
||||
if powerErr := session.PowerOnSIM(ctx, slot); powerErr != nil {
|
||||
return 0, errors.Join(err, fmt.Errorf("power on QMI UIM slot %d: %w", slot, powerErr))
|
||||
}
|
||||
if waitErr := waitNativeQMIRecovery(ctx, 5*time.Second); waitErr != nil {
|
||||
return 0, errors.Join(err, waitErr)
|
||||
}
|
||||
return session.OpenLogicalChannel(ctx, slot, aid)
|
||||
}
|
||||
|
||||
func isQMIInsufficientResources(err error) bool {
|
||||
qmiErr := qmi.GetQMIError(err)
|
||||
return qmiErr != nil && qmiErr.Service == qmi.ServiceUIM && qmiErr.ErrorCode == 0x0044
|
||||
}
|
||||
|
||||
var waitNativeQMIRecovery = func(ctx context.Context, delay time.Duration) error {
|
||||
timer := time.NewTimer(delay)
|
||||
defer timer.Stop()
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case <-timer.C:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (manager *Manager) NativeQMIRadioSnapshot(ctx context.Context, id string) (mode int, psAttached bool, err error) {
|
||||
err = manager.withNativeQMIVoWiFiSession(ctx, id, func(session nativeQMIVoWiFiSession) error {
|
||||
qmiMode, modeErr := session.GetOperatingMode(ctx)
|
||||
|
||||
@@ -36,8 +36,12 @@ func resolveEPDG(ctx context.Context, resolver *net.Resolver, host string) ([]ne
|
||||
|
||||
var systemErr error
|
||||
for _, targetHost := range hostsToTry {
|
||||
addresses, err := resolver.LookupIPAddr(ctx, targetHost)
|
||||
ips, err := resolver.LookupIP(ctx, "ip4", targetHost)
|
||||
if err == nil {
|
||||
addresses := make([]net.IPAddr, 0, len(ips))
|
||||
for _, ip := range ips {
|
||||
addresses = append(addresses, net.IPAddr{IP: ip})
|
||||
}
|
||||
valid := filterValidPublicEPDGAddresses(addresses)
|
||||
if len(valid) > 0 {
|
||||
return valid, nil
|
||||
|
||||
@@ -144,7 +144,7 @@ func (adapter *NativeQMIAdapter) AuthenticateWithPreference(ctx context.Context,
|
||||
}
|
||||
raw, err := adapter.controller.AuthenticateNativeQMI(ctx, binding.deviceID, binding.aid, buildUSIMAuthenticateAPDU(challenge))
|
||||
if err != nil {
|
||||
return AKAResult{}, ErrEC20AKACommand
|
||||
return AKAResult{}, fmt.Errorf("%w: %v", ErrEC20AKACommand, err)
|
||||
}
|
||||
return parseUSIMAuthenticateResponse(raw)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user