คลังบทความ

ผมอยากบล็อกสายต่างประเทศทั้งหมดบน iPhone แต่เลิกทำด้วย CallKit

11 กรกฎาคม 2569แปลด้วย AI

ช่วงนี้มีสายโทรเข้าที่อ้างว่าเป็นสำนักงานตรวจคนเข้าเมืองหรือตำรวจจีน บางครั้งเป็นเสียงอัตโนมัติภาษาจีน ดูไม่เหมือนสายหลอกลวงแบบสุ่มที่มุ่งเป้าคนญี่ปุ่นทั่วไป

ตอนเดินทางในจีน ผมเคยเขียนข้อมูลติดต่อที่โรงแรมและใช้หมายเลขโทรศัพท์ในการจอง หมายเลขอาจไปถึงกลุ่มมิจฉาชีพที่ใช้ภาษาจีนผ่านช่องทางใดช่องทางหนึ่ง

ผมเปิดฟังก์ชันบล็อกหมายเลขหลอกลวงและรบกวนของแอปที่สำนักงานตำรวจแห่งชาติญี่ปุ่นแนะนำไว้แล้ว แต่หมายเลขนี้ยังโทรเข้ามาได้ ระบบตรวจเทียบกับหมายเลขอันตรายที่ลงทะเบียนไว้ จึงหยุดหมายเลขใหม่หรือหมายเลขที่ยังไม่ถูกบันทึกทั้งหมดไม่ได้2

ผมแทบไม่มีเหตุให้รับสายจากต่างประเทศ จึงอยากให้สายภายในญี่ปุ่นทำงานตามปกติและปฏิเสธทุกสายที่ไม่ใช่รหัสประเทศญี่ปุ่น +81 แต่หาแอป iPhone ที่บล็อกสายต่างประเทศตามรหัสประเทศไม่ได้

ผมคิดว่าจะทำเอง โดยรับหมายเลขเป็นข้อความแล้วใช้ hasPrefix หรือ regular expression ปฏิเสธทุกอย่างนอก 81 ผมคาดว่าจะใช้เงื่อนไขคล้าย grep ได้ จึงไม่เคยคิดว่าต้องแจกแจงทุกหมายเลข

ตรวจสอบ Call Directory

Call Directory app extension ส่งรายการบล็อกให้ iOS ผ่าน CXCallDirectoryProvider.beginRequest(with:)

CXCallDirectoryPhoneNumber คือ Int64 เป็นชุดตัวเลขเริ่มจากรหัสประเทศโดยไม่มี + หรือช่องว่าง API เพิ่มได้ทีละหมายเลขเต็มเท่านั้น

context.addBlockingEntry(
    withNextSequentialPhoneNumber: phoneNumber
)

ไม่มี API สำหรับ prefix, regular expression, รหัสประเทศ หรือช่วงหมายเลข ต้องเพิ่มหมายเลขเต็มแบบไม่ซ้ำและเรียงจากน้อยไปมาก แล้วเรียก completeRequest() ข้อผิดพลาดได้แก่ entriesOutOfOrder, duplicateEntries และ maximumEntriesExceeded4

extension ไม่ทำงานทุกครั้งที่มีสายเข้า Apple ระบุว่าต้องส่งข้อมูลทั้งหมดล่วงหน้า และไม่สามารถเรียก Web service ระหว่างมีสายเข้าได้3 จึงไม่มีจุดให้รัน if !number.hasPrefix("81")

รูปแบบการทำงานของ Call Directory

  1. ผู้ใช้เปิด extension ในการตั้งค่าบล็อกสายของ iOS
  2. แอปเรียก reloadExtension(withIdentifier:)
  3. iOS เปิด extension และส่ง context ให้ beginRequest(with:)
  4. extension เพิ่มหมายเลขตามลำดับแล้วเรียก completeRequest()
  5. หลังจากนั้น iOS เป็นผู้ตรวจเทียบสาย

เมื่อ context.isIncremental เป็น true จะอัปเดตและลบแบบส่วนต่างได้ แต่ก็ยังใช้ช่วงหรือเงื่อนไขไม่ได้

เมื่อศึกษาถึงจุดนี้ ผมจึงพบว่าการบล็อกสายต่างประเทศทั้งหมดต้องแจกแจงหมายเลขแล้วโหลดเข้า Call Directory หมายเลขสากลอยู่ภายใต้มาตรฐาน ITU E.164 และยาวได้สูงสุด 15 หลักรวมรหัสประเทศ1

นี่เป็นขอบเขตบนอย่างง่าย ไม่ใช่จำนวนหมายเลขที่ถูกจัดสรรจริง พื้นที่ 15 หลักมีประมาณ 1015 แบบ การตัด +81 แทบไม่เปลี่ยนขนาด เก็บแค่ Int64 ก็ราว 8PB และเมื่อมีดัชนีอาจเป็นหลายสิบ PB ผมจึงเลิกคิดครอบคลุมทั้งหมดและจำกัดไปที่ +1 833

จำกัดเหลือ +1 833

+1 เป็นรหัสของ North American Numbering Plan ซึ่งรวมสหรัฐฯ และแคนาดา หลัง 833 เหลืออีก 7 หลัก การครอบคลุมทั้งหมดจึงต้องมี 107 = 10,000,000 รายการ

หมายเลข 64 บิตสิบล้านรายการใช้ 80,000,000 ไบต์ หรือราว 76.3MiB ขนาดประมาณ 80MB ทำได้จริงในแง่พื้นที่เก็บข้อมูล แต่ยังต้องทดสอบเพดานรายการและเวลาโหลดที่ Apple ไม่ได้เปิดเผย

Live Caller ID Lookup

Live Caller ID Lookup ติดต่อเซิร์ฟเวอร์เมื่อมีสายเข้า แต่ extension ไม่ได้รับเหตุการณ์สายเข้า LiveCallerIDLookupProtocol ให้เพียง serviceURL, tokenIssuerURL และ userTierToken5

@main
struct LookupExtension: LiveCallerIDLookupProtocol {
    var context: LiveCallerIDLookupExtensionContext {
        .init(serviceURL: serviceURL,
              tokenIssuerURL: tokenIssuerURL,
              userTierToken: token)
    }
}

iOS เป็นผู้ค้นหา Oblivious HTTP ซ่อน IP, Privacy Pass ยืนยันตัวตนแบบไม่เปิดเผย และ KPIR ค้นหาหมายเลขเต็มในฐานะ keyword ที่เข้ารหัส6

บริการต้องมี HTTP POST endpoint สามรายการที่ใช้ Protocol Buffers7

Endpointหน้าที่ข้อมูล
/configการตั้งค่า PIR และสถานะกุญแจConfigRequest / ConfigResponse
/keyอัปโหลด evaluation keyEvaluationKeys
/queriesประเมิน query ที่เข้ารหัสRequests / Responses

ข้อมูลบล็อกมี 1 ไบต์: 0 อนุญาต และ 1 บล็อก ข้อมูลผู้โทรใช้ CallIdentity Protocol Buffer8

เซิร์ฟเวอร์ไม่ได้รับหมายเลขแบบ plaintext จึงรัน hasPrefix("81") ไม่ได้ ยังต้องมี PIR dataset ที่ใช้หมายเลขเต็มเป็น key รวมถึงการตรวจ endpoint โดย Apple และ KPIR server

เปรียบเทียบ

Call DirectoryLive Caller ID Lookup
ข้อมูลโหลดล่วงหน้าบนอุปกรณ์PIR dataset บนเซิร์ฟเวอร์
ค้นหาiOS ตรวจในเครื่องiOS ส่ง query เข้ารหัส
หน่วยหมายเลขเต็มรายการที่ใช้หมายเลขเต็มเป็น key
prefixไม่มีไม่มี callback อิสระบนเซิร์ฟเวอร์

คำนวณจำนวน

ใส่ prefix และจำนวนหลักทั้งหมดเพื่อคำนวณจำนวนหมายเลขเต็ม

สรุป

+1 833 เพียงช่วงเดียวทำได้: สิบล้านรายการและราว 80MB แต่บล็อกได้เฉพาะ prefix นี้ หากผู้โทรเปลี่ยนหมายเลขหรือรหัสประเทศ ก็ต้องสำรวจ สร้าง แจกจ่าย และโหลดข้อมูลใหม่

Caller ID ไม่ใช่การยืนยันตัวตนที่แข็งแรง เส้นทาง VoIP และการโอนสายอาจแสดงหมายเลขปลอมได้910 การบล็อกตามหมายเลขมีผลจำกัดต่อผู้ที่เปลี่ยนหรือปลอมหมายเลขบ่อย

การแจกแจง +1 833 ทำได้ทางเทคนิค แต่การดูแลใหม่ทุกครั้งที่หมายเลขเปลี่ยนนั้นยุ่งยาก ผมจึงหยุดทำ

เอกสารอ้างอิง