| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225 |
- // Package integration connects the protocol runtime to vocat's persistent
- // configuration and modem inventory. It contains no IKE, IMS, or SIM protocol
- // implementation.
- package integration
- import (
- "context"
- "encoding/json"
- "errors"
- "fmt"
- "strings"
- "vocat/internal/device"
- "vocat/internal/store"
- "vocat/internal/vowifi"
- )
- type ProxyResolver struct {
- Store *store.Store
- }
- func (resolver ProxyResolver) Resolve(
- ctx context.Context,
- request vowifi.ProxyRequest,
- ) (vowifi.ProxyRoute, error) {
- if resolver.Store == nil {
- return vowifi.ProxyRoute{}, errors.New("vowifi proxy resolver: store is nil")
- }
- deviceID := strings.TrimSpace(request.DeviceID)
- if deviceID == "" {
- return vowifi.ProxyRoute{Mode: vowifi.ProxyModeDirect}, nil
- }
- binding, err := resolver.Store.DeviceProxyBinding(ctx, deviceID)
- if errors.Is(err, store.ErrNotFound) {
- return vowifi.ProxyRoute{Mode: vowifi.ProxyModeDirect}, nil
- }
- if err != nil {
- return vowifi.ProxyRoute{}, fmt.Errorf("resolve proxy binding for device %s: %w", deviceID, err)
- }
- upstream, err := resolver.Store.UpstreamProxy(ctx, binding.UpstreamProxyID)
- if err != nil {
- return vowifi.ProxyRoute{}, fmt.Errorf(
- "load upstream proxy %q for device %s: %w",
- binding.UpstreamProxyID,
- deviceID,
- err,
- )
- }
- if !upstream.Enabled {
- return vowifi.ProxyRoute{}, fmt.Errorf(
- "upstream proxy %q for device %s is disabled",
- upstream.ID,
- deviceID,
- )
- }
- return vowifi.ProxyRoute{
- Mode: vowifi.ProxyModeSOCKS5,
- ID: upstream.ID,
- Address: upstream.Addr,
- Username: upstream.Username,
- Password: upstream.Password,
- }, nil
- }
- type PhoneStore struct {
- Store *store.Store
- DeviceID string
- }
- func (phones PhoneStore) SaveAssociatedNumber(
- ctx context.Context,
- record vowifi.PhoneRecord,
- ) error {
- if phones.Store == nil {
- return errors.New("vowifi phone store: store is nil")
- }
- switch record.Source {
- case vowifi.PhoneSourceAssociatedMSISDN, vowifi.PhoneSourcePAssociatedURI:
- default:
- return fmt.Errorf("vowifi phone store: untrusted source %q", record.Source)
- }
- return phones.Store.UpsertPhoneAssociation(ctx, store.PhoneAssociation{
- ICCID: record.ICCID,
- DeviceID: strings.TrimSpace(phones.DeviceID),
- Number: record.Number,
- Source: record.Source,
- UpdatedAt: record.UpdatedAt,
- })
- }
- type DeviceReader interface {
- Get(string) (device.Device, error)
- }
- type StateProjector struct {
- Store *store.Store
- Devices DeviceReader
- }
- func (projector StateProjector) Save(
- ctx context.Context,
- state vowifi.State,
- ) error {
- if projector.Store == nil {
- return errors.New("vowifi state projector: store is nil")
- }
- runtime := store.VoWiFiRuntime{
- DeviceID: state.DeviceID,
- Phase: string(state.Phase),
- DataplaneMode: dataplaneMode(state),
- SIMReady: state.SIMReady,
- AccessReady: state.AccessReady,
- TunnelReady: state.TunnelReady,
- IMSReady: state.IMSReady,
- SMSReady: state.SMSReady,
- RegStatus: boolInt(state.IMSReady),
- RegStatusText: registrationText(state),
- NetworkMode: "Wi-Fi",
- LocalPhone: state.PhoneNumber,
- PhoneNumberSource: state.PhoneNumberSource,
- LastErrorClass: state.LastErrorClass,
- LastError: state.LastError,
- LastReason: state.LastReason,
- UpdatedAt: state.UpdatedAt,
- }
- if projector.Devices != nil {
- if entry, err := projector.Devices.Get(state.DeviceID); err == nil && entry.Snapshot != nil {
- runtime.ICCID = strings.TrimSpace(entry.Snapshot.ICCID)
- runtime.IMSI = strings.TrimSpace(entry.Snapshot.IMSI)
- }
- }
- if runtime.LocalPhone == "" && runtime.ICCID != "" {
- if association, err := projector.Store.PhoneAssociation(ctx, runtime.ICCID); err == nil {
- runtime.LocalPhone = association.Number
- runtime.PhoneNumberSource = association.Source
- } else if !errors.Is(err, store.ErrNotFound) {
- return fmt.Errorf("restore associated phone number: %w", err)
- }
- }
- var err error
- runtime.Tunnel, err = marshalObject(map[string]any{
- "established": state.TunnelReady,
- "name": state.TunnelName,
- "dataplane_mode": state.DataplaneMode,
- "epdg": state.EPDG,
- "proxy_mode": state.ProxyMode,
- "proxy_id": state.ProxyID,
- "security_audit": state.Security,
- })
- if err != nil {
- return err
- }
- runtime.IMSCore, err = marshalObject(map[string]any{
- "registered": state.IMSReady,
- "registration_state": state.IMSRegistration,
- "associated_number": runtime.LocalPhone,
- "number_source": runtime.PhoneNumberSource,
- })
- if err != nil {
- return err
- }
- runtime.SMSIP, err = marshalObject(map[string]any{
- "ready": state.SMSReady,
- })
- if err != nil {
- return err
- }
- runtime.Extra, err = marshalObject(map[string]any{
- "enabled": state.Enabled,
- "active": state.Active,
- "pure_airplane_policy": state.PureAirplanePolicy,
- "home_mcc": state.HomeMCC,
- "home_mnc": state.HomeMNC,
- "warnings": state.Warnings,
- "cleanup_errors": state.CleanupErrors,
- "attempt": state.Attempt,
- "sequence": state.Sequence,
- "started_at": state.StartedAt,
- })
- if err != nil {
- return err
- }
- return projector.Store.UpsertVoWiFiRuntime(ctx, runtime)
- }
- func dataplaneMode(state vowifi.State) string {
- if value := strings.TrimSpace(state.DataplaneMode); value == "userspace" || value == "xfrm" {
- return value
- }
- if state.TunnelReady || state.Phase == vowifi.PhaseTunnelReady ||
- state.Phase == vowifi.PhaseIMSReady || state.Phase == vowifi.PhaseSMSReady {
- return "ipsec"
- }
- return ""
- }
- func registrationText(state vowifi.State) string {
- if value := strings.TrimSpace(state.IMSRegistration); value != "" {
- return value
- }
- if state.IMSReady {
- return "registered"
- }
- if state.Phase == vowifi.PhaseFailed &&
- strings.HasPrefix(state.LastErrorClass, "ims") {
- return "registration failed"
- }
- return "not registered"
- }
- func boolInt(value bool) int {
- if value {
- return 1
- }
- return 0
- }
- func marshalObject(value map[string]any) (json.RawMessage, error) {
- encoded, err := json.Marshal(value)
- if err != nil {
- return nil, fmt.Errorf("marshal VoWiFi runtime projection: %w", err)
- }
- return encoded, nil
- }
|