openssl3.2 - exp - BIO_push()超过2个节点的应用- 以(aes-128-cbc + base64)为例

文章目录

    • openssl3.2 - exp - BIO_push()超过2个节点的应用- 以(aes-128-cbc + base64)为例
    • 概述
    • 笔记
    • END

openssl3.2 - exp - BIO_push()超过2个节点的应用- 以(aes-128-cbc + base64)为例

概述

BIO_push()形成了一个链.
理论上, 多个BIO连在一起, 只需要向BIO链头写数据, 然后BIO_flush(链头), 就可以从BIO链尾读出已经处理过的数据.
这样, 就不用逐个处理每个逻辑节点的数据输入输出, 简化了逻辑处理.

在openssl源码中找例子, 没找到. openssl中, 能看到的都是BIO_push(bio_head, bio_tail), 然后就向bio_head中写, BIO_flush(bio_head), 从bio_tail中读, 结束.

就想试试, 到底可不可以将大于2个的BIO用BIO_push()连在一起, 方便逻辑处理.
现在还没在openssl源码中找到例子, 那就自己试试.

前面做了实验, base64, aes128-cbc.
正好要将aes之后, base64的结果放到配置文件中的配置项的值中, 拿这个场景来试试.
整了2天, 将这个实验做完了.

结论:
BIO_push中的BIO数据输入输出方向可能随着操作(write, read), 语意发生变化.
并不能完全用BIO_push()来一并处理多个BIO节点.

对于我的这个实验, 加密时是可以用整个的BIO链, 如下

		BIO_push(bio_cipher, bio_to_base64);BIO_push(bio_to_base64, bio_container);// 形成了一个BIO链 head => bio_cipher => bio_to_base64 => bio_container <= tail

加密时, 将BIO链push好之后, 从链头bio_cipher写, 然后 BIO_flush(bio_cipher).
再从链尾bio_container中读, 就可以得到先aes, 再base64的结果. 确实方便.

但是, 对于(aes + base64)的结果进行解密, 就不行了.
必须先base64, 再将结果进行aes解密.
原因:

使用base64编码时, 写的方向为 data_in =>bio_to_base64 => bio_container
使用base64解码时, 写的方向为 data_in =>bio_container => bio_to_base64
这就使BIO链处理的语意发生了变化, 编码/解密的操作不一致.

其实, 用openssl的最直白的方法, 就是将每个操作封装成函数, 要用哪个功能, 就调用对应封装好的函数.
用BIO_push()来连接多个BIO, 有点得不尝试(主要是openssl没有做到用BIO读写操作在语意上的一致性, 不是所有的BIO在语义上方向都一致). 形成BIO链后, 结果是否正确, 没办法保证. 调试和验证的时间远大于使用单独封装的函数.

本来想着用BIO_push()能简化逻辑操作, 实际上写出的实现也是一大坨.
在实际应用中, 如果是操作一大堆逻辑节点的操作, 可以试试, 是否可以用BIO链的方式实现读写, 如果能的话, 确实能简化逻辑操作.
如果要操作的逻辑节点就2~3个, 还是自己调用自己封装的单独函数, 分多步来干活, 这样逻辑清晰一些, 也不用做多余验证, 就可以保证结果是对的.

经过这次实验, 可以知道读写语义不一样的BIO对象包含base64.
如果逻辑节点中有base64, 那就确定不能用同一个BIO链来完成逻辑.

笔记

/*!
* \file prj-aes-128-cbc-base64.cpp
* openssl3.2 - exp - BIO_push()超过2个节点的应用- 以(aes-128-cbc + base64)为例
* 用BIO链, 在一个函数中完成加密和baser64的组合动作
* 加密时, 先 aes-128-cbc enc 再 base64
* 解密时, 先 unbase64 再 aes-128-cbc dec
*/#include "my_openSSL_lib.h"
#include <openssl/crypto.h>
#include <openssl/bio.h>#include <stdlib.h>
#include <stdio.h>
#include <assert.h>#include "CMemHookRec.h"#include <openssl/evp.h>
#include <openssl/bioerr.h>
#include <openssl/err.h>void my_openssl_app();
bool aes_128_cbc_base64(bool isEnc,IN const UCHAR* pszBufIn, IN int lenBufIn,IN const UCHAR* key, IN int lenKey,IN const UCHAR* iv, IN int lenIv,OUT UCHAR*& pOutBuf, OUT int& lenOutBuf);bool aes_128_cbc_base64_enc(IN const UCHAR* pszBufIn, IN int lenBufIn,IN const UCHAR* key, IN int lenKey,IN const UCHAR* iv, IN int lenIv,OUT UCHAR*& pOutBuf, OUT int& lenOutBuf);bool aes_128_cbc_base64_dec(IN const UCHAR* pszBufIn, IN int lenBufIn,IN const UCHAR* key, IN int lenKey,IN const UCHAR* iv, IN int lenIv,OUT UCHAR*& pOutBuf, OUT int& lenOutBuf);int main(int argc, char** argv)
{setvbuf(stdout, NULL, _IONBF, 0); // 清掉stdout缓存, 防止调用printf时阻塞mem_hook();my_openssl_app();mem_unhook();/*! run resultbefore enc:0000 - 00 01 02 03 04 05 06 07-08 09 0a 0b 0c 0d 0e 0f   ................0010 - 10 11 12 13 14 15 16 17-18 19 1a 1b 1c 1d 1e 1f   ................0020 - 20 21 22 23 24 25 26 27-28 29 2a 2b 2c             !"#$%&'()*+,after enc:0000 - 78 71 45 37 4e 34 65 50-57 34 4a 76 54 34 46 69   xqE7N4ePW4JvT4Fi0010 - 6f 63 6a 59 65 54 58 5a-33 4e 75 43 6e 2b 77 7a   ocjYeTXZ3NuCn+wz0020 - 55 75 65 2f 45 4c 68 4c-35 4b 55 7a 5a 31 4c 71   Uue/ELhL5KUzZ1Lq0030 - 36 6d 65 4f 54 51 36 33-5a 4d 78 6b 37 6f 32 2f   6meOTQ63ZMxk7o2/0040 - 0a                                                .after dec:0000 - 00 01 02 03 04 05 06 07-08 09 0a 0b 0c 0d 0e 0f   ................0010 - 10 11 12 13 14 15 16 17-18 19 1a 1b 1c 1d 1e 1f   ................0020 - 20 21 22 23 24 25 26 27-28 29 2a 2b 2c             !"#$%&'()*+,enc / dec all okfree map, g_mem_hook_map.size() = 0D:\my_dev\my_local_git_prj\study\openSSL\exp\exp029_enc\aes-128-cbc\prj-aes-128-cbc-base64\x64\Debug\prj-aes-128-cbc-base64.exe (进程 129580)已退出,代码为 0。要在调试停止时自动关闭控制台,请启用“工具”->“选项”->“调试”->“调试停止时自动关闭控制台”。按任意键关闭此窗口. . .*/return 0;
}void my_openssl_app()
{UCHAR ucBuf[0x30 - 3];int lenBuf = sizeof(ucBuf);int i = 0;UCHAR* pEncBuf = NULL;int lenEncBuf = 0;UCHAR* pDecBuf = NULL;int lenDecBuf = 0;// 可以在EVP_CipherInit_ex()之后, 用EVP_CIPHER_CTX_get_key_length()/EVP_CIPHER_CTX_get_iv_length()看长度UCHAR key[0x10]; // aes-128-cbc's key len = 0x10UCHAR iv[0x10]; // aes-128-cbc's iv len = 0x10for (i = 0; i < 0x10; i++){key[i] = (UCHAR)i;iv[i] = (UCHAR)i;}for (i = 0; i < lenBuf; i++){ucBuf[i] = (UCHAR)i;}do {printf("before enc:\n");BIO_dump_fp(stdout, ucBuf, lenBuf);// enc// // 如果输入不是0x10对齐, 加密后, 就会自动0x10对齐(多出几个字节)// 所以要自己记录加密前的长度, 且加密时, 要将输出(加密后)buffer 16对齐(或直接比输入的长度多16字节)// 且加密后, 要自己记录加密后的长度if (!aes_128_cbc_base64(true, ucBuf, lenBuf, (UCHAR*)key, sizeof(key), (UCHAR*)iv, sizeof(iv), pEncBuf, lenEncBuf)){assert(false);break;}printf("after enc:\n");BIO_dump_fp(stdout, pEncBuf, lenEncBuf);// 加密后base64是对的...// decif (!aes_128_cbc_base64(false, pEncBuf, lenEncBuf, (UCHAR*)key, sizeof(key), (UCHAR*)iv, sizeof(iv), pDecBuf, lenDecBuf)){assert(false);break;}// 解密后的数据长度和解密前一样了printf("after dec:\n");BIO_dump_fp(stdout, pDecBuf, lenDecBuf);// 比较明文和解密后的明文是否相同if ((lenDecBuf != lenBuf) || (0 != memcmp(ucBuf, pDecBuf, lenBuf))){assert(false);break;}printf("enc / dec all ok\n");} while (false);if (NULL != pEncBuf){OPENSSL_free(pEncBuf);pEncBuf = NULL;}if (NULL != pDecBuf){OPENSSL_free(pDecBuf);pDecBuf = NULL;}
}bool aes_128_cbc_base64(bool isEnc,IN const UCHAR* pszBufIn, IN int lenBufIn,IN const UCHAR* key, IN int lenKey,IN const UCHAR* iv, IN int lenIv,OUT UCHAR*& pOutBuf, OUT int& lenOutBuf)
{if (isEnc){return aes_128_cbc_base64_enc(pszBufIn, lenBufIn, key, lenKey, iv, lenIv, pOutBuf, lenOutBuf);}else {return aes_128_cbc_base64_dec(pszBufIn, lenBufIn, key, lenKey, iv, lenIv, pOutBuf, lenOutBuf);}
}bool aes_128_cbc_base64_enc(IN const UCHAR* pszBufIn, IN int lenBufIn,IN const UCHAR* key, IN int lenKey,IN const UCHAR* iv, IN int lenIv,OUT UCHAR*& pOutBuf, OUT int& lenOutBuf)
{bool b_rc = false;int i_rc = 0;int len = 0;size_t sz_rc = 0;const EVP_CIPHER* _evp_cipher = NULL;EVP_CIPHER_CTX* _evp_cipher_ctx = NULL;BIO* bio_container = NULL;BIO* bio_cipher = NULL;BIO* bio_to_base64 = NULL;BIO* bio_header = NULL;BIO* bio_tail = NULL;BIO* bio_write_to = NULL;BIO* bio_read_from = NULL;// data(len = 0x2d) = > aes - 128 - cbc = > data_enc(len = 0x30)// data(len = 0x30) = > base64 = > data_base64(len = 0x41)do {lenOutBuf = 0;if ((NULL == pszBufIn) || (lenBufIn <= 0) ||(NULL == key) || (lenKey <= 0) ||(NULL == iv) || (lenIv <= 0)){break;}_evp_cipher = EVP_aes_128_cbc(); // 这是最初的加密, 没有任何保护的代码, 不用EVP_CIPHER_fetch()来暴露算法名称字符串// 可以从算法对象得到算法名称//psz = EVP_CIPHER_get0_name(c);//printf("EVP_aes_128_cbc()'s alg name is : %s\n", psz);// EVP_aes_128_cbc()'s alg name is : AES-128-CBCi_rc = EVP_CIPHER_get_key_length(_evp_cipher);if (i_rc != lenKey){break;}i_rc = EVP_CIPHER_get_iv_length(_evp_cipher);if (i_rc != lenIv){break;}bio_cipher = BIO_new(BIO_f_cipher());// BIO_set_cipher(bio_out, c, key, iv, (isEnc ? 1 : 0));// 由于要改变算法的上下文, 所以要调用BIO_get_cipher_ctx, 而不是调用BIO_set_cipher// 此时 _evp_cipher_ctx 是 NULL// !!! _evp_cipher_ctx 是从bio_filter取出来的, 不能自己新建ctx, 否则向bio_filter写东西时, 就不会加密了, 因为上下文不对BIO_get_cipher_ctx(bio_cipher, &_evp_cipher_ctx); // 这里将bio和算法上下文关联了// 此时 _evp_cipher_ctx 不为空, 是bio_filter要用到的算法ctx地址if (NULL == _evp_cipher_ctx){break;}// 向ctx中单独设置加密算法/key/iv// 官方原版实现是分成2步(先设置算法, 再设置key/iv, 有点脱裤子放屁的感觉)if (!EVP_CipherInit_ex(_evp_cipher_ctx, _evp_cipher, NULL, key, iv, 1)) {// ERR_print_errors(bio_err);ERR_print_errors_fp(stderr);break;}bio_container = BIO_new(BIO_s_mem());if (NULL == bio_container){break;}bio_to_base64 = BIO_new(BIO_f_base64());if (NULL == bio_to_base64){break;}// BIO_push返回的就是参数1 bio_out// 释放时, 只需要 BIO_free_all(bio_filter), 不用管bio_out, 因为 bio_out已经加入bio_filter链// !!! 必须向 bio_filter中显势写入从明文bio_in读到的内容, 而不能直接读取bio_filter或者bio_out, 否则报错// !!! 对于aes加解密, 都是从bio_header写, 从 bio_tail读// 算法类的BIO是没有办法存数据的, 所以后面要有一个能存数据的BIO(file bio or mem bio)作为容器容纳数据// 加密时 bio_cipher => bio_to_base64 => bio_containerBIO_push(bio_cipher, bio_to_base64);BIO_push(bio_to_base64, bio_container);bio_header = bio_cipher;bio_tail = bio_container;bio_write_to = bio_header;bio_read_from = bio_tail;// 必须向链条的顶部写(write to bio_filter)// 等全部写完(BIO_flush(bio_filter)), 再从链头(bio_filter)读取时, 就已经是加密完的密文了i_rc = BIO_write_ex(bio_write_to, pszBufIn, lenBufIn, &sz_rc); // 相当于 EVP_CipherUpdate()if ((1 != i_rc) && (lenBufIn != sz_rc)){break;}BIO_flush(bio_write_to);len = BIO_pending(bio_read_from);// 加密时, 在这里读到的数据长度是加密后的长度pOutBuf = (UCHAR*)OPENSSL_malloc(len + 1); // 多留一个字节(可选)if (NULL == pOutBuf){break;}pOutBuf[len] = '\0';lenOutBuf = 0;i_rc = BIO_read_ex(bio_read_from, pOutBuf, len, &sz_rc);// 这里读最后一块的时候// 加密时, 已经是0x10对齐了if ((1 != i_rc) || (sz_rc <= 0)){ERR_print_errors_fp(stderr);break;}lenOutBuf = sz_rc;b_rc = true;} while (false);if (NULL != bio_header){BIO_free_all(bio_header); // bio_filter是BIO链, 释放时要用 BIO_free_all()bio_header = NULL;}// 不用释放 _evp_cipher_ctx, 因为 _evp_cipher_ctx属于 bio_filterif (!b_rc){if (NULL != pOutBuf){OPENSSL_free(pOutBuf);pOutBuf = NULL;}}return b_rc;
}bool aes_128_cbc_base64_dec(IN const UCHAR* pszBufIn, IN int lenBufIn,IN const UCHAR* key, IN int lenKey,IN const UCHAR* iv, IN int lenIv,OUT UCHAR*& pOutBuf, OUT int& lenOutBuf)
{bool b_rc = false;int i_rc = 0;int len = 0;size_t sz_rc = 0;UCHAR* pucBufBase64Out = NULL;const EVP_CIPHER* _evp_cipher = NULL;EVP_CIPHER_CTX* _evp_cipher_ctx = NULL;BIO* bio_cipher_aes = NULL;BIO* bio_cipher_base64 = NULL;// for unbase64BIO* bio_container = NULL;BIO* bio_header = NULL;BIO* bio_tail = NULL;BIO* bio_write_to = NULL;BIO* bio_read_from = NULL;// for unaesBIO* bio_container_1 = NULL;BIO* bio_header_1 = NULL;BIO* bio_tail_1 = NULL;BIO* bio_write_to_1 = NULL;BIO* bio_read_from_1 = NULL;// data(len = 0x2d) = > aes - 128 - cbc = > data_enc(len = 0x30)// data(len = 0x30) = > base64 = > data_base64(len = 0x41)do {lenOutBuf = 0;if ((NULL == pszBufIn) || (lenBufIn <= 0) ||(NULL == key) || (lenKey <= 0) ||(NULL == iv) || (lenIv <= 0)){break;}_evp_cipher = EVP_aes_128_cbc(); // 这是最初的加密, 没有任何保护的代码, 不用EVP_CIPHER_fetch()来暴露算法名称字符串// 可以从算法对象得到算法名称//psz = EVP_CIPHER_get0_name(c);//printf("EVP_aes_128_cbc()'s alg name is : %s\n", psz);// EVP_aes_128_cbc()'s alg name is : AES-128-CBCi_rc = EVP_CIPHER_get_key_length(_evp_cipher);if (i_rc != lenKey){break;}i_rc = EVP_CIPHER_get_iv_length(_evp_cipher);if (i_rc != lenIv){break;}bio_cipher_aes = BIO_new(BIO_f_cipher());// BIO_set_cipher(bio_out, c, key, iv, (isEnc ? 1 : 0));// 由于要改变算法的上下文, 所以要调用BIO_get_cipher_ctx, 而不是调用BIO_set_cipher// 此时 _evp_cipher_ctx 是 NULL// !!! _evp_cipher_ctx 是从bio_filter取出来的, 不能自己新建ctx, 否则向bio_filter写东西时, 就不会加密了, 因为上下文不对BIO_get_cipher_ctx(bio_cipher_aes, &_evp_cipher_ctx); // 这里将bio和算法上下文关联了// 此时 _evp_cipher_ctx 不为空, 是bio_filter要用到的算法ctx地址if (NULL == _evp_cipher_ctx){break;}// 向ctx中单独设置加密算法/key/iv// 官方原版实现是分成2步(先设置算法, 再设置key/iv, 有点脱裤子放屁的感觉)if (!EVP_CipherInit_ex(_evp_cipher_ctx, _evp_cipher, NULL, key, iv, 0)) {// ERR_print_errors(bio_err);ERR_print_errors_fp(stderr);break;}bio_container = BIO_new(BIO_s_mem());if (NULL == bio_container){break;}bio_cipher_base64 = BIO_new(BIO_f_base64());if (NULL == bio_cipher_base64){break;}// base64的BIO的数据读写方向和aes的BIO读写方向不一样, 不能用BIO_push压入一个BIO链// 为了能正确解密, 这里要分为2步(先unbase64, 再un aes)才行// // ---------- unbase64 ----------//// BIO_push返回的就是参数1 bio_out// 释放时, 只需要 BIO_free_all(bio_filter), 不用管bio_out, 因为 bio_out已经加入bio_filter链// !!! 必须向 bio_filter中显势写入从明文bio_in读到的内容, 而不能直接读取bio_filter或者bio_out, 否则报错// !!! 对于aes加解密, 都是从bio_header写, 从 bio_tail读// 算法类的BIO是没有办法存数据的, 所以后面要有一个能存数据的BIO(file bio or mem bio)作为容器容纳数据// 解密时 bio_container => bio_to_base64 => bio_cipher// 解base64时, BIO_push左边是bio_base64, 右边是bio_container, 且是从右向左写, 才能正确解开base64BIO_push(bio_cipher_base64, bio_container); // <<=bio_header = bio_cipher_base64;bio_tail = bio_container;bio_write_to = bio_tail;bio_read_from = bio_header;// 必须向链条的顶部写(write to bio_filter)// 等全部写完(BIO_flush(bio_filter)), 再从链头(bio_filter)读取时, 就已经是加密完的密文了i_rc = BIO_write_ex(bio_write_to, pszBufIn, lenBufIn, &sz_rc); // 相当于 EVP_CipherUpdate()if ((1 != i_rc) && (lenBufIn != sz_rc)){break;}BIO_flush(bio_write_to);len = BIO_pending(bio_read_from);if (len <= 0){break;}pucBufBase64Out = (UCHAR*)OPENSSL_malloc(len + 1);if (NULL == pucBufBase64Out){break;}pucBufBase64Out[len] = '\0';// ---------- unAES ----------bio_container_1 = BIO_new(BIO_s_mem());if (NULL == bio_container_1){break;}BIO_push(bio_cipher_aes, bio_container_1); // =>bio_header_1 = bio_cipher_aes;bio_tail_1 = bio_container_1;bio_write_to_1 = bio_header_1;bio_read_from_1 = bio_tail_1;i_rc = BIO_read_ex(bio_read_from, pucBufBase64Out, len, &sz_rc);// 这里读最后一块的时候// 解密时, 已经是实际的size了if ((1 != i_rc) || (sz_rc <= 0)){break;}i_rc = BIO_write_ex(bio_write_to_1, pucBufBase64Out, sz_rc, &sz_rc);if (1 != i_rc){break;}BIO_flush(bio_write_to_1);len = BIO_pending(bio_read_from_1);if (len <= 0){break;}// 解密时, 在这里读到的数据长度还是密文长度. 不过可以拿这个len开buffer, 因为明文比密文短// bio_in = BIO_new_mem_buf(pszBufIn, lenBufIn);// 加密时, 如果明文长度不是0x10对齐, 那么加密后的长度可能比明文长最多0x10个字节pOutBuf = (UCHAR*)OPENSSL_malloc(len + 1); // 多留一个字节(可选)if (NULL == pOutBuf){break;}pOutBuf[len] = '\0';lenOutBuf = 0;// read unAES to out bufferi_rc = BIO_read_ex(bio_read_from_1, pOutBuf, len, &sz_rc);if ((1 != i_rc) || (len != sz_rc)){break;}lenOutBuf = sz_rc;b_rc = true;} while (false);if (NULL != pucBufBase64Out){OPENSSL_free(pucBufBase64Out);pucBufBase64Out = NULL;}if (NULL != bio_header){BIO_free_all(bio_header); // bio_filter是BIO链, 释放时要用 BIO_free_all()bio_header = NULL;}if (NULL != bio_header_1){BIO_free_all(bio_header_1); // bio_filter是BIO链, 释放时要用 BIO_free_all()bio_header_1 = NULL;}// 不用释放 _evp_cipher_ctx, 因为 _evp_cipher_ctx属于 bio_filterif (!b_rc){if (NULL != pOutBuf){OPENSSL_free(pOutBuf);pOutBuf = NULL;}}return b_rc;
}

END

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

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

相关文章

详解ARP攻击原理、类型、机制、欺骗主机、仿冒网关、泛洪攻击,以及网络攻击中如何快速判断客户端是否存在恶意连接?

详解ARP攻击原理、类型、机制、欺骗主机、仿冒网关、泛洪攻击,以及网络攻击中如何快速判断客户端是否存在恶意连接? ARP攻击(Address Resolution Protocol attack)是一种网络攻击技术,它利用了ARP协议的设计缺陷来实施攻击。ARP是网络协议中用于将网络层的IP地址解析为链路…

舵机烧录

舵机烧录 一、硬件连接1、准备物资2、连接&#xff08;1&#xff09;舵机线一侧连接舵机控制板&#xff0c;另一侧连接舵机&#xff08;2&#xff09;老安卓线一侧连接舵机控制板&#xff0c;一侧连接电脑&#xff08;3&#xff09;接上低压电池 二、软件使用1、打开舵机烧录软…

表格中的状态类型值(tag)

一&#xff1a;数字转换为简单的中文值 ** 不用转换直接用find()方法&#xff1a;在statusList里找&#xff1b; **lastHandleCode是对应的获取到的每行数据的code值&#xff1b; vue: <el-table-column label"执行状态" align"center"><templat…

Vmware虚拟机无法用root直连说明

Vmware虚拟机无法用root直连说明 背景目的SSH服务介绍无法连接检查配置 背景 今天在VM上新装了一套Centos-stream-9系统&#xff0c;网络适配器的连接方式采用的是桥接&#xff0c;安装好虚拟机后&#xff0c;在本地用ssh工具进行远程连接&#xff0c;ip、用户、密码均是成功的…

【Java程序设计】【C00384】基于(JavaWeb)Springboot的民航网上订票系统(有论文)

【C00384】基于&#xff08;JavaWeb&#xff09;Springboot的民航网上订票系统&#xff08;有论文&#xff09; 项目简介项目获取开发环境项目技术运行截图 博主介绍&#xff1a;java高级开发&#xff0c;从事互联网行业六年&#xff0c;已经做了六年的毕业设计程序开发&#x…

如何简单在手机上/移动端测试web程序

也许你在搜索到如何在手机上测试web的方法是&#xff1a; Chrome DevTools模拟手机调试 岩鼠平台真机调试 weinre远程调试工具 等等等待 但也许只是需要在手机上简单试用一下我们开发的web 目录 一、前置条件 二、安装Live Server扩展 三、以Live Server运行程序 四、访问…

JS快速排序模板

912. 排序数组 给你一个整数数组 nums&#xff0c;请你将该数组升序排列。 示例 1&#xff1a; 输入&#xff1a;nums [5,2,3,1] 输出&#xff1a;[1,2,3,5] 示例 2&#xff1a; 输入&#xff1a;nums [5,1,1,2,0,0] 输出&#xff1a;[0,0,1,1,2,5] 提示&#xff1a; 1 <…

(vue)el-table表格回显返回的已勾选的数据

(vue)el-table表格编辑时回显返回的已勾选的数据 tableData数据&#xff1a; el-tableref"multipleTable":data"tableData"... >...<el-table-column prop"result" label"相关.." align"center" width"220"…

2022 年甘肃省职业院校技能大赛 高职组 网络系统管理竞赛 网络构建模块试题

2022 年甘肃省职业院校技能大赛 高职组网络系统管理竞赛 网络构建模块试题 目 录 考试说明… 3 任务描述… 3 任务清单… 3 &#xff08;一&#xff09;基础配置… 3 &#xff08;二&#xff09;有线网络配置… 4 &#xff08;三&#xff09;无线网络配置… 6 &#xff08;四&a…

数据拨号失败之CLIENT_END

问题 今天运维同事反馈了一个数据拨号超时的问题&#xff0c;抓了modem日志&#xff0c;需要分析下原因。 分析 首先看到attach过程是成功的&#xff1a; [0xB0ED] OTA LOG 04:05:09.086400 LTE NAS EMM Plain OTA Outgoing Mes…

Halcon与C#联合开发——1.读取图片、图像二值化

在vs中引入halcon控件 修改目标平台为 x64 拖出三个控件 代码展示 using System; using System.Windows.Forms; //引用支持halcon的命名空间 using HalconDotNet;namespace _1.HalconDisplay {public partial class Form1 : Form {// HObject 是Halcon库中表示图像和其他图形…

微信小程序的页面交互练习——实现比较两数大小功能

前提&#xff1a;配置好页面后 一、在wxml里面搭建好框架&#xff1a; <navigation-bar title"Weixin" back"{{false}}" color"black" background"#FFF"></navigation-bar> <scroll-view class"scrollarea"…

设置 WebView,禁止缩放网页

在 Android 14 中,你可以通过以下步骤来设置 WebView,禁止缩放网页: 在 WebView 加载网页之前,获取 WebSettings 对象: WebSettings webSettings webView.getSettings();使用 WebSettings 对象禁用缩放: webSettings.setSupportZoom(false); webSettings.setBuiltInZoomCon…

【数据结构】受限制的线性表——队列

&#x1f9e7;&#x1f9e7;&#x1f9e7;&#x1f9e7;&#x1f9e7;个人主页&#x1f388;&#x1f388;&#x1f388;&#x1f388;&#x1f388; &#x1f9e7;&#x1f9e7;&#x1f9e7;&#x1f9e7;&#x1f9e7;数据结构专栏&#x1f388;&#x1f388;&#x1f388;&…

Nginx(面试)

NGINX 速记问答 Q 什么是Nginx&#xff1f;它的主要特点是什么&#xff1f; A Nginx是一个高性能的开源Web服务器和反向代理服务器。它以高并发、低内存消耗和高稳定性著称。 Q Nginx与Apache Web服务器有什么区别&#xff1f; A Nginx与Apache相比&#xff0c;更适用于处…

实现UI自动化测试,这5个常见问题你必须知道!

UI自动化测试一直都是如此的令人纠结&#xff0c;自动化测试初学者总是拿它入门&#xff0c;但有些经验丰富者对其又是毁誉参半&#xff0c;抑或抛出分层自动化测试那个经典的“金字塔”&#xff0c;来说明UI自动化测试还是少做为好。 我在从事7年产品研发之后&#xff0c;临危…

KMP算法模板(Java)

KMP算法是一种字符串匹配算法&#xff0c;用于匹配模式串P在文本串S中出现的所有位置&#xff0c;例如S“abc”&#xff0c;P“aba”那么出现的所有位置是1,3&#xff0c;这里我不细说他的原理&#xff0c;只告诉模板了。 import java.math.BigInteger; import java.util.*;pub…

DevOps是什么

DevOps 是一种将软件开发 (Dev) 和 IT运维 (Ops) 结合起来的实践、文化和哲学&#xff0c;旨在缩短系统开发生命周期&#xff0c;提供高质量的软件持续交付。它涉及多个关键实践和工具&#xff0c;其核心目的是加强开发和运维团队之间的协作和通信。以下是构成DevOps的一些重要…

c语言--实用调试技巧

1什么是bug 2调试是什么&#xff0c;有多重要&#xff1f; 3debug与release 4windows环境调试简绍 5一些调试的实例 6如何写出好的代码&#xff08;便于调试&#xff09; 7编程常见错误 1什么是bug 导致计算机出现问题就叫bug 2调试是什么&#xff0c;有多重要&#x…

靠谱服装库存管理系统大盘点,商陆花、管家婆、秦丝哪家强?

在服装行业&#xff0c;库存管理是至关重要的环节。对于咱服装老板来说&#xff0c;选对的库存管理系统是提高效率、降低运营成本的关键。市场里有不少系统&#xff0c;我们今天拿出来最常见的5款&#xff0c;给大家一个详细指南。 选择服装库存管理系统时应考虑以下因素&…