Android 7.0 – BLE Scan abuse protection 관련 해결

고객사에서 7.0 이상 안드로이드 기기에서 BLE 연결이 되지 않는다 란 요청을 받아서 검색해보니 악용 사례를 막기 위한 변경점이 있다는 것을 알아냈다.

해당 이슈 설명

diff -> https://android-review.googlesource.com/#/c/215844/

악용을 방지하기 위해 두 가지 검사가 추가되었는데, 내용은 아래와 같다.

  • 특정 시간대에 너무 자주 검색하지 않기
  • 너무 오래 검색하지 않기

정리해보자면 7.0 DP4에서 BLE 스캐닝을 이용한 취약점을 방지하기 위해 30초동안 5번 이상 스캐닝을 중지하고 시작하는 것을 방지하는 것이다.

해결방법

스캔 주기를 6초 이상으로 설정하면 된다.

실로 간단한 해결법이나 문제가 있다면 문서화가 안되었다는 점이다.

(지금은 없어진) DP4 변경점에 있었으나 최종 변경점 노트에는 포함되지 않았다.

왜 문서화가 안되었는지 명확한 이유는 모르겠지만 그래도 심각한 문제가 아님에 다행이라 생각했다.

2017. 09. 09 추가

약 2개월 뒤 이 문제를 좀 더 살펴볼 기회가 있어 살펴보았으나, 정확히는 아래의 조건인 것 같다.

synchronized boolean isScanningTooFrequently() {
        if (lastScans.size() < NUM_SCAN_DURATIONS_KEPT) {
            return false;
        }

        return (System.currentTimeMillis() - lastScans.get(0).timestamp) <
            EXCESSIVE_SCANNING_PERIOD_MS;
    }

AppScanStats.java 변경점

지난 스캔한 시간 리스트의 크기가 5개 미만일 경우에는 false를 리턴하고, 5개 이상이라면 처음으로 스캔한 시간과 현재 시간이 30,000ms 이상 차이나면 그런 것 같다.

스캔 주기를 늘린다고 하기 보다는 스캔하고, 바로 뒤 다시 스캔하는 등의 코드만 작성하지 않으면 문제는 없을 것 같다.

Advanced Kotlin – Inline Functions (1) – Basic

다음글: Advanced Kotlin – Inline Function (2) – Local Return


어떤 코드라고 해도 성능 이슈(Performance Impact Issue)는 있길 마련이다. 그러면 Kotlin은 이 성능 이슈를 줄이기 위해 어떠한 기능을 가지고 있을까?

앞으로 소개할 Inline Functions 가 그에 대한 해답이 될 수 있다.

Inline Functions 란?

람다 파라미터(lambda parameter) 를 가지고 있는 Functions에서 작동하는 기능으로 불필요한 코드를 제거하고 실행한 메소드 안으로 복사-붙여넣기 하는 기능이다.

function에 inline 라는 키워드를 앞에 붙임으로써 활성화 할 수 있다. 겉으로는 별 차이가 없어보이지만 실제로 JVM Bytecode로 볼 때 차이가 있다.

아래와 같은 코드를 봐보자.

inline fun operation(op: () -> Unit) {
        println("Before calling op()")
        op()
        println("After calling op()")
}

operation이란 메소드는 op 란 람다 파라미터를 가지고 있고, 전후에 println을 호출한다. 실행은 operation { println("This is the actual op function") } 로 된다.

이 메소드를 컴파일 해서 보면,

protected void onCreate(@Nullable Bundle savedInstanceState) {
        String var3 = "Before calling op()";
        System.out.println(var3);
        String var4 = "This is the actual op function";
        System.out.println(var4);
        var3 = "After calling op()";
        System.out.println(var3);
}

public final void operation(@NotNull Function0 op) {
        Intrinsics.checkParameterIsNotNull(op, "op");
        String var3 = "Before calling op()";
        System.out.println(var3);
        op.invoke();
        var3 = "After calling op()";
        System.out.println(var3);
}

onCreate 에 operation을 실행하게 하고 컴파일한 결과이다. 보이다싶이 operation 에 있는 내용들이 onCreate 안으로 흡수되었다. invoke, checkParameterIsNotNull등 불필요한 코드를 제거하고 말이다.

그렇다면 inline와 inline를 하지 않은 바이트코드는 어떤 차이를 보일까.

왼쪽이 inline를 하지 않은것, 오른쪽이 inline 된 것이다. 결과적으로 JVM에서 실행할 코드가 줄어들었다.

이걸로 보면 별 차이가 없는 것 처럼 보이지만 라이브러리 처럼 실행 속도를 중시해야하는 코드의 경우에는 거대한 차이가 있을 것이다.

만일 메소드에 람다 파라미터가 두개 이상 있고, 어떤 파라미터는 inline를 하고 싶지 않을 경우 noinline 키워드를 붙여줘서 inline 되지 않게 할 수 있다. inline fun operation(op: () -> Unit, noinline opp: () -> Unit)

그렇다면, inline functions의 제약 조건을 살펴보자.

제약 조건

오직 람다 파라미터만 가능

앞에서도 말했다싶이 inline는 람다 파라미터를 가지고 있는 메소드만 가능하다. 만일 람다 파라미터를 가지고 있지 않는 메소드에서 inline를 붙이게 되면 컴파일러는 경고를 호출한다.

이 메소드를 inline 함으로서 발생되리라 추측되는 성능 이슈는 비효율적일 수 있다. inline는 람다 파라미터에서 제대로 효과를 발휘한다.

파라미터에 대한 참조 불가

즉, invoke 이외의 기능을 사용할 수 없다.

결론

inline는 어떻게 보면 자바의 바이트코드로 인한 성능 이슈를 최적화하기 위해 나온 것과 가깝다.

물론, 위에 적혀져 있는 것 빼고 inline는 local returns에도 활용할 수 있다. 이는 다음 글에서 살펴볼 예정이다.

Inline Functions에 대한 문서: https://kotlinlang.org/docs/reference/inline-functions.html

Advanced Kotlin – Lambda implicit name and return

Lambda implicit name

해당 function 의 파라미터가 1개일 때 Lambda implicit name 를 사용할 수 있는데, 선언하는 대신 it 라는 키워드로 파라미터를 대신한다.

op(x: Int, op: (Int) -> Int): Int 라는 메소드를 선언하고, 람다식과 그렇지 않은 코드를 짜보자. 이런 모양일 것이다.

op(3, { it * it })
op(2, fun(x): Int { return x * x })

첫번째 줄은 implicit name 를 사용하였기에 it * it 라는 간단한 문법으로 표시되었고, 두번째 줄은 x를 따로 선언하여 return x * x 라고 명시했다. op란 메소드의 op 파라미터가 Int를 리턴한다고 선언하였기 때문에 it * it 라 명시해도 코틀린 컴파일러에서 자동으로 Int라 인식해 return를 적지 않아도 된다.

이 기능을 사용하면 적어도 보여지는 코드 양은 많이 줄어보인다. 하지만 이 implicit name 에도 치명적인 단점이 있다. 위에서도 보여진 것 처럼 조건에 따른 return (multi return) 을 사용하지 못한다는 것이다.

이는 자바를 짜던 습관이 있는 개발자가 놓치기 쉽다는 것인데, 아래 코드를 보자.

val value = op(3, { it * it })
val txtValue = findViewById(R.id.txtValue) as TextView
        
txtValue.setOnClickListener { 
    if (value >= 10)
        return
            
    Toast.makeText(this, "value lower than 10!", Toast.LENGTH_SHORT).show()
}

value 의 값이 10 이상이면 아래 토스트를 표시하지 않게 하는 코드이다. 이 코드는 자바에서는 매우 자연스러운 코드이나 코틀린에서는 오류를 표시한다. if (value < 10) 일 때 실행하거나, 아래 코드처럼 해야한다.

txtValue.setOnClickListener(View.OnClickListener {
    if (value >= 10)
        return@OnClickListener

    Toast.makeText(this@MainActivity, "value lower than 10!", Toast.LENGTH_SHORT).show()
})

물론 코드 스타일이 온클릭시 메소드 실행하는 형태로 한다면 전혀 문제는 없으나 놓치기 쉬운 부분이기도 하다.

해당 기능의 Document: https://kotlinlang.org/docs/reference/lambdas.html#it-implicit-name-of-a-single-parameter