mirror of
https://github.com/MengMengCode/VoCat.git
synced 2026-08-13 03:13:43 +08:00
137 lines
3.7 KiB
Go
137 lines
3.7 KiB
Go
package integration
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"strings"
|
|
|
|
"vocat/internal/device"
|
|
"vocat/internal/modem"
|
|
"vocat/internal/store"
|
|
)
|
|
|
|
type ATDeviceController interface {
|
|
Get(string) (device.Device, error)
|
|
List() []device.Device
|
|
ExecuteAT(context.Context, string, string) (modem.Response, error)
|
|
ExecuteSensitiveAT(context.Context, string, string) (modem.Response, error)
|
|
}
|
|
|
|
// ATMapper lets runtime IDs remain stable configuration IDs even when Linux
|
|
// re-enumerates a physical modem under a discovery-derived ID.
|
|
type ATMapper struct {
|
|
Store *store.Store
|
|
Devices ATDeviceController
|
|
}
|
|
|
|
type uiccLocker interface {
|
|
LockUICC()
|
|
UnlockUICC()
|
|
}
|
|
|
|
func (mapper ATMapper) LockUICC() {
|
|
if locker, ok := mapper.Devices.(uiccLocker); ok {
|
|
locker.LockUICC()
|
|
}
|
|
}
|
|
|
|
func (mapper ATMapper) UnlockUICC() {
|
|
if locker, ok := mapper.Devices.(uiccLocker); ok {
|
|
locker.UnlockUICC()
|
|
}
|
|
}
|
|
|
|
func (mapper ATMapper) Get(configuredID string) (device.Device, error) {
|
|
physicalID, err := mapper.resolve(context.Background(), configuredID)
|
|
if err != nil {
|
|
return device.Device{}, err
|
|
}
|
|
return mapper.Devices.Get(physicalID)
|
|
}
|
|
|
|
func (mapper ATMapper) ExecuteAT(
|
|
ctx context.Context,
|
|
configuredID string,
|
|
command string,
|
|
) (modem.Response, error) {
|
|
physicalID, err := mapper.resolve(ctx, configuredID)
|
|
if err != nil {
|
|
return modem.Response{}, err
|
|
}
|
|
return mapper.Devices.ExecuteAT(ctx, physicalID, command)
|
|
}
|
|
|
|
func (mapper ATMapper) ExecuteSensitiveAT(
|
|
ctx context.Context,
|
|
configuredID string,
|
|
command string,
|
|
) (modem.Response, error) {
|
|
physicalID, err := mapper.resolve(ctx, configuredID)
|
|
if err != nil {
|
|
return modem.Response{}, err
|
|
}
|
|
return mapper.Devices.ExecuteSensitiveAT(ctx, physicalID, command)
|
|
}
|
|
|
|
func (mapper ATMapper) resolve(
|
|
ctx context.Context,
|
|
configuredID string,
|
|
) (string, error) {
|
|
if mapper.Store == nil || mapper.Devices == nil {
|
|
return "", errors.New("vowifi AT mapper is not configured")
|
|
}
|
|
config, err := mapper.Store.Device(ctx, configuredID)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
// Score every physical candidate before choosing one. Returning the first
|
|
// partial match made two EC20s on the same hub vulnerable to iteration order:
|
|
// a stale USB path on one configured row could win before the other
|
|
// candidate's AT port, QMI node, or live IMEI was considered. The result was
|
|
// two logical devices issuing APDUs to the same SIM.
|
|
bestID := ""
|
|
bestScore := 0
|
|
for _, entry := range mapper.Devices.List() {
|
|
if !entry.Discovered {
|
|
continue
|
|
}
|
|
score := physicalMatchScore(configuredID, config, entry)
|
|
if score > bestScore {
|
|
bestID = entry.ID
|
|
bestScore = score
|
|
}
|
|
}
|
|
if bestID != "" {
|
|
return bestID, nil
|
|
}
|
|
return "", device.ErrNotFound
|
|
}
|
|
|
|
func physicalMatchScore(configuredID string, config store.Device, entry device.Device) int {
|
|
candidate := entry.Candidate
|
|
score := 0
|
|
// A live modem identity is the strongest evidence and must override stale
|
|
// Linux node names or a USB topology saved before devices were rearranged.
|
|
if config.ModemIMEI != "" && entry.Snapshot != nil &&
|
|
strings.EqualFold(strings.TrimSpace(config.ModemIMEI), strings.TrimSpace(entry.Snapshot.IMEI)) {
|
|
score += 10000
|
|
}
|
|
if config.ATPort != "" &&
|
|
(config.ATPort == candidate.ATPort.Path || config.ATPort == candidate.ATPort.OpenPath()) {
|
|
score += 300
|
|
}
|
|
if config.ControlDevice != "" &&
|
|
(config.ControlDevice == candidate.QMIControl || config.ControlDevice == candidate.ATPort.OpenPath()) {
|
|
score += 300
|
|
}
|
|
if config.USBPath != "" && config.USBPath == candidate.USBPath {
|
|
score += 100
|
|
}
|
|
// Discovery IDs are not persistent user IDs. Treat an exact text match only
|
|
// as a weak hint so it cannot override physical identity evidence.
|
|
if entry.ID == configuredID {
|
|
score += 25
|
|
}
|
|
return score
|
|
}
|