OOB配对原理及应用

news2024/11/27 16:48:24

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

目录

  • 前言
  • 一、OOB是啥?
  • 二、OOB配对实践
  • 总结


前言

  本文先简单介绍OOB配对的流程,然后结合CC2652蓝牙芯片调试OOB配对

一、OOB是啥?

  OOB就是Out of Band的缩写,指安全数据不经过自己通信的信道进行传输。OOB允许两个蓝牙设备通过设备的带外通道发送认证信息,比如:串口、NFC、UWB等。OOB的目的是允许两个设备在没有输入输出能力的情况下创建认证配对,认证需要发送3个认证参数:device addressrandom numberconfirm value。它这种认证配对方式能很好的反MITM攻击。方法就是在主机或者从机中产生一个密钥,然后OOB数据通过蓝牙通信之外的方式发送给其他设备。OOB数据可以选择使用椭圆曲线加密算法生成(也可以不使用)。流程大致是这样的:
  1.创建存放OOB数据的变量,有两个,一个是用于存放设备自己本身的OOB数据,另一个是用于存放对端设备的OOB数据。
  2.定义配对状态的回调函数。
  3.要用ECC密钥产生OOB数据,先调用GAPBondMgr_GenerateEccKeys()产生ECC密钥,然后在ECC密钥产生的事件中再调用GAPBondMgr_SCGetLocalOOBParameters()获取OOB数据。当协议栈的ECC密钥准备好了之后,协议栈会发送事件GAPBOND_GENERATE_ECC_DONE给应用层,该事件在配对状态回调里面处理。上面提到的两个API函数可以在蓝牙连接建立前或者后调用,但是必须是在配对开始前被调用。如果不调用GAPBondMgr_GenerateEccKeys(),那么OOB数据是没使用ECC密钥生成的。
  4.在OOB数据生成之后,设备应该将它发送到对端设备,对端设备的配对特性必须是使能了OOB配对绑定的。如果两个设备都产生和分享OOB数据,那么这两个设备的配对特性都必须使能OOB配对绑定。
  5.对端设备接收到OOB数据(Confirm值和随机数)之后,应使用函数接口GAPBondMgr_SCSetRemoteOOBParameters()记录下接收到的OOB数据到绑定管理器中。
  6.设备发起配对请求,走配对流程,整个过程两设备不存在输入输出显示操作,配对完成之后两设备BLE协议栈会向应用层发出事件GAPBOND_PAIRING_STATE_COMPLETE

二、OOB配对实践

  下面我们先做两个实验:
  (1)一个设备产生OOB数据,另一个设备不产生OOB数据,实现OOB配对。
  (2)两个设备都产生OOB数据,实现OOB配对。
  在(1)中,做法可以是这样:
  在IAR中导入CC26XX的SimpleCentral例程(不要在CCS导入,因为调用ECC密钥产生会报错,修改比较麻烦)。
-1- 先定义两个变量:

#ifdef USING_OOB_PAIR
// This is needed for the device that generates OOB data
gapBondOOBData_t localOobData;
// This is needed to store the OOB data this device receives
gapBondOOBData_t remoteOobData;
#endif

-2- 在GAP_DEVICE_INIT_DONE_EVENT事件中添加ECC密钥产生函数,该函数调用成功之后会产生一个事件:GAPBOND_GENERATE_ECC_DONE

GAPBondMgr_GenerateEccKeys();

-3- 在SimpleCentral_processPairState函数里面添加一个事件:

static void SimpleCentral_processPairState(uint8_t state,
                                           scPairStateData_t* pPairData)
{
  uint8_t status = pPairData->status;
  uint8_t pairMode = 0;
  if (state == GAPBOND_GENERATE_ECC_DONE)
  {
    if (status == SUCCESS)
    {
       //After we get the ECC key, we can get the OOB data which is generated by ECC
       GAPBondMgr_SCGetLocalOOBParameters(&localOobData);
       uint8_t i;
       for (i = 0; i < KEYLEN; i++)
       {
         Display_printf(dispHandle, SC_ROW_CUR_CONN+i+5, 0, "OOB data confirm[%d]: 0x%2x", i, localOobData.confirm[i]);
         Display_printf(dispHandle, SC_ROW_CUR_CONN+KEYLEN+i+5, 0, "OOB data rand[%d]: 0x%2x", i, localOobData.rand[i]);
       }
    }
  }
  else if (state == GAPBOND_PAIRING_STATE_STARTED)
  ...
}

-4- 编译运行Central例程,使用串口助手(我使用的是Xshell)查看Central打印出的OOB数据,包括了Confirm值和随机数。
-5- 至此,Central例程的代码已经改完了,现在改从机代码,从机代码我使用的是ProjectZero,同样,建一个变量:

#ifdef USING_OOB_PAIR
// This is needed for the device that generates OOB data
gapBondOOBData_t localOobData;
// This is needed to store the OOB data this device receives
gapBondOOBData_t remoteOobData;//we'll use
#endif

-6- 直接将Central串口打印出来的Confirm值和随机数赋值到remoteOobData,然后存储到配对管理器中:

#ifdef USING_OOB_PAIR
void oob_ready(void)
{
  uint8_t u8OOB_Enable = true;
  GAPBondMgr_SetParameter(GAPBOND_OOB_ENABLED, sizeof(uint8_t), &u8OOB_Enable);
  remoteOobData.confirm[0]  = 0xe0;
  remoteOobData.confirm[1]  = 0xa6;
  remoteOobData.confirm[2]  = 0xb5;
  remoteOobData.confirm[3]  = 0x76;
  remoteOobData.confirm[4]  = 0x3d;
  remoteOobData.confirm[5]  = 0x30;
  remoteOobData.confirm[6]  = 0xe6;
  remoteOobData.confirm[7]  = 0xa1;
  remoteOobData.confirm[8]  = 0x9f;
  remoteOobData.confirm[9]  = 0xa1;
  remoteOobData.confirm[10] = 0x9f;
  remoteOobData.confirm[11] = 0x89;
  remoteOobData.confirm[12] = 0xb0;
  remoteOobData.confirm[13] = 0xa2;
  remoteOobData.confirm[14] = 0x1f;
  remoteOobData.confirm[15] = 0x94;
  remoteOobData.rand[0]  = 0xec;
  remoteOobData.rand[1]  = 0x04;
  remoteOobData.rand[2]  = 0xf8;
  remoteOobData.rand[3]  = 0xa4;
  remoteOobData.rand[4]  = 0xd8;
  remoteOobData.rand[5]  = 0x69;
  remoteOobData.rand[6]  = 0x39;
  remoteOobData.rand[7]  = 0x94;
  remoteOobData.rand[8]  = 0xce;
  remoteOobData.rand[9]  = 0xf9;
  remoteOobData.rand[10] = 0xf0;
  remoteOobData.rand[11] = 0x1d;
  remoteOobData.rand[12] = 0x1e;
  remoteOobData.rand[13] = 0xfb;
  remoteOobData.rand[14] = 0x45;
  remoteOobData.rand[15] = 0x1c;
  GAPBondMgr_SCSetRemoteOOBParameters(&remoteOobData, 1);
}
#endif

-7- 在ProjectZero的GAP_DEVICE_INIT_DONE_EVENT事件之中添加该函数

static void ProjectZero_processGapMessage(gapEventHdr_t *pMsg)
{
    ...
    case GAP_DEVICE_INIT_DONE_EVENT:
    {
        ...
        oob_ready();
        break;
...

-8- 注意,这个期间,Central不要复位,复位后OOB数据就不一样啦。编译运行ProjectZero,这里建议把ProjectZero的蓝牙地址模式设置成Public,方便Central那边扫描连接。使用Central连接ProjectZero,如果不知道怎么操作的话,可以看下工程里边的Readme。
在这里插入图片描述
-9- 扫描到从机设备之后connect to它,由于Central是配对请求发起端,在建立连接之后马上发起配对,这个时候我们可以在Central的串口助手上看到“Pairing success”和“Bond save success”信息,表示OOB配对成功。

-10- 断开连接,重新扫描连接,你可以看到“Encryption success”信息,表示之后的连接都是加密的,也从侧面表示配对成功了。

  上面介绍了一个设备产生OOB数据,另一个设备不产生OOB数据实现OOB配对,下面就继续介绍双方都产生OOB数据实现配对应该怎么做。这里先用SmartRF flash program擦除下两个芯片,因为我们前面已经实现配对绑定了,需要擦除绑定信息,好让我们做第二个实验测试。
  在(2)中,我们需要考虑两个问题,从机怎么把自己的OOB数据发送给主机呢?主机需要在什么时候才发起配对请求?第一个问题,在这里我使用最简单的方法,就是L2CAP传输OOB数据,虽然也是带内,不过没关系,只要把数据传过去就行了。第二个问题,我们肯定是让OOB数据准备好了之后再走配对流程。
在这里插入图片描述

-1- 先把L2CAP Coc通信打通,我们需要做四件事:
在这里插入图片描述
  双方都把L2CAP Connection Oriented Channel…勾选上
在这里插入图片描述
  对于Central,需要把配对模式改成不允许配对,这样在建立连接之后就不会立即配对了。
在这里插入图片描述
  下面是两个设备注册SPSM的代码段:

#define APP_SPSM   0x0080
void L2CAP_vidConfig_Init( void )
{
  l2capPsm_t     psm;
  l2capPsmInfo_t psmInfo;
  if(L2CAP_PsmInfo(APP_SPSM, &psmInfo) == INVALIDPARAMETER)
  {
    psm.initPeerCredits = 0xFFFF;
    psm.maxNumChannels = MAX_NUM_BLE_CONNS;
    psm.mtu = MAX_PDU_SIZE;
    psm.peerCreditThreshold = 0;
    psm.pfnVerifySecCB = NULL;
    psm.psm = APP_SPSM;
    psm.taskId = ICall_getLocalMsgEntityId(ICALL_SERVICE_CLASS_BLE_MSG, selfEntity);
    
    L2CAP_RegisterPsm(&psm);
  }
  else
  {
    //Failed
  }
}

-1.1- 在Central一方的建立连接事件里添加L2CAP连接请求

// Send out L2CAP_LE_CREDIT_BASED_CONNECTION_REQ
L2CAP_ConnectReq(connHandle, App_SPSM, App_SPSM);

  两个设备都可以发送L2CAP Coc建立连接请求,但是不需要两个设备都发起。
-1.2- 在Central一方添加下面的代码段,"…"是我省略了一些例程本来就有的代码。

#if defined(USING_L2CAP)
// 在原有的connRec_t类型里添加cocCID
// Connected device information
typedef struct
{
  uint16_t connHandle;        // Connection Handle
  uint16_t charHandle;        // Characteristic Handle
  uint8_t  addr[B_ADDR_LEN];  // Peer Device Address
  Clock_Struct *pRssiClock;   // pointer to clock struct
  uint16_t cocCID;            // CID for the L2CAP channel
} connRec_t;
static void L2CAP_vidConfig_Init( void );
static bStatus_t Application_sendL2capData(uint8_t connHandle, uint16_t datalen, uint8_t *pdata);
static void Application_processL2CAPDataEvent(l2capDataEvent_t *pMsg);
static void Application_processL2CAPSignalEvent(l2capSignalEvent_t *pMsg);
#endif

static uint8_t SimpleCentral_processStackMsg(ICall_Hdr *pMsg)
{
  switch (pMsg->event)
  {
    ...
    case L2CAP_SIGNAL_EVENT:
      // place holder for L2CAP Connection Parameter Reply
      Application_processL2CAPSignalEvent((l2capSignalEvent_t *)pMsg);
      break;
    case L2CAP_DATA_EVENT:
      //L2CAP接收数据处理
      Application_processL2CAPDataEvent((l2capDataEvent_t *)pMsg);
      break;
    default:
      break;
  }
  ...
}

#if defined(USING_L2CAP)
#define APP_SPSM   0x0080
void L2CAP_vidConfig_Init( void )//在GAP_DEVICE_INIT_DONE_EVENT事件里面调用
{
  l2capPsm_t     psm;
  l2capPsmInfo_t psmInfo;
  if(L2CAP_PsmInfo(APP_SPSM, &psmInfo) == INVALIDPARAMETER)
  {
    psm.initPeerCredits = 0xFFFF;
    psm.maxNumChannels = MAX_NUM_BLE_CONNS;
    psm.mtu = MAX_PDU_SIZE;
    psm.peerCreditThreshold = 0;
    psm.pfnVerifySecCB = NULL;
    psm.psm = APP_SPSM;
    psm.taskId = ICall_getLocalMsgEntityId(ICALL_SERVICE_CLASS_BLE_MSG, selfEntity);
    
    L2CAP_RegisterPsm(&psm);
  }
  else
  {
    //Failed
  }
}

bStatus_t Application_sendL2capData(uint8_t connHandle, uint16_t datalen, uint8_t *pdata)
{
  l2capPacket_t pkt;
  bStatus_t status = SUCCESS;
  uint8_t connIndex;
  
  if(datalen != 0)
  {
    connIndex = SimpleCentral_getConnIndex(connHandle);
    pkt.CID = connList[connIndex].cocCID;
    pkt.pPayload = pdata;
    pkt.len = datalen;
    status = L2CAP_SendSDU(&pkt);
  }
  else
  {
    /*not need to do*/
    status = FAILURE;
  }

  return (status);
}

/******************************************************************************
* @fn          Application_processL2CAPDataEvent
*
* @brief       This function is used to handle the L2CAP data extraction.
*
* @param       pMsg - pointer to the signal that was received
*
* @return      None.
*/
#define OOB_DATA  0
void Application_processL2CAPDataEvent(l2capDataEvent_t *pMsg)
{
    if (!pMsg)
    {
      // Caller needs to figure out by himself that pMsg is NULL
      return;
    }

    // The data locates under pMsg->pkt.pPayload
    // Extract the data and do what you want to do
    switch(pMsg->pkt.pPayload[0])
    {
    case OOB_DATA:
      memcpy(remoteOobData.confirm, &(pMsg->pkt.pPayload[1]), KEYLEN);
      memcpy(remoteOobData.rand, &(pMsg->pkt.pPayload[16+1]), KEYLEN);
      GAPBondMgr_SCSetRemoteOOBParameters(&remoteOobData, 1);
      uint8_t oobEnabled = TRUE;
      GAPBondMgr_SetParameter(GAPBOND_OOB_ENABLED, sizeof(uint8_t), &oobEnabled);
      uint8_t pairMode = GAPBOND_PAIRING_MODE_INITIATE;
      GAPBondMgr_SetParameter(GAPBOND_PAIRING_MODE, sizeof(uint8_t), &pairMode);
      GAPBondMgr_Pair(pMsg->connHandle);
      break;
    default:
      break;
    }
    // Free the payload (must use BM_free here)
    BM_free(pMsg->pkt.pPayload);
}

/******************************************************************************
* @fn          Application_processL2CAPSignalEvent
*
* @brief       This function is used to handle all the L2CAP signal events.
*
* @param       pMsg - pointer to the signal that was received
*
* @return      None.
*/
static void Application_processL2CAPSignalEvent(l2capSignalEvent_t *pMsg)
{
  uint8_t SendOOBData_BUFF[33] = {(uint8_t)OOB_DATA};//1st byte is 0, and others are OOBDATA
  if (!pMsg)
  {
    return;
  }

  switch (pMsg->opcode)
  {
    case L2CAP_CHANNEL_ESTABLISHED_EVT:
    {
      l2capChannelEstEvt_t *pEstEvt = &(pMsg->cmd.channelEstEvt);

      if (pMsg->connHandle != LINKDB_CONNHANDLE_INVALID && pMsg->connHandle < MAX_NUM_BLE_CONNS)
      {
        // Successfully establish link over L2CAP
        // Extract te CIO and store in the application layer
        // This will be useful when sending data over L2CAP channels
        connList[pMsg->connHandle].cocCID = pEstEvt->CID;
        
        // Give max credits to the other side
        L2CAP_FlowCtrlCredit(pEstEvt->CID, 0xFFFF);
        //make sure you have got the local OOB data from GAPBOND_GENERATE_ECC_DONE event
        memcpy(&SendOOBData_BUFF[1], (uint8_t *)&localOobData.confirm[0], 32);
        Application_sendL2capData((uint8_t)pMsg->connHandle, 33, SendOOBData_BUFF);
      }
      else
      {
        // Could not establish an L2CAP link
      }
    }
    break;

    case L2CAP_SEND_SDU_DONE_EVT:
    {
      if (pMsg->hdr.status == SUCCESS)
      {
       // Successfully sending data over L2CAP
      }
      else
      {
      }
    }
    break;

    case L2CAP_CHANNEL_TERMINATED_EVT:
    {
    }
    break;
  }
}
#endif

-2- 在ProjectZero一方,同样添加L2CAP和OOB的相关代码:

void L2CAP_vidConfig_Init( void )//在GAP_DEVICE_INIT_DONE_EVENT事件里面调用
{
  l2capPsm_t     psm;
  l2capPsmInfo_t psmInfo;
  if(L2CAP_PsmInfo(APP_SPSM, &psmInfo) == INVALIDPARAMETER)
  {
    psm.initPeerCredits = 0xFFFF;
    psm.maxNumChannels = MAX_NUM_BLE_CONNS;
    psm.mtu = MAX_PDU_SIZE;
    psm.peerCreditThreshold = 0;
    psm.pfnVerifySecCB = NULL;
    psm.psm = APP_SPSM;
    psm.taskId = ICall_getLocalMsgEntityId(ICALL_SERVICE_CLASS_BLE_MSG, selfEntity);
    
    L2CAP_RegisterPsm(&psm);
  }
  else
  {
    //Failed
  }
}

/******************************************************************************
* @fn          Application_processL2CAPSignalEvent
*
* @brief       This function is used to handle all the L2CAP signal events.
*
* @param       pMsg - pointer to the signal that was received
*
* @return      None.
*/
void Application_processL2CAPSignalEvent(l2capSignalEvent_t *pMsg)
{
  // Sanity check
  if (!pMsg)
  {
    return;
  }

  switch (pMsg->opcode)
  {
    case L2CAP_CHANNEL_ESTABLISHED_EVT:
    {
      l2capChannelEstEvt_t *pEstEvt = &(pMsg->cmd.channelEstEvt);

      if (pMsg->connHandle != LINKDB_CONNHANDLE_INVALID && pMsg->connHandle < MAX_NUM_BLE_CONNS)
      {
        // Successfully establish link over L2CAP
        // Extract te CIO and store in the application layer
        // This will be useful when sending data over L2CAP channels
        connList[pMsg->connHandle].cocCID = pEstEvt->CID;
        
        // Give max credits to the other side
        L2CAP_FlowCtrlCredit(pEstEvt->CID, 0xFFFF);
      }
      else
      {
        // Could not establish an L2CAP link
      }
    }
    break;

    case L2CAP_SEND_SDU_DONE_EVT:
    {
      if (pMsg->hdr.status == SUCCESS)
      {
       // Successfully sending data over L2CAP
      }
      else
      {
      }
    }
    break;

    case L2CAP_CHANNEL_TERMINATED_EVT:
    {
    }
    break;
  }
}

bStatus_t Application_sendL2capData(uint8_t connHandle, uint16_t datalen, uint8_t *pdata)
{
  l2capPacket_t pkt;
  bStatus_t status = SUCCESS;
  uint8_t connIndex;
  
  if(datalen != 0)
  {
    connIndex = ProjectZero_getConnIndex(connHandle);
    pkt.CID = connList[connIndex].cocCID;
    pkt.pPayload = pdata;
    pkt.len = datalen;
    status = L2CAP_SendSDU(&pkt);
  }
  else
  {
    /*not need to do*/
    status = FAILURE;
  }

  return (status);
}

/******************************************************************************
* @fn          Application_processL2CAPDataEvent
*
* @brief       This function is used to handle the L2CAP data extraction.
*
* @param       pMsg - pointer to the signal that was received
*
* @return      None.
*/
#define OOB_DATA  0
void Application_processL2CAPDataEvent(l2capDataEvent_t *pMsg)
{
    uint8_t SendOOBData_BUFF[33] = {0};//1st byte is 0, and others are OOBDATA
    if (!pMsg)
    {
      // Caller needs to figure out by himself that pMsg is NULL
      return;
    }

    // The data locates under pMsg->pkt.pPayload
    // Extract the data and do what you want to do
    switch(pMsg->pkt.pPayload[0])
    {
    case OOB_DATA:
      uint8_t u8OOB_Enable            =    true;
      GAPBondMgr_SetParameter(GAPBOND_OOB_ENABLED, sizeof(uint8_t), &u8OOB_Enable);
      memcpy(remoteOobData.confirm, &(pMsg->pkt.pPayload[1]), KEYLEN);
      memcpy(remoteOobData.rand, &(pMsg->pkt.pPayload[16+1]), KEYLEN);
      GAPBondMgr_SCSetRemoteOOBParameters(&remoteOobData, 1);
      GAPBondMgr_SCGetLocalOOBParameters(&localOobData);
      memcpy(&SendOOBData_BUFF[1], (uint8_t *)&localOobData.confirm[0], 32);
      Application_sendL2capData(pMsg->connHandle, 33, SendOOBData_BUFF);
      break;
    default:
      break;
    }
    // Free the payload (must use BM_free here)
    BM_free(pMsg->pkt.pPayload);
}

static void ProjectZero_taskFxn(UArg a0, UArg a1)
{
	...
	 case HCI_GAP_EVENT_EVENT:
          ProjectZero_processHCIMsg(pMsg);
          break;
     case L2CAP_SIGNAL_EVENT:
          Application_processL2CAPSignalEvent((l2capSignalEvent_t *)pMsg);
          break;
     case L2CAP_DATA_EVENT:
          Application_processL2CAPDataEvent((l2capDataEvent_t *)pMsg);
          break;
     default:
          // do nothing
          break;
	...
}

-3- 两个工程编译没报错之后分别下载到两个板子上,开始测试OOB配对,主机连上从机之后,待L2CAP建立连接之后,主机将自己的OOB数据通过L2CAP发送到从机,从机接收到主机OOB数据之后将自己的OOB数据通过L2CAP发给主机,主机接收到从机OOB数据之后发起配对。结果如下:

在这里插入图片描述

总结

  OOB配对主要是获取双方的3个参数,BDAddr,Confirm值,rand值,它比其它的配对(数值比较,Passkey Entry)简单,无需IO能力。
  在调试OOB的时候注意配对请求发起端的配对发起条件,需要明白在什么时候才发起配对。
  本文只是记录我调试OOB配对绑定的过程,如果有错误的地方,希望大伙们可以提出来,Thx~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/551203.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

博客系统后端设计(六) -实现登录页面要求强制登录功能

文章目录 实现页面要求强制登录实现思路1.约定前后端交互接口2.实现后端代码3.修改前端代码 实现页面要求强制登录 当用户访问列表页/详情页/编辑页的时候&#xff0c;要求用户已经是登录的状态了&#xff0c; 如果用户还没登录&#xff0c;就会强制跳转到登录页面。 实现思路…

一文盘点PoseiSwap近期的生态利好

PoseiSwap 是 Nautilus Chain 上首个 DEX&#xff0c;其继承了 Nautilus Chain 的模块化、Layer3 以及 Zk-rollup 所带来的优势&#xff08;TPS 在 2000&#xff09;&#xff0c;包括吞吐量、安全度、隐私性等。基于 Nautilus Chain&#xff0c;PoseiSwap 也将具备基于 Zk 的隐…

Ceph crush运行图

Crush map介绍 ceph集群中由monitor负责维护的运行图包括&#xff1a; Monitor map&#xff1a;监视器运行图osd map&#xff1a;osd运行图PG map&#xff1a;PG运行图Crush map&#xff1a;crush运行图Mds map&#xff1a;mds运行图 crush map是ceph集群物理拓扑的抽象&…

HTML、PHP实战:搭建一个网页登录页面。

一、实验环境。 MySQL5.7.26 FTP0.9.60 Apache2.4.39 我这里用的是PHPstudy小皮一键搭建的。 数据库 二、登录页面。 登录页面前端代码 文件名&#xff1a;denglu.html <html> <head> <meta charset"UTF-8"> <title>登录界面</ti…

StarRocks 极速全场景 MPP 数据库介绍及使用

一、简介 StarRocks 是一款高性能分析型数据仓库&#xff0c;使用向量化、MPP 架构、CBO、智能物化视图、可实时更新的列式存储引擎等技术实现多维、实时、高并发的数据分析。既支持从各类实时和离线的数据源高效导入数据&#xff0c;也支持直接分析数据湖上各种格式的数据。兼…

Linux基本指令3

目录 一.基本常用指令 指令1&#xff1a;find命令&#xff1a; 指令2&#xff1a;which命令&#xff1a; 指令3&#xff1a;alias命令&#xff1a; 指令4&#xff1a;whereis which&#xff0c;find&#xff0c;whereis这三个搜索命令的区别&#xff1a; 指令5&#xff…

Vue初始

一、Vue的概述 Vue 发音类似 view Vue 游雨溪 鱿鱼须 鱿鱼须不懂Vue Vue历史 Angular React Vue 2013 Seed 2013 Seed命名为Vue 2014 Vue正式发布 0.8 - 0.10 2015 6.13 0.12 2015下半年 vue-cli vueRouter vueX 版本好1.0 vue正式跨入大众 渐进式框架(JQuery) 2016国…

[CTF/网络安全]攻防世界unserialize3解题详析及php序列化反序列化实例讲解

[CTF/网络安全]攻防世界unserialize3解题详析及php序列化反序列化实例讲解 _wakeup()及php序列化反序列化序列化字符串结构分析_wakeup()的利用 解题思路伪属性数量绕过 解题姿势总结 _wakeup()及php序列化反序列化 序列化是指将数据结构或对象转换为可传输或可存储的格式的过…

14.Kafka系列之K8S部署集群

1. 部署方式选择 基于Kafka3.X后的集群搭建方式主要分为两种&#xff0c;一种是基于Zookeeper管理方式&#xff0c;一种是基于KRaft模式&#xff0c;本文主要介绍Kafka-KRaft集群模式搭建 纠正文章1.Kafka系列之K8S部署单节点中基于Zookeeper方式的部署方式错误&#xff0c;其…

Godot引擎 4.0 文档 - 入门介绍 - Godot设计理念

本文为Google Translate英译中结果&#xff0c;DrGraph在此基础上加了一些校正。英文原版页面&#xff1a; Godots design philosophy — Godot Engine (stable) documentation in English Godot设计理念 既然你已经了解了&#xff0c;让我们来谈谈 Godot 的设计。 每个游戏…

(转载)MATLAB智能算法30个案例分析(1)——遗传算法工具箱

以下内容大部分来源于《MATLAB智能算法30个案例分析》&#xff0c;仅为学习交流所用。 1理论基础 1.1遗传算法概述 遗传算法(genetic algorithm,GA)是一种进化算法,其基本原理是仿效生物界中的“物竞天择、适者生存”的演化法则。遗传算法是把问题参数编码为染色体,再利用迭代…

第11届蓝桥杯Scratch选拔赛真题集锦

目录 一、编程题 第11届蓝桥杯Scratch选拔赛真题集锦 一、编程题 第 1 题 问答题 马克思的手稿 题目说明 背景信息: 马克思手稿中有一道趣味数学问题: 有30个人&#xff0c;其中有男人、女人和小孩。在一家饭馆吃饭共花了50先令;每个男人花了3先令&#xff0c;每个女人花了…

draw.io如何绘制带箭头的弧线

好长时间没有写draw.io相关的技巧了。今天再补充一个小技巧。 如何绘制像下图中蓝色的带箭头的弧线&#xff1f; 本来以为这个问题应该很简单&#xff0c;但是在仔细研究了很久之后我发现这个问题并没有想像得那么容易。 众所周知&#xff0c;draw.io中带箭头的线叫作“connect…

软件工程 | 期末复习习题

一、软件工程概述 1、选择 软件有无可行性和不可控性 软件工程是一门工程性学科 软件生存周期常见模型&#xff1a;螺旋模型、增量模型、瀑布模型、原型模型、融合模型、快速应用开发模型、敏捷模型 软件生存周期中时间最长的阶段是维护阶段 瀑布模型是一种软件生存周期模…

微搭低代码实现aad的sso

微搭低代码平台是一种可帮助您快速构建和部署应用程序的工具&#xff0c;而无需手动编写大量代码。要在微搭低代码平台上实现Azure Active Directory&#xff08;AAD&#xff09;的单点登录&#xff08;SSO&#xff09;&#xff0c;您需要遵循以下步骤&#xff1a; 注册Azure …

SpringBoot整合Mybatis(3000字)

SpringBoot整合Mybatis 文章目录 SpringBoot整合Mybatis依赖导入配置信息(application.yml)代码分层数据库(建库建表语句)各层代码enity:dao:service:controller: 测试 Mybatis分页查询和模糊查询分页查询:测试: 模糊查询:测试: Mybatis的分布查询多对一:测试:一对多: Mybatis的…

邮箱营销不再难:如何提高邮件的到达率和打开率?

在数字时代&#xff0c;电子邮件是企业与客户以及潜在客户沟通的重要渠道&#xff0c;是企业培养客户的有效方式之一。然而&#xff0c;由于每个人每天也要收到大量的垃圾邮件&#xff0c;所以企业必须确保在正确的时间将邮件传递给正确的人。在这篇文章中&#xff0c;小编将探…

BEVDet 论文学习

1. 解决了什么问题&#xff1f; 自动驾驶系统感知周围的环境再进行决策&#xff0c;极具挑战。基于视觉的自动驾驶系统对准确性和效率的要求很严格&#xff0c;人们会采用不同的范式来解决 3D 检测和分割任务。对于多相机 3D 目标检测&#xff0c;image-view-based 方法如 FCO…

缓存更新策略

缓存更新策略 内存淘汰超时剔除主动更新说明利用Redis的内存淘汰机制&#xff0c;不用自己维护&#xff0c;当内存不足时会自动淘汰部分数据。下次查询时更新缓存。给缓存数据添加TTL(过期时间)&#xff0c;到期后自动删除缓存。下次查询时更新缓存。编写业务逻辑&#xff0c;…

CSAPP Lab4- PerfLab

代码优化 typedef struct { unsigned short red; /* R value */ unsigned short green; /* G value */ unsigned short blue; /* B value */ } pixel图像用一维数组表示&#xff0c;第&#xff08;i,j&#xff09;个像素表示为I[RIDX(i,j,n)]&#xff0c;n为图像的维数 #def…