DEV Community

Minwook Je
Minwook Je

Posted on

[C++] Get Unique serial ID

Python get device id is easy.

import uuid

def get_device_id():
    return f"BH_{uuid.getnode():012X}"
Enter fullscreen mode Exit fullscreen mode

In C++

DeviceProvider.cpp

#include "DeviceIdProvider.h"
#include <iomanip> // std::setw, std::setfill
#include <sstream> // for std::stringstream
#include <vector>
#include <cstring>
#include <random>

// OS-specific headers
#include <sys/types.h>
#include <sys/socket.h> // 네트워크 인터페이스 접근용
#include <ifaddrs.h>    // getifaddrs(), 네트워크 인터페이스 리스트 조회
#include <net/if.h>     // IFF_LOOPBACK 플래그 정의

#if defined(__linux__)
#include <netpacket/packet.h> // sockaddr_ll 구조체 (ll = link layer)
#elif defined(__APPLE__)
#include <net/if_dl.h> // sockaddr_dl 구조체 (dl = data link, L2)
#endif

// 6-byte MAC address를 정수형 uint64_t 하나로 변환
// 이더넷, WiFi NIC의 MAC 주소는 6바이트(48비트)
uint64_t DeviceIdProvider::mac_to_uint64(const unsigned char *mac_ptr)
{
    uint64_t mac_val = 0;
    for (int i = 0; i < 6; i++)
    {
        // 1바이트(8비트) 왼쪽으로 밀고, 새 바이트를 Append
        mac_val = (mac_val << 8) | mac_ptr[i];
    }
    return mac_val;
}

// 시스템 MAC address 검색 (Python의 uuid.getnode 로직 참조)
uint64_t DeviceIdProvider::get_mac_address_uint64()
{
    // 네트워크 인터페이스if (eth0, wlan0, enp45s0 등) 리스트 조회용
    struct ifaddrs *ifaddr = nullptr, *ifa = nullptr;
    uint64_t candidate_uaa = 0; // 1순위: Universally Administered Address (제조사가 부여한 고유 MAC)
    uint64_t candidate_laa = 0; // 2순위: Locally Administered Address (사용자/OS가 임의로 설정한 로컬 MAC)

    // 시스템의 모든 네트워크 인터페이스 정보를 ll(링크드리스트) 또는 dl(더블링크드리스트)로 가져옴
    if (getifaddrs(&ifaddr) == -1)
    {
        return 0;
    }

    // 링크드 리스트 검색
    for (ifa = ifaddr; ifa != nullptr; ifa = ifa->ifa_next)
    {
        if (ifa->ifa_addr == nullptr)
            continue;

        // Loopback(127.0.0.1, localhost)은 제외
        if (ifa->ifa_flags & IFF_LOOPBACK)
            continue;

        const unsigned char *mac_ptr = nullptr;

        // Memory Overlay로 MAC 주소 추출
#if defined(__linux__)
        // AF_PACKET: Linux계열의 L2 링크레이어
        if (ifa->ifa_addr->sa_family == AF_PACKET)
        {
            struct sockaddr_ll *s = (struct sockaddr_ll *)ifa->ifa_addr;
            // 6바이트 MAC 주소인지 확인
            // halen(hardware address length)
            if (s->sll_halen == 6)
            {
                // sll_addr 배열로 MAC 주소 위치 얻기
                mac_ptr = (const unsigned char *)s->sll_addr;
            }
        }
#elif defined(__APPLE__)
        // AF_LINK: BSD계열의 L2 링크레이어
        if (ifa->ifa_addr->sa_family == AF_LINK)
        {
            struct sockaddr_dl *sdl = (struct sockaddr_dl *)ifa->ifa_addr;
            // 6바이트 MAC 주소인지 확인
            // alen(address length)
            if (sdl->sdl_alen == 6)
            {
                // LLADDR 매크로로 MAC 주소 위치 얻기
                mac_ptr = (const unsigned char *)LLADDR(sdl);
            }
        }
#endif

        // Validate MAC address
        if (mac_ptr)
        {
            // 1. Check for 00:00:00:00:00:00 (Invalid)
            bool is_zero = true;
            for (int i = 0; i < 6; i++)
            {
                if (mac_ptr[i] != 0)
                {
                    is_zero = false;
                    break;
                }
            }
            if (is_zero)
                continue;

            // 2. Check Multicast bit (Odd first byte means multicast, not physical MAC)
            // Example: 01:00:5e...
            // 유니캐스트가 아닌 멀티캐스트의 MAC은 한 장치의 고유 MAC이 아님
            if (mac_ptr[0] & 0x01)
                continue;

            uint64_t current_mac = mac_to_uint64(mac_ptr);

            // 3. Distinguish UAA(Universal) vs LAA(Local)
            // 첫 바이트의 두 번째 최하위 비트가 0이면 UAA, 1이면 LAA
            bool is_local = (mac_ptr[0] & 0x02);

            if (!is_local)
            {
                // Highest priority인 UAA를 발견 했으니 break;
                if (candidate_uaa == 0)
                {
                    candidate_uaa = current_mac;
                }
                break;
            }
            else
            {
                // LAA found, UAA를 찾을 때까지 loop 지속
                if (candidate_laa == 0)
                {
                    candidate_laa = current_mac;
                }
            }
        }
    }

    // getifaddrs로 할당받은 메모리 해제
    freeifaddrs(ifaddr);

    // Priority: UAA > LAA > 0
    if (candidate_uaa != 0)
        return candidate_uaa;
    if (candidate_laa != 0)
        return candidate_laa;

    return 0; // MAC not found
}

// RFC 4122(UUID 프로토콜) compliant random node ID generation
uint64_t DeviceIdProvider::generate_random_node_id()
{
    std::random_device rd;
    std::mt19937_64 gen(rd());
    std::uniform_int_distribution<uint64_t> dis(0, 0xFFFFFFFFFFFF); // 6바이트(48비트) 범위

    uint64_t random_node = dis(gen);

    // RFC 4122
    // 41번째(0...47)비트인 멀티캐스트 비트를 1로 설정하여 Locally Administered Address임을 표시
    // UUID의 경우, MAC 주소가 아님을 멀티캐스트 비트에 명시해야함.
    random_node |= (uint64_t(1) << 40);
    return random_node;
}

std::string DeviceIdProvider::getDeviceId()
{
    // 1. MAC 주소 시도
    uint64_t node_id = get_mac_address_uint64();

    // 2. 실패시 UUID 생성
    if (node_id == 0)
    {
        node_id = generate_random_node_id();
    }

    // 3. 포맷팅 (BH_XXXXXXXXXXXX)
    // 16진수, UPPERCASE, 12자리, 빈공간 0으로 채움
    std::stringstream ss;
    ss << "BH_" << std::hex << std::uppercase << std::setw(12) << std::setfill('0') << node_id;
    return ss.str();
}
Enter fullscreen mode Exit fullscreen mode

DeviceIdProvider.h

#pragma once

#include <string>
#include <cstdint>

class DeviceIdProvider
{
public:
    static std::string getDeviceId();

private:
    static uint64_t get_mac_address_uint64();
    static uint64_t mac_to_uint64(const unsigned char *mac_ptr);
    static uint64_t generate_random_node_id();
};

Enter fullscreen mode Exit fullscreen mode

CMakeLists.txt

# bh_device_id라는 라이브러리 생성
add_library(bh_device_id
    DeviceIdProvider.cpp
    DeviceIdProvider.h
)

# CMAKE_CURRENT_SOURCE_DIR: 현재 폴더
target_include_directories(bh_device_id PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})

add_executable(get_device_id main_tool.cc) # python에서 사용할 수 있게 main 생성
target_link_libraries(get_device_id PRIVATE bh_device_id) # bh_device_id 라이브러리를 링크

# Python 스크립트에서 실행할 수 있게, 컴파일 위치 세팅
# main.spec안에 binaries=[('../../bin/get_device_id', '.')] 로 from.. to를 지정
set_target_properties(
    get_device_id 
    PROPERTIES # 속성 설정 명시
    RUNTIME_OUTPUT_DIRECTORY # 속성 이름 및 경로를 나타내는 키
    "${PROJECT_SOURCE_DIR}/bin" # 실제 값, 프로젝트 최상위 폴더 밑의 bin 폴더
)
Enter fullscreen mode Exit fullscreen mode

main_tool.cc

#include "DeviceIdProvider.h"
#include <iostream>

int main() {
    std::cout << DeviceIdProvider::getDeviceId() << std::endl;
    return 0;
}
Enter fullscreen mode Exit fullscreen mode

파이썬에서는


def get_base_dir():
    # When frozen (e.g., PyInstaller), resolve paths relative to the executable.
    if getattr(sys, "frozen", False):
        if hasattr(sys, "_MEIPASS"):
            return sys._MEIPASS
        return os.path.dirname(sys.executable)
    return os.path.dirname(os.path.abspath(__file__))


def get_robot_id():
    # Only runs in PyInstaller bundle where get_device_id is at root
    # get_base_dir는 frozen(실행파일) 상태면, sys._MEIPASS를 반환
    # 만약 스크립트로 그냥 실행하는 거라면, 현재 위치를 반환
    bin_path = os.path.join(get_base_dir(), "get_device_id")

    # C++ 코드 실행
    if os.path.exists(bin_path):
        try:
            output = subprocess.check_output([bin_path], text=True).strip()
            if output:
                return output
        except Exception as e:
            print(f"[WARNING] C++ Tool execution failed: {e}")

    raise Exception(
        "C++로 부터 로봇 ID를 가져오지 못했습니다."
    )
Enter fullscreen mode Exit fullscreen mode

Top comments (0)