올해 초부터 CoAP 기반의 Firmware-over-the-Air 솔루션을 독자적으로 개발하고 있다. 사실 핵심 기술은 거의 완성했다. 델타 스크립트 생성기술, 델타 전달 프로토콜, 데이터 저장 및 무결성 확인 절차, 그리고 델타 업데이트와 복구를 지원하는 부트로더까지. 나는 2019년 11월 13일 뉴욕에서 열리는 ACM SenSys 2019 에서 이 기술을 시연하려고 한다.
나는 이 기술이 절실했기 때문에 개발했다. 대규모 무선 센서 네트워크 기술을 개발 중, 소스코드를 수정할 때마다 IoT 디바이스 100대의 펌웨어를 업데이트시켜야 했다. 유선 펌웨어 업데이트는 노가다였다. 한 번에 한 디바이스씩 업데이트할 수밖에 없었다. 기기 하나씩 하나씩 손수 JTAG선을 연결해서 업로드했다. 100대 디바이스를 모두 업데이트 하는데 보통 40분씩 걸렸다. 그렇게 업데이트를 해서 네트워크가 제대로 동작하면 좋은데…. 보통은 그렇지도 않았다. 코드 한 줄만 실수해도 다시 40분을 버리는 것이었다. 이런 시간 낭비를 없애고 싶었다.
앞으로 내 FOTA 프로토콜에 대해서 글을 써 보려 한다. 아직 미흡한 부분들이 많은데, 글을 쓰면서 그 부분들을 채우는 방법도 고민해볼 것이다.
이 응답 코드 클래스는 서버에서 오류가 발생했거나 요청을 수행 할 수 없는 경우를 나타냅니다. 이 응답 코드는 모든 요청 메소드에 대해 적용 할 수 있습니다. 서버가 이 유형의 응답을 보낼땐 진단 페이로드를 포함해서 전송해야 합니다. 이 유형의 응답은 캐시 형태로도 구현이 가능합니다만 기존의 캐싱처럼 validation 모델을 적용할 순 없습니다.
*RFC에선 이 유형의 응답에 대한 설명이 HTTP 의 오류 응답과 매우 유사하다고 언급만 할 뿐이여서, 이해를 돕기위해 HTTP 의 지침을 바탕으로 각 응답 코드에 대해 설명합니다.
5.00 Internal Server Error (내부 서버 오류)
서버에서 예기치 않은 조건이 발생하여 요청을 수행 할 수 없었음을 의미합니다.
5.01 Not Implemented (구현되지 않음)
서버가 요청 수행하는 데 필요한 기능을 지원하지 않음을 의미합니다. 서버가 해당 요청 메소드를 인식하지 못하고 어떤 자원도 이를 지원할 수 없을 때 사용하기 적절한 응답입니다.
예를들어, POST 기능을 지원하지 않는 Constrained Thing이 POST 요청을 받았을 시 이 응답을 요청자에게 보낼 수 있습니다.
5.02 Bad Gateway (잘못된 게이트웨이)
서버가 게이트웨이 또는 프록시 역할을 수행하는 동안 요청을 수행하기 위해 액세스 한 업스트림 서버에서 잘못된 응답을 받았음을 의미합니다.
5.03 Service Unavailable (서비스를 사용할 수 없음)
서버가 일시적인 과부하 또는 서버 유지 관리로 인해 요청을 처리 할 수 없음을 의미합니다. 이것은 곧 해결될 수 있는 일시적 조건이라는 것을 의미합니다. 서비스가 다시 재개되기까지 남은 시간을 알 수 있는 경우, Max-Age Option을 사용하여 클라이언트가 다시 시도할 수 있는 시간(초 단위)을 알립니다. Max-Age Option이 없는 응답을 수신한 클라이언트는 5.00 응답을 수신한 것처럼 응답을 처리해야 합니다.
주: HTTP 에서는 이 코드가 서버 과부하시 반드시 사용할 필요는 없다고 설명하므로, CoAP 에서도 단순히 연결을 거부하는 방식으로 서버를 구현할 수 있습니다.
5.04 Gateway Timeout (게이트웨이 시간 초과)
게이트웨이 또는 프록시 역할을하는 서버가 요청 처리 도중 접근해야 하는 지정된 URI 또는 다른 보조 서버로부터 적시에 응답을 받지 못했음을 의미합니다.
5.05 Proxying Not Supported (프록시가 지원되지 않음)
서버가 Proxy-Uri Option 또는 Proxy-Scheme 에 지정된 URI에 대해 프록시의 역할을 하지 않거나 할 수 없음을 의미합니다.
M 비트가 설정된 Block2 옵션이 포함된 응답을 수신한 요청자는 초기 요청과 동일한 옵션으로 추가 요청을 보내고 블록 번호와 블록 크기를 제공하는 Block2 Option을 보내어 자원 표현의 추가 블록을 검색 할 수 있습니다. 요청시 클라이언트는 반드시 Block2 옵션의 M 비트를 0으로 설정해야 하며 서버는 수신시 반드시 이를 무시해야 합니다.
Simple Block-Wise GET
응답에서 사용되는 블록 크기를 조절하기 위해, 요청자는 초기 요청에서 Block2 Option 을 사용할 수 있습니다. 이 때 옵션값에는 원하는 크기, 블록 번호 0, M 비트에는 0을 넣습니다. 서버는 반드시 표시된 크기 또는 더 작은 크기의 블록을 사용합니다. 첫 번째 요청 블록 다음의 블록 단위 요청들은 반드시 서버로부터 온 첫번째 응답에서 받은 Block2 Option에서 제공된 사이즈를 나타내야 합니다.
Block-Wise GET with Early Negotiation
일단 요청자가 Block2 Option을 사용하였고 블록 사이즈가 조정되었을 수 있는 첫번째 응답을 받았다면, 단일 블록 방식 전송 내 모든 이후의 요청은 마지막 블록의 내용이 적은 경우 (M 비트가 0) 를 제외하고 궁극적으로 같은 크기를 이용하게 될 것입니다. (클라이언트는 Block2 옵션없이 보낸 첫 번째 요청이 Block2 옵션이 포함된 응답을 받은 경우 두 번째 요청부터 Block2 옵션을 사용할 수 있다는 점을 참조하십시오.) 서버는 요청 내 옵션에서 표기된 것보다 작거나 같은 블록 사이즈를 사용하지만, 요청자는 반드시 초기 요청에 대한 응답에서 사용된 실제 블록 사이즈를 기록하여 후속 요청에서 이를 사용하도록 진행해야 합니다. 서버는 반드시 이러한 클라이언트의 동작으로 인해 한 시퀀스 내 모든 응답들이 같은 블록 사이즈를 갖도록 보장하도록 동작해야 합니다. (M 비트가 0인 마지막 블록과 초기 요청에 Block2 옵션이 없는 경우의 첫 번째 블럭을 제외)
블록 방식 전송은 완전히 정적인 표현형 (디바이스를 설명하는 스키마와 같이 시간에 따라 전혀 변하지 않는 값) 을 가진 자원을 GET 하거나, 동적으로 변화하는 자원을 위해 사용될 수 있습니다.후자의 경우, Block2 옵션은 재 조립되는 블록이 동일한 표현 버전에서 나온 것인지 확인하기 위해 ETag 옵션 ([RFC7252])과 함께 사용해야 합니다. 이 경우, 서버는 각 응답에 ETag를 포함해야 합니다. ETag 옵션을 사용할 수 있는 경우 클라이언트는 교환중인 블록의 표현을 재조합 할 때 반드시 ETag 옵션을 비교해야합니다. ETag 옵션이 GET 전송에서 일치하지 않으면, 요청자는 우선 검색했던 블록들의 갱신된 값을 검색하려고 시도 할 수 있습니다. 비효율성을 최소화하기 위해, 서버는 진행중인 요청 시퀀스에 대한 표현의 현재 값을 캐시에 보관할 수 있습니다. (서버는 요청하는 엔드 포인트와 각 블록 단위 요청에서 동일한 URI의 조합에 의해 시퀀스를 식별 할 수있습니다.) 이 명세가 서버에게 상태를 확립하라는 요구를 하지 않는다는 점을 짚어야 합니다. 그러나, 빠르게 변화하는 자원을 제공하는 서버는 클라이언트가 일관된 블록 집합을 검색하는 것을 불가능하게 만들 수 있습니다. 자원의 모든 블록을 검색하고자하는 클라이언트는 과도한 지연없이 그렇게 하려고 노력해야 합니다. 서버는 해당 상태에 대한 마지막 접근 후 EXCHANGE_LIFETIME ([RFC7252]) 시간이 지나면 자유롭게 캐시된 상태를 버릴 수 있다고 예상되나 항상 상태를 그렇게 오래 유지할 필요는 없습니다.
Block2 옵션은 단일 종단점이 동일한 리소스에 대해 동시에 다중 블럭 방식 응답 페이로드 전송 (예컨대, GET) 동작을 수행 할 방법을 제공하지 않습니다. 이것은 요구가 거의 없는 사항이지만 이를 해결 방법으로 클라이언트가 캐시 키를 변경하는 방법을 생각해 볼 수 있습니다 (예 : 같은 시맨틱으로 리소스에 액세스하는 여러 URI 중 하나를 사용하거나 프록시 안전 선택 옵션을 변경하는 방식으로).
서버에서 atomic 방식의 블록형 요청 페이로드(예: PUT/POST) 를 수신하는 경우, 최종 블록, 즉 M 비트가 0 인 Block1 옵션을 포함한 블록이 수신되었을 때 실제 작성/대체가 이루어집니다. 이 경우, 최종 블록이 아닌 모든 성공 응답은 응답 코드 2.31 (Continue)을 반환합니다. 마지막 블록을 처리 할 때, 서버에서 모든 이전 블록들이 가용하지 않다면, 전송은 실패하며 오류 코드 4.08 (Entity Incomplete Request)이 반드시 반환되어야 합니다. 서버는 비순차적인 임의의 (최종 또는 비 최종) Block1 전송에 대해 4.08 오류 코드를 반환할 수도 있습니다. 따라서, 이 경우를 처리할 특정 메커니즘이 없는 클라이언트는 항상 블록 0 을 시작으로 블록들을 순서대로 전송해야 합니다.
Simple Atomic Block-Wise PUT
클라이언트가 4.08 오류 코드를 수신하는 이유중 하나는, 서버에서 시간이 초과되어 부분 요청 내용을 폐기했기 때문입니다. 클라이언트는 과도한 지연없이 요청을 구성하는 모든 블록을 보내려고 노력해야 합니다. 서버는 가장 최근 블록이 전송 된 후 EXCHANGE_LIFETIME 이 경과하면 부분 요청 본문을 마음껏 버릴 수 있습니다. 그러나 서버에서 항상 부분 요청 본문을 오래 보관할 필요는 없습니다.
Expired Request Response in Atomic Block-Wise PUT
atomic 방식의 블록형 요청 페이로드 전송에서, 서버는 블록 저장에 필요한 자원이 없는 경우 오류 코드 4.13 (요청 엔티티가 너무 큼) 을 언제든지 반환 할 수 있습니다. (Block1 을 사용하지 않는 요청에 대한 4.13 응답은 클라이언트에게 Block1 로 전송해보라고 하는 힌트이며, 요청한 SZX 보다 더 작은 SZX이 들어있는 Block1 옵션이 포함된 4.13 응답은 더 작은 SZX 로 요청해 달라는 힌트임을 유의하십시오.)
Simple Stateless Block-Wise PUT
stateless 방식의 블록형 요청 페이로드 전송에서, 서버는 전송이 진행중이거나 클라이언트로부터 전송이 완료되지 않은 동안 비일관적 상태에서 운영될 수 있습니다. 이 특성은 전송 중 서버에 state 가 항상 유지되는 HTTP 보다는 원격 파일 시스템의 특성에 더 가깝습니다. 공유 파일 액세스 (예: 클라이언트 특정 임시 리소스)에서 잘 알려진 기술을 사용하여 HTTP 와의 차이를 완화 할 수 있습니다.
Simple Atomic Block-Wise PUT with Negotiation
Block1 옵션은 단일 종단점이 동일한 자원에 대해 동시에 여러 개의 블록 방식 요청 페이로드 전송 (예 : PUT 또는 POST) 작업을 수행 할 수있는 방법을 제공하지 않습니다. 동일한 종단점의 이전 시퀀스가 완료되기 전에 동일한 자원에 대한 새로운 블록 단위의 요청을 시작하면 서버가 계속 유지할 수있는 컨텍스트를 단순히 덮어 씁니다.