Re-organize CreateDataChannel function and add limited spec compliance

This commit is contained in:
Konstantin Itskov
2018-09-04 12:11:17 -04:00
parent 3ac52b89d8
commit 0a2568695a
11 changed files with 286 additions and 89 deletions

View File

@@ -50,10 +50,22 @@ var (
// IceCandidatePoolSize was made after RTCPeerConnection has been initialized.
ErrModifyingIceCandidatePoolSize = errors.New("ice candidate pool size cannot be modified")
// ErrInvalidValue indicates that an invalid value was provided.
ErrInvalidValue = errors.New("invalid value")
// ErrStringSizeLimit indicates that the character size limit of string is
// exceeded. The limit is hardcoded to 65535 according to specifications.
ErrStringSizeLimit = errors.New("data channel label exceeds size limit")
// ErrMaxDataChannels indicates that the maximum number of data channels
// was reached.
ErrMaxDataChannels = errors.New("maximum number of datachannels reached")
// ErrMaxDataChannelID indicates that the maximum number ID that could be
// specified for a data channel has been exceeded.
ErrMaxDataChannelID = errors.New("maximum number ID for datachannel specified")
// ErrNegotiatedWithoutID indicates that an attempt to create a data channel
// was made while setting the negotiated option to true without providing
// the negotiated channel ID.
ErrNegotiatedWithoutID = errors.New("negotiated set without channel id")
// ErrRetransmitsOrPacketLifeTime indicates that an attempt to create a data
// channel was made with both options MaxPacketLifeTime and MaxRetransmits
// set together. Such configuration is not supported by the specification
// and is mutually exclusive.
ErrRetransmitsOrPacketLifeTime = errors.New("both MaxPacketLifeTime and MaxRetransmits was set")
)

View File

@@ -46,9 +46,9 @@ func main() {
}
}
// Register the Onmessage to handle incoming messages
// Register the OnMessage to handle incoming messages
dataChannel.Lock()
dataChannel.Onmessage = func(payload datachannel.Payload) {
dataChannel.OnMessage = func(payload datachannel.Payload) {
switch p := payload.(type) {
case *datachannel.PayloadString:
fmt.Printf("Message '%s' from DataChannel '%s' payload '%s'\n", p.PayloadType().String(), dataChannel.Label, string(p.Data))

View File

@@ -70,7 +70,7 @@ func main() {
d.Lock()
defer d.Unlock()
d.Onmessage = func(payload datachannel.Payload) {
d.OnMessage = func(payload datachannel.Payload) {
switch p := payload.(type) {
case *datachannel.PayloadString:
fmt.Printf("Message '%s' from DataChannel '%s' payload '%s'\n", p.PayloadType().String(), d.Label, string(p.Data))

View File

@@ -31,7 +31,7 @@ func buildPeerConnection() *webrtc.RTCPeerConnection {
}
d.Lock()
d.Onmessage = func(payload datachannel.Payload) {
d.OnMessage = func(payload datachannel.Payload) {
switch p := payload.(type) {
case *datachannel.PayloadString:
fmt.Printf("Message '%s' from DataChannel '%s' payload '%s'\n", p.PayloadType().String(), d.Label, string(p.Data))

View File

@@ -31,7 +31,7 @@ func buildPeerConnection() *webrtc.RTCPeerConnection {
d.Lock()
defer d.Unlock()
d.Onmessage = func(payload datachannel.Payload) {
d.OnMessage = func(payload datachannel.Payload) {
switch p := payload.(type) {
case *datachannel.PayloadString:
fmt.Printf("Message '%s' from DataChannel '%s' payload '%s'\n", p.PayloadType().String(), d.Label, string(p.Data))

View File

@@ -13,16 +13,101 @@ import (
type RTCDataChannel struct {
sync.RWMutex
Onmessage func(datachannel.Payload)
ID uint16
// Label represents a label that can be used to distinguish this
// RTCDataChannel object from other RTCDataChannel objects. Scripts are
// allowed to create multiple RTCDataChannel objects with the same label.
Label string
// Ordered represents if the RTCDataChannel is ordered, and false if
// out-of-order delivery is allowed.
Ordered bool
// MaxPacketLifeTime represents the length of the time window (msec) during
// which transmissions and retransmissions may occur in unreliable mode.
MaxPacketLifeTime *uint16
// MaxRetransmits represents the maximum number of retransmissions that are
// attempted in unreliable mode.
MaxRetransmits *uint16
// Protocol represents the name of the sub-protocol used with this
// RTCDataChannel.
Protocol string
// Negotiated represents whether this RTCDataChannel was negotiated by the
// application (true), or not (false).
Negotiated bool
// ID represents the ID for this RTCDataChannel. The value is initally null,
// which is what will be returned if the ID was not provided at channel
// creation time, and the DTLS role of the SCTP transport has not yet been
// negotiated. Otherwise, it will return the ID that was either selected by
// the script or generated. After the ID is set to a non-null value, it will
// not change.
ID *uint16
// Priority represents the priority for this RTCDataChannel. The priority is
// assigned at channel creation time.
Priority RTCPriorityType
// ReadyState represents the state of the RTCDataChannel object.
ReadyState RTCDataChannelState
// BufferedAmount represents the number of bytes of application data
// (UTF-8 text and binary data) that have been queued using send(). Even
// though the data transmission can occur in parallel, the returned value
// MUST NOT be decreased before the current task yielded back to the event
// loop to prevent race conditions. The value does not include framing
// overhead incurred by the protocol, or buffering done by the operating
// system or network hardware. The value of BufferedAmount slot will only
// increase with each call to the send() method as long as the ReadyState is
// open; however, BufferedAmount does not reset to zero once the channel
// closes.
BufferedAmount uint64
// BufferedAmountLowThreshold represents the threshold at which the
// bufferedAmount is considered to be low. When the bufferedAmount decreases
// from above this threshold to equal or below it, the bufferedamountlow
// event fires. BufferedAmountLowThreshold is initially zero on each new
// RTCDataChannel, but the application may change its value at any time.
BufferedAmountLowThreshold uint64
// The binaryType represents attribute MUST, on getting, return the value to
// which it was last set. On setting, if the new value is either the string
// "blob" or the string "arraybuffer", then set the IDL attribute to this
// new value. Otherwise, throw a SyntaxError. When an RTCDataChannel object
// is created, the binaryType attribute MUST be initialized to the string
// "blob". This attribute controls how binary data is exposed to scripts.
// binaryType string
// OnOpen func()
// OnBufferedAmountLow func()
// OnError func()
// OnClose func()
OnMessage func(datachannel.Payload)
rtcPeerConnection *RTCPeerConnection
}
// // Close closes the RTCDataChannel. It may be called regardless of whether the
// // RTCDataChannel object was created by this peer or the remote peer.
// func (d *RTCDataChannel) Close() error {
// if d.ReadyState == RTCDataChannelStateClosing || d.ReadyState == RTCDataChannelStateClosed {
// return nil
// }
//
// d.ReadyState = RTCDataChannelStateClosing
//
// // if err := d.rtcPeerConnection.networkManager.SendOpenChannelMessage(d.ID, d.Label); err != nil {
// // return &rtcerr.UnknownError{Err: err}
// // }
// return nil
//
// }
// SendOpenChannelMessage is a test to send OpenChannel manually
func (d *RTCDataChannel) SendOpenChannelMessage() error {
if err := d.rtcPeerConnection.networkManager.SendOpenChannelMessage(d.ID, d.Label); err != nil {
if err := d.rtcPeerConnection.networkManager.SendOpenChannelMessage(*d.ID, d.Label); err != nil {
return &rtcerr.UnknownError{Err: err}
}
return nil
@@ -31,7 +116,7 @@ func (d *RTCDataChannel) SendOpenChannelMessage() error {
// Send sends the passed message to the DataChannel peer
func (d *RTCDataChannel) Send(p datachannel.Payload) error {
if err := d.rtcPeerConnection.networkManager.SendDataChannelMessage(p, d.ID); err != nil {
if err := d.rtcPeerConnection.networkManager.SendDataChannelMessage(p, *d.ID); err != nil {
return &rtcerr.UnknownError{Err: err}
}
return nil

View File

@@ -10,16 +10,16 @@ func TestGenerateDataChannelID(t *testing.T) {
c *RTCPeerConnection
result uint16
}{
{true, &RTCPeerConnection{sctp: newRTCSctpTransport(), dataChannels: map[uint16]*RTCDataChannel{}}, 0},
{true, &RTCPeerConnection{sctp: newRTCSctpTransport(), dataChannels: map[uint16]*RTCDataChannel{1: nil}}, 0},
{true, &RTCPeerConnection{sctp: newRTCSctpTransport(), dataChannels: map[uint16]*RTCDataChannel{0: nil}}, 2},
{true, &RTCPeerConnection{sctp: newRTCSctpTransport(), dataChannels: map[uint16]*RTCDataChannel{0: nil, 2: nil}}, 4},
{true, &RTCPeerConnection{sctp: newRTCSctpTransport(), dataChannels: map[uint16]*RTCDataChannel{0: nil, 4: nil}}, 2},
{false, &RTCPeerConnection{sctp: newRTCSctpTransport(), dataChannels: map[uint16]*RTCDataChannel{}}, 1},
{false, &RTCPeerConnection{sctp: newRTCSctpTransport(), dataChannels: map[uint16]*RTCDataChannel{0: nil}}, 1},
{false, &RTCPeerConnection{sctp: newRTCSctpTransport(), dataChannels: map[uint16]*RTCDataChannel{1: nil}}, 3},
{false, &RTCPeerConnection{sctp: newRTCSctpTransport(), dataChannels: map[uint16]*RTCDataChannel{1: nil, 3: nil}}, 5},
{false, &RTCPeerConnection{sctp: newRTCSctpTransport(), dataChannels: map[uint16]*RTCDataChannel{1: nil, 5: nil}}, 3},
{true, &RTCPeerConnection{SctpTransport: newRTCSctpTransport(), dataChannels: map[uint16]*RTCDataChannel{}}, 0},
{true, &RTCPeerConnection{SctpTransport: newRTCSctpTransport(), dataChannels: map[uint16]*RTCDataChannel{1: nil}}, 0},
{true, &RTCPeerConnection{SctpTransport: newRTCSctpTransport(), dataChannels: map[uint16]*RTCDataChannel{0: nil}}, 2},
{true, &RTCPeerConnection{SctpTransport: newRTCSctpTransport(), dataChannels: map[uint16]*RTCDataChannel{0: nil, 2: nil}}, 4},
{true, &RTCPeerConnection{SctpTransport: newRTCSctpTransport(), dataChannels: map[uint16]*RTCDataChannel{0: nil, 4: nil}}, 2},
{false, &RTCPeerConnection{SctpTransport: newRTCSctpTransport(), dataChannels: map[uint16]*RTCDataChannel{}}, 1},
{false, &RTCPeerConnection{SctpTransport: newRTCSctpTransport(), dataChannels: map[uint16]*RTCDataChannel{0: nil}}, 1},
{false, &RTCPeerConnection{SctpTransport: newRTCSctpTransport(), dataChannels: map[uint16]*RTCDataChannel{1: nil}}, 3},
{false, &RTCPeerConnection{SctpTransport: newRTCSctpTransport(), dataChannels: map[uint16]*RTCDataChannel{1: nil, 3: nil}}, 5},
{false, &RTCPeerConnection{SctpTransport: newRTCSctpTransport(), dataChannels: map[uint16]*RTCDataChannel{1: nil, 5: nil}}, 3},
}
for _, testCase := range testCases {

View File

@@ -5,8 +5,7 @@ package webrtc
type RTCDataChannelInit struct {
// Ordered indicates if data is allowed to be delivered out of order. The
// default value of true, guarantees that data will be delivered in order.
// TODO make sure defaults to true
Ordered bool
Ordered *bool
// MaxPacketLifeTime limits the time (in milliseconds) during which the
// channel will transmit or retransmit data if not acknowledged. This value
@@ -19,7 +18,7 @@ type RTCDataChannelInit struct {
MaxRetransmits *uint16
// Protocol describes the subprotocol name used for this channel.
Protocol string
Protocol *string
// Negotiated describes if the data channel is created by the local peer or
// the remote peer. The default value of false tells the user agent to
@@ -27,11 +26,11 @@ type RTCDataChannelInit struct {
// corresponding RTCDataChannel. If set to true, it is up to the application
// to negotiate the channel and create an RTCDataChannel with the same id
// at the other peer.
Negotiated bool
Negotiated *bool
// ID overrides the default selection of ID for this channel.
ID uint16
ID *uint16
// Priority describes the priority of this channel.
Priority RTCPriorityType
Priority *RTCPriorityType
}

View File

@@ -67,7 +67,7 @@ type RTCPeerConnection struct {
// IceConnectionState attribute returns the ICE connection state of the
// RTCPeerConnection instance.
// IceConnectionState RTCIceConnectionState
// IceConnectionState RTCIceConnectionState // FIXME SWAP-FOR-THIS
IceConnectionState ice.ConnectionState // FIXME REMOVE
// ConnectionState attribute returns the connection state of the
@@ -81,15 +81,15 @@ type RTCPeerConnection struct {
isClosed bool
negotiationNeeded bool // FIXME NOT-USED
// lastOffer string
// lastAnswer string
LastOffer string // FIXME NOT-USED
LastAnswer string // FIXME NOT-USED
// Media
mediaEngine *MediaEngine
rtpTransceivers []*RTCRtpTransceiver
// SCTP
sctp *RTCSctpTransport
// SctpTransport
SctpTransport *RTCSctpTransport
// DataChannels
dataChannels map[uint16]*RTCDataChannel
@@ -123,7 +123,7 @@ func New(configuration RTCConfiguration) (*RTCPeerConnection, error) {
SignalingState: RTCSignalingStateStable,
ConnectionState: RTCPeerConnectionStateNew,
mediaEngine: DefaultMediaEngine,
sctp: newRTCSctpTransport(),
SctpTransport: newRTCSctpTransport(),
dataChannels: make(map[uint16]*RTCDataChannel),
}
var err error
@@ -553,31 +553,105 @@ func (pc *RTCPeerConnection) AddTransceiver() RTCRtpTransceiver {
// --- FIXME - BELOW CODE NEEDS RE-ORGANIZATION - https://w3c.github.io/webrtc-pc/#peer-to-peer-data-api
// ------------------------------------------------------------------------
// CreateDataChannel creates a new RTCDataChannel object with the given label and optitional options.
// CreateDataChannel creates a new RTCDataChannel object with the given label
// and optitional RTCDataChannelInit used to configure properties of the
// underlying channel such as data reliability.
func (pc *RTCPeerConnection) CreateDataChannel(label string, options *RTCDataChannelInit) (*RTCDataChannel, error) {
// https://w3c.github.io/webrtc-pc/#peer-to-peer-data-api (Step #2)
if pc.isClosed {
return nil, &rtcerr.InvalidStateError{Err: ErrConnectionClosed}
}
// https://w3c.github.io/webrtc-pc/#peer-to-peer-data-api (Step #5)
if len(label) > 65535 {
return nil, &rtcerr.TypeError{Err: ErrInvalidValue}
return nil, &rtcerr.TypeError{Err: ErrStringSizeLimit}
}
// Defaults
ordered := true
priority := RTCPriorityTypeLow
negotiated := false
// https://w3c.github.io/webrtc-pc/#peer-to-peer-data-api (Step #3)
// Some variables defined explicitly despite their implicit zero values to
// allow better readability to understand what is happening. Additionally,
// some members are set to a non zero value default due to the default
// definitions in https://w3c.github.io/webrtc-pc/#dom-rtcdatachannelinit
// which are later overwriten by the options if any were specified.
channel := RTCDataChannel{
// https://w3c.github.io/webrtc-pc/#peer-to-peer-data-api (Step #4)
Label: label,
Ordered: true,
MaxPacketLifeTime: nil,
MaxRetransmits: nil,
Protocol: "",
Negotiated: false,
ID: nil,
Priority: RTCPriorityTypeLow,
// https://w3c.github.io/webrtc-pc/#dfn-create-an-rtcdatachannel (Step #2)
ReadyState: RTCDataChannelStateConnecting,
// https://w3c.github.io/webrtc-pc/#dfn-create-an-rtcdatachannel (Step #3)
BufferedAmount: 0,
}
if options != nil {
ordered = options.Ordered
priority = options.Priority
negotiated = options.Negotiated
// https://w3c.github.io/webrtc-pc/#peer-to-peer-data-api (Step #7)
if options.MaxPacketLifeTime != nil {
channel.MaxPacketLifeTime = options.MaxPacketLifeTime
}
// https://w3c.github.io/webrtc-pc/#peer-to-peer-data-api (Step #8)
if options.MaxRetransmits != nil {
channel.MaxRetransmits = options.MaxRetransmits
}
// https://w3c.github.io/webrtc-pc/#peer-to-peer-data-api (Step #9)
if options.Ordered != nil {
channel.Ordered = *options.Ordered
}
// https://w3c.github.io/webrtc-pc/#peer-to-peer-data-api (Step #10)
if options.Protocol != nil {
channel.Protocol = *options.Protocol
}
// https://w3c.github.io/webrtc-pc/#peer-to-peer-data-api (Step #12)
if options.Negotiated != nil {
channel.Negotiated = *options.Negotiated
}
// https://w3c.github.io/webrtc-pc/#peer-to-peer-data-api (Step #13)
if options.ID != nil && channel.Negotiated {
channel.ID = options.ID
}
// https://w3c.github.io/webrtc-pc/#peer-to-peer-data-api (Step #15)
if options.Priority != nil {
channel.Priority = *options.Priority
}
}
// https://w3c.github.io/webrtc-pc/#peer-to-peer-data-api (Step #11)
if len(channel.Protocol) > 65535 {
return nil, &rtcerr.TypeError{Err: ErrStringSizeLimit}
}
// https://w3c.github.io/webrtc-pc/#peer-to-peer-data-api (Step #14)
if channel.Negotiated && channel.ID == nil {
return nil, &rtcerr.TypeError{Err: ErrNegotiatedWithoutID}
}
// https://w3c.github.io/webrtc-pc/#peer-to-peer-data-api (Step #16)
if channel.MaxPacketLifeTime != nil && channel.MaxRetransmits != nil {
return nil, &rtcerr.TypeError{Err: ErrRetransmitsOrPacketLifeTime}
}
// FIXME https://w3c.github.io/webrtc-pc/#dom-rtcpeerconnection-createdatachannel (Step #17)
// FIXME - Where sctp transport is going to go
// if pc.SctpTransport == nil {
// pc.SctpTransport = &RTCSctpTransport{
//
// }
// }
var id uint16
if negotiated {
id = options.ID
} else {
if !channel.Negotiated {
var err error
id, err = pc.generateDataChannelID(true) // TODO: base on DTLS role
if err != nil {
@@ -585,30 +659,46 @@ func (pc *RTCPeerConnection) CreateDataChannel(label string, options *RTCDataCha
}
}
if id > 65534 {
return nil, &rtcerr.TypeError{Err: ErrInvalidValue}
// // https://w3c.github.io/webrtc-pc/#peer-to-peer-data-api (Step #18)
if *channel.ID > 65534 {
return nil, &rtcerr.TypeError{Err: ErrMaxDataChannelID}
}
if pc.sctp.State == RTCSctpTransportStateConnected &&
id >= pc.sctp.MaxChannels {
return nil, &rtcerr.OperationError{Err: ErrMaxDataChannels}
if pc.SctpTransport.State == RTCSctpTransportStateConnected &&
id >= *pc.SctpTransport.MaxChannels {
return nil, &rtcerr.OperationError{Err: ErrMaxDataChannelID}
}
_ = ordered // TODO
_ = priority // TODO
res := &RTCDataChannel{
Label: label,
ID: id,
rtcPeerConnection: pc,
}
// _ = ordered // TODO
// _ = priority // TODO
// res := &RTCDataChannel{
// Label: label,
// ID: id,
// rtcPeerConnection: pc,
// }
// Remember datachannel
pc.dataChannels[id] = res
pc.dataChannels[id] = &channel
// Send opening message
// pc.networkManager.SendOpenChannelMessage(id, label)
return res, nil
return &channel, nil
}
func (pc *RTCPeerConnection) generateDataChannelID(client bool) (uint16, error) {
var id uint16
if !client {
id++
}
for ; id < *pc.SctpTransport.MaxChannels-1; id += 2 {
_, ok := pc.dataChannels[id]
if !ok {
return id, nil
}
}
return 0, &rtcerr.OperationError{Err: ErrMaxDataChannelID}
}
// SetMediaEngine allows overwriting the default media engine used by the RTCPeerConnection
@@ -649,7 +739,7 @@ func (pc *RTCPeerConnection) Close() error {
/* Everything below is private */
func (pc *RTCPeerConnection) generateChannel(ssrc uint32, payloadType uint8) (buffers chan<- *rtp.Packet) {
if pc.Ontrack == nil {
if pc.OnTrack == nil {
return nil
}
@@ -698,7 +788,8 @@ func (pc *RTCPeerConnection) dataChannelEventHandler(e network.DataChannelEvent)
switch event := e.(type) {
case *network.DataChannelCreated:
newDataChannel := &RTCDataChannel{ID: event.StreamIdentifier(), Label: event.Label, rtcPeerConnection: pc}
id := event.StreamIdentifier()
newDataChannel := &RTCDataChannel{ID: &id, Label: event.Label, rtcPeerConnection: pc}
pc.dataChannels[e.StreamIdentifier()] = newDataChannel
if pc.OnDataChannel != nil {
go pc.OnDataChannel(newDataChannel)
@@ -710,10 +801,10 @@ func (pc *RTCPeerConnection) dataChannelEventHandler(e network.DataChannelEvent)
datachannel.RLock()
defer datachannel.RUnlock()
if datachannel.Onmessage != nil {
go datachannel.Onmessage(event.Payload)
if datachannel.OnMessage != nil {
go datachannel.OnMessage(event.Payload)
} else {
fmt.Printf("Onmessage has not been set for Datachannel %s %d \n", datachannel.Label, e.StreamIdentifier())
fmt.Printf("OnMessage has not been set for Datachannel %s %d \n", datachannel.Label, e.StreamIdentifier())
}
} else {
fmt.Printf("No datachannel found for streamIdentifier %d \n", e.StreamIdentifier())
@@ -815,21 +906,6 @@ func (pc *RTCPeerConnection) addDataMediaSection(d *sdp.SessionDescription, midV
d.WithMedia(media)
}
func (pc *RTCPeerConnection) generateDataChannelID(client bool) (uint16, error) {
var id uint16
if !client {
id++
}
for ; id < pc.sctp.MaxChannels-1; id += 2 {
_, ok := pc.dataChannels[id]
if !ok {
return id, nil
}
}
return 0, &rtcerr.OperationError{Err: ErrMaxDataChannels}
}
// NewRTCTrack is used to create a new RTCTrack
func (pc *RTCPeerConnection) NewRTCTrack(payloadType uint8, id, label string) (*RTCTrack, error) {
codec, err := pc.mediaEngine.getCodec(payloadType)

View File

@@ -251,6 +251,17 @@ func TestRTCPeerConnection_GetConfiguration(t *testing.T) {
assert.Equal(t, expected.IceCandidatePoolSize, actual.IceCandidatePoolSize)
}
// TODO - This unittest needs to be completed when CreateDataChannel is complete
// func TestRTCPeerConnection_CreateDataChannel(t *testing.T) {
// pc, err := New(RTCConfiguration{})
// assert.Nil(t, err)
//
// _, err = pc.CreateDataChannel("data", &RTCDataChannelInit{
//
// })
// assert.Nil(t, err)
// }
// TODO Fix this test
const minimalOffer = `v=0
o=- 7193157174393298413 2 IN IP4 127.0.0.1

View File

@@ -1,14 +1,27 @@
package webrtc
import "math"
import (
"math"
)
// RTCSctpTransport provides details about the SCTP transport.
type RTCSctpTransport struct {
State RTCSctpTransportState // TODO: Set RTCSctpTransportState
// transport *RTCDtlsTransport // TODO: DTLS introspection API
// Transport represents the transport over which all SCTP packets for data
// channels will be sent and received.
Transport RTCDtlsTransport
// State represents the current state of the SCTP transport.
State RTCSctpTransportState
// MaxMessageSize represents the maximum size of data that can be passed to
// RTCDataChannel's send() method.
MaxMessageSize float64
MaxChannels uint16
// onstatechange func()
// MaxChannels represents the maximum amount of RTCDataChannel's that can
// be used simultaneously.
MaxChannels *uint16
// OnStateChange func()
}
func newRTCSctpTransport() *RTCSctpTransport {
@@ -50,5 +63,6 @@ func (r *RTCSctpTransport) calcMessageSize(remoteMaxMessageSize, canSendSize flo
}
func (r *RTCSctpTransport) updateMaxChannels() {
r.MaxChannels = 65535 // TODO: Get from implementation
val := uint16(65535)
r.MaxChannels = &val // TODO: Get from implementation
}