internal/pipewire: implement Core::Hello
All checks were successful
Test / Create distribution (push) Successful in 39s
Test / Sandbox (push) Successful in 2m30s
Test / Hakurei (push) Successful in 3m15s
Test / Hpkg (push) Successful in 4m13s
Test / Sandbox (race detector) (push) Successful in 4m25s
Test / Hakurei (race detector) (push) Successful in 5m18s
Test / Flake checks (push) Successful in 1m28s
All checks were successful
Test / Create distribution (push) Successful in 39s
Test / Sandbox (push) Successful in 2m30s
Test / Hakurei (push) Successful in 3m15s
Test / Hpkg (push) Successful in 4m13s
Test / Sandbox (race detector) (push) Successful in 4m25s
Test / Hakurei (race detector) (push) Successful in 5m18s
Test / Flake checks (push) Successful in 1m28s
This implements enough types to correctly marshal and unmarshal Core::Hello. Signed-off-by: Ophestra <cat@gensokyo.uk>
This commit is contained in:
parent
9f7b0c2f46
commit
5bcafcf734
79
internal/pipewire/core.go
Normal file
79
internal/pipewire/core.go
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
package pipewire
|
||||||
|
|
||||||
|
/* pipewire/core.h */
|
||||||
|
|
||||||
|
const (
|
||||||
|
PW_TYPE_INTERFACE_Core = PW_TYPE_INFO_INTERFACE_BASE + "Core"
|
||||||
|
PW_TYPE_INTERFACE_Registry = PW_TYPE_INFO_INTERFACE_BASE + "Registry"
|
||||||
|
PW_CORE_PERM_MASK = PW_PERM_R | PW_PERM_X | PW_PERM_M
|
||||||
|
PW_VERSION_CORE = 4
|
||||||
|
PW_VERSION_REGISTRY = 3
|
||||||
|
|
||||||
|
PW_DEFAULT_REMOTE = "pipewire-0"
|
||||||
|
PW_ID_CORE = 0
|
||||||
|
PW_ID_ANY = Word(0xffffffff)
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
PW_CORE_CHANGE_MASK_PROPS = 1 << iota
|
||||||
|
|
||||||
|
PW_CORE_CHANGE_MASK_ALL = 1<<iota - 1
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
PW_CORE_EVENT_INFO = iota
|
||||||
|
PW_CORE_EVENT_DONE
|
||||||
|
PW_CORE_EVENT_PING
|
||||||
|
PW_CORE_EVENT_ERROR
|
||||||
|
PW_CORE_EVENT_REMOVE_ID
|
||||||
|
PW_CORE_EVENT_BOUND_ID
|
||||||
|
PW_CORE_EVENT_ADD_MEM
|
||||||
|
PW_CORE_EVENT_REMOVE_MEM
|
||||||
|
PW_CORE_EVENT_BOUND_PROPS
|
||||||
|
PW_CORE_EVENT_NUM
|
||||||
|
|
||||||
|
PW_VERSION_CORE_EVENTS = 1
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
PW_CORE_METHOD_ADD_LISTENER = iota
|
||||||
|
PW_CORE_METHOD_HELLO
|
||||||
|
PW_CORE_METHOD_SYNC
|
||||||
|
PW_CORE_METHOD_PONG
|
||||||
|
PW_CORE_METHOD_ERROR
|
||||||
|
PW_CORE_METHOD_GET_REGISTRY
|
||||||
|
PW_CORE_METHOD_CREATE_OBJECT
|
||||||
|
PW_CORE_METHOD_DESTROY
|
||||||
|
PW_CORE_METHOD_NUM
|
||||||
|
|
||||||
|
PW_VERSION_CORE_METHODS = 0
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
PW_REGISTRY_EVENT_GLOBAL = iota
|
||||||
|
PW_REGISTRY_EVENT_GLOBAL_REMOVE
|
||||||
|
PW_REGISTRY_EVENT_NUM
|
||||||
|
|
||||||
|
PW_VERSION_REGISTRY_EVENTS = 0
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
PW_REGISTRY_METHOD_ADD_LISTENER = iota
|
||||||
|
PW_REGISTRY_METHOD_BIND
|
||||||
|
PW_REGISTRY_METHOD_DESTROY
|
||||||
|
PW_REGISTRY_METHOD_NUM
|
||||||
|
|
||||||
|
PW_VERSION_REGISTRY_METHODS = 0
|
||||||
|
)
|
||||||
|
|
||||||
|
// CoreHello is the first message sent by a client.
|
||||||
|
type CoreHello struct {
|
||||||
|
// version number of the client; PW_VERSION_CORE
|
||||||
|
Version Int
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalBinary satisfies [encoding.BinaryMarshaler] via [MarshalAppend].
|
||||||
|
func (c *CoreHello) MarshalBinary() ([]byte, error) { return MarshalAppend(make([]byte, 0, 24), c) }
|
||||||
|
|
||||||
|
// UnmarshalBinary satisfies [encoding.BinaryUnmarshaler] via [Unmarshal].
|
||||||
|
func (c *CoreHello) UnmarshalBinary(data []byte) error { return Unmarshal(data, c) }
|
||||||
22
internal/pipewire/core_test.go
Normal file
22
internal/pipewire/core_test.go
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
package pipewire_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"hakurei.app/internal/pipewire"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCoreHello(t *testing.T) {
|
||||||
|
encodingTestCases[pipewire.CoreHello, *pipewire.CoreHello]{
|
||||||
|
{"sample", []byte{
|
||||||
|
0x10, 0, 0, 0,
|
||||||
|
0xe, 0, 0, 0,
|
||||||
|
4, 0, 0, 0,
|
||||||
|
4, 0, 0, 0,
|
||||||
|
4, 0, 0, 0,
|
||||||
|
0, 0, 0, 0,
|
||||||
|
}, pipewire.CoreHello{
|
||||||
|
Version: pipewire.PW_VERSION_CORE,
|
||||||
|
}, nil},
|
||||||
|
}.run(t)
|
||||||
|
}
|
||||||
@ -22,16 +22,16 @@ var (
|
|||||||
// A Header is the fixed-size message header described in protocol native.
|
// A Header is the fixed-size message header described in protocol native.
|
||||||
type Header struct {
|
type Header struct {
|
||||||
// The message id this is the destination resource/proxy id.
|
// The message id this is the destination resource/proxy id.
|
||||||
ID Uint `json:"Id"`
|
ID Word `json:"Id"`
|
||||||
// The opcode on the resource/proxy interface.
|
// The opcode on the resource/proxy interface.
|
||||||
Opcode byte `json:"opcode"`
|
Opcode byte `json:"opcode"`
|
||||||
// The size of the payload and optional footer of the message.
|
// The size of the payload and optional footer of the message.
|
||||||
// Note: this value is only 24 bits long in the format.
|
// Note: this value is only 24 bits long in the format.
|
||||||
Size uint32 `json:"size"`
|
Size uint32 `json:"size"`
|
||||||
// An increasing sequence number for each message.
|
// An increasing sequence number for each message.
|
||||||
Sequence Uint `json:"seq"`
|
Sequence Word `json:"seq"`
|
||||||
// Number of file descriptors in this message.
|
// Number of file descriptors in this message.
|
||||||
FileCount Uint `json:"n_fds"`
|
FileCount Word `json:"n_fds"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// append appends the protocol native message header to data.
|
// append appends the protocol native message header to data.
|
||||||
|
|||||||
@ -82,72 +82,6 @@ const (
|
|||||||
PW_VERSION_DEVICE_METHODS = 0
|
PW_VERSION_DEVICE_METHODS = 0
|
||||||
)
|
)
|
||||||
|
|
||||||
/* pipewire/core.h */
|
|
||||||
|
|
||||||
const (
|
|
||||||
PW_TYPE_INTERFACE_Core = PW_TYPE_INFO_INTERFACE_BASE + "Core"
|
|
||||||
PW_TYPE_INTERFACE_Registry = PW_TYPE_INFO_INTERFACE_BASE + "Registry"
|
|
||||||
PW_CORE_PERM_MASK = PW_PERM_R | PW_PERM_X | PW_PERM_M
|
|
||||||
PW_VERSION_CORE = 4
|
|
||||||
PW_VERSION_REGISTRY = 3
|
|
||||||
|
|
||||||
PW_DEFAULT_REMOTE = "pipewire-0"
|
|
||||||
PW_ID_CORE = 0
|
|
||||||
PW_ID_ANY = uint32(0xffffffff)
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
PW_CORE_CHANGE_MASK_PROPS = 1 << iota
|
|
||||||
|
|
||||||
PW_CORE_CHANGE_MASK_ALL = 1<<iota - 1
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
PW_CORE_EVENT_INFO = iota
|
|
||||||
PW_CORE_EVENT_DONE
|
|
||||||
PW_CORE_EVENT_PING
|
|
||||||
PW_CORE_EVENT_ERROR
|
|
||||||
PW_CORE_EVENT_REMOVE_ID
|
|
||||||
PW_CORE_EVENT_BOUND_ID
|
|
||||||
PW_CORE_EVENT_ADD_MEM
|
|
||||||
PW_CORE_EVENT_REMOVE_MEM
|
|
||||||
PW_CORE_EVENT_BOUND_PROPS
|
|
||||||
PW_CORE_EVENT_NUM
|
|
||||||
|
|
||||||
PW_VERSION_CORE_EVENTS = 1
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
PW_CORE_METHOD_ADD_LISTENER = iota
|
|
||||||
PW_CORE_METHOD_HELLO
|
|
||||||
PW_CORE_METHOD_SYNC
|
|
||||||
PW_CORE_METHOD_PONG
|
|
||||||
PW_CORE_METHOD_ERROR
|
|
||||||
PW_CORE_METHOD_GET_REGISTRY
|
|
||||||
PW_CORE_METHOD_CREATE_OBJECT
|
|
||||||
PW_CORE_METHOD_DESTROY
|
|
||||||
PW_CORE_METHOD_NUM
|
|
||||||
|
|
||||||
PW_VERSION_CORE_METHODS = 0
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
PW_REGISTRY_EVENT_GLOBAL = iota
|
|
||||||
PW_REGISTRY_EVENT_GLOBAL_REMOVE
|
|
||||||
PW_REGISTRY_EVENT_NUM
|
|
||||||
|
|
||||||
PW_VERSION_REGISTRY_EVENTS = 0
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
PW_REGISTRY_METHOD_ADD_LISTENER = iota
|
|
||||||
PW_REGISTRY_METHOD_BIND
|
|
||||||
PW_REGISTRY_METHOD_DESTROY
|
|
||||||
PW_REGISTRY_METHOD_NUM
|
|
||||||
|
|
||||||
PW_VERSION_REGISTRY_METHODS = 0
|
|
||||||
)
|
|
||||||
|
|
||||||
/* pipewire/factory.h */
|
/* pipewire/factory.h */
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|||||||
@ -1,15 +1,33 @@
|
|||||||
package pipewire
|
package pipewire
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"io"
|
||||||
|
"math"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
// A Word is a 32-bit unsigned integer.
|
// A Word is a 32-bit unsigned integer.
|
||||||
//
|
//
|
||||||
// Values internal to a message appear to always be aligned to 32-bit boundary.
|
// Values internal to a message appear to always be aligned to 32-bit boundary.
|
||||||
Word = uint32
|
Word = uint32
|
||||||
|
|
||||||
// An Int is a signed integer the size of a PipeWire Word.
|
// A Bool is a boolean value representing SPA_TYPE_Bool.
|
||||||
|
Bool = bool
|
||||||
|
// An Int is a signed integer value representing SPA_TYPE_Int.
|
||||||
Int = int32
|
Int = int32
|
||||||
// An Uint is an unsigned integer the size of a PipeWire Word.
|
// A Long is a signed integer value representing SPA_TYPE_Long.
|
||||||
Uint = Word
|
Long = int64
|
||||||
|
// A Float is a floating point value representing SPA_TYPE_Float.
|
||||||
|
Float = float32
|
||||||
|
// A Double is a floating point value representing SPA_TYPE_Double.
|
||||||
|
Double = float64
|
||||||
|
// A String is a string value representing SPA_TYPE_String.
|
||||||
|
String = string
|
||||||
|
// Bytes is a byte slice representing SPA_TYPE_Bytes.
|
||||||
|
Bytes = []byte
|
||||||
)
|
)
|
||||||
|
|
||||||
/* Basic types */
|
/* Basic types */
|
||||||
@ -47,6 +65,210 @@ const (
|
|||||||
_SPA_TYPE_LAST // not part of ABI
|
_SPA_TYPE_LAST // not part of ABI
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// An UnsupportedTypeError is returned by [Marshal] when attempting
|
||||||
|
// to encode an unsupported value type.
|
||||||
|
type UnsupportedTypeError struct{ Type reflect.Type }
|
||||||
|
|
||||||
|
func (e *UnsupportedTypeError) Error() string { return "unsupported type: " + e.Type.String() }
|
||||||
|
|
||||||
|
// An UnsupportedSizeError is returned by [Marshal] when attempting
|
||||||
|
// to encode a value with its encoded size exceeding what could be
|
||||||
|
// represented by the format.
|
||||||
|
type UnsupportedSizeError int
|
||||||
|
|
||||||
|
func (e UnsupportedSizeError) Error() string { return "size out of range: " + strconv.Itoa(int(e)) }
|
||||||
|
|
||||||
|
// Marshal returns the PipeWire POD encoding of v.
|
||||||
|
func Marshal(v any) ([]byte, error) { return MarshalAppend(make([]byte, 0), v) }
|
||||||
|
|
||||||
|
// MarshalAppend appends the PipeWire POD encoding of v to data.
|
||||||
|
func MarshalAppend(data []byte, v any) ([]byte, error) {
|
||||||
|
return marshalValueAppend(data, reflect.ValueOf(v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// marshalValueAppendRaw implements [MarshalAppend] on [reflect.Value].
|
||||||
|
func marshalValueAppend(data []byte, v reflect.Value) ([]byte, error) {
|
||||||
|
data = append(data, make([]byte, 4)...)
|
||||||
|
|
||||||
|
rData, err := marshalValueAppendRaw(data, v)
|
||||||
|
if err != nil {
|
||||||
|
return data, err
|
||||||
|
}
|
||||||
|
|
||||||
|
size := len(rData) - len(data) + 4
|
||||||
|
paddingSize := (8 - (size)%8) % 8
|
||||||
|
// compensated for size and type prefix
|
||||||
|
wireSize := size - 8
|
||||||
|
if wireSize > math.MaxUint32 {
|
||||||
|
return data, UnsupportedSizeError(wireSize)
|
||||||
|
}
|
||||||
|
binary.NativeEndian.PutUint32(rData[len(data)-4:len(data)], Word(wireSize))
|
||||||
|
rData = append(rData, make([]byte, paddingSize)...)
|
||||||
|
|
||||||
|
return rData, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// marshalValueAppendRaw implements [MarshalAppend] on [reflect.Value] without the size prefix.
|
||||||
|
func marshalValueAppendRaw(data []byte, v reflect.Value) ([]byte, error) {
|
||||||
|
switch v.Kind() {
|
||||||
|
|
||||||
|
case reflect.Int32:
|
||||||
|
data = binary.NativeEndian.AppendUint32(data, SPA_TYPE_Int)
|
||||||
|
data = binary.NativeEndian.AppendUint32(data, Word(v.Int()))
|
||||||
|
return data, nil
|
||||||
|
|
||||||
|
case reflect.Struct:
|
||||||
|
data = binary.NativeEndian.AppendUint32(data, SPA_TYPE_Struct)
|
||||||
|
var err error
|
||||||
|
for i := 0; i < v.NumField(); i++ {
|
||||||
|
data, err = marshalValueAppend(data, v.Field(i))
|
||||||
|
if err != nil {
|
||||||
|
return data, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return data, nil
|
||||||
|
|
||||||
|
case reflect.Pointer:
|
||||||
|
if v.IsNil() {
|
||||||
|
data = binary.NativeEndian.AppendUint32(data, SPA_TYPE_None)
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
|
return marshalValueAppendRaw(data, v.Elem())
|
||||||
|
|
||||||
|
default:
|
||||||
|
return data, &UnsupportedTypeError{v.Type()}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// An InvalidUnmarshalError describes an invalid argument passed to [Unmarshal].
|
||||||
|
// (The argument to [Unmarshal] must be a non-nil pointer.)
|
||||||
|
type InvalidUnmarshalError struct{ Type reflect.Type }
|
||||||
|
|
||||||
|
func (e *InvalidUnmarshalError) Error() string {
|
||||||
|
if e.Type == nil {
|
||||||
|
return "attempting to unmarshal to nil"
|
||||||
|
}
|
||||||
|
|
||||||
|
if e.Type.Kind() != reflect.Pointer {
|
||||||
|
return "attempting to unmarshal to non-pointer type: " + e.Type.String()
|
||||||
|
}
|
||||||
|
return "attempting to unmarshal to nil " + e.Type.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unmarshal parses the JSON-encoded data and stores the result
|
||||||
|
// in the value pointed to by v. If v is nil or not a pointer,
|
||||||
|
// Unmarshal returns an [InvalidUnmarshalError].
|
||||||
|
func Unmarshal(data []byte, v any) error {
|
||||||
|
rv := reflect.ValueOf(v)
|
||||||
|
if rv.Kind() != reflect.Pointer || rv.IsNil() {
|
||||||
|
return &InvalidUnmarshalError{reflect.TypeOf(v)}
|
||||||
|
}
|
||||||
|
return unmarshalValue(data, rv.Elem(), new(Word))
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalSetError describes a value that cannot be set during [Unmarshal].
|
||||||
|
// This is likely an unexported struct field.
|
||||||
|
type UnmarshalSetError struct{ Type reflect.Type }
|
||||||
|
|
||||||
|
func (u *UnmarshalSetError) Error() string { return "cannot set: " + u.Type.String() }
|
||||||
|
|
||||||
|
// unmarshalValue implements [Unmarshal] on [reflect.Value].
|
||||||
|
func unmarshalValue(data []byte, v reflect.Value, sizeP *Word) error {
|
||||||
|
switch v.Kind() {
|
||||||
|
|
||||||
|
case reflect.Int32:
|
||||||
|
*sizeP = 4
|
||||||
|
if err := unmarshalCheckTypeBounds(&data, SPA_TYPE_Int, sizeP); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !v.CanSet() {
|
||||||
|
return &UnmarshalSetError{v.Type()}
|
||||||
|
}
|
||||||
|
v.SetInt(int64(binary.NativeEndian.Uint32(data)))
|
||||||
|
return nil
|
||||||
|
|
||||||
|
case reflect.Struct:
|
||||||
|
if err := unmarshalCheckTypeBounds(&data, SPA_TYPE_Struct, sizeP); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var fieldWireSize Word
|
||||||
|
for i := 0; i < v.NumField(); i++ {
|
||||||
|
if err := unmarshalValue(data, v.Field(i), &fieldWireSize); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
paddingSize := (8 - (fieldWireSize)%8) % 8
|
||||||
|
// already bounds checked by the successful unmarshalValue call
|
||||||
|
data = data[8+fieldWireSize+paddingSize:]
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
|
||||||
|
case reflect.Pointer:
|
||||||
|
if !v.CanSet() {
|
||||||
|
return &UnmarshalSetError{v.Type()}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(data) < 8 {
|
||||||
|
return io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
switch binary.NativeEndian.Uint32(data[4:]) {
|
||||||
|
case SPA_TYPE_None:
|
||||||
|
v.SetZero()
|
||||||
|
return nil
|
||||||
|
|
||||||
|
default:
|
||||||
|
v.Set(reflect.New(v.Type().Elem()))
|
||||||
|
return unmarshalValue(data, v.Elem(), sizeP)
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
return &UnsupportedTypeError{v.Type()}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// An InconsistentSizeError describes an inconsistent size prefix encountered
|
||||||
|
// in data passed to [Unmarshal].
|
||||||
|
type InconsistentSizeError struct{ Prefix, Expect Word }
|
||||||
|
|
||||||
|
func (e *InconsistentSizeError) Error() string {
|
||||||
|
return "unexpected size prefix: " + strconv.Itoa(int(e.Prefix)) + ", want " + strconv.Itoa(int(e.Expect))
|
||||||
|
}
|
||||||
|
|
||||||
|
// An UnexpectedTypeError describes an unexpected type encountered
|
||||||
|
// in data passed to [Unmarshal].
|
||||||
|
type UnexpectedTypeError struct{ Type, Expect Word }
|
||||||
|
|
||||||
|
func (u *UnexpectedTypeError) Error() string {
|
||||||
|
return "unexpected type: " + strconv.Itoa(int(u.Type)) + ", want " + strconv.Itoa(int(u.Expect))
|
||||||
|
}
|
||||||
|
|
||||||
|
// unmarshalCheckTypeBounds performs bounds checks on data and validates the type and size prefixes.
|
||||||
|
// An expected size of zero skips further bounds checks.
|
||||||
|
func unmarshalCheckTypeBounds(data *[]byte, t Word, sizeP *Word) error {
|
||||||
|
if len(*data) < 8 {
|
||||||
|
return io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
|
||||||
|
wantSize := *sizeP
|
||||||
|
gotSize := binary.NativeEndian.Uint32(*data)
|
||||||
|
*sizeP = gotSize
|
||||||
|
|
||||||
|
if wantSize != 0 && gotSize != wantSize {
|
||||||
|
return &InconsistentSizeError{gotSize, wantSize}
|
||||||
|
}
|
||||||
|
if len(*data)-8 < int(wantSize) {
|
||||||
|
return io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
|
||||||
|
gotType := binary.NativeEndian.Uint32((*data)[4:])
|
||||||
|
if gotType != t {
|
||||||
|
return &UnexpectedTypeError{gotType, t}
|
||||||
|
}
|
||||||
|
|
||||||
|
*data = (*data)[8:]
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
/* Pointers */
|
/* Pointers */
|
||||||
const (
|
const (
|
||||||
SPA_TYPE_POINTER_START = 0x10000 + iota
|
SPA_TYPE_POINTER_START = 0x10000 + iota
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user