Jump to content
C4 Forums | Control4

Intrinsic AppleTV Driver NOT compatible with TVOS 15


RAV

Recommended Posts


16 minutes ago, Control4Savant said:

I am not sure I understand this …Are you referring to mini apps?

No the actual API in which drivers are created with Control4 - it’s based on a Sandboxed (restricted) version of LUA and the raw function calls and libraries needed to leverage the systems employed by Apple just didn’t exist, so we’ve had to build them from scratch and find a way to get them to run within this framework.

Link to comment
Share on other sites

23 minutes ago, IntrinsicGroup said:

No the actual API in which drivers are created with Control4 - it’s based on a Sandboxed (restricted) version of LUA and the raw function calls and libraries needed to leverage the systems employed by Apple just didn’t exist, so we’ve had to build them from scratch and find a way to get them to run within this framework.

I must say, I am still confused given you has TVOS14 working…. But I am not a techie!

Link to comment
Share on other sites

I haven't touched the API in question but I can hopefully explain the time things can take and libraries.

A while ago I wanted to create an Xbox driver that works over the SmartGlass protocol. While this would be relatively easy if you had the SDK that Microsoft provides it is much more difficult in a sandboxed environment (Control4) where you cannot use the SDK provided by Microsoft. Instead you need to work with the raw protocol (bits and bytes) and write all of the logic required just to even communicate to the Xbox. Sometimes you can find opensource MIT licensed code to do portions of what you need, other times you need to write it yourself.

So what would it take to write everything required where the libraries don't exist in Control4? Let's start with JUST the crypto portion: https://openxbox.org/smartglass-documentation/cryptography/#key-exchange

  • On Discovery, the console responds with a Discovery Response including a certificate, this certificate holds the console's public key.
  • The client generates elliptic curve and derives the shared secret with console's public key using ECDH and a randomly generated public/private keypair
  • The shared secret is salted using static salts, see KDF Salts
  • The salted shared secret is hashed using SHA-512
  • The salted & hashed shared secret is split into the following individual keys:
  • bytes 0-16: Encryption key (AES-128-CBC)
  • bytes 16-32: Initialization Vector key (AES-128-CBC)
  • bytes 32-64: Hashing key (HMAC-SHA-256)
  • The client's public key is sent inside the Connect Request message to the console

First things first we need to get the public key from the certificate. So we need to parse out the response and format it. It's received in an ASN. The following code (incomplete, needs to support more types, also using a custom library, BitHelper, to read bits and bytes) is used just to parse the ASN to get the public key used in step 2. After that we still need to do encryption using ECDH, which if not supported in Control4's encryption methods also needs to be written or sourced from somewhere. This can go on and on.
 


ASN = (function()
    local class = {}; class.__index = class

    local Types = {
        BOOLEAN  = 0x01,
        INTEGER  = 0x02,
        STRING   = 0x03,
        OCTET    = 0x04,
        NULL     = 0x05,
        OBJECT   = 0x06,
        UTF8String = 0x0C,
        IA5STRING= 0x16,
        SEQUENCE = 0x30,
        SET      = 0x31,
    }

    class.Map = {
        [0x01] = "DecodeBoolean",
        [0x02] = "DecodeInteger",
        [0x03] = "DecodeString",
        [0x04] = "DecodeOctet",
        [0x05] = "DecodeNull",
        [0x06] = "DecodeObject",
        [0x0C] = "DecodeUTF8String",
        [0x13] = "DecodeUTF8String",
        [0x16] = "DecodeIA5String",
        [0x17] = "DecodeUTCTime",
        [0x30] = "DecodeSequence",
        [0x31] = "DecodeSet",
    }

    class.Class = {
        ["Universal"] = 0,
        ["Application"] = 1,
        ["Context"] = 2,
        ["Private" ] = 3
    }

    function class.DecodeSet(data, pos)
        Logger.Trace("ASN.DecodeSet: " .. pos)
        pos, l = class.DecodeLength(data, pos)
        pos, d = BitHelper.GetString(data, pos, l)

        local p, obj = class.Parse(d)

        return pos, obj
    end

    function class.DecodeOctet(data, pos)
        local l,d

        pos, l = class.DecodeLength(data, pos)
        pos, d = BitHelper.GetString(data, pos, l)

        return pos, d
    end

    function class.DecodeUTCTime(data, pos)
        Logger.Trace("ASN.DecodeUTCTime")

        pos, length = class.DecodeLength(data, pos)

        --print("STRING LENGTH: " .. length)

        pos, str = BitHelper.GetString(data, pos, length)

        --print("STRING: " .. str)

        return pos, str
    end

    function class.DecodeString(data, pos)
        Logger.Trace("ASN.DecodeString")

        pos, length = class.DecodeLength(data, pos)

        --print("STRING LENGTH: " .. length)

        pos, str = BitHelper.GetString(data, pos, length)

        --print("STRING: " .. str)

        return pos, str
    end

    function class.DecodeUTF8String(data, pos)
        Logger.Trace("ASN.DecodeUTF8String")

        pos, length = class.DecodeLength(data, pos)

        --print("STRING LENGTH: " .. length)

        pos, str = BitHelper.GetString(data, pos, length)

        --print("STRING: " .. str)

        return pos, str
    end

    function class.DecodeLength(data, pos)
        Logger.Trace("ASN.DecodeLength")

        local length = nil

        pos, length = BitHelper.GetInt8(data, pos)

        local isLong = bit.band(length, 0x80) ~= 0

        --print("LONG: " .. tostring(isLong))

        if (isLong) then
            local bytes = bit.band(length, 0x7F)

            Logger.Debug("BYTES: " .. bytes)
            Logger.Debug(hexdump(string.sub(data, pos, pos + bytes - 1)))

            if (bytes == 1) then
                pos, length = BitHelper.GetInt8(data, pos)
            elseif (bytes == 2) then
                pos, length = BitHelper.GetInt16(data, pos)
            elseif (bytes == 4) then
                pos, length = BitHelper.GetInt32(data, pos)
            else
                Logger.Error("BAD LENGTH")
            end

            --print("LENGTH: " .. length)
        else
            Logger.Debug("LENGTH: " .. length)
            Logger.Debug(hexdump(string.sub(data, pos - 1, pos)))
        end

        return pos, length
    end

    function class.DecodeSequence(data, pos)
        Logger.Trace("ASN.DecodeSequence: " .. pos)
        --print(hexdump(string.sub(data, pos)))

        pos, l = class.DecodeLength(data, pos)
        pos, d = BitHelper.GetString(data, pos, l)

        local p, obj = class.Parse(d)

        return pos, obj
    end

    function class.DecodeSequenceOf()

    end

    function class.DecodeInteger(data, pos)
        Logger.Trace("ASN.DecodeInteger")

        local start = pos

        pos, length = class.DecodeLength(data, pos)

        if (length == 1) then
            return BitHelper.GetInt8(data, pos)
        elseif (length == 2) then
            return BitHelper.GetInt16(data, pos)
        elseif (length == 4) then
            return BitHelper.GetInt32(data, pos)
        else
            return BitHelper.GetString(data, pos, length)
        end
    end

    function class.DecodeContext(data, pos)
        Logger.Trace("ASN.DecodeContext")

        local length, d

        pos, length = class.DecodeLength(data, pos)
        pos, d = BitHelper.GetString(data, pos, length)

        local _, obj = class.Parse(d)

        return pos, obj
    end

    function class.DecodeObject(data, pos)
        Logger.Trace("ASN.DecodeObject")

        local length = 0

        pos, length = class.DecodeLength(data, pos)

        local d = string.sub(data, pos, pos + length)

        local p, firstByte = BitHelper.GetInt8(d)

        local firstDigit = firstByte / 40;
        local secondDigit = firstByte % 40;
        local nodes = { firstDigit, secondDigit }

        -- TODO: Determine OID value

        return pos + length, d
    end

    function class.DecodeNull(data, pos)
        pos, length = class.DecodeLength(data, pos)

        return pos, false
    end

    function class.DecodeBoolean(data, pos)
        Logger.Trace("ASN.DecodeBoolean")

        pos, length = class.DecodeLength(data, pos)
        pos, d = BitHelper.GetInt8(data, pos)

        return pos, d
    end

    function class.DecodeChoice()

    end

    function class.Parse(certificate, pos)
        Logger.Trace("ASN.Parse")

        local pos = pos or 1
        local limit = 1
        local obj = {}

        while (pos < #certificate) do
            local t, l, d

            pos, t = BitHelper.GetInt8(certificate, pos)

            local f = bit.rshift(bit.band(t, 0x20), 5)
            local c = bit.rshift(bit.band(t, 0xC0), 6)

            local method = class.Map[t]

            if (method and class[method]) then
                Logger.Trace(method)

                pos, d = class[method](certificate, pos)

                obj[#obj + 1] = d
            else
                if (c == class.Class.Context) then
                    pos, d = class.DecodeContext(certificate, pos)
                else
                    Logger.Error("BAD METHOD: " .. t)
                end
            end
        end
        return pos, obj
    end

    local mt = {
        __call = function(self)
            local instance = {}

            setmetatable(instance, class)

            return instance
        end,
    }

    setmetatable(class, mt)

    return class
end) ()



 

Link to comment
Share on other sites

23 minutes ago, Rexabyte said:

I haven't touched the API in question but I can hopefully explain the time things can take and libraries.

A while ago I wanted to create an Xbox driver that works over the SmartGlass protocol. While this would be relatively easy if you had the SDK that Microsoft provides it is much more difficult in a sandboxed environment (Control4) where you cannot use the SDK provided by Microsoft. Instead you need to work with the raw protocol (bits and bytes) and write all of the logic required just to even communicate to the Xbox. Sometimes you can find opensource MIT licensed code to do portions of what you need, other times you need to write it yourself.

So what would it take to write everything required where the libraries don't exist in Control4? Let's start with JUST the crypto portion: https://openxbox.org/smartglass-documentation/cryptography/#key-exchange

  • On Discovery, the console responds with a Discovery Response including a certificate, this certificate holds the console's public key.
  • The client generates elliptic curve and derives the shared secret with console's public key using ECDH and a randomly generated public/private keypair
  • The shared secret is salted using static salts, see KDF Salts
  • The salted shared secret is hashed using SHA-512
  • The salted & hashed shared secret is split into the following individual keys:
  • bytes 0-16: Encryption key (AES-128-CBC)
  • bytes 16-32: Initialization Vector key (AES-128-CBC)
  • bytes 32-64: Hashing key (HMAC-SHA-256)
  • The client's public key is sent inside the Connect Request message to the console

First things first we need to get the public key from the certificate. So we need to parse out the response and format it. It's received in an ASN. The following code (incomplete, needs to support more types, also using a custom library, BitHelper, to read bits and bytes) is used just to parse the ASN to get the public key used in step 2. After that we still need to do encryption using ECDH, which if not supported in Control4's encryption methods also needs to be written or sourced from somewhere. This can go on and on.
 


ASN = (function()
    local class = {}; class.__index = class

    local Types = {
        BOOLEAN  = 0x01,
        INTEGER  = 0x02,
        STRING   = 0x03,
        OCTET    = 0x04,
        NULL     = 0x05,
        OBJECT   = 0x06,
        UTF8String = 0x0C,
        IA5STRING= 0x16,
        SEQUENCE = 0x30,
        SET      = 0x31,
    }

    class.Map = {
        [0x01] = "DecodeBoolean",
        [0x02] = "DecodeInteger",
        [0x03] = "DecodeString",
        [0x04] = "DecodeOctet",
        [0x05] = "DecodeNull",
        [0x06] = "DecodeObject",
        [0x0C] = "DecodeUTF8String",
        [0x13] = "DecodeUTF8String",
        [0x16] = "DecodeIA5String",
        [0x17] = "DecodeUTCTime",
        [0x30] = "DecodeSequence",
        [0x31] = "DecodeSet",
    }

    class.Class = {
        ["Universal"] = 0,
        ["Application"] = 1,
        ["Context"] = 2,
        ["Private" ] = 3
    }

    function class.DecodeSet(data, pos)
        Logger.Trace("ASN.DecodeSet: " .. pos)
        pos, l = class.DecodeLength(data, pos)
        pos, d = BitHelper.GetString(data, pos, l)

        local p, obj = class.Parse(d)

        return pos, obj
    end

    function class.DecodeOctet(data, pos)
        local l,d

        pos, l = class.DecodeLength(data, pos)
        pos, d = BitHelper.GetString(data, pos, l)

        return pos, d
    end

    function class.DecodeUTCTime(data, pos)
        Logger.Trace("ASN.DecodeUTCTime")

        pos, length = class.DecodeLength(data, pos)

        --print("STRING LENGTH: " .. length)

        pos, str = BitHelper.GetString(data, pos, length)

        --print("STRING: " .. str)

        return pos, str
    end

    function class.DecodeString(data, pos)
        Logger.Trace("ASN.DecodeString")

        pos, length = class.DecodeLength(data, pos)

        --print("STRING LENGTH: " .. length)

        pos, str = BitHelper.GetString(data, pos, length)

        --print("STRING: " .. str)

        return pos, str
    end

    function class.DecodeUTF8String(data, pos)
        Logger.Trace("ASN.DecodeUTF8String")

        pos, length = class.DecodeLength(data, pos)

        --print("STRING LENGTH: " .. length)

        pos, str = BitHelper.GetString(data, pos, length)

        --print("STRING: " .. str)

        return pos, str
    end

    function class.DecodeLength(data, pos)
        Logger.Trace("ASN.DecodeLength")

        local length = nil

        pos, length = BitHelper.GetInt8(data, pos)

        local isLong = bit.band(length, 0x80) ~= 0

        --print("LONG: " .. tostring(isLong))

        if (isLong) then
            local bytes = bit.band(length, 0x7F)

            Logger.Debug("BYTES: " .. bytes)
            Logger.Debug(hexdump(string.sub(data, pos, pos + bytes - 1)))

            if (bytes == 1) then
                pos, length = BitHelper.GetInt8(data, pos)
            elseif (bytes == 2) then
                pos, length = BitHelper.GetInt16(data, pos)
            elseif (bytes == 4) then
                pos, length = BitHelper.GetInt32(data, pos)
            else
                Logger.Error("BAD LENGTH")
            end

            --print("LENGTH: " .. length)
        else
            Logger.Debug("LENGTH: " .. length)
            Logger.Debug(hexdump(string.sub(data, pos - 1, pos)))
        end

        return pos, length
    end

    function class.DecodeSequence(data, pos)
        Logger.Trace("ASN.DecodeSequence: " .. pos)
        --print(hexdump(string.sub(data, pos)))

        pos, l = class.DecodeLength(data, pos)
        pos, d = BitHelper.GetString(data, pos, l)

        local p, obj = class.Parse(d)

        return pos, obj
    end

    function class.DecodeSequenceOf()

    end

    function class.DecodeInteger(data, pos)
        Logger.Trace("ASN.DecodeInteger")

        local start = pos

        pos, length = class.DecodeLength(data, pos)

        if (length == 1) then
            return BitHelper.GetInt8(data, pos)
        elseif (length == 2) then
            return BitHelper.GetInt16(data, pos)
        elseif (length == 4) then
            return BitHelper.GetInt32(data, pos)
        else
            return BitHelper.GetString(data, pos, length)
        end
    end

    function class.DecodeContext(data, pos)
        Logger.Trace("ASN.DecodeContext")

        local length, d

        pos, length = class.DecodeLength(data, pos)
        pos, d = BitHelper.GetString(data, pos, length)

        local _, obj = class.Parse(d)

        return pos, obj
    end

    function class.DecodeObject(data, pos)
        Logger.Trace("ASN.DecodeObject")

        local length = 0

        pos, length = class.DecodeLength(data, pos)

        local d = string.sub(data, pos, pos + length)

        local p, firstByte = BitHelper.GetInt8(d)

        local firstDigit = firstByte / 40;
        local secondDigit = firstByte % 40;
        local nodes = { firstDigit, secondDigit }

        -- TODO: Determine OID value

        return pos + length, d
    end

    function class.DecodeNull(data, pos)
        pos, length = class.DecodeLength(data, pos)

        return pos, false
    end

    function class.DecodeBoolean(data, pos)
        Logger.Trace("ASN.DecodeBoolean")

        pos, length = class.DecodeLength(data, pos)
        pos, d = BitHelper.GetInt8(data, pos)

        return pos, d
    end

    function class.DecodeChoice()

    end

    function class.Parse(certificate, pos)
        Logger.Trace("ASN.Parse")

        local pos = pos or 1
        local limit = 1
        local obj = {}

        while (pos < #certificate) do
            local t, l, d

            pos, t = BitHelper.GetInt8(certificate, pos)

            local f = bit.rshift(bit.band(t, 0x20), 5)
            local c = bit.rshift(bit.band(t, 0xC0), 6)

            local method = class.Map[t]

            if (method and class[method]) then
                Logger.Trace(method)

                pos, d = class[method](certificate, pos)

                obj[#obj + 1] = d
            else
                if (c == class.Class.Context) then
                    pos, d = class.DecodeContext(certificate, pos)
                else
                    Logger.Error("BAD METHOD: " .. t)
                end
            end
        end
        return pos, obj
    end

    local mt = {
        __call = function(self)
            local instance = {}

            setmetatable(instance, class)

            return instance
        end,
    }

    setmetatable(class, mt)

    return class
end) ()



 

Did you ever get this working? 
I bet people would employ you to do an Xbox driver for them. I know I would. 

Link to comment
Share on other sites

6 minutes ago, BuffaloC4 said:

Did you ever get this working? 
I bet people would employ you to do an Xbox driver for them. I know I would. 

It was put on the back burner as we took on some non Control4 related projects.

We never revisited it afterwards as the Series X released and it doesn't work over the same local communication method, it requires an internet connection and uses xCloud.

Link to comment
Share on other sites

  • 2 months later...
On 3/10/2022 at 8:36 PM, beingboard247 said:

So is there any update? Is this still happening or is it just smoke and mirrors…

Use Jesse IR driver, u won’t need anything else, whatever Apple do with TvOS u will be immune to these changes …

Link to comment
Share on other sites

On 3/10/2022 at 7:36 PM, beingboard247 said:

So is there any update? Is this still happening or is it just smoke and mirrors…

We're still working on the new driver for tvOS 15 to supplement our tvOS 14 driver. We're sorry it's taking longer than planned but we're still actively working on it. We will update this forum as soon as we have more information and a release date.

Glad you've found an alternative solution to get you sorted though 🙂

Link to comment
Share on other sites

1 hour ago, IntrinsicGroup said:

We're still working on the new driver for tvOS 15 to supplement our tvOS 14 driver. We're sorry it's taking longer than planned but we're still actively working on it. We will update this forum as soon as we have more information and a release date.

Glad you've found an alternative solution to get you sorted though 🙂

Will there be any improvements in the new driver (e.g. mini apps or meta data handling would make the wait worthwhile!).

Link to comment
Share on other sites

On 3/17/2022 at 7:56 PM, tmj4 said:

Was thinking the same. We're closer to tvOS 16 Beta than 15 GM at this point. 

16 won’t change the control introduced in 15, although this is Apple and they can do whatever they think it’s an advantage

Link to comment
Share on other sites

  • 2 months later...
On 3/18/2022 at 4:10 AM, IntrinsicGroup said:

Hopefully - I can't make any promises on it though until the driver is completed but that's our intention.

Have you an update on this please, very frustrating that your product was recomended and i purchased last year only to have it to be forced redundant shortly afterward.

Update expectations have been moved a number of times, I've been patiently waiting and using 2 remotes.

Link to comment
Share on other sites

8 hours ago, bk said:

Have you an update on this please, very frustrating that your product was recomended and i purchased last year only to have it to be forced redundant shortly afterward.

Update expectations have been moved a number of times, I've been patiently waiting and using 2 remotes.

+1. This driver was supposed to be ready last October. All we've heard is it'll be ready soon, it'll be ready next month and so on, without any results. If you are not going to stand behind your product. go ahead and say so and be done with it.

Link to comment
Share on other sites

4 hours ago, South Africa C4 user said:

I must say that I still have no issues with the native driver (6 Apple TV’s across 2 houses) but I was hoping this driver would add something extra like mini Apps or meta data.

https://github.com/13mralex/c4-pyatv-remote
 

This driver will provide you with MiniApps on Apple TV!

Link to comment
Share on other sites

On 6/3/2022 at 12:49 PM, schaudhary77 said:

+1. This driver was supposed to be ready last October. All we've heard is it'll be ready soon, it'll be ready next month and so on, without any results. If you are not going to stand behind your product. go ahead and say so and be done with it.

It would be reasonable for @IntrinsicGroup to provide some update at this point, bad news would be better than no news.   If you have dropped the product be upfront about it.  Apple has now announced the next version of tvOS 16.

Link to comment
Share on other sites

14 minutes ago, jmb said:

It would be reasonable for @IntrinsicGroup to provide some update at this point, bad news would be better than no news.   If you have dropped the product be upfront about it.  Apple has now announced the next version of tvOS 16.

I have fears intrinsic isn't doing any updates. They've also been working on Tesla driver for at least 6 mo.

Link to comment
Share on other sites

Guys move on there are good alternatives out there, myself am going to move to C4 original AppleTV driver once I move to EA5, planned before Christmas this year 😄. Unofficial drivers using unpublished API’s will always breaks at one point!

Link to comment
Share on other sites

This thread is quite old. Please consider starting a new thread rather than reviving this one.

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...

Important Information

By using this site, you agree to our Terms of Use.