From 559635793aacc538252fb06991ee4c4a9eae4aa3 Mon Sep 17 00:00:00 2001 From: Sharad Binjola <31142146+sharadb-amazon@users.noreply.github.com> Date: Mon, 25 Apr 2022 06:17:28 -0700 Subject: [PATCH] tv-casting-app/android: Open commissioning window through JNI in a new fragment (#17655) --- .../android/App/app/build.gradle | 1 + .../app/CommissionerDiscoveryFragment.java | 95 ++++++++++ .../casting/app/CommissioningFragment.java | 68 +++++++ .../com/chip/casting/app/MainActivity.java | 105 ++++++----- .../casting/util/GlobalCastingConstants.java | 3 + .../jni/com/chip/casting/DACProvider.java | 32 ++++ .../jni/com/chip/casting/DACProviderStub.java | 57 ++++++ .../jni/com/chip/casting/TvCastingApp.java | 4 +- .../app/src/main/jni/cpp/JNIDACProvider.cpp | 172 ++++++++++++++++++ .../App/app/src/main/jni/cpp/JNIDACProvider.h | 43 +++++ .../app/src/main/jni/cpp/TvCastingApp-JNI.cpp | 29 ++- .../app/src/main/res/layout/activity_main.xml | 12 +- .../fragment_commissioner_discovery.xml | 24 +++ .../res/layout/fragment_commissioning.xml | 21 +++ examples/tv-casting-app/android/BUILD.gn | 4 + 15 files changed, 618 insertions(+), 52 deletions(-) create mode 100644 examples/tv-casting-app/android/App/app/src/main/java/com/chip/casting/app/CommissionerDiscoveryFragment.java create mode 100644 examples/tv-casting-app/android/App/app/src/main/java/com/chip/casting/app/CommissioningFragment.java create mode 100644 examples/tv-casting-app/android/App/app/src/main/jni/com/chip/casting/DACProvider.java create mode 100644 examples/tv-casting-app/android/App/app/src/main/jni/com/chip/casting/DACProviderStub.java create mode 100644 examples/tv-casting-app/android/App/app/src/main/jni/cpp/JNIDACProvider.cpp create mode 100644 examples/tv-casting-app/android/App/app/src/main/jni/cpp/JNIDACProvider.h create mode 100644 examples/tv-casting-app/android/App/app/src/main/res/layout/fragment_commissioner_discovery.xml create mode 100644 examples/tv-casting-app/android/App/app/src/main/res/layout/fragment_commissioning.xml diff --git a/examples/tv-casting-app/android/App/app/build.gradle b/examples/tv-casting-app/android/App/app/build.gradle index 03d8fed015e6ca..b697bf8b6369d4 100644 --- a/examples/tv-casting-app/android/App/app/build.gradle +++ b/examples/tv-casting-app/android/App/app/build.gradle @@ -62,6 +62,7 @@ dependencies { implementation 'androidx.appcompat:appcompat:1.3.1' implementation 'com.google.android.material:material:1.4.0' implementation 'androidx.constraintlayout:constraintlayout:2.1.1' + implementation 'androidx.legacy:legacy-support-v4:1.0.0' testImplementation 'junit:junit:4.+' testImplementation 'org.mockito:mockito-core:3.+' androidTestImplementation 'androidx.test.ext:junit:1.1.3' diff --git a/examples/tv-casting-app/android/App/app/src/main/java/com/chip/casting/app/CommissionerDiscoveryFragment.java b/examples/tv-casting-app/android/App/app/src/main/java/com/chip/casting/app/CommissionerDiscoveryFragment.java new file mode 100644 index 00000000000000..b649e43f3ebf2b --- /dev/null +++ b/examples/tv-casting-app/android/App/app/src/main/java/com/chip/casting/app/CommissionerDiscoveryFragment.java @@ -0,0 +1,95 @@ +package com.chip.casting.app; + +import android.content.Context; +import android.net.nsd.NsdManager; +import android.net.wifi.WifiManager; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; +import com.chip.casting.dnssd.CommissionerDiscoveryListener; +import com.chip.casting.util.GlobalCastingConstants; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +/** A {@link Fragment} to discover commissioners on the network */ +public class CommissionerDiscoveryFragment extends Fragment { + + public CommissionerDiscoveryFragment() {} + + /** + * Use this factory method to create a new instance of this fragment using the provided + * parameters. + * + * @return A new instance of fragment CommissionerDiscoveryFragment. + */ + public static CommissionerDiscoveryFragment newInstance() { + return new CommissionerDiscoveryFragment(); + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + startCommissionerDiscovery(); + } + + @Override + public View onCreateView( + LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + // Inflate the layout for this fragment + return inflater.inflate(R.layout.fragment_commissioner_discovery, container, false); + } + + @Override + public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + Button manualCommissioningButton = getView().findViewById(R.id.manualCommissioningButton); + Callback callback = (Callback) this.getActivity(); + manualCommissioningButton.setOnClickListener( + new View.OnClickListener() { + @Override + public void onClick(View v) { + callback.handleManualCommissioningButtonClicked(); + } + }); + } + + private void startCommissionerDiscovery() { + WifiManager wifi = (WifiManager) this.getContext().getSystemService(Context.WIFI_SERVICE); + WifiManager.MulticastLock multicastLock = wifi.createMulticastLock("multicastLock"); + multicastLock.setReferenceCounted(true); + multicastLock.acquire(); + + CastingContext castingContext = new CastingContext(this.getActivity()); + NsdManager.DiscoveryListener commissionerDiscoveryListener = + new CommissionerDiscoveryListener(castingContext); + + NsdManager nsdManager = castingContext.getNsdManager(); + nsdManager.discoverServices( + GlobalCastingConstants.CommissionerServiceType, + NsdManager.PROTOCOL_DNS_SD, + commissionerDiscoveryListener); + + // Stop discovery after specified timeout + Executors.newSingleThreadScheduledExecutor() + .schedule( + new Runnable() { + @Override + public void run() { + nsdManager.stopServiceDiscovery(commissionerDiscoveryListener); + multicastLock.release(); + } + }, + 10, + TimeUnit.SECONDS); + } + + /** Interface for notifying the host. */ + interface Callback { + /** Notifies listener of Skip to manual Commissioning Button click. */ + void handleManualCommissioningButtonClicked(); + } +} diff --git a/examples/tv-casting-app/android/App/app/src/main/java/com/chip/casting/app/CommissioningFragment.java b/examples/tv-casting-app/android/App/app/src/main/java/com/chip/casting/app/CommissioningFragment.java new file mode 100644 index 00000000000000..9299d52be3f42c --- /dev/null +++ b/examples/tv-casting-app/android/App/app/src/main/java/com/chip/casting/app/CommissioningFragment.java @@ -0,0 +1,68 @@ +package com.chip.casting.app; + +import android.os.Bundle; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; +import com.chip.casting.TvCastingApp; +import com.chip.casting.dnssd.CommissionerDiscoveryListener; +import com.chip.casting.util.GlobalCastingConstants; + +/** A {@link Fragment} to get the TV Casting App commissioned. */ +public class CommissioningFragment extends Fragment { + private static final String TAG = CommissionerDiscoveryListener.class.getSimpleName(); + + private final TvCastingApp tvCastingApp; + + public CommissioningFragment(TvCastingApp tvCastingApp) { + this.tvCastingApp = tvCastingApp; + } + + /** + * Use this factory method to create a new instance of this fragment using the provided + * parameters. + * + * @param tvCastingApp TV Casting App (JNI) + * @return A new instance of fragment CommissioningFragment. + */ + // TODO: Rename and change types and number of parameters + public static CommissioningFragment newInstance(TvCastingApp tvCastingApp) { + return new CommissioningFragment(tvCastingApp); + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + } + + @Override + public View onCreateView( + LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + // Inflate the layout for this fragment + return inflater.inflate(R.layout.fragment_commissioning, container, false); + } + + @Override + public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + boolean status = + tvCastingApp.openBasicCommissioningWindow( + GlobalCastingConstants.CommissioningWindowDurationSecs); + Log.d( + TAG, + "CommissioningFragment.onViewCreated tvCastingApp.openBasicCommissioningWindow returned " + + status); + TextView commissioningWindowStatusView = getView().findViewById(R.id.commissioningWindowStatus); + TextView onboardingPayloadView = getView().findViewById(R.id.onboardingPayload); + if (status == true) { + commissioningWindowStatusView.setText("Commissioning window opened!"); + onboardingPayloadView.setText("Onboarding Pin: " + GlobalCastingConstants.SetupPasscode); + } else { + commissioningWindowStatusView.setText("Commissioning window could not be opened!"); + } + } +} diff --git a/examples/tv-casting-app/android/App/app/src/main/java/com/chip/casting/app/MainActivity.java b/examples/tv-casting-app/android/App/app/src/main/java/com/chip/casting/app/MainActivity.java index 258f400dcde5e1..0936b4fcb60bac 100644 --- a/examples/tv-casting-app/android/App/app/src/main/java/com/chip/casting/app/MainActivity.java +++ b/examples/tv-casting-app/android/App/app/src/main/java/com/chip/casting/app/MainActivity.java @@ -1,61 +1,84 @@ package com.chip.casting.app; import android.content.Context; -import android.net.nsd.NsdManager; -import android.net.wifi.WifiManager; import android.os.Bundle; import androidx.appcompat.app.AppCompatActivity; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentTransaction; +import chip.appserver.ChipAppServer; +import chip.platform.AndroidBleManager; +import chip.platform.AndroidChipPlatform; +import chip.platform.ChipMdnsCallbackImpl; +import chip.platform.DiagnosticDataProviderImpl; +import chip.platform.NsdManagerServiceResolver; +import chip.platform.PreferencesConfigurationManager; +import chip.platform.PreferencesKeyValueStoreManager; +import com.chip.casting.DACProviderStub; import com.chip.casting.TvCastingApp; -import com.chip.casting.dnssd.CommissionerDiscoveryListener; import com.chip.casting.util.GlobalCastingConstants; -import java.util.concurrent.Executors; -import java.util.concurrent.TimeUnit; -public class MainActivity extends AppCompatActivity { +public class MainActivity extends AppCompatActivity + implements CommissionerDiscoveryFragment.Callback { + + private ChipAppServer chipAppServer; + private TvCastingApp tvCastingApp; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); - startCommissionerDiscovery(); - testJni(); + initJni(); + + Fragment fragment = CommissionerDiscoveryFragment.newInstance(); + getSupportFragmentManager() + .beginTransaction() + .add(R.id.main_fragment_container, fragment, fragment.getClass().getSimpleName()) + .commit(); + } + + @Override + public void handleManualCommissioningButtonClicked() { + showFragment(CommissioningFragment.newInstance(tvCastingApp)); } - private void startCommissionerDiscovery() { - WifiManager wifi = (WifiManager) getApplicationContext().getSystemService(Context.WIFI_SERVICE); - WifiManager.MulticastLock multicastLock = wifi.createMulticastLock("multicastLock"); - multicastLock.setReferenceCounted(true); - multicastLock.acquire(); - - CastingContext castingContext = new CastingContext(this); - NsdManager.DiscoveryListener commissionerDiscoveryListener = - new CommissionerDiscoveryListener(castingContext); - - NsdManager nsdManager = castingContext.getNsdManager(); - nsdManager.discoverServices( - GlobalCastingConstants.CommissionerServiceType, - NsdManager.PROTOCOL_DNS_SD, - commissionerDiscoveryListener); - - // Stop discovery after specified timeout - Executors.newSingleThreadScheduledExecutor() - .schedule( - new Runnable() { - @Override - public void run() { - nsdManager.stopServiceDiscovery(commissionerDiscoveryListener); - multicastLock.release(); - } - }, - 10, - TimeUnit.SECONDS); + private void initJni() { + tvCastingApp = + new TvCastingApp((app, clusterId, duration) -> app.openBasicCommissioningWindow(duration)); + + tvCastingApp.setDACProvider(new DACProviderStub()); + Context applicationContext = this.getApplicationContext(); + AndroidChipPlatform chipPlatform = + new AndroidChipPlatform( + new AndroidBleManager(), + new PreferencesKeyValueStoreManager(applicationContext), + new PreferencesConfigurationManager(applicationContext), + new NsdManagerServiceResolver(applicationContext), + new ChipMdnsCallbackImpl(), + new DiagnosticDataProviderImpl(applicationContext)); + + chipPlatform.updateCommissionableDataProviderData( + null, null, 0, GlobalCastingConstants.SetupPasscode, GlobalCastingConstants.Discriminator); + + chipAppServer = new ChipAppServer(); + chipAppServer.startApp(); + } + + private void showFragment(Fragment fragment, boolean showOnBack) { + System.out.println( + "showFragment called with " + fragment.getClass().getSimpleName() + " and " + showOnBack); + FragmentTransaction fragmentTransaction = + getSupportFragmentManager() + .beginTransaction() + .replace(R.id.main_fragment_container, fragment, fragment.getClass().getSimpleName()); + if (showOnBack) { + fragmentTransaction.addToBackStack(null); + } + + fragmentTransaction.commit(); } - /** TBD: Temp dummy function for testing */ - private void testJni() { - TvCastingApp tvCastingApp = - new TvCastingApp((app, clusterId, endpoint) -> app.doSomethingInCpp(endpoint)); - tvCastingApp.doSomethingInCpp(0); + private void showFragment(Fragment fragment) { + showFragment(fragment, true); } } diff --git a/examples/tv-casting-app/android/App/app/src/main/java/com/chip/casting/util/GlobalCastingConstants.java b/examples/tv-casting-app/android/App/app/src/main/java/com/chip/casting/util/GlobalCastingConstants.java index f6e60ec63a5b67..2e558143d54b05 100644 --- a/examples/tv-casting-app/android/App/app/src/main/java/com/chip/casting/util/GlobalCastingConstants.java +++ b/examples/tv-casting-app/android/App/app/src/main/java/com/chip/casting/util/GlobalCastingConstants.java @@ -2,4 +2,7 @@ public class GlobalCastingConstants { public static final String CommissionerServiceType = "_matterd._udp."; + public static final int CommissioningWindowDurationSecs = 3 * 60; + public static int SetupPasscode = 20202021; + public static int Discriminator = 0xF00; } diff --git a/examples/tv-casting-app/android/App/app/src/main/jni/com/chip/casting/DACProvider.java b/examples/tv-casting-app/android/App/app/src/main/jni/com/chip/casting/DACProvider.java new file mode 100644 index 00000000000000..64dd43be96c4ab --- /dev/null +++ b/examples/tv-casting-app/android/App/app/src/main/jni/com/chip/casting/DACProvider.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2021 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.chip.casting; + +public interface DACProvider { + byte[] GetCertificationDeclaration(); + + byte[] GetFirmwareInformation(); + + byte[] GetDeviceAttestationCert(); + + byte[] GetProductAttestationIntermediateCert(); + + byte[] GetDeviceAttestationCertPrivateKey(); + + byte[] GetDeviceAttestationCertPublicKeyKey(); +} diff --git a/examples/tv-casting-app/android/App/app/src/main/jni/com/chip/casting/DACProviderStub.java b/examples/tv-casting-app/android/App/app/src/main/jni/com/chip/casting/DACProviderStub.java new file mode 100644 index 00000000000000..f8c7dfcc807eec --- /dev/null +++ b/examples/tv-casting-app/android/App/app/src/main/jni/com/chip/casting/DACProviderStub.java @@ -0,0 +1,57 @@ +package com.chip.casting; + +import android.util.Base64; + +public class DACProviderStub implements DACProvider { + + private String kDevelopmentDAC_Cert_FFF1_8001 = + "MIIB5zCCAY6gAwIBAgIIac3xDenlTtEwCgYIKoZIzj0EAwIwPTElMCMGA1UEAwwcTWF0dGVyIERldiBQQUkgMHhGRkYxIG5vIFBJRDEUMBIGCisGAQQBgqJ8AgEMBEZGRjEwIBcNMjIwMjA1MDAwMDAwWhgPOTk5OTEyMzEyMzU5NTlaMFMxJTAjBgNVBAMMHE1hdHRlciBEZXYgREFDIDB4RkZGMS8weDgwMDExFDASBgorBgEEAYKifAIBDARGRkYxMRQwEgYKKwYBBAGConwCAgwEODAwMTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABEY6xpNCkQoOVYj8b/Vrtj5i7M7LFI99TrA+5VJgFBV2fRalxmP3k+SRIyYLgpenzX58/HsxaznZjpDSk3dzjoKjYDBeMAwGA1UdEwEB/wQCMAAwDgYDVR0PAQH/BAQDAgeAMB0GA1UdDgQWBBSI3eezADgpMs/3NMBGJIEPRBaKbzAfBgNVHSMEGDAWgBRjVA5H9kscONE4hKRi0WwZXY/7PDAKBggqhkjOPQQDAgNHADBEAiABJ6J7S0RhDuL83E0reIVWNmC8D3bxchntagjfsrPBzQIga1ngr0Xz6yqFuRnTVzFSjGAoxBUjlUXhCOTlTnCXE1M="; + + private String kDevelopmentDAC_PrivateKey_FFF1_8001 = + "qrYAroroqrfXNifCF7fCBHCcppRq9fL3UwgzpStE+/8="; + + private String kDevelopmentDAC_PublicKey_FFF1_8001 = + "BEY6xpNCkQoOVYj8b/Vrtj5i7M7LFI99TrA+5VJgFBV2fRalxmP3k+SRIyYLgpenzX58/HsxaznZjpDSk3dzjoI="; + + private String KPAI_FFF1_8000_Cert_Array = + "MIIByzCCAXGgAwIBAgIIVq2CIq2UW2QwCgYIKoZIzj0EAwIwMDEYMBYGA1UEAwwPTWF0dGVyIFRlc3QgUEFBMRQwEgYKKwYBBAGConwCAQwERkZGMTAgFw0yMjAyMDUwMDAwMDBaGA85OTk5MTIzMTIzNTk1OVowPTElMCMGA1UEAwwcTWF0dGVyIERldiBQQUkgMHhGRkYxIG5vIFBJRDEUMBIGCisGAQQBgqJ8AgEMBEZGRjEwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARBmpMVwhc+DIyHbQPM/JRIUmR/f+xeUIL0BZko7KiUxZQVEwmsYx5MsDOSr2hLC6+35ls7gWLC9Sv5MbjneqqCo2YwZDASBgNVHRMBAf8ECDAGAQH/AgEAMA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUY1QOR/ZLHDjROISkYtFsGV2P+zwwHwYDVR0jBBgwFoAUav0idx9RH+y/FkGXZxDc3DGhcX4wCgYIKoZIzj0EAwIDSAAwRQIhALLvJ/Sa6bUPuR7qyUxNC9u415KcbLiPrOUpNo0SBUwMAiBlXckrhr2QmIKmxiF3uCXX0F7b58Ivn+pxIg5+pwP4kQ=="; + + /** + * format_version = 1 vendor_id = 0xFFF1 product_id_array = [ 0x8000,0x8001...0x8063] + * device_type_id = 0x1234 certificate_id = "ZIG20141ZB330001-24" security_level = 0 + * security_information = 0 version_number = 0x2694 certification_type = 0 dac_origin_vendor_id is + * not present dac_origin_product_id is not present + */ + private String kCertificationDeclaration = + "MIICGQYJKoZIhvcNAQcCoIICCjCCAgYCAQMxDTALBglghkgBZQMEAgEwggFxBgkqhkiG9w0BBwGgggFiBIIBXhUkAAElAfH/NgIFAIAFAYAFAoAFA4AFBIAFBYAFBoAFB4AFCIAFCYAFCoAFC4AFDIAFDYAFDoAFD4AFEIAFEYAFEoAFE4AFFIAFFYAFFoAFF4AFGIAFGYAFGoAFG4AFHIAFHYAFHoAFH4AFIIAFIYAFIoAFI4AFJIAFJYAFJoAFJ4AFKIAFKYAFKoAFK4AFLIAFLYAFLoAFL4AFMIAFMYAFMoAFM4AFNIAFNYAFNoAFN4AFOIAFOYAFOoAFO4AFPIAFPYAFPoAFP4AFQIAFQYAFQoAFQ4AFRIAFRYAFRoAFR4AFSIAFSYAFSoAFS4AFTIAFTYAFToAFT4AFUIAFUYAFUoAFU4AFVIAFVYAFVoAFV4AFWIAFWYAFWoAFW4AFXIAFXYAFXoAFX4AFYIAFYYAFYoAFY4AYJAMWLAQTWklHMjAxNDJaQjMzMDAwMy0yNCQFACQGACUHlCYkCAAYMX0wewIBA4AUYvqCM1ms+qmWPhz6FArd9QTzcWAwCwYJYIZIAWUDBAIBMAoGCCqGSM49BAMCBEcwRQIgJOXR9Hp9ew0gaibvaZt8l1e3LUaQid4xkuZ4x0Xn9gwCIQD4qi+nEfy3m5fjl87aZnuuRk4r0//fw8zteqjKX0wafA=="; + + @Override + public byte[] GetCertificationDeclaration() { + return Base64.decode(kCertificationDeclaration, Base64.DEFAULT); + } + + @Override + public byte[] GetFirmwareInformation() { + return new byte[0]; + } + + @Override + public byte[] GetDeviceAttestationCert() { + return Base64.decode(kDevelopmentDAC_Cert_FFF1_8001, Base64.DEFAULT); + } + + @Override + public byte[] GetProductAttestationIntermediateCert() { + return Base64.decode(KPAI_FFF1_8000_Cert_Array, Base64.DEFAULT); + } + + @Override + public byte[] GetDeviceAttestationCertPrivateKey() { + return Base64.decode(kDevelopmentDAC_PrivateKey_FFF1_8001, Base64.DEFAULT); + } + + @Override + public byte[] GetDeviceAttestationCertPublicKeyKey() { + return Base64.decode(kDevelopmentDAC_PublicKey_FFF1_8001, Base64.DEFAULT); + } +} diff --git a/examples/tv-casting-app/android/App/app/src/main/jni/com/chip/casting/TvCastingApp.java b/examples/tv-casting-app/android/App/app/src/main/jni/com/chip/casting/TvCastingApp.java index 0257c0d95f122a..7963bffaeb921d 100644 --- a/examples/tv-casting-app/android/App/app/src/main/jni/com/chip/casting/TvCastingApp.java +++ b/examples/tv-casting-app/android/App/app/src/main/jni/com/chip/casting/TvCastingApp.java @@ -37,8 +37,10 @@ private void postClusterInit(int clusterId, int endpoint) { public native void nativeInit(); + public native void setDACProvider(DACProvider provider); + /** TBD: Temp dummy function for testing */ - public native void doSomethingInCpp(int endpoint); + public native boolean openBasicCommissioningWindow(int duration); static { System.loadLibrary("TvCastingApp"); diff --git a/examples/tv-casting-app/android/App/app/src/main/jni/cpp/JNIDACProvider.cpp b/examples/tv-casting-app/android/App/app/src/main/jni/cpp/JNIDACProvider.cpp new file mode 100644 index 00000000000000..81f23f36a24dec --- /dev/null +++ b/examples/tv-casting-app/android/App/app/src/main/jni/cpp/JNIDACProvider.cpp @@ -0,0 +1,172 @@ +/* + * + * Copyright (c) 2021 Project CHIP Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "JNIDACProvider.h" +#include "lib/support/logging/CHIPLogging.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace chip; + +JNIDACProvider::JNIDACProvider(jobject provider) +{ + JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread(); + VerifyOrReturn(env != nullptr, ChipLogError(Zcl, "Failed to GetEnvForCurrentThread for JNIDACProvider")); + + mJNIDACProviderObject = env->NewGlobalRef(provider); + VerifyOrReturn(mJNIDACProviderObject != nullptr, ChipLogError(Zcl, "Failed to NewGlobalRef JNIDACProvider")); + + jclass JNIDACProviderClass = env->GetObjectClass(provider); + VerifyOrReturn(JNIDACProviderClass != nullptr, ChipLogError(Zcl, "Failed to get JNIDACProvider Java class")); + + mGetCertificationDeclarationMethod = env->GetMethodID(JNIDACProviderClass, "GetCertificationDeclaration", "()[B"); + if (mGetCertificationDeclarationMethod == nullptr) + { + ChipLogError(Zcl, "Failed to access JNIDACProvider 'GetCertificationDeclaration' method"); + env->ExceptionClear(); + } + + mGetFirmwareInformationMethod = env->GetMethodID(JNIDACProviderClass, "GetFirmwareInformation", "()[B"); + if (mGetFirmwareInformationMethod == nullptr) + { + ChipLogError(Zcl, "Failed to access JNIDACProvider 'GetFirmwareInformation' method"); + env->ExceptionClear(); + } + + mGetDeviceAttestationCertMethod = env->GetMethodID(JNIDACProviderClass, "GetDeviceAttestationCert", "()[B"); + if (mGetDeviceAttestationCertMethod == nullptr) + { + ChipLogError(Zcl, "Failed to access JNIDACProvider 'GetDeviceAttestationCert' method"); + env->ExceptionClear(); + } + + mGetProductAttestationIntermediateCertMethod = + env->GetMethodID(JNIDACProviderClass, "GetProductAttestationIntermediateCert", "()[B"); + if (mGetProductAttestationIntermediateCertMethod == nullptr) + { + ChipLogError(Zcl, "Failed to access JNIDACProvider 'GetProductAttestationIntermediateCert' method"); + env->ExceptionClear(); + } + + mGetDeviceAttestationCertPrivateKeyMethod = env->GetMethodID(JNIDACProviderClass, "GetDeviceAttestationCertPrivateKey", "()[B"); + if (mGetDeviceAttestationCertPrivateKeyMethod == nullptr) + { + ChipLogError(Zcl, "Failed to access JNIDACProvider 'GetDeviceAttestationCertPrivateKey' method"); + env->ExceptionClear(); + } + + mGetDeviceAttestationCertPublicKeyKeyMethod = + env->GetMethodID(JNIDACProviderClass, "GetDeviceAttestationCertPublicKeyKey", "()[B"); + if (mGetDeviceAttestationCertPublicKeyKeyMethod == nullptr) + { + ChipLogError(Zcl, "Failed to access JNIDACProvider 'GetDeviceAttestationCertPublicKeyKey' method"); + env->ExceptionClear(); + } +} + +CHIP_ERROR JNIDACProvider::GetJavaByteByMethod(jmethodID method, MutableByteSpan & out_buffer) +{ + JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread(); + VerifyOrReturnLogError(mJNIDACProviderObject != nullptr, CHIP_ERROR_INCORRECT_STATE); + VerifyOrReturnLogError(method != nullptr, CHIP_ERROR_INCORRECT_STATE); + VerifyOrReturnLogError(env != nullptr, CHIP_JNI_ERROR_NO_ENV); + + jbyteArray outArray = (jbyteArray) env->CallObjectMethod(mJNIDACProviderObject, method); + if (env->ExceptionCheck()) + { + ChipLogError(Zcl, "Java exception in get Method"); + env->ExceptionDescribe(); + env->ExceptionClear(); + return CHIP_ERROR_INCORRECT_STATE; + } + + if (outArray == nullptr || env->GetArrayLength(outArray) <= 0) + { + out_buffer.reduce_size(0); + return CHIP_NO_ERROR; + } + + JniByteArray JniOutArray(env, outArray); + return CopySpanToMutableSpan(JniOutArray.byteSpan(), out_buffer); +} + +CHIP_ERROR JNIDACProvider::GetCertificationDeclaration(MutableByteSpan & out_cd_buffer) +{ + ChipLogProgress(Zcl, "Received GetCertificationDeclaration"); + return GetJavaByteByMethod(mGetCertificationDeclarationMethod, out_cd_buffer); +} + +CHIP_ERROR JNIDACProvider::GetFirmwareInformation(MutableByteSpan & out_firmware_info_buffer) +{ + ChipLogProgress(Zcl, "Received GetFirmwareInformation"); + return GetJavaByteByMethod(mGetFirmwareInformationMethod, out_firmware_info_buffer); +} + +CHIP_ERROR JNIDACProvider::GetDeviceAttestationCert(MutableByteSpan & out_dac_buffer) +{ + ChipLogProgress(Zcl, "Received GetDeviceAttestationCert"); + return GetJavaByteByMethod(mGetDeviceAttestationCertMethod, out_dac_buffer); +} + +CHIP_ERROR JNIDACProvider::GetProductAttestationIntermediateCert(MutableByteSpan & out_pai_buffer) +{ + ChipLogProgress(Zcl, "Received GetProductAttestationIntermediateCert"); + return GetJavaByteByMethod(mGetProductAttestationIntermediateCertMethod, out_pai_buffer); +} + +// TODO: This should be moved to a method of P256Keypair +CHIP_ERROR LoadKeypairFromRaw(ByteSpan private_key, ByteSpan public_key, Crypto::P256Keypair & keypair) +{ + Crypto::P256SerializedKeypair serialized_keypair; + ReturnErrorOnFailure(serialized_keypair.SetLength(private_key.size() + public_key.size())); + memcpy(serialized_keypair.Bytes(), public_key.data(), public_key.size()); + memcpy(serialized_keypair.Bytes() + public_key.size(), private_key.data(), private_key.size()); + return keypair.Deserialize(serialized_keypair); +} + +CHIP_ERROR JNIDACProvider::SignWithDeviceAttestationKey(const ByteSpan & digest_to_sign, MutableByteSpan & out_signature_buffer) +{ + ChipLogProgress(Zcl, "Received SignWithDeviceAttestationKey"); + Crypto::P256ECDSASignature signature; + Crypto::P256Keypair keypair; + + VerifyOrReturnError(IsSpanUsable(out_signature_buffer), CHIP_ERROR_INVALID_ARGUMENT); + VerifyOrReturnError(IsSpanUsable(digest_to_sign), CHIP_ERROR_INVALID_ARGUMENT); + VerifyOrReturnError(out_signature_buffer.size() >= signature.Capacity(), CHIP_ERROR_BUFFER_TOO_SMALL); + + uint8_t privateKeyBuf[Crypto::kP256_PrivateKey_Length]; + MutableByteSpan privateKeyBufSpan(privateKeyBuf); + ReturnErrorOnFailure(GetJavaByteByMethod(mGetDeviceAttestationCertPrivateKeyMethod, privateKeyBufSpan)); + + uint8_t publicKeyBuf[Crypto::kP256_PublicKey_Length]; + MutableByteSpan publicKeyBufSpan(publicKeyBuf); + ReturnErrorOnFailure(GetJavaByteByMethod(mGetDeviceAttestationCertPublicKeyKeyMethod, publicKeyBufSpan)); + + // In a non-exemplary implementation, the public key is not needed here. It is used here merely because + // Crypto::P256Keypair is only (currently) constructable from raw keys if both private/public keys are present. + ReturnErrorOnFailure(LoadKeypairFromRaw(privateKeyBufSpan, publicKeyBufSpan, keypair)); + ReturnErrorOnFailure(keypair.ECDSA_sign_hash(digest_to_sign.data(), digest_to_sign.size(), signature)); + + return CopySpanToMutableSpan(ByteSpan{ signature.ConstBytes(), signature.Length() }, out_signature_buffer); +} diff --git a/examples/tv-casting-app/android/App/app/src/main/jni/cpp/JNIDACProvider.h b/examples/tv-casting-app/android/App/app/src/main/jni/cpp/JNIDACProvider.h new file mode 100644 index 00000000000000..a9181dc59295b3 --- /dev/null +++ b/examples/tv-casting-app/android/App/app/src/main/jni/cpp/JNIDACProvider.h @@ -0,0 +1,43 @@ +/* + * + * Copyright (c) 2021 Project CHIP Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#include "lib/support/logging/CHIPLogging.h" +#include +#include + +class JNIDACProvider : public chip::Credentials::DeviceAttestationCredentialsProvider +{ +public: + JNIDACProvider(jobject provider); + CHIP_ERROR GetCertificationDeclaration(chip::MutableByteSpan & out_cd_buffer) override; + CHIP_ERROR GetFirmwareInformation(chip::MutableByteSpan & out_firmware_info_buffer) override; + CHIP_ERROR GetDeviceAttestationCert(chip::MutableByteSpan & out_dac_buffer) override; + CHIP_ERROR GetProductAttestationIntermediateCert(chip::MutableByteSpan & out_pai_buffer) override; + CHIP_ERROR SignWithDeviceAttestationKey(const chip::ByteSpan & digest_to_sign, + chip::MutableByteSpan & out_signature_buffer) override; + +private: + CHIP_ERROR GetJavaByteByMethod(jmethodID method, chip::MutableByteSpan & out_buffer); + jobject mJNIDACProviderObject = nullptr; + jmethodID mGetCertificationDeclarationMethod = nullptr; + jmethodID mGetFirmwareInformationMethod = nullptr; + jmethodID mGetDeviceAttestationCertMethod = nullptr; + jmethodID mGetProductAttestationIntermediateCertMethod = nullptr; + jmethodID mGetDeviceAttestationCertPrivateKeyMethod = nullptr; + jmethodID mGetDeviceAttestationCertPublicKeyKeyMethod = nullptr; +}; diff --git a/examples/tv-casting-app/android/App/app/src/main/jni/cpp/TvCastingApp-JNI.cpp b/examples/tv-casting-app/android/App/app/src/main/jni/cpp/TvCastingApp-JNI.cpp index c53e644f2f18a2..b73574aa83c5fa 100644 --- a/examples/tv-casting-app/android/App/app/src/main/jni/cpp/TvCastingApp-JNI.cpp +++ b/examples/tv-casting-app/android/App/app/src/main/jni/cpp/TvCastingApp-JNI.cpp @@ -17,7 +17,11 @@ */ #include "TvCastingApp-JNI.h" +#include "JNIDACProvider.h" +#include #include +#include +#include #include #include #include @@ -79,8 +83,27 @@ JNI_METHOD(void, nativeInit)(JNIEnv *, jobject app) TvCastingAppJNIMgr().InitializeWithObjects(app); } -// TBD: Temp dummy function for testing -JNI_METHOD(void, doSomethingInCpp)(JNIEnv *, jobject, jint endpoint) +JNI_METHOD(void, setDACProvider)(JNIEnv *, jobject, jobject provider) { - ChipLogProgress(AppServer, "JNI_METHOD doSomethingInCpp called with endpoint %d", endpoint); + if (!chip::Credentials::IsDeviceAttestationCredentialsProviderSet()) + { + JNIDACProvider * p = new JNIDACProvider(provider); + chip::Credentials::SetDeviceAttestationCredentialsProvider(p); + } +} + +JNI_METHOD(jboolean, openBasicCommissioningWindow)(JNIEnv *, jobject, jint duration) +{ + ChipLogProgress(AppServer, "JNI_METHOD openBasicCommissioningWindow called with duration %d", duration); + + Server::GetInstance().GetFabricTable().DeleteAllFabrics(); + CHIP_ERROR err = + Server::GetInstance().GetCommissioningWindowManager().OpenBasicCommissioningWindow(System::Clock::Seconds16(duration)); + if (err != CHIP_NO_ERROR) + { + ChipLogError(Controller, "GetCommissioningWindowManager failed: %" CHIP_ERROR_FORMAT, err.Format()); + return false; + } + + return true; } diff --git a/examples/tv-casting-app/android/App/app/src/main/res/layout/activity_main.xml b/examples/tv-casting-app/android/App/app/src/main/res/layout/activity_main.xml index 62a1aa33d57f63..1a8bfe110a0495 100644 --- a/examples/tv-casting-app/android/App/app/src/main/res/layout/activity_main.xml +++ b/examples/tv-casting-app/android/App/app/src/main/res/layout/activity_main.xml @@ -6,11 +6,9 @@ android:layout_height="match_parent" tools:context="MainActivity"> - + + diff --git a/examples/tv-casting-app/android/App/app/src/main/res/layout/fragment_commissioner_discovery.xml b/examples/tv-casting-app/android/App/app/src/main/res/layout/fragment_commissioner_discovery.xml new file mode 100644 index 00000000000000..eee824f5c4092d --- /dev/null +++ b/examples/tv-casting-app/android/App/app/src/main/res/layout/fragment_commissioner_discovery.xml @@ -0,0 +1,24 @@ + + + + + +