package device import ( "context" "encoding/hex" "errors" "fmt" "strings" ) var ( ErrESIMDisableProfileNotFound = errors.New("esim: profile to disable was not found on the eUICC") ErrESIMProfileNotEnabled = errors.New("esim: profile is not currently enabled") ErrESIMDisableDisallowedByPolicy = errors.New("esim: profile disabling is not allowed by its policy") ErrESIMDisableCATBusy = errors.New("esim: card application toolkit is busy; retry disabling later") ) func buildDisableProfileRequest(iccid string) ([]byte, error) { bcd, err := encodeICCID(strings.TrimSpace(iccid)) if err != nil { return nil, err } // SGP.22 ES10c DisableProfileRequest: // BF32 { A0 { 5A } 81 01 FF } (refreshFlag = true). profileID := derConstruct(0xA0, derEncode(0x5A, bcd)) return derConstruct(0xBF32, profileID, derEncode(0x81, []byte{0xFF})), nil } func disableProfileResult(payload []byte) (byte, bool) { nodes := derParse(payload) if len(nodes) != 1 || nodes[0].tag != 0xBF32 { return 0, false } result := derFindValue(payload, 0x80) if len(result) != 1 { return 0, false } return result[0], true } func disableProfileResponseError(result byte, payload []byte) error { raw := strings.ToUpper(hex.EncodeToString(payload)) switch result { case 0: return nil case 1: return fmt.Errorf("%w (result=0x%02X, raw %s)", ErrESIMDisableProfileNotFound, result, raw) case 2: return fmt.Errorf("%w (result=0x%02X, raw %s)", ErrESIMProfileNotEnabled, result, raw) case 3: return fmt.Errorf("%w (result=0x%02X, raw %s)", ErrESIMDisableDisallowedByPolicy, result, raw) case 5: return fmt.Errorf("%w (result=0x%02X, raw %s)", ErrESIMDisableCATBusy, result, raw) default: return fmt.Errorf("esim: eUICC rejected DisableProfile, result=0x%02X (raw %s)", result, raw) } } // ESIMDisableProfile disables the currently enabled profile through ES10c. // With refreshFlag=true, the modem must be reset/re-discovered after commit. func (manager *Manager) ESIMDisableProfile(ctx context.Context, id, iccid, aidHex string) error { request, err := buildDisableProfileRequest(iccid) if err != nil { return err } manager.esimMu.Lock() defer manager.esimMu.Unlock() if err := manager.waitForESIMRecovery(ctx, id); err != nil { return err } channel, err := manager.openEuiccAID(ctx, id, targetEuiccAID(aidHex)) if err != nil { return err } commitContext, cancelCommit := context.WithTimeout(context.WithoutCancel(ctx), csimAPDUTimeout) payload, err := channel.es10(commitContext, request) cancelCommit() closeContext, cancelClose := context.WithTimeout(context.Background(), csimAPDUTimeout) channel.close(closeContext) cancelClose() if err != nil { // The card may have committed immediately before a transport failure. manager.startProfileSwitchRecovery(id) return err } result, ok := disableProfileResult(payload) if !ok { manager.startProfileSwitchRecovery(id) return fmt.Errorf("esim: unexpected DisableProfile response %s", strings.ToUpper(hex.EncodeToString(payload))) } if err := disableProfileResponseError(result, payload); err != nil { return err } manager.markCachedProfileDisabled(id, strings.TrimSpace(iccid)) manager.startProfileSwitchRecovery(id) return nil }