mirror of
https://github.com/MengMengCode/VoCat.git
synced 2026-08-16 12:53:43 +08:00
294 lines
10 KiB
Go
294 lines
10 KiB
Go
package device
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"testing"
|
|
|
|
"vocat/internal/modem"
|
|
"vocat/internal/pcsc"
|
|
)
|
|
|
|
type testPCSCBackend struct{ readers []pcsc.Reader }
|
|
|
|
func (backend testPCSCBackend) Readers(context.Context) ([]pcsc.Reader, error) {
|
|
return append([]pcsc.Reader(nil), backend.readers...), nil
|
|
}
|
|
func (testPCSCBackend) Open(context.Context, pcsc.Selector) (pcsc.Card, error) {
|
|
return nil, pcsc.ErrNoCard
|
|
}
|
|
|
|
func TestManagerDiscoversWiFiCallingOnlyReaderWithoutATPort(t *testing.T) {
|
|
manager, err := NewManager(Options{
|
|
Discoverer: staticDiscoverer{}, Opener: &staticOpener{},
|
|
CardReaders: pcsc.NewWithBackend(testPCSCBackend{readers: []pcsc.Reader{{
|
|
Name: "Alcor Link AK9563 00 00", USBPath: "1-3", Product: "AK9563",
|
|
}}}),
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := manager.Start(context.Background()); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
t.Cleanup(func() { _ = manager.Stop(context.Background()) })
|
|
items := manager.List()
|
|
if len(items) != 1 || items[0].Candidate.HardwareKind != pcsc.HardwareKind || items[0].Candidate.HasATPort() {
|
|
t.Fatalf("discovered readers = %#v", items)
|
|
}
|
|
snapshot, err := manager.Refresh(context.Background(), items[0].ID)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !snapshot.Responsive || snapshot.SIMReady || snapshot.SIMStatus != "" || !snapshot.FlightMode {
|
|
t.Fatalf("reader snapshot = %#v", snapshot)
|
|
}
|
|
}
|
|
|
|
func TestManagerRefreshBuildsEC20Snapshot(t *testing.T) {
|
|
client := &transcriptClient{steps: []clientStep{
|
|
{
|
|
command: "ATI",
|
|
response: okResponse(
|
|
"Quectel",
|
|
"EC20CEFAGR06A04M4G",
|
|
"Revision: EC20CEHCLGR06A04M1G",
|
|
),
|
|
},
|
|
{command: "AT+CPIN?", response: okResponse("+CPIN: READY")},
|
|
{
|
|
command: "AT+CCID",
|
|
response: modem.Response{Final: "+CME ERROR: 100"},
|
|
err: errors.New("CCID unsupported"),
|
|
},
|
|
{command: "AT+QCCID", response: okResponse("+QCCID: 8986001234567890123F")},
|
|
{command: "AT+CIMI", response: okResponse("460001234567890")},
|
|
{command: "AT+CRSM=176,28486,0,0,17", response: okResponse(`+CRSM: 144,0,"00434D4343FFFFFFFFFFFFFFFFFFFFFFFF"`)},
|
|
{command: "AT+CSQ", response: okResponse("+CSQ: 20,99")},
|
|
{
|
|
command: `AT+QENG="servingcell"`,
|
|
response: okResponse(
|
|
`+QENG: "servingcell","NOCONN","LTE","FDD",460,01,5F1E805,37,1650,3,5,5,8340,-97,-10,-68,15,9`,
|
|
),
|
|
},
|
|
{command: "AT+COPS?", response: okResponse(`+COPS: 0,0,"China Mobile",7`)},
|
|
{command: "AT+CEREG?", response: okResponse(`+CEREG: 0,5`)},
|
|
{command: "AT+CGSN", response: okResponse("867123456789012")},
|
|
{command: "AT+CFUN?", response: okResponse("+CFUN: 1")},
|
|
{command: "AT+CNUM", response: okResponse(`+CNUM: "","+8613800138000",145`)},
|
|
}}
|
|
manager, id := newStartedTestManager(t, client)
|
|
|
|
snapshot, err := manager.Refresh(context.Background(), id)
|
|
if err != nil {
|
|
t.Fatalf("Refresh: %v", err)
|
|
}
|
|
if !snapshot.Responsive || !snapshot.SIMReady || snapshot.SIMStatus != "ready" {
|
|
t.Fatalf("modem/SIM state = %#v", snapshot)
|
|
}
|
|
if snapshot.Manufacturer != "Quectel" ||
|
|
snapshot.Model != "EC20CEFAGR06A04M4G" ||
|
|
snapshot.Firmware != "EC20CEHCLGR06A04M1G" {
|
|
t.Fatalf(
|
|
"identity = manufacturer %q, model %q, firmware %q",
|
|
snapshot.Manufacturer,
|
|
snapshot.Model,
|
|
snapshot.Firmware,
|
|
)
|
|
}
|
|
if snapshot.SignalRaw == nil || *snapshot.SignalRaw != 20 ||
|
|
snapshot.SignalPercent == nil || *snapshot.SignalPercent != 65 ||
|
|
snapshot.RSSIDBm == nil || *snapshot.RSSIDBm != -68 ||
|
|
snapshot.RSRP == nil || *snapshot.RSRP != -97 ||
|
|
snapshot.RSRQ == nil || *snapshot.RSRQ != -10 ||
|
|
snapshot.SINR == nil || *snapshot.SINR != 15 {
|
|
t.Fatalf("signal metrics = %#v", snapshot)
|
|
}
|
|
if snapshot.AccessTech != "LTE" || snapshot.Band != "B3" ||
|
|
snapshot.Channel != "1650" || snapshot.OperatorName != "China Unicom" ||
|
|
snapshot.OperatorCode != "46001" ||
|
|
snapshot.RegistrationStatus != 5 || snapshot.RegistrationSource != "CEREG" {
|
|
t.Fatalf("network = %#v", snapshot)
|
|
}
|
|
if snapshot.IMEI != "867123456789012" ||
|
|
snapshot.ICCID != "8986001234567890123" ||
|
|
snapshot.IMSI != "460001234567890" || snapshot.SPN != "CMCC" {
|
|
t.Fatalf("subscriber identifiers = %#v", snapshot)
|
|
}
|
|
if !snapshot.ModeKnown || snapshot.OperatingMode != 1 ||
|
|
snapshot.FlightMode || snapshot.RadioOff {
|
|
t.Fatalf("operating mode = %#v", snapshot)
|
|
}
|
|
if snapshot.Phone.Number != "+8613800138000" ||
|
|
snapshot.Phone.Source != PhoneSourceCNUM {
|
|
t.Fatalf("phone = %#v", snapshot.Phone)
|
|
}
|
|
|
|
device, err := manager.Get(id)
|
|
if err != nil {
|
|
t.Fatalf("Get: %v", err)
|
|
}
|
|
if device.Snapshot == nil || device.Snapshot.Phone.Number != snapshot.Phone.Number {
|
|
t.Fatalf("stored device = %#v", device)
|
|
}
|
|
client.assertDone(t)
|
|
}
|
|
|
|
func TestParseSPNASCIIAndUCS2(t *testing.T) {
|
|
if got := parseSPN(okResponse(`+CRSM: 144,0,"004C6562617261FFFFFFFFFFFFFFFFFFFF"`)); got != "Lebara" {
|
|
t.Fatalf("ASCII SPN = %q", got)
|
|
}
|
|
if got := parseSPN(okResponse(`+CRSM: 144,0,"0080004C00650062006100720061FFFF"`)); got != "Lebara" {
|
|
t.Fatalf("UCS2 SPN = %q", got)
|
|
}
|
|
if got := parseSPN(okResponse(`+CRSM: 106,130,""`)); got != "" {
|
|
t.Fatalf("failed CRSM SPN = %q", got)
|
|
}
|
|
}
|
|
|
|
func TestParseICCIDIdentifierStripsTwoFillerNibbles(t *testing.T) {
|
|
response := modem.Response{Lines: []string{"+CCID: 894921007608519523FF"}}
|
|
if got := parseICCIDIdentifier(response, []string{"+CCID:", "+QCCID:"}, 18, 22); got != "894921007608519523" {
|
|
t.Fatalf("parseICCIDIdentifier = %q", got)
|
|
}
|
|
sierra := okResponse("!ICCID: 89441000400316048687")
|
|
if got := parseICCIDIdentifier(sierra, []string{"+CCID:", "+QCCID:", "!ICCID:"}, 18, 22); got != "89441000400316048687" {
|
|
t.Fatalf("parse Sierra ICCID = %q", got)
|
|
}
|
|
}
|
|
|
|
func TestManagerRequiresStartAndKnownDevice(t *testing.T) {
|
|
manager, err := NewManager(Options{
|
|
Discoverer: staticDiscoverer{},
|
|
Opener: &staticOpener{},
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := manager.Refresh(context.Background(), "missing"); !errors.Is(err, ErrNotStarted) {
|
|
t.Fatalf("before Start error = %v", err)
|
|
}
|
|
if err := manager.Start(context.Background()); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := manager.Refresh(context.Background(), "missing"); !errors.Is(err, ErrNotFound) {
|
|
t.Fatalf("unknown device error = %v", err)
|
|
}
|
|
}
|
|
|
|
func TestManagerBackendSelectionIsExplicit(t *testing.T) {
|
|
manager, id := newStartedTestManager(t, &transcriptClient{})
|
|
if err := manager.SetBackend(id, "qmi"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
state, err := manager.lookup(id)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got := manager.backendFor(state); got != "qmi" {
|
|
t.Fatalf("backend = %q, want qmi", got)
|
|
}
|
|
if err := manager.SetBackend(id, "mbim"); err != nil {
|
|
t.Fatalf("MBIM backend was rejected: %v", err)
|
|
}
|
|
if got := manager.backendFor(state); got != "mbim" {
|
|
t.Fatalf("backend = %q, want mbim", got)
|
|
}
|
|
if err := manager.SetBackend(id, "invalid"); err == nil {
|
|
t.Fatal("unsupported backend was accepted")
|
|
}
|
|
}
|
|
|
|
func TestParseSierraGStatus(t *testing.T) {
|
|
metrics := parseSierraGStatus(okResponse(
|
|
"!GSTATUS:",
|
|
"System mode: LTE PS state: Attached",
|
|
"LTE band: B3 LTE bw: 20 MHz",
|
|
"LTE Rx chan: 1650 LTE Tx chan: 19650",
|
|
"RSSI (dBm): -63.0 Tx Power: --",
|
|
"RSRP (dBm): -92.0 RSRQ (dB): -7.2",
|
|
"SINR (dB): 17.6",
|
|
))
|
|
if metrics.AccessTech != "LTE" || metrics.Band != "B3" || metrics.Channel != "1650" {
|
|
t.Fatalf("identity metrics = %#v", metrics)
|
|
}
|
|
if metrics.RSSI == nil || *metrics.RSSI != -63 || metrics.RSRP == nil || *metrics.RSRP != -92 ||
|
|
metrics.RSRQ == nil || *metrics.RSRQ != -7 || metrics.SINR == nil || *metrics.SINR != 18 {
|
|
t.Fatalf("radio metrics = %#v", metrics)
|
|
}
|
|
}
|
|
|
|
func TestParseSierraSIMIdentityFromCRSM(t *testing.T) {
|
|
iccid := parseCRSMICCID(okResponse(`+CRSM: 144,0,"98103254769810325476"`))
|
|
if iccid != "89012345678901234567" {
|
|
t.Fatalf("ICCID = %q", iccid)
|
|
}
|
|
imsi := parseCRSMIMSI(okResponse(`+CRSM: 144,0,"080910108967452301"`))
|
|
if imsi != "001019876543210" {
|
|
t.Fatalf("IMSI = %q", imsi)
|
|
}
|
|
manufacturer, model, firmware := parseATI([]string{
|
|
"Manufacturer: Sierra Wireless, Incorporated",
|
|
"Model: EM7430",
|
|
"Revision: SWI9X30C_02.24.05.06 r7040",
|
|
})
|
|
if manufacturer != "Sierra Wireless, Incorporated" || model != "EM7430" || firmware != "SWI9X30C_02.24.05.06 r7040" {
|
|
t.Fatalf("ATI = manufacturer %q model %q firmware %q", manufacturer, model, firmware)
|
|
}
|
|
}
|
|
|
|
func TestManagerForcesRFOffBeforeInspectingChangedSIMNetwork(t *testing.T) {
|
|
client := &transcriptClient{steps: []clientStep{
|
|
{command: "ATI", response: okResponse("Quectel", "EC20", "Revision: test")},
|
|
{command: "AT+CPIN?", response: okResponse("+CPIN: READY")},
|
|
{command: "AT+CCID", response: okResponse("+CCID: 8900000000000000002")},
|
|
// This must precede CIMI, signal, serving-cell and operator queries.
|
|
{command: "AT+CFUN=4", response: okResponse()},
|
|
{command: "AT+CIMI", response: okResponse("234150000000002")},
|
|
{command: "AT+CRSM=176,28486,0,0,17", response: okResponse(`+CRSM: 144,0,"004C6562617261FFFFFFFFFFFFFFFFFFFF"`)},
|
|
{command: "AT+CSQ", response: okResponse("+CSQ: 99,99")},
|
|
{command: `AT+QENG="servingcell"`, response: okResponse(`+QENG: "servingcell","SEARCH"`)},
|
|
{command: "AT+COPS?", response: okResponse("+COPS: 0")},
|
|
{command: "AT+CEREG?", response: okResponse("+CEREG: 0,0")},
|
|
{command: "AT+CGSN", response: okResponse("867123456789012")},
|
|
{command: "AT+CFUN?", response: okResponse("+CFUN: 4")},
|
|
{command: "AT+CNUM", response: okResponse(`+CNUM: "","+447700900002",145`)},
|
|
}}
|
|
manager, id := newStartedTestManager(t, client)
|
|
state, err := manager.lookup(id)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
state.lastICCID = "8900000000000000001"
|
|
|
|
snapshot, err := manager.Refresh(context.Background(), id)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !snapshot.SIMChanged || !snapshot.FlightMode || snapshot.OperatingMode != 4 {
|
|
t.Fatalf("changed SIM snapshot = %#v", snapshot)
|
|
}
|
|
client.assertDone(t)
|
|
}
|
|
|
|
func TestExecuteSensitiveATDoesNotPersistCommandOrModemError(t *testing.T) {
|
|
const secretCommand = `AT+CSIM=78,"00880081221000112233445566778899AABBCCDDEEFF1000112233445566778899AABBCCDDEEFF00"`
|
|
client := &transcriptClient{steps: []clientStep{{
|
|
command: secretCommand,
|
|
err: &modem.CommandError{Command: secretCommand, Final: "ERROR"},
|
|
}}}
|
|
manager, id := newStartedTestManager(t, client)
|
|
|
|
if _, err := manager.ExecuteSensitiveAT(context.Background(), id, secretCommand); err == nil {
|
|
t.Fatal("ExecuteSensitiveAT() error = nil")
|
|
}
|
|
entry, err := manager.Get(id)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if entry.LastError != "sensitive AT command failed" {
|
|
t.Fatalf("LastError = %q", entry.LastError)
|
|
}
|
|
client.assertDone(t)
|
|
}
|