当前位置: 首页 > news >正文

PostgreSQL源码分析——口令认证

认证机制

对于数据库系统来说,其作为服务端,接受来自客户端的请求。对此,必须有对客户端的认证机制,只有通过身份认证的客户端才可以访问数据库资源,防止非法用户连接数据库。PostgreSQL支持认证方法有很多:

typedef enum UserAuth
{uaReject,uaImplicitReject,			/* Not a user-visible option */uaTrust,uaIdent,uaPassword,uaMD5,uaSCRAM,uaGSS,uaSSPI,uaPAM,uaBSD,uaLDAP,uaCert,uaRADIUS,uaPeer
#define USER_AUTH_LAST uaPeer	/* Must be last value of this enum */
} UserAuth;

对此,我们仅分析常用的口令认证方式。目前PostgreSQL支持MD5、SCRAM-SHA-256对密码哈希和身份验证支持,建议使用SCRAM-SHA-256,相较MD5安全性更高。不同的基于口令的认证方法的可用性取决于用户的口令在服务器上是如何被加密(或者更准确地说是哈希)的。这由设置口令时的配置参数password_encryption控制。可用值为scham-sha-256md5

host    all             all             0.0.0.0/0               scham-sha-256

口令认证并不是简单的客户端告诉服务端密码,服务端比对成功就可以了。而是要设计一个客户端和服务器协商身份验证机制,避免密码明文存储等等问题。对此,PostgreSQL引入SCRAM身份验证机制(RFC5802、RFC7677)

image.png

也就是说,后面的口令认证方法中,遵循的是如上的口令认证协议。

参考文档:53.3.1. SCRAM-SHA-256认证

源码分析

这里只分析口令认证的情况。 口令认证分为明文口令认证和加密口令认证。明文口令认证要求客户端提供一个未加密的口令进行认证,安全性较差,已经被禁止使用。加密口令认证要求客户端提供一个经过SCRAM-SHA-256加密的口令进行认证,该口令在传送过程中使用了结合salt的单向哈希加密,增强了安全性。口令都存储在pg_authid系统表中,可通过CREATE USER test WITH PASSWORD '******';等命令进行设置。

客户端

以psql客户端为例,当客户端发出连接请求后,分析口令认证的过程。

main
--> parse_psql_options(argc, argv, &options);// 连接数据库,中间会有认证这块,有不同的认证方法,这里列的是口令认证方式的流程
--> do {// 输入host、port、user、password、dbname等信息PQconnectdbParams(keywords, values, true);--> PQconnectStartParams(keywords, values, expand_dbname);--> makeEmptyPGconn();--> conninfo_array_parse(keywords, values, &conn->errorMessage, true, expand_dbname);--> fillPGconn(conn, connOptions)--> connectDBStart(conn)--> PQconnectPoll(conn)    // 尝试建立TCP连接--> socket(addr_cur->ai_family, SOCK_STREAM, 0);--> connect(conn->sock, addr_cur->ai_addr, addr_cur->ai_addrlen)--> connectDBComplete(conn);// 认证这块与数据库服务端交互以及状态很多,这里没有全部列出。--> PQconnectPoll(conn);--> pqReadData(conn);--> pqsecure_read(conn, conn->inBuffer + conn->inEnd, conn->inBufSize - conn->inEnd);--> pqsecure_raw_read(conn, ptr, len);--> recv(conn->sock, ptr, len, 0);--> pg_fe_sendauth(areq, msgLength, conn);--> pg_SASL_init(conn, payloadlen)--> conn->sasl->init(conn,password,selected_mechanism);--> pg_saslprep(password, &prep_password);--> conn->sasl->exchange(conn->sasl_state, NULL, -1, &initialresponse, &initialresponselen, &done, &success);--> build_client_first_message(state);--> pg_strong_random(raw_nonce, SCRAM_RAW_NONCE_LEN)PQconnectionNeedsPassword(pset.db)     // 是否需要密码,如果需要,等待用户数据密码}// 进入主循环,等待用户输入命令,执行命令
--> MainLoop(stdin);  --> psql_scan_create(&psqlscan_callbacks);--> while (successResult == EXIT_SUCCESS){// 等待用户输入命令gets_interactive(get_prompt(prompt_status, cond_stack), query_buf);SendQuery(query_buf->data); // 把用户输入的SQL命令发送到数据库服务--> ExecQueryAndProcessResults(query, &elapsed_msec, &svpt_gone, false, NULL, NULL) > 0);--> PQsendQuery(pset.db, query);    // 通过libpq与数据库交互--> PQgetResult(pset.db);}
服务端

服务端相关流程:

main()
--> PostmasterMain(argc, argv);--> InitProcessGlobals();   // set MyProcPid, MyStartTime[stamp], random seeds--> pg_prng_strong_seed(&pg_global_prng_state)--> srandom(pg_prng_uint32(&pg_global_prng_state));--> InitializeGUCOptions();--> ProcessConfigFile(PGC_POSTMASTER);--> ProcessConfigFileInternal(context, true, elevel);--> ParseConfigFp(fp, abs_path, depth, elevel, head_p, tail_p);--> load_hba()  // 读pg_hba.conf--> parse_hba_line(tok_line, LOG)   // 解析pg_hba.conf中的每条记录,解析到HbaLine的链表结构中--> load_ident()--> ServerLoop();--> BackendStartup(port);--> InitPostmasterChild();--> BackendInitialize(port);--> pq_init();--> RegisterTimeout(STARTUP_PACKET_TIMEOUT, StartupPacketTimeoutHandler);--> enable_timeout_after(STARTUP_PACKET_TIMEOUT, AuthenticationTimeout * 1000);--> ProcessStartupPacket(port, false, false);   // Read a client's startup packet--> pq_startmsgread();--> pq_getbytes((char *) &len, 1)--> InitProcess();--> BackendRun(port);--> PostgresMain(port->database_name, port->user_name);--> BaseInit();// 在接收用户SQL请求前进行认证--> InitPostgres(dbname, InvalidOid, username, InvalidOid,!am_walsender, false, NULL);--> PerformAuthentication(MyProcPort);--> enable_timeout_after(STATEMENT_TIMEOUT, AuthenticationTimeout * 1000);--> ClientAuthentication(port);--> hba_getauthmethod(port);--> check_hba(port);// 根据不同的认证方法进行认证--> switch (port->hba->auth_method){		case uaTrust:status = STATUS_OK;break;case uaMD5:case uaSCRAM:// 口令认证status = CheckPWChallengeAuth(port, &logdetail);--> break;// ...}--> if (status == STATUS_OK)sendAuthRequest(port, AUTH_REQ_OK, NULL, 0);elseauth_failed(port, status, logdetail);--> disable_timeout(STATEMENT_TIMEOUT, false);--> initialize_acl();// 认证通过后才能接受用户的SQL请求--> for (;;){// ...exec_simple_query(query_string);}

每次客户端发起连接时,postgres主进程都会fork一个子进程:

static int BackendStartup(Port *port)
{// ...pid = fork_process();if (pid == 0)				/* child */{free(bn);/* Detangle from postmaster */InitPostmasterChild();/* Close the postmaster's sockets */ClosePostmasterPorts(false);/* Perform additional initialization and collect startup packet */BackendInitialize(port);/** Create a per-backend PGPROC struct in shared memory. We must do* this before we can use LWLocks. In the !EXEC_BACKEND case (here)* this could be delayed a bit further, but EXEC_BACKEND needs to do* stuff with LWLocks before PostgresMain(), so we do it here as well* for symmetry.*/InitProcess();  /* And run the backend */BackendRun(port);  // }
}

每个子进程在接收用户的SQL请求前,需要先进行认证。主要实现在ClientAuthentication函数中,通过认证才能继续进行下一步。

/** Client authentication starts here.  If there is an error, this* function does not return and the backend process is terminated. */
void ClientAuthentication(Port *port)
{int			status = STATUS_ERROR;const char *logdetail = NULL;hba_getauthmethod(port);/** This is the first point where we have access to the hba record for the* current connection, so perform any verifications based on the hba* options field that should be done *before* the authentication here. */if (port->hba->clientcert != clientCertOff){/* If we haven't loaded a root certificate store, fail */if (!secure_loaded_verify_locations())ereport(FATAL,(errcode(ERRCODE_CONFIG_FILE_ERROR),errmsg("client certificates can only be checked if a root certificate store is available")));/* If we loaded a root certificate store, and if a certificate is* present on the client, then it has been verified against our root* certificate store, and the connection would have been aborted* already if it didn't verify ok. */if (!port->peer_cert_valid)ereport(FATAL,(errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION), errmsg("connection requires a valid client certificate")));}// 根据不同的认证方法,进行不同的处理switch (port->hba->auth_method){// ...case uaMD5:case uaSCRAM:status = CheckPWChallengeAuth(port, &logdetail);break;case uaPassword:status = CheckPasswordAuth(port, &logdetail);break;case uaTrust:status = STATUS_OK;break;}if ((status == STATUS_OK && port->hba->clientcert == clientCertFull)|| port->hba->auth_method == uaCert){/** Make sure we only check the certificate if we use the cert method* or verify-full option.*/
#ifdef USE_SSLstatus = CheckCertAuth(port);
#elseAssert(false);
#endif}if (ClientAuthentication_hook)(*ClientAuthentication_hook) (port, status);if (status == STATUS_OK)sendAuthRequest(port, AUTH_REQ_OK, NULL, 0);elseauth_failed(port, status, logdetail);
}

对于口令认证,具体实现为CheckPWChallengeAuth函数。

/* MD5 and SCRAM authentication. */
static int CheckPWChallengeAuth(Port *port, const char **logdetail)
{int			auth_result;char	   *shadow_pass;PasswordType pwtype;/* First look up the user's password. */ // 查找pg_authid系统表中的用户密码shadow_pass = get_role_password(port->user_name, logdetail);if (!shadow_pass)pwtype = Password_encryption;elsepwtype = get_password_type(shadow_pass);/* If 'md5' authentication is allowed, decide whether to perform 'md5' or* 'scram-sha-256' authentication based on the type of password the user* has.  If it's an MD5 hash, we must do MD5 authentication, and if it's a* SCRAM secret, we must do SCRAM authentication.** If MD5 authentication is not allowed, always use SCRAM.  If the user* had an MD5 password, CheckSASLAuth() with the SCRAM mechanism will fail. */if (port->hba->auth_method == uaMD5 && pwtype == PASSWORD_TYPE_MD5)auth_result = CheckMD5Auth(port, shadow_pass, logdetail);elseauth_result = CheckSASLAuth(&pg_be_scram_mech, port, shadow_pass,logdetail);if (shadow_pass)pfree(shadow_pass);/* If get_role_password() returned error, return error, even if the authentication succeeded. */if (!shadow_pass){Assert(auth_result != STATUS_OK);return STATUS_ERROR;}if (auth_result == STATUS_OK)set_authn_id(port, port->user_name);return auth_result;
}int CheckSASLAuth(const pg_be_sasl_mech *mech, Port *port, char *shadow_pass, const char **logdetail)
{StringInfoData sasl_mechs;int			mtype;StringInfoData buf;void	   *opaq = NULL;char	   *output = NULL;int			outputlen = 0;const char *input;int			inputlen;int			result;bool		initial;/** Send the SASL authentication request to user.  It includes the list of* authentication mechanisms that are supported.*/initStringInfo(&sasl_mechs);mech->get_mechanisms(port, &sasl_mechs);/* Put another '\0' to mark that list is finished. */appendStringInfoChar(&sasl_mechs, '\0');sendAuthRequest(port, AUTH_REQ_SASL, sasl_mechs.data, sasl_mechs.len);pfree(sasl_mechs.data);/** Loop through SASL message exchange.  This exchange can consist of* multiple messages sent in both directions.  First message is always* from the client.  All messages from client to server are password* packets (type 'p').*/initial = true;do{pq_startmsgread();mtype = pq_getbyte();if (mtype != 'p'){/* Only log error if client didn't disconnect. */if (mtype != EOF){ereport(ERROR,(errcode(ERRCODE_PROTOCOL_VIOLATION), errmsg("expected SASL response, got message type %d",mtype)));}elsereturn STATUS_EOF;}/* Get the actual SASL message */initStringInfo(&buf);if (pq_getmessage(&buf, PG_MAX_SASL_MESSAGE_LENGTH)){/* EOF - pq_getmessage already logged error */pfree(buf.data);return STATUS_ERROR;}elog(DEBUG4, "processing received SASL response of length %d", buf.len);/* The first SASLInitialResponse message is different from the others.* It indicates which SASL mechanism the client selected, and contains* an optional Initial Client Response payload.  The subsequent* SASLResponse messages contain just the SASL payload. */if (initial){const char *selected_mech;selected_mech = pq_getmsgrawstring(&buf);/** Initialize the status tracker for message exchanges.** If the user doesn't exist, or doesn't have a valid password, or* it's expired, we still go through the motions of SASL* authentication, but tell the authentication method that the* authentication is "doomed". That is, it's going to fail, no matter what.** This is because we don't want to reveal to an attacker what* usernames are valid, nor which users have a valid password. */opaq = mech->init(port, selected_mech, shadow_pass);inputlen = pq_getmsgint(&buf, 4);if (inputlen == -1)input = NULL;elseinput = pq_getmsgbytes(&buf, inputlen);initial = false;}else{inputlen = buf.len;input = pq_getmsgbytes(&buf, buf.len);}pq_getmsgend(&buf);/* Hand the incoming message to the mechanism implementation. */result = mech->exchange(opaq, input, inputlen,&output, &outputlen,logdetail);/* input buffer no longer used */pfree(buf.data);if (output){/*PG_SASL_EXCHANGE_FAILURE with some output is forbidden by SASL.Make sure here that the mechanism used got that right. */if (result == PG_SASL_EXCHANGE_FAILURE)elog(ERROR, "output message found after SASL exchange failure");/* Negotiation generated data to be sent to the client. */elog(DEBUG4, "sending SASL challenge of length %d", outputlen);if (result == PG_SASL_EXCHANGE_SUCCESS)sendAuthRequest(port, AUTH_REQ_SASL_FIN, output, outputlen);elsesendAuthRequest(port, AUTH_REQ_SASL_CONT, output, outputlen);pfree(output);}} while (result == PG_SASL_EXCHANGE_CONTINUE);/* Oops, Something bad happened */if (result != PG_SASL_EXCHANGE_SUCCESS){return STATUS_ERROR;}return STATUS_OK;
}

相关文章:

PostgreSQL源码分析——口令认证

认证机制 对于数据库系统来说,其作为服务端,接受来自客户端的请求。对此,必须有对客户端的认证机制,只有通过身份认证的客户端才可以访问数据库资源,防止非法用户连接数据库。PostgreSQL支持认证方法有很多&#xff1…...

Stability-AI(图片生成视频)

1.项目地址 GitHub - Stability-AI/generative-models: Generative Models by Stability AI 2.模型地址 魔搭社区 3.克隆项目后,按照教程安装 conda create --name Stability python3.10 conda activate Stability pip3 install -r requirements/pt2.txt py…...

Linux机器通过Docker-Compose安装Jenkins发送Allure报告

目录 一、安装Docker 二、安装Docker Compose 三、准备测试用例 四、配置docker-compose.yml 五、启动Jenkins 六、配置Jenkins和Allure插件 七、创建含pytest的Jenkins任务 八、项目结果通知 1.通过企业微信通知 2.通过邮件通知 九、配置域名DNS解析 最近小编接到一…...

基于Gunicorn+Flask+Docker模型高并发部署

关于猫头虎 大家好,我是猫头虎,别名猫头虎博主,擅长的技术领域包括云原生、前端、后端、运维和AI。我的博客主要分享技术教程、bug解决思路、开发工具教程、前沿科技资讯、产品评测图文、产品使用体验图文、产品优点推广文稿、产品横测对比文…...

java:类型变量(TypeVariable)解析--基于TypeResolver实现将类型变量替换为实际类型

上一篇博客《java:类型变量(TypeVariable)解析–获取泛型类(Generic Class)所有的类型变量(TypeVariable)的实际映射类型》中介绍如何如何正确解析泛型类的类型变量(TypeVariable),获取对应的实际类型。 有了类型变量(TypeVariable)–实际类型的映射,我们…...

ru俄罗斯域名如何申请SSL证书?

我们日常看到的都是com这种国际域名比较普遍,尤其是主流网站,主要原因考虑的其通用性,那么对于地方性的域名大家很少看到,比如俄罗斯国家域名.ru大家还是有些陌生的,但要说中国.CN域名那你就很熟悉了。 有用户在申请过…...

python实现购物车的功能

模拟购物车,准备一个列表 goodList [{name:笔记本电脑,price:8000}, {name:鼠标, price:100}] 5个函数 1.加入购物车 2.收藏商品 3.去结算 4.删除购物车商品 5.清空购物车 购物车 cartList [] 收藏列表 collectSet {笔记本电脑,鼠标} 数据示例 去结算计算出总价…...

日元预计明年开始上涨

被称为“日元先生”的前大藏省(现财务省)财务官榊原英资预测,美元兑日元汇率将在今年底或2025年初逐步升至130。他认为,通缩时代已经过去,通货膨胀即将来临。 《日本经济新闻》6月5日报道,日本财务省于5月3…...

8、PHP 实现二进制中1的个数、数值的整数次方

题目&#xff1a; 二进制中1的个数 描述&#xff1a; 输入一个整数&#xff0c;输出该数二进制表示中1的个数。其中负数用补码表示。 <?phpfunction NumberOf1($n) {$count 0;if($n < 0){$n $n & 0x7FFFFFFF;$count;}while($n ! 0){$count;$n $n & ($n - 1…...

linux git凭证管理

linux git 凭证管理 解决命令行git登录github的问题&#xff0c;支持两步验证 同样适用于Azure Devops, Bitbucket 官网&#xff1a; https://github.com/git-ecosystem/git-credential-manager https://github.com/git-ecosystem/git-credential-manager/blob/release/docs/…...

WIC 图像处理初体验——读取像素的值

先放上运行结果&#xff1a; 可以发现红绿蓝是从后往前的。 必须以C方式编译代码&#xff01; #define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <wincodec.h>int main(void) {CoInitialize(nullptr);IWICImagingFactory* fac;CoCreateInstance(CLS…...

使用Server-Sent Events (SSE),并获取message里面的内容

什么是Server-Sent Events (SSE)? Server-Sent Events (SSE)是一种服务器推送技术&#xff0c;允许服务器向客户端&#xff08;浏览器&#xff09;发送实时消息。与WebSocket不同&#xff0c;SSE是单向通信&#xff0c;只能从服务器到客户端。SSE在HTML5中作为标准实现&#…...

LabVIEW项目管理中如何平衡成本、时间和质量

在LabVIEW项目管理中&#xff0c;平衡成本、时间和质量是实现项目成功的关键。通过制定详细的项目计划、合理分配资源、严格控制进度、进行质量保证和灵活应对变化&#xff0c;项目管理者可以有效地协调这三者的关系&#xff0c;确保项目按时、按质、按预算完成。 1. 制定详细…...

如何检查 Kubernetes 网络配置

简介 Kubernetes 是一个容器编排系统&#xff0c;可以管理集群中的容器化应用程序。在集群中保持所有容器之间的网络连接需要一些高级网络技术。在本文中&#xff0c;我们将简要介绍一些工具和技术&#xff0c;用于检查这种网络设置。 如果您正在调试连接问题&#xff0c;调查…...

如何将网站封装成App:小猪APP分发助你实现

你有没有想过&#xff0c;将你的网站变成一个App会是什么样子&#xff1f;想象一下&#xff0c;用户只需点击一下图标&#xff0c;就能立刻访问你的内容&#xff0c;而不是在浏览器中输入网址。这不仅提升了用户体验&#xff0c;还能增加用户粘性。这一切都可以通过将网站封装成…...

探索C嘎嘎的奇妙世界:第十六关---STL(vector的练习)

1.只出现一次的数字 我们可以使用异或运算来解决这个问题&#xff1a; 异或运算有一个重要的性质&#xff1a;两个相同的数进行异或运算结果为 0&#xff0c;任何数与 0 异或结果为其本身。对于数组中的元素&#xff0c;依次进行异或运算&#xff0c;出现两次的元素异…...

最新扣子(Coze)实战案例:扣子卡片的制作及使用,完全免费教程

&#x1f9d9;‍♂️ 大家好&#xff0c;我是斜杠君&#xff0c;手把手教你搭建扣子AI应用。 &#x1f4dc; 本教程是《AI应用开发系列教程之扣子(Coze)实战教程》&#xff0c;完全免费学习。 &#x1f440; 关注斜杠君&#xff0c;可获取完整版教程。&#x1f44d;&#x1f3f…...

Node-red win11安装

文章目录 前言一、安装node.js和npm二、安装Node-red三、 运行Node-red 前言 Node-RED 是一种编程工具&#xff0c;用于以新颖有趣的方式将硬件设备、API 和在线服务连接在一起。 它提供了一个基于浏览器的编辑器&#xff0c;只需单击一下即可将调色板中的各种节点轻松连接在…...

永久更改R包的安装目录

要永久更改 R 包的安装目录&#xff0c;可以通过设置 R 配置文件来实现。以下是步骤说明&#xff1a; 1. 查找和修改 R 配置文件 R 有几个配置文件用于保存用户和系统的设置&#xff1a; 用户级配置文件&#xff1a;通常位于 ~/.Rprofile系统级配置文件&#xff1a;通常位于…...

Webrtc支持FFMPEG硬解码之NVIDA(二)

一、前言 此系列文章分分为三篇, Webrtc支持FFMPEG硬解码之Intel(一)-CSDN博客 Webrtc支持FFMPEG硬解码之NVIDA(二)-CSDN博客 Webrtc支持FFMPEG硬解码之解码实现-CSDN博客 AMD硬解目前还没找到可用解码器,欢迎留言交流 二、环境 Windows平台 VS2019 Cmake 三、下…...

如何高效使用fre:ac音频转换器:从入门到精通的完整指南

如何高效使用fre:ac音频转换器&#xff1a;从入门到精通的完整指南 【免费下载链接】freac The fre:ac audio converter project 项目地址: https://gitcode.com/gh_mirrors/fr/freac fre:ac音频转换器是一款功能强大、完全免费的开源工具&#xff0c;专为音乐爱好者和专…...

Oxygen氧气17.1.0.131完整包|含教程33讲+实战课7节+强哥专属MI码工具套件(含16.3旧版+视频)

温馨提示&#xff1a;文末有联系方式Oxygen氧气17.1.0.131权威合集 本套资料为当前最新稳定版Oxygen 17.1.0.131的全维度配套&#xff0c;涵盖从入门到进阶所需的全部学习与实战组件&#xff0c;专为移动终端取证工程师、数据恢复技术人员及专业培训机构定制。认证教学体系&…...

SCH16T-K20陀螺仪、加速度计、惯性测量单元IMU、组合惯导系统

SCH16T-K20专为机器人、无人机和摄像头系统领域的OEM厂商以及需要安全关键型IMU的。 | SCH16T-K20专为机器人、无人机和摄像头系统领域的OEM厂商以及需要安全关键型IMU的IMU模块制造商和系统集成商而设计&#xff0c;SCH16T-K20提供市场居先的精度、机械稳健性和稳定性。SCH16T…...

企业AI落地两年,我学到最贵的一课:别升级你的Agent架构

最近我参与了一个企业AI项目的架构评审。团队花了三个月&#xff0c;搭建了一套他们称之为”多Agent协作系统”的东西&#xff1a;一个编排器LLM负责任务分解&#xff0c;四个工人LLM并行处理&#xff0c;外加一个评估器LLM做质量审核。架构图画了三页PPT&#xff0c;代码量超过…...

别再问GPS多久能定位了!手把手教你用Python模拟计算TTFF理论极限(附代码)

用Python拆解GPS定位极限&#xff1a;18秒理论值背后的工程密码 刚拆封的新款GPS模块说明书上赫然标注着"冷启动TTFF≤35秒"&#xff0c;而隔壁极客论坛却有人宣称"18秒是物理极限"。作为开发者&#xff0c;我们更关心的是&#xff1a;这个数字从何而来&am…...

设计验证的主要内容

医疗器械设计开发中的设计验证是确保产品满足用户需求和设计要求的关键环节&#xff0c;需符合相关法规要求。以下是核心内容及对应法规条款&#xff1a; 设计验证的主要内容 性能验证 通过测试、模拟或分析手段确认产品性能符合设计输入要求。例如电气安全、机械强度、生物相容…...

MTK Camera调试实战:Dump Buffer定位花屏与竖线问题

1. 初识MTK Camera Dump Buffer调试技巧 第一次遇到手机摄像头预览画面出现彩色条纹时&#xff0c;我盯着屏幕愣了半天。作为刚入行的Camera调试工程师&#xff0c;这种花屏问题简直就像天书。直到同事教我用了MTK平台的Dump Buffer功能&#xff0c;才发现原来这些看似复杂的图…...

Cesium 3D可视化实战:给你的地理围栏加上‘跑马灯’特效(基于MaterialProperty自定义材质)

Cesium 3D可视化实战&#xff1a;地理围栏动态光效的深度实现 在三维地理信息系统中&#xff0c;动态视觉效果往往能带来更直观的数据感知体验。想象一下&#xff0c;当监控区域边界以流动光带形式呈现&#xff0c;不仅美观更能突出警示作用——这正是我们今天要探讨的Cesium高…...

【Dify国产化测试黄金标准】:12类中间件兼容矩阵、5轮压力测试阈值、4项国密SM4/SM2集成验证

第一章&#xff1a;Dify国产化部署测试的总体框架与合规基线 Dify作为开源大模型应用开发平台&#xff0c;其国产化部署需兼顾技术可行性、安全可控性与行业监管要求。总体框架以“信创适配层—平台运行层—数据治理层—合规验证层”四维协同为设计核心&#xff0c;覆盖从硬件基…...

MSP432P401R开发第一步:在CCS里正确导入DriverLib库并配置头文件路径(避坑指南)

MSP432P401R开发实战&#xff1a;从DriverLib库配置到工程模板搭建全解析 刚接触MSP432P401R的开发者往往会在第一步——配置CCS工程环境时遇到各种"坑"。这些看似基础的问题如果处理不当&#xff0c;可能导致后续开发效率大幅降低。本文将手把手带你完成从零开始的…...