// mautrix-signal - A Matrix-signal puppeting bridge. // Copyright (C) 2024 Tulir Asokan // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . package libsignalgo /* #include "./libsignal-ffi.h" #include */ import "C" import ( "database/sql/driver" "fmt" "strings" "unsafe" "github.com/google/uuid" "github.com/rs/zerolog" ) type ServiceIDType byte const ( ServiceIDTypeACI ServiceIDType = 0 ServiceIDTypePNI ServiceIDType = 1 ) func (st ServiceIDType) String() string { switch st { case ServiceIDTypeACI: return "ACI" case ServiceIDTypePNI: return "PNI" default: panic(fmt.Sprintf("invalid ServiceIDType: %d", st)) } } func (st ServiceIDType) GoString() string { return fmt.Sprintf("libsignalgo.ServiceIDType%s", st.String()) } type ServiceID struct { Type ServiceIDType UUID uuid.UUID } var EmptyServiceID ServiceID func NewPNIServiceID(uuid uuid.UUID) ServiceID { return ServiceID{ Type: ServiceIDTypePNI, UUID: uuid, } } func NewACIServiceID(uuid uuid.UUID) ServiceID { return ServiceID{ Type: ServiceIDTypeACI, UUID: uuid, } } func (s ServiceID) ToACIAndPNI() (aci, pni uuid.UUID) { if s.Type == ServiceIDTypeACI { return s.UUID, uuid.Nil } else { return uuid.Nil, s.UUID } } func (s ServiceID) IsEmpty() bool { return s.UUID == uuid.Nil } func (s ServiceID) Address(deviceID uint) (*Address, error) { if s.IsEmpty() { return nil, fmt.Errorf("cannot create address from empty ServiceID") } return newAddress(s.String(), deviceID) } func (s ServiceID) Bytes() []byte { if s.Type == ServiceIDTypeACI { return s.UUID[:] } return append([]byte{byte(s.Type)}, s.UUID[:]...) } func (s ServiceID) Value() (driver.Value, error) { return s.String(), nil } func (s ServiceID) String() string { if s.Type == ServiceIDTypeACI { return s.UUID.String() } return fmt.Sprintf("%s:%s", s.Type, s.UUID) } func (s ServiceID) GoString() string { return fmt.Sprintf(`libsignalgo.ServiceID{Type: %#v, UUID: uuid.MustParse("%s")}`, s.Type, s.UUID) } func (s ServiceID) MarshalText() ([]byte, error) { return []byte(s.String()), nil } func (s *ServiceID) UnmarshalText(text []byte) error { parsed, err := ServiceIDFromString(string(text)) if err != nil { return err } *s = parsed return nil } func (s ServiceID) MarshalZerologObject(e *zerolog.Event) { e.Stringer("type", s.Type) e.Stringer("uuid", s.UUID) } type ServiceIDFixedBytes [17]byte func (s ServiceID) FixedBytes() *ServiceIDFixedBytes { var result ServiceIDFixedBytes result[0] = byte(s.Type) copy(result[1:], s.UUID[:]) return &result } func ServiceIDFromString(val string) (ServiceID, error) { if len(val) < 36 { return EmptyServiceID, fmt.Errorf("invalid UUID string: %s", val) } if strings.ToUpper(val[:4]) == "PNI:" { parsed, err := uuid.Parse(val[4:]) if err != nil { return EmptyServiceID, err } return NewPNIServiceID(parsed), nil } else { parsed, err := uuid.Parse(val) if err != nil { return EmptyServiceID, err } return NewACIServiceID(parsed), nil } } func ServiceIDFromBytes(bytes []byte) (ServiceID, error) { if len(bytes) == 16 { return NewACIServiceID(uuid.UUID(bytes)), nil } else if len(bytes) == 17 { return ServiceID{ Type: ServiceIDType(bytes[0]), UUID: uuid.UUID(bytes[1:]), }, nil } return EmptyServiceID, fmt.Errorf("invalid ServiceID byte length: %d (expected 16 or 17)", len(bytes)) } func ServiceIDFromCFixedBytes(serviceID *C.SignalServiceIdFixedWidthBinaryBytes) ServiceID { var id ServiceID fixedBytes := (*ServiceIDFixedBytes)(unsafe.Pointer(serviceID)) id.Type = ServiceIDType(fixedBytes[0]) copy(id.UUID[:], fixedBytes[1:]) return id } func (s ServiceID) CFixedBytes() cPNIType { return cPNIType(unsafe.Pointer(s.FixedBytes())) }